redis使用总结

REDIS 使用总结

  • Redis基本数据类型
    • 字符串
    • 链表linkedlist
    • 字典hashtable
    • 跳跃表skiplist
    • 整数集合intset
    • 压缩列表ziplist
  • redis 数据结构
  • redis 为什么这么快
  • redis 持久化
    • RDB
    • AOF
  • redis 过期删除策略和内存淘汰机制
    • 惰性删除
    • 定期删除
    • 内存淘汰机制
  • redis 高可用
    • 主从架构
    • 哨兵
    • 集群
      • 节点
      • 槽slot
    • 故障转移
    • 缓存击穿
    • 缓存穿透
    • 缓存雪崩

Redis基本数据类型

字符串

redis没有直接使⽤C语⾔传统的字符串表示,⽽是⾃⼰实现的叫做简单动态字符串SDS的抽象类型。C语⾔的字符串不记录⾃身的⻓度信息,⽽SDS则保存了⻓度信息,这样将获取字符串⻓度的时间由O(N)降低到了O(1),同时可以避免缓冲区溢出和减少修改字符串⻓度时所需的内存重
分配次数。

链表linkedlist

redis链表是⼀个双向⽆环链表结构,很多发布订阅、慢查询、监视器功能都是使⽤到了链表来实现,每个链表的节点由⼀个listNode结构来表示,每个节点都有指向前置节点和后置节点的指针,同时表头节点的前置和后置节点都指向NULL。

字典hashtable

⽤于保存键值对的抽象数据结构。redis使⽤hash表作为底层实现,每个字典带有两个hash表,供平时使⽤和rehash时使⽤,hash表使⽤链地址法来解决键冲突,被分配到同⼀个索引位置的多个键值对会形成⼀个单向链表,在对hash表进⾏扩容或者缩容的时候,为了服务的可⽤性,rehash的过程不是⼀次性完成的,⽽是渐进式的。

跳跃表skiplist

跳跃表是有序集合的底层实现之⼀,redis中在实现有序集合键和集群节点的内部结构中都是⽤到了跳跃表。redis跳跃表由zskiplist和zskiplistNode组成,zskiplist⽤于保存跳跃表信息(表头、表尾节点、⻓度等),zskiplistNode⽤于表示表跳跃节点,每个跳跃表的层⾼都是1-32的随机数,在同⼀个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯⼀的,节点按照分值⼤⼩排序,如果分值相同,则按照成员对象的⼤⼩排序。

整数集合intset

⽤于保存整数值的集合抽象数据结构,不会出现重复元素,底层实现为数组。

压缩列表ziplist

压缩列表是为节约内存⽽开发的顺序性数据结构,他可以包含多个节点,每个节点可以保存⼀个字节数组或者整数值。

redis 数据结构

基于redis基本数据结构,redis封装了⾃⼰的对象系统,包含字符串对象string、列表对象list、哈希对象hash、集合对象set、有序集合对象zset,每种对象都⽤到了⾄少⼀种基础的数据结构。
redis通过encoding属性设置对象的编码形式来提升灵活性和效率,基于不同的场景redis会⾃动做出优化。不同对象的编码如下:

  1. 字符串对象string:int整数、embstr编码的简单动态字符串、raw简单动态字符串
  2. 列表对象list:ziplist、linkedlist
  3. 哈希对象hash:ziplist、hashtable
  4. 集合对象set:intset、hashtable
  5. 有序集合对象zset:ziplist、skiplist

redis 为什么这么快

redis的速度⾮常的快,单机的redis就可以⽀撑每秒10⼏万的并发,相对于mysql来说,性能是mysql的⼏⼗倍。速度快的原因主要有⼏点:

  1. 完全基于内存操作
  2. C语⾔实现,优化过的数据结构,基于⼏种基础的数据结构,redis做了⼤量的优化,性能极⾼
  3. 使⽤单线程,⽆上下⽂的切换成本,(但是避免数据量大的操作,会影响并发量)
  4. 基于⾮阻塞的IO多路复⽤机制

