2020-05-21-redis
1.redis简介
简单来说,redis就是一个数据库,不过与传统数据库不同的是redis的数据是存储在内存中(ps:redis也可以持久化),所以存写速度非常快,因为redis被广泛应用于缓存方向。另外,redis也可以用来做分布式锁。redis提供多种数据类型来支持不同的业务场景。除此之外,redis还支持事物,持久化,LUA脚本,LRU驱动事件,多种集群方案。
2.为什么要用redis/为什么要用缓存
主要从“高性能”和“高并发”这两点来看问题
高性能
加入用户第一次访问数据库中的某些数据。这个过程会比较缓慢,因为是从硬盘上读取。将用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中读取。操作缓存就是直接操作内存,所以速度相当快。如果数据库中,对应的数据改变之后,同步改变缓存中的数据即可。
在使用redis作为缓存时需要注意:无法保证数据库与缓存的强一致性,只可能使用更新策略去尽可能地保证数据的强一致性,对于对数据一致性要求较高的业务,不建议使用缓存。
高并发
直接操作缓存能够承受的请求远远大于直接访问数据库,所以我们可以将数据库中的部分数据(热点数据
)放入到缓存中去,这样,用户的一部分请求会直接到缓存这里而无需访问数据库,大大地提升了数据的查询效率。
3.为什么要用redis,而不用map/guava做缓存?
缓存分为本地缓存和分布式缓存。以Java为例,使用自带的map或者guava实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着jvm的销毁而结束,并且在多实例的情况下,无法保证缓存的一致性。
使用redis和memcached之类的可以作为分布式缓存,在多实例的情况下,各实例共享一份缓存数据,缓存具有一致性。确定是需要保证redis或者memcached服务的高可用,整个程序架构上较为复杂。
4.redis和memcached的区别
对于redis和memcached,总结了下面四点。现在公司一般使用redis来实现缓存,而且redis自身也越来越强大。
1.redis支持更丰富的数据类型(支持更复杂的应用场景):redis不仅仅支持简单的key/value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcached支持简单的数据类型,string。
2.redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用,而memcached把数据全部存在内存中,不支持数据持久化。
3.集群模式:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是redis目前是原生cluster模式的。
-
4.memcached是多线程,非阻塞IO复用的网络模型;redis使用单线程的IO多路复用模型。
redis和memcached对比
5.redis常见数据结构以及使用场景分析
①String
常用命令:set,get,decr,incr,mget
String数据结构是简单的key-value类型,value其实不仅仅可以是String,也可以是数字。常规key-value缓存应用;常规计数:微博数,粉丝数,排行榜等等。
②Hash
常用命令:hget,hset,hgetall等
Hash是一个string类型的field和value的映射表,hash特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中某个字段的值。比如我们可以Hash数据结构来存储用户信息,商品信息等等。比如下面我们就用hash类型存放一些用户信息:
key = JavaUser293847
value = {
“id”: 1,
“name”: “SnailClimb”,
“age”: 22,
“location”: “Wuhan, Hubei”
}
③List
常用命令:lpush,rpush,lpop,rpop,lrange等等
list就是链表,redis中list的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以使用redis的list来实现。
Redis list的实现为一个双向链表,既可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,另外可以通过lrange命令,就是从某个元素开始读取多少个元素,可以基于list实现分页查询,这是一个非常棒的功能,基于redis实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页地往下走),性能高。
④Set
常用命令:sadd,spop,smembers,sunion等等
set对外提供的功能与list类似 ,是一个列表的功能,特殊之处在于set可以自动去重。
当你希望存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个是list所不具备的。可以基于set轻易实现交集,并集,差集,的操作。
比如:微博应用中,可以将一个用户的所有关注人存储在一个集合中,将其所有粉丝存储在一个集合中。redis可以非常方便地实现共同关注 、共同粉丝,共同喜好等功能。这个过程也就是求交集的过程,具体命令如下:
sinterstore key1 key2
⑤Sorted Set
常用命令:zadd,zrange,zrem,zcard等
和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
举例:在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。适合使用redis中的sorted set结构进行存储。
6.redis设置过期时间
redis中有个设置过期时间的功能,即对存储在数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中都有token或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般我们都自己判断过期,这样无疑会影响项目性能。
我们set key的时候,可以给定一个expire time,就是过期时间,通过过期时间我们可以指定这个key可以存活的时间。
如果假设你设置了一批key,只能存活一个小时,那么接下来一小时后,redis是怎么对这些key进行删除的?定期删除,惰性删除
。
通过名字大概就能猜出这两种删除方式的意思了。
- 定期删除:redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查是否过期,如果过期就删除。注意,是随机抽取,为什么随机呢?我们想一想,假如,redis存储了几十万个key,每隔100ms就遍历所有的设置了过期时间的key的话,就会给cpu带来很大的负载。
- 惰性删除:定期删除会导致很多过期key到了时间并没有被删除掉。所以有了惰性删除。假如你的过期key,靠定期删除没有被删除掉,还停留在内存里,除非你去系统去查那个key,才会被redis删除掉。这就是所谓的惰性删除。
但是仅仅通过设置过期时间还是会有问题。我们想一下:如果定期删除漏掉了很多过期key,然后你也没有及时去查,也就是没有走惰性删除,此时会怎么样?如果大量的过期key堆积在内存里,导致redis的内存耗尽了。如何解决这个问题呢?
这个时候就需要靠redis内存淘汰机制。
7.redis内存淘汰机制(mysql里有2000w数据,redis只存20w数据,如何保证redis中的数据都是热点数据?)
redis配置文件redis.conf中有相关注释。
redis配置文件地址
redis提供6种数据淘汰策略:
①.volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
②.volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
③.volatile-random:从已设置过期时间的数据集(server.db[i].expires)中挑选任意数据淘汰。
④.allkeys-lru:当内存不足以容纳新数据时,在键空间中,移除最近最少使用的key(这个是最常用的)。
⑤.allkeys-random:从数据集(server.db[i].dict)中选择任意选择数据淘汰。
⑥:no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,写入操作报错。这个一般没人使用。
8.redis持久化机制(怎么保证redis挂掉之后重启数据可以进行恢复)
很多时候我们需要持久化数据也就是将内存中的数据保存到硬盘里面,大部分是为了之后重用数据(比如重启机器,机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
Redis不同于Memcached的很重一点就是,Redis支持持久化,而且支持两种不同的持久化操作。Redis的一种持久化方式叫做快照(snapshotting,RDB),另一种方式只是追加文件(append-only file,AOF)。这两种方式各有千秋,下面详细介绍这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方式。
-
快照(snapshotting)持久化(RDB)
Redis可以通过创建快照来获得存储在内存里面的数据在某个时间节点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器而创建具有相同数据的服务器副本(Redis主从结构,主要来提高Redis的性能),还可以将快照留在原地以便在重启服务器的适合使用它来恢复数据。
快照持久化是redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照
-
AOF(append-only file)持久化
与快照持久化相比,AOF持久化的实时性更好,因此成为主流的持久化方案。默认情况下Redis没有开启AOF(append-only file)方式持久化,可以通过appendonly参数开启:
appendonly yes
开启AOF持久化后每执行一条会更改redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。
在Redis的配置文件中存在三种不同的AOF持久化方式,他们分别是:
appendfsync #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度。
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时同步
为了兼顾数据和写入性能,用户可以考虑appendfsync everysec选项,让redis每秒同步一次AOF文件,Redis性能几乎不会受到影响。而且这样即使出现系统崩溃,系统最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,redis还会优雅地放慢自己的速度以便适应硬盘的最大读写速度。
-
Redis4.0对于持久化机制的优化
Redis4.0开始支持RDB和AOF的混合持久化(默认关闭,可以通过配置项aof-use-rdb-preamble yes
)
如果把混合持久化打开,AOF重写的时候就直接把RDB的内容写到AOF的开头。这样的好处是可以结合RDB和AOF的优点,快速加载同时避免丢失过多的数据。当时缺点也有的,AOF里面的RDB部分是压缩格式不再是AOF格式,可读性较差。
-
补充内容:AOF重写
AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小。
AOF重写是一个有歧义的名字,该功能可以通过读取redis数据库中的键值对来实现,程序无须对现有AOF文件进行读取,分析或者写入操作。
在执行BGREWRITEAOF命令时,Redis服务器会维护一个AOF重写缓冲区
,该缓冲区会在子进程创建新的AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新的AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作。
9.Redis事务
Redis通过MULTI,EXEC,WATCH等命令来实现事务(transaction)功能。事务提供了一种可以将多个命令请求打包,然后一次性,按顺序地执行多个命令的机制,并且事务执行期间,服务器不会中断事务而改去执行其他客户端命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其它客户端的命令请求。