java基础(十二)redis 日志机制以及常见问题
1 Redis 持久化机制
1.1 持久化概述
Redis 作为基于内存的数据库,其所有读写操作均在内存中进行,因此具备极高的性能。然而,内存中的数据在 Redis 重启后会丢失。为了确保数据的持久性,Redis 实现了数据持久化机制,将数据保存到磁盘中,以便在重启后能够恢复数据。Redis 主要支持两种持久化方式:AOF(Append Only File)日志和RDB(Redis Database)快照。
1.2 AOF 日志持久化
1.2.1 实现原理
AOF 日志通过记录每一条写操作命令来实现持久化。每当执行一条写命令时,Redis 会将该命令以追加的方式写入 AOF 文件。当 Redis 重启时,通过重新执行 AOF 文件中的命令来恢复数据。
例如,执行命令 set name xiaolin
后,AOF 文件中会记录如下内容:
*3
$3
set
$4
name
$7
xiaolin
1.2.2 写回策略
Redis 提供了三种 AOF 日志写回硬盘的策略,可通过 appendfsync
配置项设置:
Always:每次写命令执行完成后,立即同步将 AOF 日志数据写回硬盘。这种策略能保证最好的数据安全性,但性能开销最大。
Everysec:每次写命令执行完后,先将命令写入内核缓冲区,然后每隔一秒将缓冲区内容写回硬盘。在性能和数据安全之间取得平衡,是默认推荐策略。
No:写命令执行完后,只写入内核缓冲区,由操作系统决定何时写回硬盘。性能最好,但数据安全性最低。
1.2.3 优缺点分析
优点:
数据安全性高,默认每接收到一个写命令就会追加到文件末尾,即使服务器宕机,也只会丢失最后一次写入前的数据。
支持多种同步策略,可根据需要在数据安全性和性能之间进行权衡。
提供 AOF 重写机制,优化文件体积,加快恢复速度。
提供
redis-check-aof
工具修复损坏的 AOF 文件。
缺点:
AOF 文件通常比 RDB 文件更大,消耗更多磁盘空间。
频繁的磁盘 IO 操作(尤其是
always
策略)可能对写入性能造成影响。恢复过程可能需要重放所有操作命令,速度较慢。
1.3 RDB 快照持久化
1.3.1 实现原理
RDB 快照通过某一时刻的内存数据生成二进制快照文件来实现持久化。RDB 文件记录的是实际数据,而非操作命令,因此恢复效率较高。
Redis 提供了两个命令生成 RDB 文件:
save:在主线程执行,会阻塞主线程,适用于数据量小或可接受短暂阻塞的场景。
bgsave:创建子进程执行,避免阻塞主线程,是推荐的生产环境用法。
1.3.2 优缺点分析
优点:
文件体积小,备份和恢复速度快。
通过子进程操作,对 Redis 服务性能影响小。
适合大规模数据备份和灾难恢复。
缺点:
两次快照之间的数据可能丢失,可靠性不如 AOF。
快照生成期间的大量写操作可能导致恢复数据不一致。
1.4 持久化策略选择建议
如果数据安全性要求极高,可同时开启 AOF 和 RDB,但需要注意性能开销。
如果可容忍分钟级数据丢失,可仅使用 RDB。
通常建议使用 AOF 的
everysec
策略,兼顾性能和安全。
2 Redis 内存管理
2.1 过期键删除策略
Redis 采用惰性删除和定期删除两种策略结合的方式管理过期键。
2.1.1 惰性删除
在访问或修改 key 前检查是否过期,若过期则删除。这种方式节省 CPU 时间,但可能导致大量过期 key 堆积。
// 示例:Java 中模拟惰性删除逻辑
public Object get(String key) {Object value = store.get(key);if (value != null && isExpired(key)) {store.remove(key);return null;}return value;
}
2.1.2 定期删除
Redis 默认每秒进行 10 次过期检查(可通过 hz
配置),每次随机抽取 20 个 key 检查并删除过期 key。如果本轮删除的过期 key 数量超过 5 个(25%),则继续重复抽查过程,直到已过期 key 比例低于 25% 或超过 25ms 时间限制。
2.2 内存淘汰策略
当内存使用达到上限时,Redis 会根据配置的内存淘汰策略删除键值对。Redis 3.0 以后默认策略为 noeviction
。
2.2.1 淘汰策略分类
不淘汰数据:
noeviction
(默认策略),拒绝所有写入请求,只允许读和删除操作。在设置了过期时间的数据中淘汰:
volatile-random
:随机淘汰volatile-ttl
:优先淘汰更早过期的volatile-lru
:淘汰最久未使用的volatile-lfu
:淘汰最少使用的(Redis 4.0+)
在所有数据范围内淘汰:
allkeys-random
:随机淘汰allkeys-lru
:淘汰最久未使用的allkeys-lfu
:淘汰最少使用的(Redis 4.0+)
2.2.2 策略选择建议
如果数据重要性不同,建议为关键数据设置过期时间并使用
volatile-xxx
策略。如果所有数据重要性相当,可使用
allkeys-lru
或allkeys-lfu
。如果数据访问模式相对随机,可使用
allkeys-random
。
3 Redis 集群与高可用
3.1 主从复制
3.1.1 全量同步
当从服务器首次连接主服务器或数据差异过大时,会触发全量同步:
从服务器发送
SYNC
命令主服务器生成 RDB 快照并发送给从服务器
从服务器加载 RDB 文件
主服务器将期间的写命令发送给从服务器
3.1.2 增量同步
基于 PSYNC
命令实现,使用运行 ID 和复制偏移量机制:
从服务器发送
psync
命令携带偏移量主服务器判断偏移量是否在复制积压缓冲区中
如果在,发送增量数据;否则触发全量同步
优化建议:适当增大 repl_backlog_size
可减少全量同步概率。
3.2 哨兵机制
哨兵(Sentinel)用于实现主从节点故障转移,主要功能包括监控、选主和通知。
3.2.1 故障转移流程
主观下线:单个 Sentinel 节点检测到主节点无响应
客观下线:多个 Sentinel 节点确认主节点故障
选举 Leader:Sentinel 集群选举 Leader 负责故障转移
选择新主节点:基于优先级、复制偏移量和 runid 选择新主节点
3.2.2 选举算法
Sentinel Leader 选举基于 Raft 算法,需要获得多数票(quorum 和 Sentinel 节点数/2+1 的最大值)。
3.3 Redis Cluster
Redis 集群采用分片技术,通过哈希槽(16384 个槽)将数据分布到不同节点。
3.3.1 数据分片
键值对通过 CRC16 算法计算哈希值,然后对 16384 取模确定所属哈希槽。哈希槽可以手动或自动分配到集群节点。
3.3.2 优缺点分析
优点:
高可用性:主从复制保证故障转移
高性能:数据分片提高读写性能
扩展性好:可动态增加或减少节点
缺点:
部署和维护复杂
集群同步可能存在延迟
某些操作不支持跨节点执行
4 Redis 应用场景
4.1 为什么使用 Redis
Redis 因其高性能和高并发特性被广泛应用:
高性能:基于内存操作,速度极快
高并发:单机 QPS 可达 10w+,远高于 MySQL
4.2 常见应用场景
4.2.1 缓存
最常见的用途,将热点数据存储在内存中,减轻数据库压力。
4.2.2 排行榜
使用有序集合实现实时排行榜系统。
4.2.3 分布式锁
通过 SETNX 等命令实现分布式环境下的互斥访问。
// 示例:基于 Redis 的分布式锁实现
public boolean tryLock(String key, String value, long expireTime) {String result = jedis.set(key, value, "NX", "PX", expireTime);return "OK".equals(result);
}
public boolean unlock(String key, String value) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value));return result.equals(1L);
}
4.2.4 计数器
利用原子操作实现高性能计数器功能。
4.2.5 消息队列
使用 List 结构或 Pub/Sub 模式实现轻量级消息队列。
4.3 本地缓存 vs Redis 缓存
4.3.1 本地缓存
优点:访问速度快、低延迟、减轻网络压力
缺点:可扩展性有限、数据一致性难保证
4.3.2 Redis 缓存(分布式缓存)
优点:可扩展性强、数据一致性高、易于维护
缺点:访问速度相对慢、网络开销大
选择建议:根据数据大小、网络状况和业务特点权衡选择。
5 Redis 实践问题与解决方案
5.1 缓存一致性
5.1.1 读写策略
采用旁路缓存策略:
读数据:先读缓存,未命中则读数据库并写入缓存
写数据:先更新数据库,再删除缓存
5.1.2 保证最终一致性
删除缓存重试机制(通过消息队列)
订阅 MySQL binlog(通过 Canal 中间件)
5.2 缓存异常场景
5.2.1 缓存雪崩
问题:大量缓存同时失效或 Redis 故障,导致所有请求直接访问数据库。
解决方案:
均匀设置过期时间(加随机数)
互斥锁更新缓存
后台更新缓存策略
5.2.2 缓存击穿
问题:热点数据过期,大量请求直接访问数据库。
解决方案:
互斥锁更新
热点数据永不过期,后台异步更新
5.2.3 缓存穿透
问题:请求不存在的数据,绕过缓存直接访问数据库。
解决方案:
接口层校验请求参数
缓存空值或默认值
使用布隆过滤器
5.3 大 Key 与热 Key 问题
5.3.1 大 Key 问题
定义:通常指 value 大小 > 1MB 或集合元素数量 > 1w。
影响:内存占用高、性能下降、阻塞操作、网络拥塞等。
解决方案:
拆分大 Key
清理不必要数据
监控内存水位
定期清理过期数据
5.3.2 热 Key 问题
定义:访问频率异常高的 Key。
解决方案:
复制热 Key 到多个分片
使用读写分离架构
本地缓存热 Key
5.4 秒杀场景设计
5.4.1 数据库层面解决
加排他锁:
select * from goods where goods_id = ? for update
库存限制:
update goods set stock = stock - 1 where goods_id = ? and stock > 0
5.4.2 分布式锁方案
同一时间只允许一个客户端处理同一商品的秒杀请求。
5.4.3 分布式锁+分段缓存
将库存分段存储,提高并发处理能力。
5.4.4 Redis 原子操作+异步队列
预减库存:
DECR
原子操作请求入队:异步处理秒杀请求
出队处理:生成订单,减少数据库库存
客户端轮询:检查秒杀结果
6 总结
Redis 作为高性能的内存数据库,在现代应用架构中扮演着重要角色。通过合理使用 Redis 的持久化机制、内存管理策略和集群方案,可以构建出高可用、高性能的应用系统。同时,需要注意缓存一致性、异常场景处理以及大 Key 热 Key 等实践问题,确保系统的稳定性和可靠性。
在实际应用中,应根据具体业务场景选择合适的技术方案,并结合监控和运维手段,持续优化系统性能。