注:redis使⽤多线程并⾮是完全摒弃单线程,redis还是使⽤单线程模型来处理客户端的请求,只是使⽤多线程来处理数据的读写和协议解析,执⾏命令还是使⽤单线程。
这样做的⽬的是因为redis的性能瓶颈在于⽹络IO⽽⾮CPU,使⽤多线程能提升IO读写的效率,从⽽整体提⾼redis的性能。

redis 持久化

RDB

RDB持久化可以⼿动执⾏也可以根据配置定期执⾏,它的作⽤是将某个时间点上的数据库状态保存到RDB⽂件中,RDB⽂件是⼀个压缩的⼆进制⽂件,通过它可以还原某个时刻数据库的状态。由于RDB⽂件是保存在硬盘上的,所以即使redis崩溃或者退出,只要RDB⽂件存在,就可以⽤它来恢复还原数据库的状态。
可以通过SAVE或者BGSAVE来⽣成RDB⽂件。
SAVE命令会阻塞redis进程,直到RDB⽂件⽣成完毕,在进程阻塞期间,redis不能处理任何命令请求,这显然是不合适的。BGSAVE则是会fork出⼀个⼦进程,然后由⼦进程去负责⽣成RDB⽂件,⽗进程还可以继续处理命令请求,不会阻塞进程。

AOF

AOF和RDB不同,AOF是通过保存redis服务器所执⾏的写命令来记录数据库状态的。
AOF通过追加、写⼊、同步三个步骤来实现持久化机制。

  1. 当AOF持久化处于激活状态,服务器执⾏完写命令之后,写命令将会被追加append到aof_buf缓冲区的末尾
  2. 在服务器每结束⼀个事件循环之前,将会调⽤flushAppendOnlyFile函数决定是否要将aof_buf的内容保存到AOF⽂件中,可以通过配置appendfsync来决定。
    如果不设置,默认选项将会是everysec,因为always来说虽然最安全(只会丢失⼀次事件循环的写命令),但是性能较差,⽽everysec模式只不过会可能丢失1秒钟的数据,⽽no模式的效率和everysec相仿,但是会丢失上次同步AOF⽂件之后的所有写命令数据。

redis 过期删除策略和内存淘汰机制

惰性删除

惰性删除指的是当我们查询key的时候才对key进⾏检测,如果已经达到过期时间,则删除。显然,他有⼀个缺点就是如果这些过期的key没有被访问,那么他就⼀直⽆法被删除,⽽且⼀直占⽤内存。

定期删除

定期删除指的是redis每隔⼀段时间对数据库做⼀次检查,删除⾥⾯的过期key。由于不可能对所有key去做轮询来删除,所以redis会每次随机取⼀些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 高可用

主从架构

主从模式是最简单的实现⾼可⽤的⽅案,核⼼就是主从同步。主从同步的原理如下:

  1. slave发送sync命令到master
  2. master收到sync之后,执⾏bgsave,⽣成RDB全量⽂件
  3. master把slave的写命令记录到缓存
  4. bgsave执⾏完毕之后,发送RDB⽂件到slave,slave执⾏
  5. master发送缓存中的写命令到slave,slave执⾏
  • always ##aof_buf内容写⼊并同步到AOF⽂件
  • everysec ##将aof_buf中内容写⼊到AOF⽂件,如果上次同步AOF⽂件时间距离现在超过1秒,则再次对AOF⽂件进⾏同步
  • no ##将aof_buf内容写⼊AOF⽂件,但是并不对AOF⽂件进⾏同步,同步时间由操作系统决定
    这⾥我写的这个命令是sync,但是在redis2.8版本之后已经使⽤psync来替代sync了,原因是sync命令⾮常消耗系统资源,⽽psync的效率更⾼。

哨兵

