Redis
说说Redis基本数据类型有哪些吧
1. 字符串:redis没有直接使⽤C语⾔传统的字符串表示,⽽是⾃⼰实现的叫做简单动态字符串SDS的抽象类型。C语⾔的字符串不记录⾃身的⻓度信息,⽽SDS则保存了⻓度信息,这样将获取字符串⻓度的时间由O(N)降低到了O(1),同时可以避免缓冲区溢出和减少修改字符串⻓度时所需的内存重分配次数。
2. 链表linkedlist:redis链表是⼀个双向⽆环链表结构,很多发布订阅、慢查询、监视器功能都是使⽤到了链表来实现,每个链表的节点由⼀个listNode结构来表示,每个节点都有指向前置节点和后置节点的指针,同时表头节点的前置和后置节点都指向NULL。
3. 字典hashtable:⽤于保存键值对的抽象数据结构。redis使⽤hash表作为底层实现,每个字典带有两个hash表,供平时使⽤和rehash时使⽤,hash表使⽤链地址法来解决键冲突,被分配到同⼀个索引位置的多个键值对会形成⼀个单向链表,在对hash表进⾏扩容或者缩容的时候,为了服务的可⽤性,rehash的过程不是⼀次性完成的,⽽是渐进式的。
4. 跳跃表skiplist:跳跃表是有序集合的底层实现之⼀,redis中在实现有序集合键和集群节点的内部结构中都是⽤到了跳跃表。redis跳跃表由zskiplist和zskiplistNode组成,zskiplist⽤于保存跳跃表信息(表头、表尾节点、⻓度等),zskiplistNode⽤于表示表跳跃节点,每个跳跃表的层⾼都是1-32的随机数,在同⼀个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是
唯⼀的,节点按照分值⼤⼩排序,如果分值相同,则按照成员对象的⼤⼩排序。
5. 整数集合intset:⽤于保存整数值的集合抽象数据结构,不会出现重复元素,底层实现为数组。
6. 压缩列表ziplist:压缩列表是为节约内存⽽开发的顺序性数据结构,他可以包含多个节点,每个节点可以保存⼀个字节数组或者整数值。
基于这些基础的数据结构,redis封装了⾃⼰的对象系统,包含字符串对象string、列表对象list、哈希对象hash、集合对象set、有序集合对象zset,每种对象都⽤到了⾄少⼀种基础的数据结构。
redis通过encoding属性设置对象的编码形式来提升灵活性和效率,基于不同的场景redis会⾃动做出优化。不同对象的编码如下:
字符串对象string: int整数、embstr编码的简单动态字符串、raw简单动态字符串
列表对象list: ziplist、linkedlist
哈希对象hash: ziplist、hashtable
集合对象set: intset、hashtable
有序集合对象zset: ziplist、skiplist
Redis为什么快呢?
redis的速度⾮常的快,单机的redis就可以⽀撑每秒10⼏万的并发,相对于mysql来说,性能是mysql的⼏⼗倍。速度快的原因主要有⼏点:
1. 完全基于内存操作
2. C语⾔实现,优化过的数据结构,基于⼏种基础的数据结构,redis做了⼤量的优化,性能极⾼
3. 使⽤单线程,⽆上下⽂的切换成本
4. 基于⾮阻塞的IO多路复⽤机制
那为什么Redis6.0之后⼜改⽤多线程呢?
redis使⽤多线程并⾮是完全摒弃单线程,redis还是使⽤单线程模型来处理客户端的请求,只是使⽤多线程来处理数据的读写和协议解析,执⾏命令还是使⽤单线程。
这样做的⽬的是因为redis的性能瓶颈在于⽹络IO⽽⾮CPU,使⽤多线程能提升IO读写的效率,从⽽整体提⾼redis的性能。
(⻅⽹络篇下单线程Reactor模型)
知道什么是热key吗?热key问题怎么解决?
所谓热key问题就是,突然有⼏⼗万的请求去访问redis上的某个特定key,那么这样会造成流量过于集中,达到物理⽹卡上限,从⽽导致这台redis的服务器宕机引发雪崩。编辑
针对热key的解决⽅案:
提前把热key打散到不同的服务器,降低压⼒
加⼊⼆级缓存,提前加载热key数据到内存中,如果redis宕机,⾛内存查询
什么是缓存击穿、缓存穿透、缓存雪崩?
缓存击穿
缓存击穿的概念就是单个key并发访问过⾼,过期时导致所有请求直接打到db上,这个和热key的问题⽐较类似,只是说的点在于过期导致请求全部打到DB上⽽已。
解决⽅案:
加锁更新,⽐如请求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数据,写⼊缓存,再返回给⽤户,这样后⾯的请求就可以从缓存中拿到数据了。
将过期时间组合写在value中,通过异步的⽅式不断的刷新过期时间,防⽌此类现象。

