分布式缓存Redis简单总结

一、为什么使用缓存

  • 支持高并发,减轻DB压力;
  • 高性能,基于内存的缓存能够相较于DB更快的返回;

二、Redis VS Memcached

  • Redis:支持数据结构多,单线程,适合小数据,自带集群
  • Memcached:多线程,没有自带集群(最大的缺点)

三、使用缓存易出现的问题

  • 数据一致性问题:缓存数据和DB数据不一致
  • 缓存雪崩:缓存崩溃导致的DB压力暴增甚至压垮DB
  • 缓存穿透:恶意攻击导致的频繁读取数据库和缓存都没有的数据导致的DB压力暴增
  • 缓存并发竞争:多个客户端同时写同一个键导致的数据不正确问题

后面介绍各个解决方案

四、Redis支持的数据结构

  • String:简单的字符串存取
  • hash:类似map键值对结构
  • list:有序列表
  • set:无需set,不重复
  • zorted set: 有序去重集合

五、key过期时间相关

定义及使用

可以手动设置某个key值得过期时间,但是注意两点:

  • 即使手动设置了过期时间,但是在底层不是时间一到立马进行了删除。
  • 即使不设置过期时间,内存不够的时候也有可能会出现键丢失(被回收)的情况。

删除策略

定期删除

redis 会每隔一段时间进行随机检测,注意,这里不是所有key全部检测,仅仅只是随机检测,如果key已经过期那么则删除

惰性删除

定时随机检测不能保证删除所有过期键被删除,那么有了第二条:惰性删除,即客户端在使用key的时候redis会检测改key是否过期,如果过期则删除。

内存淘汰

上面两个方法依然不能保证所有过期key会被删除,比如一个key一直没有被随机抽到,并且之后一直没有被使用过,则他就幸存下来了,因此就有第三个流程,内存淘汰,在内存不够或者达到一定值的时候删除key来腾地方。响应的内存淘汰策略有:

  • no-enviction: 内存不否后拒绝新key(一般没人这么干)
  • allkey-lru:内存不够时对所有key进行lru(最近最少被使用)淘汰
  • allkey-random:内存不够时对所有key进行随机淘汰
  • volatile-lru:内存不够时对所有设置了过期时间的key进行lru(最近最少被使用)淘汰
  • volatile-random:内存不够时对所有设置了过期时间的key进行随机淘汰
  • volatile-ttl:内存不够时对所有设置了过期时间的马上就要过期的(不一定已经过期)key进行淘汰

六、Redis线程模型

都说redis快,之前还有memcached竞争,但是现在很少使用了。redis是单线程的,那么为什么依然能够保证如此的高效呢?

文件事件处理器

Redis单线程的原因是由于redis使用的文件事件处理器是单线程的,使用了IO多路复用技术同时监控多个socket。

  1. 客户端使用socket链接redis 的 socket server,发送链接请求
  2. redis 收到链接请求之后通过IO多路复用程序,将链接请求时间压送到事件队列
  3. 文件事件分派器从队列拿取事件,发现是一个连接事件,则将事件分发给连接应答处理器
  4. 连接应答处理器负责建立和客户端的通信以及关联该socket的AE_READABLE事件和命令请求处理器相关联
  5. 客户端发送读写请求
  6. 文件事件分派器发现是一个AE_READABLE时间,则将事件分发给命令请求处理器
  7. 命令请求处理器会执行相关操作并将该socket的AE_WRITABLE事件和命令回复处理器相关联
  8. 客户端准备读取返回
  9. 文件事件分派器发现是一个AE_WRITABLE时间,则将事件分发给命令回复处理器
  10. 命令回复处理器返回结果并且解除AE_WRITABLE和命令回复处理器的关联

高性能的原因

  • 纯内存操作
  • 非阻塞IO多路复用
  • 单线程避免上下文切换

七、Redis高并发

单机Redis QPS一般在万级到几万级(视机器性能差别),要支持更高的并发怎么办?

  • 主从部署
  • 集群部署

主从部署

  • 节点类型会分为master节点和slave节点
  • master节点负责写请求,slave负责读请求
  • master 会一部复制数据到slave节点
  • 复制并不会block其他操作,slave接受新的数据的过程中会使用旧数据提供服务,加载到内存后再删除旧数据集

注意的问题

