Appearance
问:Redis支持哪些数据类型?分别适用于什么场景?
五种基础数据类型:
- 字符串(String):例如JSON字符串缓存,例如锁。
- 列表(List):例如消息。
- 哈希(Hash):相比将整个对象JSON序列化成字符串,哈希对字段分开存储更高效。
- 集合(Set):去重效果,交集/并集,例如共同好友功能
- 有序集合(Sorted Set):排序,例如排行榜
问:为什么Redis的读写性能比MySQL高?
- 内存 vs 磁盘:内存的访问速度碾压磁盘。
- 单线程 vs 多线程:单线程避免锁竞争,I/O多路复用支撑高并发(网络I/O和命令处理解耦)。
- 数据结构优化:内存友好的数据结构 vs 磁盘优化的B+树。
- 功能取舍:Redis牺牲复杂功能(事务、复杂查询)换取极简高效。
问:如何保证缓存与数据库的数据一致性?是否使用过延迟双删?
- Cache Aside(旁路缓存)机制:最常用的缓存模式,读的时候先缓存,没有才查库,写的时候先写库,然后删缓存。优点是简单有效,缺点是并发场景下可能因延迟导致短暂不一致(比如A更新数据库后删缓存,B在A删缓存前读到旧数据并回填)。
- 延迟双删:为了解决数据库有主从架构的情况下,主从延迟或并发导致的脏数据问题。主要操作是先删一次缓存,然后写库,然后延迟一段时间(比如主从同步耗时+业务处理时间),再次删除缓存。
- 分布式锁:在更新数据库和缓存时加锁,确保同一时间只有一个线程操作数据。
- 版本号控制:在缓存中存储数据版本号(如时间戳),更新数据库时版本号递增,读操作校验版本号是否匹配。
问:如何用Redis实现分布式锁?需要注意哪些问题(如锁续期、原子性)?
我们使用Redisson客户端实现分布式锁。
- 加锁:不存在才加锁。设置一个代表当前线程的唯一ID(防误删)。设置过期时间。
- 删除锁:需要用Lua脚本执行“判断值+删除”两步操作,确保原子性。
- 锁续期:比如设置锁的过期时间为10秒,但业务可能需要15秒才能完成。这时候需要启动一个后台线程,每隔一段时间(比如3秒)检查锁是否还在持有,如果还在,就延长过期时间(比如重置为10秒)。Redisson库的分布式锁就内置了这个机制。
问:Redis分布式锁有哪些坑?如何解决锁续期、脑裂等问题?
- 锁续期问题:业务没执行完,锁过期了。(使用锁续期)
- 脑裂问题(主从切换导致锁丢失):RedLock算法向多个独立Redis实例申请锁,超过半数成功才算获取锁。即使某个实例崩溃,其他实例仍能保证锁的唯一性。
- 锁误删:设置一个代表当前线程的唯一ID。
- 同一线程多次加锁失败:Redisson的可重入锁。
问:持久化的对比与选择?
RDB(Redis Database)
核心机制:
- 定时快照:RDB通过生成某个时间点的数据快照(Snapshot)来保存数据。比如每隔1小时,或者当满足“N秒内修改了M个键”的条件时触发快照。
- 默认方式:是Redis的默认持久化方式,生成的RDB文件是二进制的,文件名类似dump.rdb。
优点:
- 性能高:生成快照时通过子进程处理,对主线程影响小。
- 恢复快:RDB文件紧凑,重启时加载速度远快于AOF。
- 适合备份:文件小,方便传输到远程服务器或归档。
缺点:
- 数据丢失风险:如果Redis宕机,最后一次快照后的数据会丢失。比如每1小时保存一次,宕机可能丢1小时的数据。
- 大数据量时耗时:生成快照时如果数据量很大,可能会导致主线程短暂阻塞(尤其在虚拟机环境中)。
AOF(Append-Only File)
核心机制:
- 日志追加:AOF记录每一个写操作命令(如SET、DEL),以文本形式追加到文件末尾。
- 重写机制:为了避免日志文件过大,Redis会定期重写AOF文件(比如BGREWRITEAOF),删除冗余命令,合并为最小指令集。
优点:
- 数据更安全:根据同步策略(appendfsync),最多丢失1秒数据(默认配置为everysec)。
- 可读性强:AOF文件是文本格式,方便人工查看或修复。
缺点:
- 文件体积大:AOF文件通常比RDB大,尤其在未重写时。
- 恢复慢:重启时需要逐条执行AOF中的命令,耗时长。
- 写入性能略低:如果配置为always(每次写都同步到磁盘),性能会明显下降。
两种持久化如何选择
回答:
- 纯缓存场景:如果Redis仅用作缓存,允许数据丢失,可以关闭持久化,或者只用RDB。
- 数据持久化要求高:比如订单、支付系统,必须用AOF,并配置appendfsync everysec(平衡性能与安全)。
- 混合使用:同时开启RDB和AOF。这样既保留AOF的数据安全性,又能用RDB做冷备或快速恢复。
总结:
- RDB:快照式备份,速度快、文件小,但可能丢数据。
- AOF:日志式记录,数据安全、文件大,但恢复慢。
- 最佳实践:两者结合,用AOF保数据,用RDB做备份。
问:内存淘汰策略:
Redis的内存淘汰策略主要有以下几种:
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
- allkeys-lru:从所有键中挑选最近最少使用的键进行淘汰。
- allkeys-random:从所有键中随机淘汰键。
- volatile-lru:从设置了过期时间的键中挑选最近最少使用的键进行淘汰。
- volatile-random:从设置了过期时间的键中随机淘汰键。
- volatile-ttl:从设置了过期时间的键中挑选即将过期的键进行淘汰。
- 常用的有:有过期时间的最少使用的淘汰、当内存不足不允许写入、即将过期的淘汰、随机淘汰。
- 实际场景:核心数据(如订单锁)用noeviction单独部署,缓存数据用volatile-lru。
问:Lua脚本
Lua脚本主要是解决原子性问题。比如有个需求是库存足够才扣减,需要先检查库存,再扣减。如果用普通命令,可能存在竞态条件。用lua脚本可以保证库存不会被扣减为负数。
重点注意:
- 避免长脚本,用SCRIPT KILL设置超时;
- 禁用全局变量,防止内存泄漏;
- 优先使用Redis命令而不是Lua函数,比如用redis.call('GET', key)而不是Lua的循环遍历。
问:Redis的批处理(Pipeline)
客户端将多个命令一次性发送到服务器,而不需要等待每个命令的响应。
重点注意:
- 不要在一个Pipeline里塞太多命令,避免单次请求过大;
- Pipeline不保证事务性,中间命令失败不会回滚;
- 需要根据业务场景评估是否真能提升性能,比如低延迟环境下收益可能不明显。