Redis常见八股文
Redis常见八股文
Redis 中常见的数据类型有哪些?
Redis常见的数据类型主要有五种:String、List、Set、Hash和Zset(sorted set),高级数据结构有BitMap、HyperLogLog、GEO、stream
String: 可以存储任何类型的数据,包括文本、数字和二进制数据,最大长度512MB
场景: 缓存(用户会话、页面缓存)、计数器(统计访问量、点赞数等)
List: 列表是有序的字符串集合,支持从两端插入和弹出。可以用做队列或栈,底层双向链表
场景: 消息队列(任务调度、消息传递等)、历史记录
Set: 集合是无序且不重复的字符串集合,哈希表实现,可以实现快速查询,和去重操作
场景: 标签存储、去重(如统计独立IP)
Hash: 存储键值对集合,适合存储对象。内部类似于一个小型的键值对集合
场景: 存储用户信息,存储配置数据
Zset: 类似于Set,但每个元素会关联一个分数,自动按分数排序。支持快速的范围查询
场景: 排行榜,任务调度(优先级)
BitMap: 一种以位为单位存储数据的高效方式,适合用来表示布尔值(存在性、状态)
场景: 用户在线状态
HyperLogLog: 一种概率性数据结构,主要用于估算基数,适合大规模处理数据的去重和计数
GEO: 一种用于存储地理位置信息的数据结构。
Stream: 一种日志数据结构,适合存储时间序列数据或消息流。
Redis 为什么这么快?
redis快的原因
首先是因为redis存储在内存中,提供了快速的读写速度,内存的访问速度远快于磁盘,能够达到微秒级的响应时间。虽然数据存储在内存中,但 Redis 支持持久化(RDB、AOF)以保证数据安全。
其次是单线程模型,Redis 使用单线程模型处理请求,避免了多线程的上下文切换和锁竞争。
最后是redis拥有高效的数据结构,Redis 使用高度优化的底层数据结构(如哈希表、跳表、压缩列表等)操作复杂度低,常见操作都是 O(1) 或 O(logN)。
针对不同场景选择最佳数据结构,减少时间和空间消耗。
为什么 Redis 设计为单线程?6.0 版本为何引入多线程?
Redis 在早期的设计中采用了 单线程模型,主要是为了追求极致的简单性和性能,但从 6.0 版本开始引入了多线程支持。
设计成单线程的原因:
避免多线程的复杂性:多线程需要处理线程间的竞争、锁、上下文切换等问题,这会导致编程复杂性和性能瓶颈。单线程模型避免了锁的引入,避免了死锁、资源争用等问题。
充分利用 CPU 性能:对于大多数场景,Redis 的性能瓶颈并不在于 CPU,而是在内存访问速度和网络 I/O。单线程可以完全用 epoll 等 I/O 多路复用机制高效地处理并发请求,而 CPU 通常不会成为瓶颈。
引入多线程的原因:
主要是为了解决高并发场景下网络 I/O 的瓶颈,充分利用多核 CPU 的资源
Redis 中跳表的实现原理是什么?
首先跳表是什么?
跳表是一种用于实现有序集合(Sorted Set)数据结构的算法。它提供了一种快速的方式来查找、插入和删除元素,尤其适用于大规模的数据存储场景。
跳表是一种多级链表结构 ,底层链表保存所有元素,而每一层链表都是下一层的子集。
实现原理:
查找操作
从最高层索引的头节点开始,
- 如果当前节点的下一个节点的值小于要查找的值,则向右移动;
- 如果当前节点的下一个节点的值大于要查找的值,则向下移动,
- 直到找到目标值或者确定目标值不存在。
插入操作 首先进行查找操作,找到插入位置。然后随机生成一个层数,根据这个层数在每一层插入新节点。
删除操作: 首先进行查找操作,找到要删除的节点。然后在每一层删除这个节点。
Redis 的 hash 是什么?
Redis 的hash是一种键值对集合,非常适合存储对象类型的数据,如用户信息,商品信息等,可以提供高效的查询和更新操作。
应用场景:
存储用户信息:假设你有一个用户管理系统,你可以为每个用户创建一个 hash,将其用户名、邮箱、密码等信息存储在该 hash 中。
1 | HSET user:1001 username "johndoe" email "john@example.com" password "hashedpassword" |
可以方便的对对象的其中某一个字段进行修改,而不像string需要全部修改
Redis Zset 的实现原理是什么?
Redis 中的Zset的实现原理主要是结合了 跳表 和 哈希表 这两种数据结构。
跳表用于按分数对元素进行排序,可以提供高效的查询和范围查询能力。
哈希表用于存储成员与分数的映射,提供快速查找
内存优化:
Redis 会对 Zset 进行内存优化。对于元素较少的 Zset,Redis 会使用 Ziplist 编码来减少内存消耗。当有序集合中的元素数量超过一定阈值时,Redis 会切换到使用跳表和哈希表的方式。
Redis 中如何保证缓存与数据库的数据一致性?
先更新缓存,再更新数据库
网络会出现顺序不一致,导致数据不一致
先更新数据库,再更新缓存
网络会出现顺序不一致,导致数据不一致
先删除缓存,再更新数据库,后续查询将数据库数据回存到缓存中
网络可能会导致回存的数据是过时数据
先更新数据库,再删除缓存,后续查询将数据库数据回存到缓存中
一定时间内可能不一致,保证最终一致性,可能存在缓存失效后恰好数据库更新的删除缓存失败,缓存更新为过期数据,而数据库中为最新数据不一致
缓存双删策略,更新数据库前,删除一次缓存,更新完后再延迟删除一次
避免了过期数据被回存导致的不一致
使用binog异步更新缓存,监听数据库Binog变化,通过异步方式更新Redis缓存
先修改数据库,通过Canal监听数据库的binlog日志,记录修改信息,通过消息队列异步修改缓存的数据
实时一致性:先写数据库,再删除缓存,短期可能数据不一致
最终一致性:推荐使用binlog+消息队列,顺序消费加上重试机制保证最终一致性
强一致性:
- 通过分布式读写锁实现,读和读操作不互斥,读写互斥,写写互斥。
- 写操作:获取写锁,更新数据库,删除缓存,释放写锁
- 读操作:获取读锁,查询缓存,命中缓存释放锁,返回结果。未命中读取数据库,更新到缓存后,释放读锁
Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?
缓存击穿: 是指某个热点数据在缓存中过期了,导致该热点数据的大量请求直接访问数据库,造成数据库压力过大,导致崩溃
缓存穿透: 是指查询一个不存在于缓存之中的数据,导致每次请求都回去数据库查询,造成数据库压力大
缓存雪崩: 是指多个缓存数据在同一时间过期,导致这些数据的大量请求同时访问数据库,导致数据库压力过大
概括:击穿-热点过期 穿透-查询不存在的 雪崩-大量同时过期
扩展:
缓存击穿: 比如大家都在抢茅台,但某一时刻茅台的缓存失效了,大家都请求打到了数据库中,这就是缓存击穿。
缓存击穿现象:
- 数据库访问压力瞬时增加
- redis里面没有大量的key过期
- redis正常运行,但是数据库可能已经瘫痪了
解决方案:
设置热门数据
在redis高峰访问之前,把一些热门数据提前存入redis里面,加大这些热门数据key的时长。
或者不要给热门数据设置过期时间,在后台异步更新缓存
使用锁
保证同一时间只有一个请求来构建缓存
缓存穿透: 攻击者可以通过构造不存在的key发起大量请求,对数据库造成很大的压力,可能会造成系统宕机。
缓存穿透的现象:
- 应用服务器压力变大
- redis命中率降低
- 一直查数据库
解决方案:
对空值缓存
如果一个查询返回的数据为空,将这个空值也缓存起来,避免频繁查询数据库,设置空结果的过期时间应该短些,不超过五分钟。
设置可访问白名单
定义一个可以访问的名单,每次访问和白名单的 id 进行比较,如果访问 id 不在白名单里面,进行拦截,不允许访问, 比如使用 bitmaps 实现 。
使用布隆过滤器
使用布隆过滤器来快速判断一个请求的数据是否存在,如果布隆过滤器判断数据不存在,则直接返回,避免查询数据库。
实时监控
当发现 Redis 的命中率开始急速降低, 需要排查访问对象和访问的数据, 和运维人员配合,可以设置黑名单限制服务 。
缓存雪崩: 比如一个电商系统,用户量很大,假设某一系列商品缓存突然同一时间失效,那就会造成缓存雪崩,导致服务全部打到数据库,引发严重后果
缓存雪崩的现象:
- 数据库访问压力变大,服务器崩溃
- 在极短时间内,访问大量key,而这些 key 集中过期
解决方案:
缓存键同时失效:
构建多级缓存架构
引入多级缓存机制,如本地缓存加分布式缓存相结合,减少单点故障风险。
过期时间随机化
设置缓存过期时间时,在原油过期时间上加一个随机值,比如1-5分钟随机,这样每个缓存过期时间重复率降低,避免同一时间大量缓存失效
缓存预热
系统启动前提前加载缓存数据,避免大量请求落到冷启动状态下的数据库
加互斥锁
使得没缓存或缓存失效的情况下,同一时间只有一个请求来构建缓存,防止数据库压力过大。
Redis String 类型的底层实现是什么?(SDS)
Redis 中 String 类型的底层实现是 简单动态字符串(SDS, Simple Dynamic String)。并结合int、embstr、raw等不同编码方式进行优化。
扩展:
SDS: 当我们操作一个 SDS 字符串时,Redis 会预留一定的空间用于追加内容,而不是每次操作都重新分配内存。
如果你向 SDS 中追加字符串,SDS 会在 free 空间用完时自动扩展内存。如果内存使用率非常高,SDS 也可以根据需要减少分配的内存大小,从而避免浪费。
不同编码方式:
Int 当字符串可以表示为一个整数时,Redis 会使用 int 编码。这种编码方式非常高效,因为它直接将字符串存储为整数,而不是像传统的字符串一样存储字符数组。
Embstr embstr 编码是一种针对短字符串优化的编码方式。当字符串的长度较短时(通常为 39 字节或更少),Redis 会将字符串数据和 SDS 头部存储在一个连续的内存块中,从而减少内存分配的次数和内存碎片。
Raw raw 编码是 Redis 中最常见的字符串编码方式,用于较长的字符串。与 embstr 编码不同,raw 编码将字符串的数据和 SDS 头部分开存储,适用于长度较长的字符串。Redis 会将字符串数据存储在堆内存中,并在 SDS 头部记录相关的元数据(如字符串长度、剩余可用空间等)。
Redis 中如何实现分布式锁?
Redis中实现分布式锁常见方法是:通过set ex nx命令+lua脚本组合使用。
具体实现:
指令:
setnx key value- setnx 可以理解是上锁/加锁指令
- key 是锁的键
- value 是锁的值
- 在这个 key 没有删除前, 不能执行相同 key 的上锁指令.指令:
del key删除key,这里可以理解为释放锁
指令:
expire key seconds给锁key设置过期时间,防止死锁
指令:
ttl key查看某个锁key的过期时间,-1代表永不过期 -2代表已过期
指令:
set key value nx ex seconds设置锁的同时,指定该锁的过期时间
这个指令是原子性的,防止
setnx key value / expire key seconds两条指令, 中间执行被打断
Java简单实现
1 |
|
此时没有设置过期时间,容易造成死锁,所以需要加上过期时间
1 | Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "ok", 3, TimeUnit.SECONDS); |
过期时间为三秒,但此时又产生了新的问题,如果业务操作出现了卡顿,导致锁超时,自动释放,此时其他用户将上锁进行业务操作,而当第一个用户业务操作结束后,要删除锁,此时删除的并不是自己上的锁,而是后面用户上的锁,就出现了误删锁问题,要解决该问题,可以采用UUID防误删锁。
思路分析:
- 在获取锁的时候, 给锁设置的值是唯一的 uuid
- 在释放锁时,判断释放的锁是不是同一把锁.
- 造成这个问题的本质原因, 是因为删除操作缺乏原子性
1 | //得到一个 uuid 值,作为锁的值 |
此时虽然能保证大部分不会误删锁,但是考虑到一个极端问题,如果当刚判断完当前锁是前面获取到的锁,此时正好该锁过期了,然后第二个用户已经又上锁了,此时第一个用户进行删除锁操作,就会把第二个用户的锁删除。要解决该问题就得用lua脚本保证删除原子性
1 | //=====使用 lua 脚本来锁, 控制删除原子性======== |
注意事项
- 定义锁的 key, key 可以根据业务, 分别设置, 比如操作某商品, key 应该是为每个 sku 定义的, 也就是每个 sku 有一把锁
- 为了确保分布式锁可用,要确保锁的实现同时满足以下四个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 加锁和解锁必须是同一个客户端,A 客户端不能把 B 客户端加的锁给解了
- 加锁和解锁必须具有原子性
Redis 的 Red Lock 是什么?你了解吗?
Red Lock是一种分布式锁算法,用于在分布式系统中实现可靠的锁机制。它的设计解决了单一Redis实例作为分布式锁可能出现的单点故障问题。
一般情况存在的问题
一般使用主从+哨兵实现redis,如果发生了主从切换,此时从节点如果还没有同步主节点的锁信息,,新的主节点便没有锁信息,此时某个业务去加锁,此时该业务和主从切换之前加锁的业务就产生了竞争,会导致数据不一致问题。
RedLock的实现原理
RedLock不在单个Redis实例上加锁,而是在多个独立的Redis实例上同时尝试获取锁。通常建议使用奇数个Redis实例(如5个),以确保系统具有较好的容错性。系统只有在获得了大多数Redis实例的锁(即N/2 + 1个节点,N为节点总数)之后,才认为成功获取了分布式锁。这样即使部分Redis实例发生故障,整体锁服务仍然可用。
RedLock的具体流程
- 获取锁:客户端尝试顺序地向所有Redis实例发送加锁命令。对于每个实例,客户端在指定的超时时间内尝试获取锁。
- 计算成功加锁的实例数量:客户端计算已经成功加锁的实例数量,如果达到多数(N/2 + 1),则认为客户端成功获取了分布式锁。
- 释放锁:如果获取锁失败,客户端需要向所有实例发送释放锁的命令,以避免留下未释放的锁。
Redis 实现分布式锁时可能遇到的问题有哪些?
业务逻辑未执行完,锁已过期
如果业务逻辑执行时间过长,而锁已经过期,就会导致过期后,被其他业务加锁的情况,导致并发问题
可以引入续锁机制、合理设置锁过期时间
锁被误释放
业务A上锁执行操作,但由于网络卡顿,导致锁超时,此时业务B上锁进行操作,此时业务A操作结束,需要释放锁,就将业务B的锁给释放了,出现误删锁问题
可以采用UUID防误删锁(在释放锁时判断是不是同一把锁)
原子性不足
在一个极端情况下,当刚判断完当前锁就是自己前面加的锁,准备要去释放,此时锁过期,然后另一个业务加了锁,此时第一个业务进行了释放锁,还是导致了误删锁。
此时就要用lua脚本保证删除原子性
单点故障问题
如果Redis是单机部署的,当该服务宕机,那么整个分布式锁都不可用
可以采用主从+哨兵形式部署,引入RedLock,通过在多个独立的 Redis 实例上实现锁的冗余,确保分布式锁的安全性和容错性
时钟漂移问题
分布式环境中,服务器时钟可能不同步,导致时间计算不准确,导致加锁或释放锁失败
使用NTP,同步服务器时钟
Redis 的持久化机制有哪些?
Redis提供了两种持久化机制,RDB和AOD。
RDB(Redis Database Backup)快照: RDB是通过生成某一时刻都数据快照来实现持久化,可以在特定时间间隔内保存数据的快照,但是RDB在奔溃时,可能会丢失最后一次快照之后的数据。
AOF(Append Only File)日志: AOF是通过将每个写操作追加到日志文件中实现持久化,数据恢复更精确,但文件体积较大,重写也会消耗更多资源。
扩展:
RDB执行流程
- 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,恢复时将快照文件读入内存。
- RDB是redis默认持久化策略。
- 具体流程如下:
- redis 客户端执行
bgsave命令或者自动触发bgsave命令; - 主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回;
- 如果不存在正在执行的子进程,那么就 fork 一个新的子进程进行持久化数据,fork 过程是阻塞的,fork 操作完成后主进程即可执行其他操作;
- 子进程先将数据写入到临时的 rdb 文件中,待快照数据写入完成后再原子替换旧的 rdb文件;
- 同时发送信号给主进程,通知主进程 rdb 持久化完成,主进程更新相关的统计信息
- redis 客户端执行
- 优缺点:
- 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能
- 如果需要进行大规模数据的恢复,切对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效
- RDB的缺点是最后一次持久化后的数据可能丢失(如果是正常关闭redis,仍然会进行持久化,不会造成数据丢失,如果是redis异常终止/宕机,就会造成数据丢失)
Fork&Copy-On-Write
- Fork的作用是复制一个与当前进程一样的进程,新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
- 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux引入了“写时复制技术 即:Copy-on-Write”
- 一般情况父进程和子进程会同用一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
RDB配置
dump.rdb文件
快照文件默认为dump.rdb,在redis.conf中可以配置
dump.rdb保存位置默认为:redis启动命令行所在的目录下
在哪个目录下启动,dump.rdb就保存在哪,如果每次启动位置不一致,会导致数据恢复时缺失,所以最好讲rdb文件的保存路径进行统一设置,不论在哪启动都将数据保存在一个rdb文件中。
如果没有开启save的注释,那么在退出redis时,也会进行备份,更新dump.rdb
save & bgsave
- save:用save命令时,只进行保存,其他不管,会导致客户端其他命令阻塞,直到 RDB 过程完成为止,对于内存比较大的实例造成长时间阻塞,基本不采用;
- bgsave:Redis 会在后台异步进行快照操作, 快照同时还可以响应客户端请求。 阻塞只发生在 fork 创建进程阶段,这个时间很短;
- 可以通过 lastsave 命令获取最后一次成功执行快照的时间(unix 时间戳) , 可以使用工具转换
- 禁用save:给save传入空字符串 动态停止 RDB: redis-cli config set save “”
flushall
- 清空整个redis 服务器的数据(删除所有数据库的所有数据)
- 同时也会产生dump.rdb,只不过数据为空
stop-write-on-bgsave-error
当redis无法写入磁盘的话(比如磁盘满了),直接关掉redis的写操作
rdbcompression
- 对于存储在磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩
- 如果不想消耗CPU来进行压缩的话,可以设置为关闭此功能,默认yes
RDB备份&恢复
重要/敏感的数据建议在MySQL保存一份
查询rdb文件目录
将dump.rdb进行备份,如果有必要可以写shell脚本来定时备份。
清空数据
删除dump.rdb
将备份文件改名并重启redis
数据恢复
RDB优势&劣势
优势:
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
劣势:
- 虽然redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
- 在备份周期在一定时间间隔做一次备份,如果redis意外down掉(如果正常关闭redis,仍然会进行RDB备份,不会丢失数据),就会丢失最后一次快照后的所有改动。
AOF(Append Of File)
AOF是什么?
- 以日志的形式来记录每个写操作(增量保存),将redis执行过的都所有写指令记录下来(比如set/del 操作会被记录,读操作get不会被记录)
- 只许追加文件但不可以改写文件
- redis启动之初会读取该文件重构数据
- redis重启动的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
AOF执行流程
- 客户端的请求写命令会被append追加到AOF缓冲区内
- AOF缓冲区根据AOF持久化策略【always、everysec、no】将操作sync同步到磁盘的AOF文件中
- AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量
- redis服务重启时,会重新load加载AOF文件中的写操作达到恢复数据的目的
AOF开启
在redis.conf文件中配置
AOF文件的保存路径,和RDB的路径一致
AOF和RDB同时开启时,系统默认取AOF的数据
AOF演示
开启AOF
重启redis服务,查看AOF文件,大小为0
对redis进行操作
查看AOF文件
AOF备份&恢复
AOF 的备份机制和性能虽然和 RDB 不同, 但是备份和恢复的操作同 RDB 一样, 都是拷贝备
份文件, 需要恢复时再拷贝到 Redis 工作目录下, 启动系统即加载
正常恢复:
- 修改默认的 appendonly no, 改为 yes
- 将有数据的 aof 文件定时备份, 需要恢复时, 复制一份保存到对应目录(查看目录:config
get dir) - 恢复:重启 redis 然后重新加载
异常恢复:
遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof --fix appendonly.aof 进行恢复
示例:
当前数据
在AOF文件后添加内容,破坏AOF文件
重启redis,发现启动失败
使用
/usr/local/bin/redis-check-aof --fix appendonly.aof修复重启,可以使用了
修复文件可能会造成数据丢失,但总比完全不能用好
AOF同步频率
- **appendfsync always :**始终同步,每次redis都写入都会立刻写入日志;性能较差,但数据完整性较好
- **appendfsync everysec:**【**默认**】每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能会丢失
- **appendfsync no:**redis不主动同步,把同步时机交给操作系统
Rewrite压缩
- AOF 文件越来越大,需要定期对 AOF 文件进行重写达到压缩
- 旧的 AOF 文件含有无效命令会被忽略,保留最新的数据命令 , 比如 set a a1 ; set a b1 ;set a c1; 保留最后一条指令就可以了
- 多条写命令可以合并为一个 , 比如 set a c1 b b1 c c1
- AOF 重写降低了文件占用空间
- 更小的 AOF 文件可以更快的被 redis 加载
手动触发:
直接调用bgrewriteaof命令
自动触发:
- auto-aof-rewrite-min-size: AOF 文件最小重写大小, 只有当 AOF 文件大小大于该值时候才能重写, 默认配置 64MB
- auto-aof-rewrite-percentage: 当前 AOF 文件大小和最后一次重写后的大小之间的比率等于或者大于指定的增长百分比,如 100 代表当前 AOF 文件是上次重写的两倍时候才重写
- 系统载入时或者上次重写完毕时,Redis 会记录此时 AOF 大小,设为base_size,如果 Redis 的 AOF 当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis 会对 AOF 进行重写
AOF优势&劣势
优势:
1、 备份机制更稳健, 丢失数据概率更低。
2、可读的日志文本,通过操作 AOF 稳健,可以处理误操作
劣势:
1、 比起 RDB 占用更多的磁盘空间
2、恢复备份速度要慢
3、每次读写都同步的话,有一定的性能压力
RDB or AOF ?
官方建议:两个都用
如果只做缓存:如果你只希望你的数据在服务器运行的时候存在, 你也可以不使用任何持久化方式
Redis 主从复制的实现原理是什么?
主从复制流程:
- Slave 启动成功连接到 master 后会发送一个 sync 命令
- Master 接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后, master 将传送整个数据文件到 slave,以完成一次完全同步
- slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中, 即 全量复制
- Master 数据变化了, 会将新的收集到的修改命令依次传给 slave, 完成同步, 即 增量复制
- 但是只要是重新连接 master,一次完全同步(全量复制)将被自动执行
【注意】:
- 如果从服务器宕机了, 重新启动, 仍然可以获取 Master 的最新数据
- 如果主服务器 宕机了, 从服务器并不会抢占为主服务器, 当主服务器恢复后, 从服务器仍然指向原来的主服务器.
Redis 数据过期后的删除策略是什么?
Redis过期删除策略主要有两种:惰性删除和定期删除
惰性删除: 当客户端访问某个键时,Redis 会检查该键是否过期。如果过期,则立即删除该键,并返回一个空值或错误。
- 优点:不需要额外的后台操作,过期键只会在被访问时才被删除。
- 缺点:如果某些过期键从未被访问,Redis 将一直保留这些过期键,导致内存浪费。
定期删除:Redis 会周期性地检查一部分键,删除已经过期的键。默认情况下,Redis 每 100 毫秒会进行一次检查,随机选择约 10 个键进行过期判断。
- 优点:可以避免内存中积累大量过期键,即使这些键没有被访问。
- 缺点:定期删除是一项后台操作,可能会带来一定的性能开销,尤其是在有大量过期键的情况下。
如何解决 Redis 中的热点 key 问题?
Redis 中的热点 key 问题是由于某些 key 被频繁访问,导致 Redis 压力过大,进而影响整体性能甚至引发集群故障。
热点 Key 拆分
将热点数据分散到多个 Key 中,例如通过引入随机前缀,使得不同用户请求分散到多个 Key,多 Key 分布在多实例中,避免集中的访问单一 Key。
多级缓存
在 Redis 前增加其他缓存层(如 CDN、本地缓存),以分担 Redis 的访问压力。
读写分离
通过 Redis 主从架构,将读请求分发到多个从节点,从而减轻单节点压力。
限流和降级
在热点 Key 访问过高时,应用限流策略,减少对 Redis 的请求,或者在必要时返回降级的数据或空值。
补充:
使用 Lua 脚本
在 Redis 中使用 Lua 脚本执行复杂操作,避免多次网络往返并减少锁竞争。
缓存预热
在系统启动或高峰前,提前将热点数据加载到 Redis,减少高峰期间的访问延迟。
动态过期策略
为热点 Key 设置动态过期时间,防止缓存雪崩,尽量分散失效时间。
热点 Key 监控与报警
使用 Redis 自带的慢查询日志或者监控工具(如 Redis Insight)定期分析和监控热点 Key,及时发现问题。
引入分布式缓存
结合 Memcached、Ehcache 等其他缓存系统,减轻 Redis 的负担。
使用大 Key 切片
如果热点 Key 存储的是大对象(如列表、集合等),可以对其内容进行切片存储,分散访问量。
使用一致性哈希
结合分布式 Redis 集群的哈希槽机制,将 Key 均匀分布到不同的节点,避免单节点过载。
Redis 集群的实现原理是什么?
- Redis 集群组成: Redis 集群是由多个 Redis 实例(节点)组成的,每个节点存储部分数据,并且各节点之间的数据不重复。
- 数据分布机制:
- Redis 集群采用哈希槽(Hash Slot)机制来分配数据。
- 整个键空间被划分为 16384 个槽(slots)。
- 每个 Redis 节点负责一定范围的哈希槽。
- 数据的 key 经过哈希函数计算后,根据结果对 16384 取模,确定属于哪个槽,进而定位到负责该槽的节点。
- 客户端访问数据:
- 客户端在发送请求时,会自动与集群的任意节点连接。
- 如果连接的节点存储了请求的数据,则直接返回;否则,该节点会根据哈希槽映射的规则,将请求路由到正确的节点处理。
- 作用: Redis 集群通过多个节点分担单节点的压力,实现了数据分布式存储和处理。
Redis 中的 Big Key 问题是什么?如何解决?
Redis 中的 Big Key 指的是占用内存较大的键(Key),即存储的数据量或对象较大。Big Key 会带来以下问题:
内存分布不均: 在集群模式下,如果多个 Big Key 集中在同一个节点,会导致内存分布不均,进而影响查询效率。
操作耗时: 操作 Big Key(如读取、删除、迁移等)会消耗较长时间,可能导致 Redis 阻塞和其他命令响应变慢。
网络流量开销: 传输 Big Key 时,因数据量大可能产生大量网络流量,增加网络传输延迟,甚至出现阻塞。
客户端超时: 客户端执行 Big Key 操作时耗时较长,可能会导致超时错误,影响用户体验。
如何解决:
- 开发层面:
- 数据压缩:对存储的数据进行压缩,减少占用内存后再存储到 Redis。
- 拆分大 Key:将一个大 Key 拆分为多个小 Key。例如,将大对象按字段或子项进行拆分,从而减少单个 Key 的内存占用。
- 优化数据结构:根据存储场景选择合适的数据结构。例如,将 String 类型存储改为 Hash、Set、ZSet 等分片结构,降低大 Key 的影响。
- 业务层面:
- 减少不必要的数据:根据实际需求调整存储策略,只存储必要的数据。例如,不保存用户的历史数据,只存储实时数据或必要的信息。
- 优化业务逻辑:从源头避免 Big Key 的产生,例如定期清理不需要的数据。
- 数据分布层面:
- Redis 集群分片:在 Redis 集群中,通过将 Big Key 拆分后分配到不同的服务器上,减少单节点压力,加快响应速度。


