持久化
- RDB持久化:能够在指定的时间间隔对数据进行快照存储。
- AOF持久化:记录每次的操作,在重启服务器时,重新执行这些命令,来恢复原始数据。这样能确保所有数据都能够被持久化。
RDB持久化:
优点:
- 恢复大数据量时,速度比AOF快一些
- 根据不同时间,保存不同的版本。
- 保存时,只需要fork一个子进程,然后全都由子进程来完成,能够最大化Redis的性能。
缺点:
- 保存时间间隔长,可能丢失部分数据
RDB能够把当前的数据生成快照保存到硬盘,触发方式分为手动触发和自动触发。
- 手动触发:
- 手动触发对应save命令,会阻塞当前Redis服务器,知道RDB结束。对于内存较大的实例会造成长时间阻塞,线上环境不建议使用。
- 自动触发:
- 自动触发对应bgsave命令,Redis进程执行fork操作创建子进程,持久化过程由子进程来完成,阻塞只发生在fork阶段,一般时间很短。如果执行shutdown命令并且没有开启AOF功能,则会自动执行bgsave。
AOF持久化:
优点:
- 拥有fsync策略(每秒保存或每次保存),保存的数据比RDB更加完整。
缺点:
- 对于相同数据,AOF文件的体积比RDB的更大
- 数据恢复时间比RDB更长。
开启AOF功能需要配置:appendonly yes
AOF文件名通过appendfilename配置设置,保存路径和RDB持久化方式一致。
命令会先存放到缓冲区aof_buf中。如果每次都直接追加到硬盘,那么效率完全决定于当前硬盘负载。并且Redis提供多种缓冲区同步硬盘的策略,能够在性能和安全性方面做出平衡。
AOF为了文件过大,拥有重写机制。将无效的语句省略。
AOF的重写分为手动触发和自动触发
- 手动触发:直接调用bgrewriteaof命令。
- 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。
常见问题
缓存穿透:
指用户请求的数据在缓存中不存在即没有命中,同时数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍,然后返回空。
解决方案:
- 布隆过滤器(Bloom Filter 简称BF):专门用来检测集合中是否存在特定元素。
- 优点:
- 节省空间:不需要存储数据本身,只需要存储数据对于hash比特位
- 时间复杂度低:插入和查找的时间复杂度都为O(K),K为哈希函数的个数
- 缺点:
- 存在假阳性:布隆过滤器判断存在,元素可能不在集合中,判断准确率取决于哈希函数的个数
- 不能删除元素:如果一个元素被删除,但是不能从布隆过滤器中删除,就会造成假阳性。
- 优点:
- 返回空对象:当缓存未命中,查询持久层也为空,那么返回一个空对象到缓存中,下次请求时直接返回空对象,为了存储过多空对象,通常设置一个过期时间。
- 问题所在:
- 如果有大量key穿透,缓存空对象会占用宝贵的内存空间。
- 空对象的key设置了过期时间,在这段时间内,可能存在缓存和持久层数据不一致。
- 问题所在:
缓存击穿:
指一个key非常热点,不停地扛着大并发,当这个key失效的瞬间,持续的大并发就会穿破缓存,直接请求数据库,此时的数据库压力巨大呀!
解决方案:
- 使用互斥锁:当缓存失效时,只让大并发中的一个线程去读取数据库,并且写回缓存,此时其他的请求都等待。
- 热点数据永不过期:
- 物理不过期:不给热点key设置过期时间。
- 逻辑过期:把过期时间存放在value中,如果要过期了,那么就通过一个异步线程,进行缓存的重新构建。
缓存雪崩:
指缓存中数据大批量到过期时间,并且访问数据量巨大,请求直接落到数据库上,引起数据库压力过大。与缓存击穿不同的是,雪崩是多条数据过期。
解决方案:
- 均匀过期:设置不同的过期时间,是失效的时间点尽量均匀。
- 加互斥锁:同一时间只让一个线程构建缓存,其他线程阻塞排队。
- 缓存永不过期:类似缓存击穿中的数据不过期。
- 双层缓存策略:使用主备两层缓存:
- 主缓存:有效期按照经验值设置,设置为主读取的缓存,主缓存失效后从数据库加载最新值。
- 备份缓存:有效期长,获取锁失败时读取的缓存,主缓存更新时需要同步更新备份缓存。
热点预热:
指系统上线后,将相关的缓存数据直接加载到缓存系统,这样可以避免用户请求时,先查询数据库,再将数据写回到缓存中。
如果不进行预热,Redis初始为空,对于高并发的流量都会直接访问到数据库中。
操作方法:
- 数据量不大的时候,工程启动时进行加载缓存动作。
- 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新。
- 数据量超级大的时候,优先保证热点数据进行提前加载到缓存中。
缓存降级:
指缓存失效或则和缓存服务器挂掉的时候,不去访问数据库,而是直接返回默认数据或者访问服务的内存数据。
内存淘汰机制
Redis缓存内存不足时,淘汰旧数据处理新加入数据的策略。
淘汰分类:
- noeviction:默认策略,对于写请求直接返回错误,不进行淘汰。
- allkeys-lru:最近最少使用,从全体key中使用近似LRU算法进行淘汰
- volatile-lru:最近最少使用,从设置了过期时间的key中使用近似LRU算法进行淘汰
- allkeys-random:从所有key中随机淘汰。
- volatile-random:从设置了过期时间的key中随机淘汰。
- volatile-ttl:越早过期的越先淘汰。
- allkeys-lfu:最少使用频率,再所有key中,使用近似LFU算法进行淘汰
- volatile-lfu:最少使用频率,再设置了过期时间的key中,使用近似LFU算法进行淘汰
以上算法中的要加上“近似”两个字,是因为redis使用的淘汰算法是先随机出m个key,再从中按照算法淘汰掉一个key,m是可以设置的参数(maxmemory-samples)
Redis事务与关系型数据库中的事务对于原子性的概念是不同的
Redis发送错误时,不会停止执行并回滚数据。
Redis主从复制
指将一台Redis服务器的数据复制到其他Redis服务器,前者称为主节点,其余称为从节点。
作用:
- 数据冗余:实现了数据热备份。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。
- 负载均衡:实现读写分离,主节点提供写服务,从节点提供读服务,分担服务压力。
- 高可用基石:
实现原理:
- 连接建立阶段:主从节点之间建立连接,为数据同步做准备。
- 保存主节点信息:
- 建立socket连接:
- 发送ping命令:
- 身份验证:
- 发送从节点端口信息:
- 数据同步阶段:进行从节点数据的初始化,向主节点发送psync命令,开始同步。
- 命令传播节点:数据同步完成后,主节点进入命令传播阶段,将自己执行的写命令发送给从节点,从节点接收并执行,来保证主从节点数据的一致性。由于命令传播是异步问题,并且主节点发送不会验证从节点是否接收到,以及主从节点的网络问题,很容易造成主从节点数据不一致。
Sentinel(哨兵模式)
当主节点发生故障,不能提供服务时,能够自动完成故障发生和故障转移,并通知客户端。
心跳机制:
- Sentinel和Redis Node:哨兵模式创建的时候,会通过配置,讲Sentinel和Redis Master Node连接,Sentinel从主节点上获取所有从节点的信息。之后定时(10s)向主从节点发送info命令,获取所有拓扑结构和状态信息。
- Sentinel和Sentinel:基于Redis的订阅发布功能,每个Sentinel节点会每秒一次向主节点,从节点以及其他Sentinel进程发送一个ping命令。
故障转移:
每个sentinel会定时进行心跳检查,如果发现主节点出现心跳检测超时,此时会认为该主节点已经不可用,被称为主观下线。随后向其他sentinel节点发送“sentinel ismaster-down-by-addr”命令,询问大家对主节点的判断,当人数超过quorum(法定人数)就会认为该主节点故障,执行客观下线。(通常认为quorum的值是sentinel节点总数/2+1)
判断客观下线之后,sentinel节点之间会再做一次选举工作,基于Raft算法选一个领导者来执行故障转移工作。步骤如下:
- 在从节点中选一个作为主节点
- 领导者节点对选出来的从节点执行“slaveof on one”命令使其成为主节点
- 向剩余从节点发送命令,让他们从新的主节点上复制数据
- 领导者将原来的主节点更新为从节点,并对其进行监控,回复后命令他去复制新主节点数据