基于主从⽅案的缺点还是很明显的,假设master宕机,那么就不能写⼊数据,那么slave也就失去了作
⽤,整个架构就不可⽤了,除⾮你⼿动切换,主要原因就是因为没有⾃动故障转移机制。⽽哨兵
(sentinel)的功能⽐单纯的主从架构全⾯的多了,它具备⾃动故障转移、集群监控、消息通知等功能。哨兵可以同时监视多个主从服务器,并且在被监视的master下线时,⾃动将某个slave提升为master,
然后由新的master继续接收命令。整个过程如下:

  1. 初始化sentinel,将普通的redis代码替换成sentinel专⽤代码
  2. 初始化masters字典和服务器信息,服务器信息主要保存ip:port,并记录实例的地址和ID
  3. 创建和master的两个连接,命令连接和订阅连接,并且订阅sentinel:hello频道
  4. 每隔10秒向master发送info命令,获取master和它下⾯所有slave的当前信息
  5. 当发现master有新的slave之后,sentinel和新的slave同样建⽴两个连接,同时每个10秒发送info
    命令,更新master信息
  6. sentinel每隔1秒向所有服务器发送ping命令,如果某台服务器在配置的响应时间内连续返回⽆效回
    复,将会被标记为下线状态
  7. 选举出领头sentinel,领头sentinel需要半数以上的sentinel同意
  8. 领头sentinel从已下线的的master所有slave中挑选⼀个,将其转换为master
  9. 让所有的slave改为从新的master复制数据
  10. 将原来的master设置为新的master的从服务器,当原来master重新回复连接时,就变成了新master的从服务器
    sentinel会每隔1秒向所有实例(包括主从服务器和其他sentinel)发送ping命令,并且根据回复判断是否已经下线,这种⽅式叫做主观下线。当判断为主观下线时,就会向其他监视的sentinel询问,如果超过
    半数的投票认为已经是下线状态,则会标记为客观下线状态,同时触发故障转移。

集群

如果说依靠哨兵可以实现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协议做选举。

缓存击穿

缓存击穿的概念就是单个key并发访问过⾼,过期时导致所有请求直接打到db上,这个和热key的问题⽐较类似,只是说的点在于过期导致请求全部打到DB上⽽已。
解决⽅案:

  1. 加锁更新,⽐如请求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数据,写⼊缓存,再返回给⽤户,这样后⾯的请求就可以从缓存中拿到数据了。
  2. 将过期时间组合写在value中,通过异步的⽅式不断的刷新过期时间,防⽌此类现象。

缓存穿透

缓存穿透是指查询不存在缓存中的数据,每次请求都会打到DB,就像缓存不存在⼀样。
针对这个问题,加⼀层布隆过滤器。布隆过滤器的原理是在你存⼊数据的时候,会通过散列函数将它映射为⼀个位数组中的K个点,同时把他们置为1。
这样当⽤户再次来查询A,⽽A在布隆过滤器值为0,直接返回,就不会产⽣击穿请求打到DB了。
显然,使⽤布隆过滤器之后会有⼀个问题就是误判,因为它本身是⼀个数组,可能会有多个值落到同⼀个位置,那么理论上来说只要我们的数组⻓度够⻓,误判的概率就会越低,这种问题就根据实际情况来就好了。

缓存雪崩

当某⼀时刻发⽣⼤规模的缓存失效的情况,⽐如你的缓存服务宕机了,会有⼤量的请求进来直接打到DB
上,这样可能导致整个系统的崩溃,称为雪崩。雪崩和击穿、热key的问题不太⼀样的是,他是指⼤规模的缓存都过期失效了。
针对雪崩⼏个解决⽅案:

  1. 针对不同key设置不同的过期时间,避免同时过期
  2. 限流,如果redis宕机,可以限流,避免同时刻⼤量请求打崩DB
  3. ⼆级缓存,同热key的⽅案。

你可能感兴趣的:(redis,redis)