一、Redis概念
1.1 Redis定义
Redis(Remote Dictionary Server)是一个开源的、基于内存的数据结构存储系统,通常用作数据库、缓存或消息代理。它支持多种数据结构,如字符串(string)、哈希(hashes)、列表(list)、集合(sets)、有序集合(sorted sets)等,并提供丰富的操作命令。
1.2 Redis的特点
**高性能:**数据存储在内存中,读写速度极快,适合高并发场景。
**持久化:**支持RDB(快照)和AOF(日志)两种持久化机制,确保是数据安全。
**数据结构丰富:**支持多种数据结构,满足不同业务需求。
**原子操作:**所有操作都是原子性的,适合分布式锁等场景。
**扩展性:**支持主从复制、分片(集群)等功能。
1.3 Redis为什么要持久化?
Redis是一个内存数据库,也就是说,你的数据默认是放到内存里的。
内存的特点是什么?速度飞快,但是一断电全都没了 ,所以为了防止Redis重启或者服务器宕机后数据丢失,他必须把内存里的数据保存到磁盘上,这个把内存数据保存到硬盘的动作就叫持久化
什么是RDB?(像给游戏存档拍一张快照)
怎么理解?
RDB是Redis的"快照"持久化方式
它的核心动作是:在某个时间点,把Redis内存中的所有数据,完整的写入磁盘上的一个二进制文件,理解成给Redis数据库拍了一张"全量照片"。
比如你在玩一个大型单机游戏,玩了两个小时,此时点一下存档(BGSAVE),游戏会把你当前这一刻的完整进度,全部压缩成一个存档文件存在硬盘里,这个"存档文件",就是Redis里的RDB文件
RDB什么时候会"拍照存档"?
手动按保存:Redis执行BGSAVE命令
设置自动定时存档:比如配置save 60 100,意思是"如果60s内至少有100个数据被修改了,就自动存一次档"
正常退出:Redis正常关闭时,也会自动存一次档
RDB是怎么拍快照的?
RDB最精妙的地方:利用Linux的fork()系统调用,创建子进程来生成快照,不影响主进程继续处理客户端请求。
主进程(Redis Server)
|
fork()
|
├── 主进程继续处理请求(不阻塞)
│
└── 子进程(独立)
|
遍历内存中的所有数据
|
写入临时 RDB 文件
|
完成后替换旧 RDB 文件
关键技术点:(写时复制)
fork()出的子进程和父进程共享同一块物理内存页。
父进程(主redis)在写入数据时,如果发现某个内存页被共享,会先复制一份新页面再修改
子进程从头到尾读的都是触发快照那一瞬间的完整数据,不会受后续写入的影响
RDB有什么优缺点?
优点:文件非常小;恢复极快
缺点:容易丢数据;存档时耗性能
什么是AOF?(像给游戏写操作日记)
怎么理解?
比如你玩游戏,旁边坐了一个书记员,你每做一个动作,书记员就立马在日记本上记一笔:玩家获得了一把剑;玩家砍死了一只猪;玩家走到河边
这个日记本就是Redis里的AOF文件。
AOF什么时候记日记?
Redis默认是每执行一条写命令,就会立刻把这条命令写到AOF文件末尾。
写到硬盘还是写到内存?
**最勤快:**写完一条命令,立马把日记放到磁盘里锁好
**一般勤快:**先把命令记住,每隔一秒才统一写到硬盘里
**最懒:**什么时候写到硬盘里交给操作系统决定
AOF的"日记本"会无限变大,怎么办?
很重要的机制:重写机制
分身不看旧的日记本,而是直接去看当前游戏的最终状态(重写)
玩了一整天游戏,书记员的日记本已经写了有一万页了。其中有一页写"拿到一块钱",后面有1000页都在写"花掉1块钱",最后还有一页写"银行存款0"
如果要恢复今天的游戏进展,书记员要从头翻这一万页日记,逐条执行,太慢了
所以Redis会不定期做一件事:重新整理日记本(AOF重写)
- Redis叫来一个分身
- 分身不看旧的日记本,而是直接去当前最终的状态
- 把这一万页日记,浓缩成两页:SET 银行存款 0;删除前面那一万页废话
- AOF重写,就是把"过程日记"精简成"当前结果状态",让日记本永远保持苗条
AOF优缺点?
优点:数据安全性高;实时性好
缺点:文件体积大;恢复速度慢
1.4 Redis的用途
缓存:加速数据访问,减轻后端数据库压力
会话存储:存储用户会话信息,支持分布式系统
消息队列:利用列表或者发布/订阅模式实现消息传递
实时排行榜:使用有序集合实现实时排行榜功能,使用Redis有序集合(ZSET)
Redis的ZSET,完美解决了这个问题。它像是一个自动排序的花名册,每个元素都有一个分数(score),内部通过跳表(skip list)实现,让增删改查排名的操作时间复杂度都变为O(logN)
可以把它当作一个自带排序功能的超级数组,无论有几百万个元素,插入一个新数据或者查询某个数据的排名都非常快
缓存策略:redis和Buffer Pool是如何协作的?
实际采用的是旁路缓存模式
读请求:
应用->先查redis
->命中->直接返回
->未命中->查MySQL(此时MySQL可能查Buffer Pool或者磁盘)
->将查到的结果写入Redis(设置过期时间)
->返回
写请求:
应用->先更新Mysql(数据落盘/落到 Buffer Pool)
->然后删除Redis 中的旧缓存(或更新)
为什么先更新MySQL,而不是先更新Redis?
因为要保证最终数据一致性。如果先更新Redis成功了,但是MySQL更新失败了,Redis里就成了脏数据,所以一般都是先更新数据库,再删缓存(下次要读请求重新加载)