Redis知识小结

部分内容整理自:

https://www.cnblogs.com/yiwangzhibujian/p/7047458.html
https://blog.csdn.net/striveb/article/details/95110502
https://mp.weixin.qq.com/s/2OTVJUTLOetYTD4Hpk-hFA
https://juejin.cn/post/6844903663224225806

1. Redis为啥这么快

  • 纯内存操作

    • Redis是一个KV内存数据库,它内部构建了一个哈希表,根据指定的KEY访问时,只需要O(1)的时间复杂度就可以找到对应的数据。同时,Redis提供了丰富的数据类型,并使用高效的操作方式进行操作,这些操作都在内存中进行,并不会大量消耗CPU资源,所以速度极快。image-20210929151720644

      image-20210929152137061

  • 单线程

    • Redis Server是多线程的,只是它的请求处理整个流程是单线程处理的
    • 单线程的方式是无法发挥多核CPU 性能,不过可以通过在单机开多个Redis 实例来完善
    • 优势
      • 没有了多线程上下文切换的性能损耗
      • 没有了访问共享资源加锁的性能损耗
      • 开发和调试非常友好,可维护性高
    • 缺点
      • 单线程处理最大的缺点就是,如果前一个请求发生耗时比较久的操作,那么整个Redis就会阻塞住,其他请求也无法进来,直到这个耗时久的操作处理完成并返回,其他请求才能被处理到。
  • 使用IO多路复用技术

    • 这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
    • Redis采用了IO多路复用技术和非阻塞IO,这个技术由操作系统实现提供,Redis可以方便地操作系统的API即可。Redis可以在单线程中监听多个Socket的请求,在任意一个Socket可读/可写时,Redis去读取客户端请求,在内存中操作对应的数据,然后再写回到Socket中。
    • 多路I/O复用模型是利用select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
  • 非CPU密集型任务

    • 采用单线程的缺点很明显,无法使用多核CPU。Redis作者提到,由于Redis的大部分操作并不是CPU密集型任务,而Redis的瓶颈在于内存和网络带宽。
    • 如果你觉得单个Redis实例的性能不足以支撑业务,Redis作者推荐部署多个Redis节点,组成集群的方式来利用多核CPU的能力,而不是在单个实例上使用多线程来处理。

2. Redis架构

  • 单点

  • 主从

    • 一个Master可以有多个Slaves
    • 默认配置下,master节点可以进行读和写,slave节点只能进行读操作,写操作被禁止
    • 不要修改配置让slave节点支持写操作,没有意义,原因一,写入的数据不会被同步到其他节点;原因二,当master节点修改同一条数据后,slave节点的数据会被覆盖掉
    • slave节点挂了不影响其他slave节点的读和master节点的读和写,重新启动后会将数据从master节点同步过来
    • master节点挂了以后,不影响slave节点的读,Redis将不再提供写服务,master节点启动后Redis将重新对外提供写服务
    • master节点挂了以后,不会slave节点重新选一个master
    • 用于数据备份和读写分离,master挂了就不能写入
  • 哨兵

    • sentinel模式是建立在主从模式的基础上,如果只有一个Redis节点,sentinel就没有任何意义

    • 当master节点重新启动后,它将不再是master而是做为slave接收新的master节点的同步数据

    • 当master节点挂了以后,sentinel会在slave中选择一个做为master,并修改它们的配置文件,其他slave的配置文件也会被修改,比如slaveof属性会指向新的master

    • sentinel因为也是一个进程有挂掉的可能,所以sentinel也会启动多个形成一个sentinel集群

    • 当主从模式配置密码时,sentinel也会同步将配置信息修改到配置文件中。

    • 一个sentinel或sentinel集群可以管理多个主从Redis。

    • sentinel最好不要和Redis部署在同一台机器,不然Redis的服务器挂了以后,sentinel也挂了

    • sentinel监控的Redis集群都会定义一个master名字,这个名字代表Redis集群的master Redis

    • 客户端就不要直接连接Redis,而是连接sentinel的ip和port

    • 具有高可用性,但存储量不够时要选用集群模式

      image-20210929152429775

  • 集群

    • sentinel和主从模式的结合体,通过cluster可以实现主从和master重选功能

    • 适合数据量巨大的缓存要求,当数据量不是很大使用sentinel即可

    • 群集至少需要3主3从

    • redis-master节点一般用于接收读写,而redis-slave节点则一般只用于备份,其与对应的master拥有相同的slot集合,若某个redis-master意外失效,则再将其对应的slave进行升级为临时redis-master

    • 默认的,一般redis-master用于接收读写,而redis-slave则用于备份,当有请求是在向slave发起时,会直接重定向到对应key所在的master来处理。但如果不介意读取的是redis-cluster中有可能过期的数据并且对写请求不感兴趣时,则亦可通过readonly命令,将slave设置成可读,然后通过slave获取相关的key,达到读写分离

    • 对Cluter读写有需求,可以水平扩展Master节点

    • 单机上通过多线程建立新redis-master实例(一般线程数为CPU核数的倍数)

    • 扩展更多的机器,部署新redis-master实例,如3主3从变成6主6从

    • redis-cluster进行新的水平扩容后,需要对master进行新的hash slot重新分配,这相当于需要重新加载所有的key,并按算法平均分配到各个Master的slot当中

      image-20210929152453743