一定要开启master节点的持久化,避免master重启后数据丢失,会将空数据集同步到slave,则整个缓存数据丢失

主从复制策略

  • 直接生成RDB文件快照发送给slave(全量复制)
  • slave先保存RDB,再进行加载(这段时间slave使用旧数据集提供服务)
  • master生成快照过程中新接受到的请求再slave加载完成后会使用异步的形式发送给slave,不是RDB形式(部分复制)
  • 主从同步会以一个offset标记来记录上次同步的位置,因此支持断点续传
  • master key过期会发送模拟的del操作给slave(避免slave单独淘汰导致的主从数据不一致)

主从容易导致的问题

  • 脑裂问题 因网络问题导致的master和slave节点通讯中断,此时写给master的数据将无法同步给slave

  • 主从切换 主从切换后主数据还没来的及同步给slave就宕机了

  • 解决: min-slaves-to-write:能够写数据必选有的slave节点数 min-slaves-max-lag:数据同步复制时间不能超过的时间

八、Redis高可用

  • 准备切换,哨兵模式
  • 分布式集群模式

Redis哨兵模式

最好至少3个实例 哨兵的功能:

  • 集群监控
  • 消息通知
  • 故障转移
  • 配置中心

切换流程

  1. 某个哨兵认为master掉线,此时处于sdown(主观宕机模式)
  2. 需要数量为quorum的哨兵都认为master掉线,则转化为odown(客观宕机模式)
  3. 需要majority数量的哨兵授权开始主备切换,哨兵才能开始主备切换
  4. 选举出合适的slave进行切换
  5. 选举影响因素 a. slave和master断开连接的时间长短,越短越好 b. 按每个slave配置的优先级(配置文件配置),越高越好

主从模式的缺陷

主从模式在所有节点都有一份完整的数据,因此整个缓存的数据上线就是master节点的数据上线,受限制于机器的内存。因此,引出redis的集群模式--redis cluster

Redis集群模式

redis 集群模式的每个节点都只存储了集群的部分数据,所以如果有大数据量需要扩展,直接扩展节点数量即可,适合大数据量场景

实现模式:主从模式组 + 多个组集群;其中每一个组相当于一套主从结构的系统。多个组间存储不同数据,共同构成集群。因此也具备了高并发(读写分离)和高可用(主从切换)

Redis Cluster为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会从从节点选取一个来充当主节点,从而保证集群不会挂掉。

例如集群有ABC三个主节点, 如果这3个节点都没有加入从节点,如果B挂掉了,我们就无法访问整个集群了。A和C的slot也无法访问。

所以我们在集群建立的时候,一定要为每个主节点都添加从节点, 比如像这样, 集群包含主节点A、B、C, 以及从节点A1、B1、C1, 那么即使B挂掉系统也可以继续正确工作。

B1节点替代了B节点,所以Redis集群将会选择B1节点作为新的主节点,集群将会继续正确地提供服务。 当B重新开启后,它就会变成B1的从节点。

不过需要注意,如果节点B和B1同时挂了,Redis集群就无法继续正确地提供服务了。

数据分布算法

结构特点:

  1. 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
  2. 节点的fail是通过集群中超过半数的节点检测失效时才生效。
  3. 客户端与redis节点直连,不需要中间proxy层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
  4. redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster负责维护node<->slot<->value。
  5. Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
  • redis cluster节点分配 假如现在有三个主节点分别是:A, B, C三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot)的方式来分配16384个slot的话,它们三个节点分别承担的slot区间是: 节点A覆盖0-5460; 节点B覆盖5461-10922; 节点C覆盖10923-16383.

  • 获取数据: 如果存入一个值,按照redis cluster哈希槽的算法: CRC16('key')%16384 = 6782。 那么就会把这个 key 的存储分配到节点B上了。同样,当我连接(A,B,C)任何一个节点想获取'key'这个key时,也会这样的算法,然后内部跳转到B节点上获取数据。

  • 新增一个主节点: 新增一个节点D,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上,我会在接下来的实践中实验。大致就会变成这样: 节点A覆盖1365-5460 节点B覆盖6827-10922 节点C覆盖12288-16383 节点D覆盖0-1364,5461-6826,10923-12287 同样删除一个节点也是类似,移动完成后就可以删除这个节点了。

集群节点间通讯的数据

节点间通讯使用gossip协议,传输的数据包括:故障消息,节点增删数据,hash slot分布信息

