redis学习
Redis简介
数据库
Redis数据存储在内存中(内存数据库),存储数据为Key-Value形式,单线程(指网络IO以及数据读写只由一个线程完成),其他功能例如持久化,异步删除,集群数据同步等是由额外的线程完成的,采用epoll异步IO多路复用,Redis所有操作均为原子操作,能够确保数据的一致性和完整性,广泛用于缓存方向,支持数据的持久化
单线程与多线程
Redis在V4.0以前一直采用单线程的原因如下:
(1)单线程的维护和开发简单,在出现问题时更好定位
(2)单线程通过IO多路复用和非阻塞IO也能够并发地处理多客户端请求
(3)Redis操作的数据都存储在内存,资源瓶颈大多在内存和网络带宽,并非CPU
Redis在V4.0引入多线程的原因(删除操作LazyFree线程)
(1)删除问题,使用DEL命令删除key非常大的对象时,会造成主线程卡顿,引入多线程可以将删除工作交给子线程异步删除数据
DEL删除操作是在主线程处理的,会导致卡顿
UNLINK/FLUSHALL删除操作是交给后台线程执行的,不会导致卡顿
Redis在V6.0引入真正多线程的原因(网络IO请求多线程)
(1)随着网络硬件的提升,Redis的瓶颈会出现在网络IO上,单主线程处理网络请求的速度跟不上底层网络硬件的速度,Redis6/7采用多线程来处理网络IO请求,读写命令仍然使用单线程处理
线程协作(RedisV6.0)
阶段1ACCEPT:服务端和客户端建立连接,分配处理线程
主线程负责接收连接请求,当客户端请求建立Socket连接时,主线程(内核监听)会创建和客户端的连接并把Socket放入全局等待队列中,之后主线程会通过轮询的方法把Socket连接分配给IO线程
阶段2READ:IO线程读取并解析请求
主线程把Socket分配给IO线程之后会进入阻塞状态,IO线程会完成客户端请求的读取和解析,多个IO线程并行处理会让主线程的阻塞时间很短
阶段3EXECUTE:主线程执行解析出的请求操作
主线程仍会以单线程执行网络IO线程解析出来的命令
阶段4WRITE:处理完成网络IO线程将数据回写Socket
主线程处理完成请求后会将数据写入缓冲区并进入阻塞状态,等待IO线程完成数据回写到Socket(并发执行),完成了会从等待队列中清除
Redis V7.0新特性
(1)redis.conf配置 网络IO多线程参数
io-threads 4
(2)读进程IO多线程参数
io-threads-do-reads yes设置为yes可以让READ环节也为多线程处理,一般帮助不大
(3)DEL异步等价(V6.0引入)
lazyfree-lazy-user-del yes设置为yes在执行DEL命令时释放内存的操作也会放到后台线程中异步执行
(4)FLUSHALL FLUSHDB异步等价(V6.2引入)
lazyfree-lazy-user-flush yes设置为yes在执行FLUSH命令时会等同于后面加ASYNC进行异步删除,
数据持久化
RBD
优点:RDB持久化可以生成紧凑的RDB文件(compact类型)且使用RDB恢复数据的速度非常快,但是全量持久化模式可能会在停机时丢失大量数据
AOF
将用于Redis服务器收到的写操作追加到日志文件,该机制可以保证服务器重启之后依然可以依靠日志文件恢复数据
优点:
(1)AOF持久化可以通过appendfsync everysec配置将丢失数据的时间窗口限制在1s之内,但是AOF文件是协议文本形式,体积比RDB大很多,数据恢复也相对较慢
(2)使用命令追加的形式来构造,在服务器异常关机时AOF不完整的问题也可以使用redis-check-aof工具来修正AOF文件
缺点:
(1)相同数据量的情况下,AOF文件通常比RDB文件体积更大
RDB-AOF混合持久化
aof-use-rdb-preamble yes设置为yes,在redis4.0版本之后推出
同步机制
> # appendfsync always 表示每次写入都执行fsync(刷新)函数
> # appendfsync everysec 每秒执行一次fsync函数 默认1s一次,最多有1s丢失
> # appendfsync no 由操作系统保证数据同步到磁盘,速度最快
重写机制
# 重写触发机制
auto-aof-rewrite-percentage 100(64MB->128MB才会重写)
auto-aof-rewrite-min-size 64mb(首次触发后则不依据这个变量)
机制:直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件去替换原来的AOF文件
缓冲区
AOF缓冲区:Redis在执行完命令进行持久化的时候,需要先写入AOF缓冲区内,之后再同步刷盘
AOF重写缓冲区:Redis是单线程工作,重写AOF直接在主进程上运行需要比较长的时间,Redis采用fork一个子进程来执行AOF重写,这样不会影响主进程的正常运行,但是会消耗额外的内存,AOF重写缓冲区是在创建子进程之后使用,在进行AOF重写的过程中,父进程会将开始重写之后执行的写命令发送到AOF缓冲区的同时也发送到AOF重写缓冲区,当子进程完成了AOF重写后,就会给父进程发一个信号(这个信号会极短暂阻塞服务器进程),父进程调用函数将重写缓冲区内的内容追加到重写后的AOF文件中
数据删除策略
定时删除:当key设置有过期时间,且过期时间到达时,立即执行key的删除操作
优点:节约内存,到时就删除,立即释放不必要的内存占用
缺点:CPU压力较大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
定期删除:由redis.conf中的hz参数决定,每秒执行多少次 hz(value)次,默认值10,每100ms执行一次定期删除。从数据库中取出一定数量的随机键进行检查,并删除其中的过期键
优点:cpu峰值可控,缓解内存压力
缺点:可能存在大量key过期但是没有被随机抽取到,一直存放在内存中
惰性删除:数据到达过期时间,先不做处理。等下次访问该数据时,发现数据已过期,删除,给客户端返回不存在
优点:中和以上两种方案
缺点:可能存在过期key一直未被访问到,一直存放在内存中
内存淘汰策略
主要分为进行数据淘汰和不进行数据淘汰,默认为noeviction不进行任何淘汰,在64位操作系统中,如果Key没有设置过期时间默认情况下就会导致Redis异常
针对「进行数据淘汰」这一类策略,又可以细分为「在设置了过期时间的数据中进行淘汰」和「在所有数据范围内进行淘汰」这两类策略
config get maxmemory-policy 命令,来查看当前 Redis 的内存淘汰策略
缓存击穿
缓存击穿的场景主要是发生在热点数据失效的一瞬间,大量的热点数据访问导致大量请求直接打到了数据库
解决方案:
(1)设置热点数据永不过期,定时更新热点数据的值
(2)提前续期热点数据,防止失效
(3)设置互斥锁
缓存穿透
缓存穿透的主要场景主要是发生在大量查询的数据都不在Redis数据库中,导致请求直接访问到了数据库
解决方案:
(1)为不存在的数据缓存一个空值,这个方案对于小型系统可行,但是对于恶意不同的不存在数据的大量访问也无法避免
(2)布隆过滤器,用于快速判断不存在的数据,布隆过滤器上hash key不存在那么缓存中一定不存在,存在可能缓存里也没有,有一定的误判率,数据集发生变化无法更新
(3)请求参数校验,过滤掉一些明显不符合要求的请求,判断key值
缓存雪崩
缓存雪崩的主要场景就是大量数据在同一时间过期,导致这些请求都访问到了后端数据库,造成了很大的访问压力
解决方案:
(1)过期时间在允许范围内随机设
(2)高可用集群,多副本,数据分片,缓解访问压力
(3)业务熔断限流,限流根本原理是通过令牌桶或者漏桶原理来做,可以在业务层去做,最好是在Nginx负载均衡那里去做
Redis选举策略
Redis集群选举机制主要基于Raft算法,是一种分布式强一致算法
所有节点之间通过ping-pong机制建立通信且传输状态信息
Redis分片集群一共有16384个槽位,每个Slave都会与自己的Master通信,当Slave发现Error condition on socket for SYNC: Connection refused,同步消息失败,超时之后就会将集群状态标为Cluster state changed: fail,Slave就会开始进行选举Starting a failover election for epoch 9,如果获得过半的投票,Current Epoch就会增加1,后Cluster state changed: ok(OK表示每一个槽位都能找到对应的master节点)
RDB持久化流程
Bgsave()使用fork()原理和copy on write写时复制
主进程Fork出一个子进程,此时子进程共享主进程创建子进程时刻的内存空间,内核把主进程的内存页设置为只读,子进程开始持久化,主进程依旧对外提供服务,主进程写数据的时候由于只读会触发缺页异常,内核就会把异常的页拷贝一份用于主进程修改。