3. Redis配置

  • maxmemory 设置最大内存,配合缓存释放策略

    • noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。
    • allkeys-lru:在所有键中采用lru算法删除键,直到腾出足够内存为止。
    • volatile-lru:在设置了过期时间的键中采用lru算法删除键,直到腾出足够内存为止。
    • allkeys-random:在所有键中采用随机删除键,直到腾出足够内存为止。
    • volatile-random:在设置了过期时间的键中随机删除键,直到腾出足够内存为止。
    • volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除。
  • 持久化

    • RDB

      • 默认方式

      • RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储

      • 占用空间小,容易丢数据

        image-20210929152518744

    • AOF

      • AOF持久化以日志的形式记录服务器所处理的每一个写。删操作,查询操作不会被记录。以文本的方式记录,可以打开文件看到详细的操作记录。
      • 3种同步策略:每秒同步,每修改同步和不同步
      • 占用空间大,IO大,恢复慢,适合分布式

4. Redis消息队列

  • Pub/sub断电清空
  • 实时性高但不可靠
  • Redis List功能单一

5. Redis vs Memcache

  • Redis 可持久化,支持更多存储类型,内存利用率一般
  • Memcache 不可持久化,只支持kv存储,内存利用率高,支持多核CPU

6. 雪崩

  • 大规模请求过程中,缓存同时失效,导致请求全部到数据库
  • 解决方法
    • 把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效
    • setRedis(Key,value,time + Math.random() * 10000)

7. 击穿

  • 跟雪崩有点类似,是请求一个热点Key,缓存突然失效,导致爆数据库
  • 解决方法
    • 设置热点数据永远不过期
    • 互斥锁
      • 在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,释放锁。若其他线程发现获取锁失败,则睡眠50ms后重试
      • 单机环境用并发包的Lock类型就行,集群环境则使用分布式锁(redis的setnx)

8. 穿透

  • 请求的Key不在缓存也不在数据库,导致数据库承担大量请求
  • 解决方法
    • 设置参数校验,比如ID<0的数据直接返回
    • 异常流量拦截(网关层,nginx、云防火墙)
    • 布隆过滤器
      • 能够迅速判断一个元素是否在一个集合中
        • 网页爬虫对URL的去重,避免爬取相同的URL地址
        • 反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱(同理,垃圾短信)
        • 缓存击穿,将已存在的缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉
      • 利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在就return,存在就去查了DB刷新KV再return

9. 缓存更新模式

  • Cache Aside Pattern(旁路缓存)

    • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

    • 命中:应用程序从cache中取数据,取到后返回。

    • 更新:先把数据存到数据库中,成功后,再让缓存失效。

    • PS:要么通过2PC或是Paxos协议保证一致性,要么就是拼命的降低并发时脏数据的概率,而Facebook使用了这个降低概率的玩法,因为2PC太慢,而Paxos太复杂。当然,最好还是为缓存设置上过期时间。

      image-20210929153012575

  • Read/Write Through Pattern

    • 可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache

    • Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。

    • Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)

      image-20210929153100201

  • Write Behind Caching Pattern

    • 是Linux文件系统的Page Cache的算法

    • Write Back套路,一句说就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库

    • 好处:I/O操作快(因为直接操作内存 ),因为异步,可以合并对同一个数据的多次操作,所以性能的提高是相当可观的

    • 缺点:数据不是强一致性的,而且可能会丢失

      image-20210929153129078

10. 缓存更新策略

  • 更新缓存 VS 淘汰缓存
    • 更新缓存:数据不但写入数据库,还会写入缓存;优点:缓存不会增加一次miss,命中率高
    • 淘汰缓存:数据只会写入数据库,不会写入缓存,只会把数据淘汰掉;优点:简单
    • 取决于“更新缓存的复杂度”
    • 淘汰缓存操作简单,并且带来的副作用只是增加了一次cache miss,建议作为通用的处理方式
  • 先操作数据库 vs 先操作缓存
    • 如果出现不一致,谁先做对业务的影响较小,就谁先执行
    • 数据和缓存的操作时序:先淘汰缓存,再写数据库。使用缓存过程中,经常会遇到缓存数据的不一致性和脏读现象。一般情况下,采取缓存双淘汰机制,在更新数据库的前淘汰缓存。此外,设定超时时间,例如三十分钟
  • 缓存架构优化
    • 应用读写数据库和缓存:业务方需要同时关注缓存与DB
    • 加入一个服务层,业务线不需要关注数据是来自于cache还是DB
    • 异步缓存更新:业务线所有的写操作都走数据库,所有的读操作都走缓存,由一个异步的工具来做数据库与缓存之间数据的同步
      • 通过MySQL自动同步刷新Redis,MySQL触发器+UDF函数实现
        • 这种方案适合于读多写少,并且不存并发写的场景
        • 因为MySQL触发器本身就会造成效率的降低,如果一个表经常被操作,这种方案显示是不合适的
      • 解析MySQL的binlog实现,将数据库中的数据同步到Redis
        • 解析binlog(Canal 阿里)
          • 伪装自己为mysql slave
          • mysql master收到dump请求,开始推送binary log给slave(也就是canal)
          • canal解析binary log对象
        • 要有一个init cache的过程,将需要缓存的数据全量写入cache
        • 如果DB有写操作,异步更新程序读取binlog,更新cache

Redis知识小结

https://wurang.net/redis/

作者

Wu Rang

发布于

2021-08-27

更新于

2021-09-29

许可协议

评论