面向集群的Redis客户端

  • 重定向:

    • 客户端挑选任意节点发送请求
    • 实例收到后计算所属hash slot位置,如果不存在本机则发送重定向
    • 因此节点间存在大量重定向请求,效率不高,一般不使用
  • smart jedis: 本地维护一份hash slot的分布映射表,使用jediscluster api

九、Redis持久化

持久化的意义:数据落盘(不建议用redis做落盘数据库)、故障恢复(redis重启数据的还原)

持久化方式

RDB:对数据进行周期化的快照,直接存储原始数据

优点:

  • 适合做冷备,周期性的将rdb文件复制到其他地方
  • 备份速度快
  • 使用rdb文件恢复数据很快

缺点:

  • 周期性备份数据间隔时间较长,如果发生数据丢失则可能一丢失就丢失很长时间的数据,但是鉴于其备份方式又不适合把间隔调的很短
  • 数据量大的时候生成快照可能会暂停服务

AOF:每条写命令都记录日志

对每条写数据命令写入日志,使用append-only的方式追加日志,恢复数据是通过AOF文件进行回放 AOF文件较大时会基于当前的redis数据进行rewrite操作--重新构造更小的AOF文件

优点:

  • 备份间隔短,如果发生数据丢失则可能很少时间的数据、
  • append-only,写文件较快
  • 生成的日志是人类可读的数据,如果有修改数据的需求可以对AOF文件进行修改

缺点:

  • AOF文件一般会大于RDB文件
  • AOF开启后会降低QPS
  • AOF备份机制较复杂,可能出现还原的数据不一致的情况

建议两种备份都开启,互相弥补

十、Redis问题解决

缓存雪崩

缓存系统不可用导致的请求全部落到数据库 解决(其实不算解决,只能算补救):

  • 事前:尽量保证高可用,避免崩溃
  • 事中:使用encache + hystrix 进行接口限流/降级,避免更多的数据问题
  • 事后:redis持久化,尽快恢复

缓存穿透

恶意攻击导致的频繁读取数据库和缓存都没有的数据导致的DB压力暴增 解决: 走接口的数据请求可以在nginx层就做出恶意攻击的识别以及阻断 如果识别不了则在数据库查不到时在redis存储一个空值,之后的请求就直接走redis了

缓存数据库双写一致性

Cache Aside Pattern:

  • 读:读缓存,没有则查询数据库,再讲数据放到缓存
  • 写:先操作数据库,再淘汰缓存

为什么删除不用修改? lazy load思想:缓存的值可能需要通过计算得出且并不一定会用到,干脆用的时候再生成,从代码层次不会增加任何代码。

  • 问题:如果先操作数据库,再淘汰缓存,在原子性被破坏时:修改数据库成功了但淘汰缓存失败了,导致数据不一致问题。

那么改成:

  • 读:读缓存,没有则查询数据库,再讲数据放到缓存
  • 写:先删除缓存,再进行数据库操作。防止写数据库成功但是删除缓存失败导致的不一致,使用先删缓存再修改数据库时即使修改数据库失败,之后查出来缓存的值和数据库也是一致的(相应的数据库失败的问题会由业务逻辑处理)

上面的一定没问题吗?不一定 例如,一个请求刚删除了缓存,写数据库还没完成,另一个请求来了发现缓存没值,就去数据库查询并放到了缓存,依然出现了数据不一致。 如何解决:对于这类请求提取特征,比如这些请求都是对同一个商品进行的读写,那么,就将这些请求路由到同一个内存队列顺序处理。

这里注意

先考虑业务是否需要保证每次查询都要准确一致,比如抢购场景(抢购场景流量大会有上述高并发问题)商品库存显示和实际库存差一点可能问题也不大,反正下单时保证下单出货正确即可

如果需要保证:
并发的顺序性,如果在保证并发的同时还要保证某些顺序性,我们可以将有先后顺序特性的已批请求队列起来!但是没有关联的一批请求之间依然是并发的!
典型的例子:CurrentHashMap,不同的node之间不存在锁竞争

参考:https://www.cnblogs.com/rjzheng/p/9041659.html

并发竞争

具有竞争关系的操作串行化 不能的话可以使用分布式锁。 保证同时只有一个在操作,然后使用CAS保证不会出现旧数据覆盖新数据的情况。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×