编辑 缓存穿透
缓存穿透是指查询不存在缓存中的数据,每次请求都会打到DB,就像缓存不存在⼀样。
编辑
针对这个问题,加⼀层布隆过滤器。布隆过滤器的原理是在你存⼊数据的时候,会通过散列函数将它映射为⼀个位数组中的K个点,同时把他们置为1。
这样当⽤户再次来查询A,⽽A在布隆过滤器值为0,直接返回,就不会产⽣击穿请求打到DB了。
显然,使⽤布隆过滤器之后会有⼀个问题就是误判,因为它本身是⼀个数组,可能会有多个值落到同⼀个位置,那么理论上来说只要我们的数组⻓度够⻓,误判的概率就会越低,这种问题就根据实际情况来就好了。编辑
缓存雪崩
当某⼀时刻发⽣⼤规模的缓存失效的情况,⽐如你的缓存服务宕机了,会有⼤量的请求进来直接打到DB上,这样可能导致整个系统的崩溃,称为雪崩。雪崩和击穿、热key的问题不太⼀样的是,他是指⼤规模
的缓存都过期失效了。编辑
针对雪崩⼏个解决⽅案:
针对不同key设置不同的过期时间,避免同时过期
限流,如果redis宕机,可以限流,避免同时刻⼤量请求打崩DB
⼆级缓存,同热key的⽅案。
Redis的过期策略有哪些?
redis主要有2种过期删除策略
惰性删除
惰性删除指的是当我们查询key的时候才对key进⾏检测,如果已经达到过期时间,则删除。显然,他有⼀个缺点就是如果这些过期的key没有被访问,那么他就⼀直⽆法被删除,⽽且⼀直占⽤内存。
编辑
定期删除
定期删除指的是redis每隔⼀段时间对数据库做⼀次检查,删除⾥⾯的过期key。由于不可能对所有key去做轮询来删除,所以redis会每次随机取⼀些key去做检查和删除。
那么定期+惰性都没有删除过期的key怎么办?
假设redis每次定期随机查询key的时候没有删掉,这些key也没有做查询的话,就会导致这些key⼀直保存在redis⾥⾯⽆法被删除,这时候就会⾛到redis的内存淘汰机制。
1. volatile-lru:从已设置过期时间的key中,移出最近最少使⽤的key进⾏淘汰
2. volatile-ttl:从已设置过期时间的key中,移出将要过期的key
3. volatile-random:从已设置过期时间的key中随机选择key淘汰
4. allkeys-lru:从key中选择最近最少使⽤的进⾏淘汰
5.allkeys-random:从key中随机选择key进⾏淘汰
6.noeviction:当内存达到阈值的时候,新写⼊操作报错
持久化方式有哪些?有什么区别?
redis持久化⽅案分为RDB和AOF两种。
RDB
RDB持久化可以⼿动执⾏也可以根据配置定期执⾏,它的作⽤是将某个时间点上的数据库状态保存到RDB⽂件中,RDB⽂件是⼀个压缩的⼆进制⽂件,通过它可以还原某个时刻数据库的状态。由于RDB⽂件是保存在硬盘上的,所以即使redis崩溃或者退出,只要RDB⽂件存在,就可以⽤它来恢复还原数据库的状态。
可以通过SAVE或者BGSAVE来⽣成RDB⽂件。
SAVE命令会阻塞redis进程,直到RDB⽂件⽣成完毕,在进程阻塞期间,redis不能处理任何命令请求,这显然是不合适的。BGSAVE则是会fork出⼀个⼦进程,然后由⼦进程去负责⽣成RDB⽂件,⽗进程还可以继续处理命令请求,不会阻塞进程。
AOF
AOF和RDB不同,AOF是通过保存redis服务器所执⾏的写命令来记录数据库状态的。AOF通过追加、写⼊、同步三个步骤来实现持久化机制。
当AOF持久化处于激活状态,服务器执⾏完写命令之后,写命令将会被追加append到aof_buf缓冲区的末尾
在服务器每结束⼀个事件循环之前,将会调⽤flushAppendOnlyFile函数决定是否要将aof_buf的内容保存到AOF⽂件中,可以通过配置appendfsync来决定。
always ##aof_buf内容写⼊并同步到AOF⽂件
everysec ##将aof_buf中内容写⼊到AOF⽂件,如果上次同步AOF⽂件时间距离现在超过1秒,则再次对AOF⽂件进⾏同步
no ##将aof_buf内容写⼊AOF⽂件,但是并不对AOF⽂件进⾏同步,同步时间由操作系统决定
如果不设置,默认选项将会是everysec,因为always来说虽然最安全(只会丢失⼀次事件循环的写命令),但是性能较差,⽽everysec模式只不过会可能丢失1秒钟的数据,⽽no模式的效率和everysec相仿,但是会丢失上次同步AOF⽂件之后的所有写命令数据。
怎么实现Redis的高可用?
要想实现⾼可⽤,⼀台机器肯定是不够的,⽽redis要保证⾼可⽤,有2个可选⽅案。
主从架构
主从模式是最简单的实现⾼可⽤的⽅案,核⼼就是主从同步。主从同步的原理如下:
slave发送sync命令到master
master收到sync之后,执⾏bgsave,⽣成RDB全量⽂件
master把slave的写命令记录到缓存
bgsave执⾏完毕之后,发送RDB⽂件到slave,slave执⾏
master发送缓存中的写命令到slave,slave执⾏
编辑
这⾥我写的这个命令是sync,但是在redis2.8版本之后已经使⽤psync来替代sync了,原因是sync命令⾮常消耗系统资源,⽽psync的效率更⾼。
哨兵
基于主从⽅案的缺点还是很明显的,假设master宕机,那么就不能写⼊数据,那么slave也就失去了作⽤,整个架构就不可⽤了,除⾮你⼿动切换,主要原因就是因为没有⾃动故障转移机制。⽽哨兵
(sentinel)的功能⽐单纯的主从架构全⾯的多了,它具备⾃动故障转移、集群监控、消息通知等功能。
编辑
哨兵可以同时监视多个主从服务器,并且在被监视的master下线时,⾃动将某个slave提升为master,然后由新的master继续接收命令。整个过程如下:
初始化sentinel,将普通的redis代码替换成sentinel专⽤代码
初始化masters字典和服务器信息,服务器信息主要保存ip:port,并记录实例的地址和ID
创建和master的两个连接,命令连接和订阅连接,并且订阅sentinel:hello频道
每隔10秒向master发送info命令,获取master和它下⾯所有slave的当前信息
当发现master有新的slave之后,sentinel和新的slave同样建⽴两个连接,同时每个10秒发送info命令,更新master信息
sentinel每隔1秒向所有服务器发送ping命令,如果某台服务器在配置的响应时间内连续返回⽆效回复,将会被标记为下线状态
选举出领头sentinel,领头sentinel需要半数以上的sentinel同意
领头sentinel从已下线的的master所有slave中挑选⼀个,将其转换为master
让所有的slave改为从新的master复制数据
将原来的master设置为新的master的从服务器,当原来master重新回复连接时,就变成了新master的从服务器
sentinel会每隔1秒向所有实例(包括主从服务器和其他sentinel)发送ping命令,并且根据回复判断是否已经下线,这种⽅式叫做主观下线。当判断为主观下线时,就会向其他监视的sentinel询问,如果超过
半数的投票认为已经是下线状态,则会标记为客观下线状态,同时触发故障转移。
能说说redis集群的原理吗?
如果说依靠哨兵可以实现redis的⾼可⽤,如果还想在⽀持⾼并发同时容纳海量的数据,那就需要redis集群。redis集群是redis提供的分布式数据存储⽅案,集群通过数据分⽚sharding来进⾏数据的共享,同
时提供复制和故障转移的功能。
节点
⼀个redis集群由多个节点node组成,⽽多个node之间通过cluster meet命令来进⾏连接,节点的握⼿过程:
1. 节点A收到客户端的cluster meet命令
2. A根据收到的IP地址和端⼝号,向B发送⼀条meet消息
3. 节点B收到meet消息返回pong
4. A知道B收到了meet消息,返回⼀条ping消息,握⼿成功
5. 最后,节点A将会通过gossip协议把节点B的信息传播给集群中的其他节点,其他节点也将和B进⾏握⼿
编辑
槽slot
redis通过集群分⽚的形式来保存数据,整个集群数据库被分为16384个slot,集群中的每个节点可以处理0-16384个slot,当数据库16384个slot都有节点在处理时,集群处于上线状态,反之只要有⼀个slot没有得到处理都会处理下线状态。通过cluster addslots命令可以将slot指派给对应节点处理。
slot是⼀个位数组,数组的⻓度是16384/8=2048,⽽数组的每⼀位⽤1表示被节点处理,0表示不处理,如图所示的话表示A节点处理0-7的slot。
编辑
当客户端向节点发送命令,如果刚好找到slot属于当前节点,那么节点就执⾏命令,反之,则会返回⼀个MOVED命令到客户端指引客户端转向正确的节点。(MOVED过程是⾃动的)
编辑
如果增加或者移出节点,对于slot的重新分配也是⾮常⽅便的,redis提供了⼯具帮助实现slot的迁移,整个过程是完全在线的,不需要停⽌服务。
故障转移
如果节点A向节点B发送ping消息,节点B没有在规定的时间内响应pong,那么节点A会标记节点B为pfail疑似下线状态,同时把B的状态通过消息的形式发送给其他节点,如果超过半数以上的节点都标记B为pfail状态,B就会被标记为fail下线状态,此时将会发⽣故障转移,优先从复制数据较多的从节点选择⼀个成为主节点,并且接管下线节点的slot,整个过程和哨兵⾮常类似,都是基于Raft协议做选举。
了解Redis事务机制吗?
redis通过MULTI、EXEC、WATCH等命令来实现事务机制,事务执⾏过程将⼀系列多个命令按照顺序⼀次性执⾏,并且在执⾏期间,事务不会被中断,也不会去执⾏客户端的其他请求,直到所有命令执⾏完毕。事务的执⾏过程如下:
服务端收到客户端请求,事务以MULTI开始
如果客户端正处于事务状态,则会把事务放⼊队列同时返回给客户端QUEUED,反之则直接执⾏这个命令
当收到客户端EXEC命令时,WATCH命令监视整个事务中的key是否有被修改,如果有则返回空回复到客户端表示失败,否则redis会遍历整个事务队列,执⾏队列中保存的所有命令,最后返回结果
给客户端
WATCH的机制本身是⼀个CAS的机制,被监视的key会被保存到⼀个链表中,如果某个key被修改,那么REDIS_DIRTY_CAS标志将会被打开,这时服务器会拒绝执⾏事务。