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资源,所以速度极快。
单线程
- 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
具有高可用性,但存储量不够时要选用集群模式
集群
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当中
3. Redis配置
maxmemory 设置最大内存,配合缓存释放策略
- noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。
- allkeys-lru:在所有键中采用lru算法删除键,直到腾出足够内存为止。
- volatile-lru:在设置了过期时间的键中采用lru算法删除键,直到腾出足够内存为止。
- allkeys-random:在所有键中采用随机删除键,直到腾出足够内存为止。
- volatile-random:在设置了过期时间的键中随机删除键,直到腾出足够内存为止。
- volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除。
持久化
RDB
默认方式
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储
占用空间小,容易丢数据
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太复杂。当然,最好还是为缓存设置上过期时间。
Read/Write Through Pattern
可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache
Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。
Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)
Write Behind Caching Pattern
是Linux文件系统的Page Cache的算法
Write Back套路,一句说就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库
好处:I/O操作快(因为直接操作内存 ),因为异步,可以合并对同一个数据的多次操作,所以性能的提高是相当可观的
缺点:数据不是强一致性的,而且可能会丢失
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
- 解析binlog(Canal 阿里)
- 通过MySQL自动同步刷新Redis,MySQL触发器+UDF函数实现
Redis知识小结