Redis
Redis 能用来做什么¶
- 缓存
- 分布式锁:通常基于 Redisson 实现。
- 限流:可以用 Redis+Lua 来实现,如 https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA。
- 消息队列:list 可作为简单的队列;Redis 5.0 新增 Stream 类型更适合,类似 Kafka,有主题和消费组的概念,支持消息持久化与 ACK 机制。
- 其他:基于 bitmap 统计活跃用户、HyperLogLog 统计 UV、Zset 维护排行榜等
Redis 和 Memcached 的区别和共同点¶
共同点¶
- 都是基于内存的数据库,都可以用来作为缓存。
- 都有过期策略。
- 两者的性能都非常高。
区别¶
- Redis 支持的数据类型更丰富,如 list、hash、set 等。
- Redis 支持数据持久化,能够将数据保存到磁盘。
- Redis 有灾难恢复机制(其实还是持久化)。
- Redis 在用完内存时,可以把不用的数据放到磁盘,而 Memcached 在服务器内存用完后会报异常。
- Redis 原生支持 cluster 集群模式。
- Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 是单线程的多路 IO 复用模型(Redis 6.0 引入多线程 IO)。
- Redis 支持发布订阅、Lua 脚本、事务等功能,支持更多编程语言。
- Memcached 过期数据删除策略只有惰性删除,Redis 是惰性+定期。
Redis 数据类型¶
- String 字符串:一种二进制安全的数据类型,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。使用 SDS 简单动态字符串实现。
- List 列表:实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
- Set 集合:一个 String 类型的 field-value(键值对) 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接修改这个对象中的某些字段的值。
- Hash 散列:无序集合,集合中的元素没有先后顺序但都唯一,有点类似于 Java 中的
HashSet
。 - Zset 有序集合:和 Set 相比,Sorted Set 增加了一个权重参数
score
,使得集合中的元素能够按score
进行有序排列,还可以通过score
的范围来获取元素的列表。有点像是 Java 中HashMap
和TreeSet
的结合体。 - HyperLogLog 基数统计:Redis 提供的 HyperLogLog 占用空间非常非常小,只需要 12k 的空间就能存储接近
2^64
个不同元素。不过,HyperLogLog 的计数结果并不是一个精确值,存在一定的误差(标准误差为0.81%
)。 - Bitmap 位图存储:可以将 Bitmap 看作是一个存储二进制数字(0 和 1)的数组,数组中每个元素的下标叫做 offset(偏移量)。通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 Bitmap 本身会极大的节省储存空间。
- Geospatial 地理位置:Geospatial index(地理空间索引,简称 GEO) 主要用于存储地理位置信息,基于 Sorted Set 实现。
SDS 简单动态字符串¶
Redis 中的 SDS 是对 C语言字符串的封装,提供了以下功能:
- 增加长度字段 len,快速返回长度。
- 增加空余空间,便于后续追加数据。在扩容时若预留空间充足,就不用重新分配内存;缩容时也可以先保留减少的空间,方便后续再次使用。
- 不再以 '\0' 作为判断标准,二进制安全。
Redis 为什么快¶
- Redis 的所有数据都放在内存中,内存访问速度比磁盘快的多。
- 采用了基于 IO 多路复用技术的单线程事件驱动模型来处理客户端请求和执行 Redis 命令,避免了多线程上下文切换和竞争条件,提高了并发处理效率。
- 对底层数据结构进行优化,能够快速完成各种操作。如 String 的底层数据结构动态字符串支持动态扩容、预分配冗余空间,能够减少内存碎片和内存分配的开销。
Redis 单线程模型¶
Redis 基于 Reactor 模式来设计开发了高效的事件处理模型,这套模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器是单线程方式运行,所以一般说 Redis 是单线程模型。
Redis 通过 IO 多路复用程序来监听来自客户端的大量连接(或者说监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
所说的 Redis 是单线程,主要指的是 Redis 网络 IO 和键值对读写这些操作是由一个线程完成的,持久化、集群等机制都是有后台线程执行的。
Redis 并不一直是单线程的,4.0 版本引入了多线程指令,6.0 版本正式引入了多线程机制。多线程指的是针对网络请求过程使用多线程,其对于数据读写命令的处理依旧是单线程的。
为什么 Redis 设计为单线程?6.0 版本为什么引入多线程?¶
单线程设计原因: - Redis 的操作是基于内存的,其大多数操作的性能瓶颈主要不是 CPU 导致的。 - 使用单线程模型,代码简便的同时,也减少了线程上下文切换带来的性能开销。 - Redis 在单线程的情况下,使用 I/O 多路复用模型就可以提高 Redis 的 IO 利用率了。
6.0 版本引入多线程的原因: - 随着数据规模的增长、请求量的增多,Redis 的执行瓶颈主要在于网络 IO。引入多线程处理可以提高网络 IO 处理速度。
持久化¶
两种持久化方案:RDB 和 AOF。
RDB 通过保存数据库中所有数据实现持久化,相当于创建一个副本。
AOF 通过保存修改数据的命令,恢复数据时重新执行一遍,来实现持久化。
- RDB
- 优点:加载速度快、存储体积小
- 缺点:存储速度慢,大量消耗资源,可能会发生数据丢失
- AOF
- 优点:存储速度快、消耗资源少,支持实时存储
- 缺点:加载速度慢,数据体积大
事务¶
使用 multi
开启事务,开启后输入的命令会放到执行队列中,并不会立即执行。
然后使用 exec
命令结束事务,此时会统一执行所有命令。
事务过程中,执行 discard
退出事务,中途取消。
锁¶
Redis 中也会出现多个命令同时竞争同一条数据的情况,如两条命令同时执行,都要修改 a 的值,那就只能通过锁机制来保证同一时间只能有一个命令操作。
不同于 MySQL 中的悲观锁,Redis 中是乐观锁。
- 乐观锁
假设数据在处理期间不会被其他事务频繁修改。它通常在提交事务时检查数据一致性,而非在操作开始时加锁。
优点: 提升并发性能,减少锁竞争,适合读多写少的场景。
缺点: 在写操作频繁的情况下,可能导致多次重试,影响性能。
- 悲观锁
假设数据在处理期间会被其他事务频繁修改。它在操作开始时获取锁,防止其他事务修改数据,直到锁释放。
优点: 保证数据一致性,防止冲突,适合写操作频繁的场景。
缺点: 可能导致锁竞争和资源等待,降低并发性能。
Redis 中可以使用 watch
来监视一个目标,如果执行事务之前被监视目标发生了修改,则取消本次事务。
Redis 缓存¶
关于缓存¶
缓存的作用
- 降低后端负载
- 提高读写效率,降低响应时间
缓存的成本
- 数据一致性成本
- 代码维护成本
- 运维成本(集群分布式部署的人力、软硬件成本)
缓存更新策略¶
主动更新¶
- Cache Aside Pattern:由缓存的调用者,在更新数据库的同时更新缓存。
- Read/Write Through Pattern:缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。
- Write Behind Caching Pattern:调用者只操作缓存,由其它线程异步的将缓存数据持久化到数据库,保证最终一致。
Cache Aside Pattern 并发操作时,先删再写还是先写再删?
如果先删除缓存,再更新数据库,有可能在一个线程删除缓存后,另一个线程来查询,发现缓存中没用这个键,然后就会数据库更新前进行查询,并写入到缓存中,这样缓存中就是旧的值了。
先更新数据库,再删除缓存,有同样的线程安全问题。但由于缓存操作的速度高于数据库操作,所以第一种情况出现的概论相对较大。
总结¶
缓存更新策略的最佳实践方案.
- 低一致性需求:使用 Redis 自带的内存淘汰机制
- 高一致性需求:主动更新,并以超时剔除作为兜底方案
- 读操作:
- 缓存命中则直接返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作:
- 先写数据库,然后再删除缓存
- 要确保数据库与缓存操作的原子性
- 读操作:
缓存穿透¶
缓存穿透是指客户端请求的数据,在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
常见的解决方案有两种:
- 缓存空对象
- 优点:
- 实现简单,维护方便,不会对数据库造成额外压力
- 缺点:
- 额外的内存消耗
- 可能造成短期的不一致
- 优点:
- 布隆过滤
- 优点:
- 内存占用较少,没有多余 key
- 缺点:
- 实现复杂
- 存在误判可能
- 优点:
缓存雪崩¶
缓存雪崩是指在同一时段大量的缓存 key 同时失效或者 Redis 服务宕机,导致大量请求到达数据库/带来巨大压力。
解决方案:
- 给不同的 key 的 TTL 添加随机值
- 利用 Redis 集群提高服务的可用性(哨兵机制)
- 给缓存业务添加降级限流策略
- 给业务增加多级缓存
缓存击穿¶
缓存击穿问题也叫热点 key 问题,就是一个被高并发访问并且缓存重建业务较复杂的 key 突然消失了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
常见解决方案:
- 互斥锁:重建缓存前获取锁,写入缓存后释放锁。其他线程遇到锁后,休眠一段时间后再次尝试获取,直到缓存命中。
- 优点:
- 没有额外内存消耗
- 保证了数据一致性
- 实现简单
- 缺点:
- 线程需要等待,影响性能
- 可能有死锁风险
- 优点:
- 逻辑过期:存储数据时,不设置 TTL,而是添加一个过期时间的字段。缓存命中后,如果已过期,尝试获取锁。获取到锁的线程,新开一个线程进行缓存重建,同时返回逻辑上过期的数据。未获取到锁的线程直接返回过期的数据,不需要管缓存重建的事情。
- 优点:
- 线程无需等待,性能较好
- 缺点:
- 不保证一致性
- 有额外内存消耗
- 实现复杂
- 优点:
可通过 Redis setnx
命令实现互斥锁
Redis 缓存怎么实现的¶
Redis 缓存通过将数据存储在内存中,利用内存的高速读写特性来提高数据访问速度。当应用程序请求数据时,首先检查 Redis 缓存中是否存在所需数据。如果存在,则直接从缓存中返回数据,避免了对后端数据库的查询,从而大大提高了系统的响应速度。当数据发生变化时,需要及时更新缓存中的数据,以保证数据的一致性。更新策略通常有多种,如定时更新、根据数据变化实时更新等。
说说 Redis 中的 ZSet¶
Redis 的 ZSet 是有序集合(Sorted Set),它是 Redis 中一种非常重要的数据结构。以下是关于 ZSet 的详细介绍:
- 数据结构特点:ZSet 中的每个元素都由一个成员(member)和一个分数(score)组成,分数用于对成员进行排序。ZSet 中的成员是唯一的,但分数可以重复。它通过跳跃表(Skip List)和哈希表来实现,能够在保证元素有序的同时,提供高效的插入、删除和查找操作。
- 应用场景
- 排行榜:例如游戏中的玩家排行榜、网站的文章阅读量排行榜等。通过将玩家或文章的相关数据作为成员,对应的分数作为排名依据,就可以轻松实现各种排行榜功能。
- 带权重的任务队列:在任务队列中,每个任务可能有不同的优先级或权重。可以将任务作为成员,任务的权重作为分数,这样就可以按照权重来优先处理高优先级的任务。
- 时间序列数据:如果数据具有时间属性,比如股票价格的实时数据、服务器的性能监控数据等,可以将时间戳作为分数,数据本身作为成员,方便按照时间顺序进行查询和分析。
- 常用命令
ZADD
:用于向 ZSet 中添加一个或多个成员及其分数。例如,ZADD myzset 10 "apple" 20 "banana"
会将 "apple" 和 "banana" 添加到名为 "myzset" 的 ZSet 中,分别对应分数 10 和 20。ZRANGE
:按照分数从小到大的顺序返回指定范围内的成员。如ZRANGE myzset 0 5
会返回 "myzset" 中分数排名第 0 到第 5 的成员。ZREVRANGE
:与ZRANGE
相反,按照分数从大到小的顺序返回指定范围内的成员。ZSCORE
:获取指定成员的分数。例如,ZSCORE myzset "apple"
会返回 "apple" 在 "myzset" 中的分数。ZREM
:从 ZSet 中移除指定的成员。例如,ZREM myzset "apple"
会将 "apple" 从 "myzset" 中删除。
为什么 Redis 的 string 类型效率要比传统字符串的效率高¶
- 数据结构简单:Redis 的字符串内部实现采用了简单动态字符串(SDS)结构,它克服了传统 C 字符串在长度计算、内存分配等方面的一些缺陷。SDS 可以快速获取字符串长度,时间复杂度为 $ O(1) $,而传统 C 字符串获取长度需要遍历整个字符串,时间复杂度为 $ (n) $。
- 内存分配优化:Redis 在分配内存时采用了预分配和惰性释放策略。当字符串增长时,会预先分配一定的额外空间,减少内存重新分配的次数;当字符串缩短时,不会立即释放内存,而是将释放的内存用于后续可能的增长操作,避免了频繁的内存分配和释放带来的开销。
- 操作原子性:Redis 的字符串操作是原子性的,这意味着在执行字符串操作时不会被其他操作打断,保证了数据的一致性和操作的高效性。例如,对字符串进行自增操作
INCR
,在 Redis 中是一个原子操作,不会出现并发安全问题。
Redis 的持久化¶
Redis 提供了两种持久化方式:
- RDB(Redis Database)全量备份:RDB 是一种快照式的持久化方式,它会在指定的时间间隔内将内存中的数据以二进制的形式保存到磁盘上的一个
.rdb
文件中。优点是恢复速度快,因为是直接将数据加载到内存中;缺点是可能会丢失最后一次快照到当前时间的数据,数据一致性相对较差。 - AOF(Append Only File)增量备份:AOF 是一种日志式的持久化方式,它会将每一个写命令追加到一个日志文件中。当 Redis 重启时,会重新执行这些命令来恢复数据。优点是数据安全性高,基本可以保证不丢失数据;缺点是日志文件可能会越来越大,恢复速度相对较慢,而且在重写日志文件时可能会消耗一定的性能。
Redis 使用过程中需要有哪些注意的点¶
- 内存管理:Redis 是内存数据库,内存空间有限,需要合理规划内存使用,避免内存溢出。可以通过设置
maxmemory
配置项来限制 Redis 使用的最大内存,并选择合适的内存淘汰策略,如volatile-lru
(对设置了过期时间的键值对采用 LRU 算法淘汰)、allkeys-lru
(对所有键值对采用 LRU 算法淘汰)等。 - 数据一致性:在使用缓存时,要注意数据的一致性问题。当后端数据库的数据发生变化时,需要及时更新 Redis 缓存中的数据,或者设置合理的过期时间,让缓存数据在一定时间后自动失效,以保证缓存数据与数据库数据的一致性。
- 并发访问:虽然 Redis 本身是单线程模型,但在多个客户端并发访问时,仍然需要注意一些并发问题。例如,在使用
INCR
等原子操作时,可以保证数据的准确性,但对于一些复杂的操作,可能需要使用分布式锁来保证数据的一致性。 - 过期时间设置:合理设置过期时间可以有效利用内存空间,同时避免缓存数据长时间不更新导致数据不一致。对于一些经常变化的数据,可以设置较短的过期时间;对于一些相对稳定的数据,可以设置较长的过期时间。但要注意避免大量数据同时过期,导致缓存雪崩问题。
Redis 支持事务 ACID 特性吗¶
Redis 的事务支持并不符合传统数据库中的 ACID 特性,而是基于乐观锁实现的一种简单事务机制。
Redis 中的事务使用 MULTI、EXEC、WATCH 和 DISCARD 等命令来实现,它们的作用如下:
- MULTI: 开启一个事务块,将后续的命令加入到事务队列中等待执行。
- EXEC: 执行事务队列中的所有命令。
- WATCH: 监视一个或多个键,如果在事务执行之前监视的键被修改,则事务将被打断。
- DISCARD: 取消事务,清空事务队列中的所有命令。
Redis 中的事务并不具有原子性,即在 EXEC 命令执行期间,如果某个命令出现了错误,不会影响其他命令的执行。另外,Redis 中的事务也不支持回滚操作,即使在 EXEC 执行之后发生错误,也无法撤销之前的操作。因此,Redis 的事务并不满足 ACID 中的原子性和一致性特性。
需要注意的是,Redis 的事务机制更适用于一系列原子性较弱的操作,例如批量写入或者批量删除等操作。对于需要满足严格的 ACID 特性的场景,建议使用传统的关系型数据库来实现。
锁¶
Redis 中也会出现多个命令同时竞争同一条数据的情况,如两条命令同时执行,都要修改 a 的值,那就只能通过锁机制来保证同一时间只能有一个命令操作。
不同于 MySQL 中的悲观锁,Redis 中是乐观锁。
- 乐观锁
假设数据在处理期间不会被其他事务频繁修改。它通常在提交事务时检查数据一致性,而非在操作开始时加锁。
优点: 提升并发性能,减少锁竞争,适合读多写少的场景。
缺点: 在写操作频繁的情况下,可能导致多次重试,影响性能。
- 悲观锁
假设数据在处理期间会被其他事务频繁修改。它在操作开始时获取锁,防止其他事务修改数据,直到锁释放。
优点: 保证数据一致性,防止冲突,适合写操作频繁的场景。
缺点: 可能导致锁竞争和资源等待,降低并发性能。
Redis 中可以使用 watch
来监视一个目标,如果执行事务之前被监视目标发生了修改,则取消本次事务。
缓存更新策略¶
主动更新¶
- Cache Aside Pattern:由缓存的调用者,在更新数据库的同时更新缓存。
- Read/Write Through Pattern:缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。
- Write Behind Caching Pattern:调用者只操作缓存,由其它线程异步的将缓存数据持久化到数据库,保证最终一致。
Cache Aside Pattern 并发操作时,先删再写还是先写再删?
如果先删除缓存,再更新数据库,有可能在一个线程删除缓存后,另一个线程来查询,发现缓存中没用这个键,然后就会数据库更新前进行查询,并写入到缓存中,这样缓存中就是旧的值了。
先更新数据库,再删除缓存,有同样的线程安全问题。但由于缓存操作的速度高于数据库操作,所以第一种情况出现的概论相对较大。
总结¶
缓存更新策略的最佳实践方案.
- 低一致性需求:使用 Redis 自带的内存淘汰机制
- 高一致性需求:主动更新,并以超时剔除作为兜底方案
- 读操作:
- 缓存命中则直接返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作:
- 先写数据库,然后再删除缓存
- 要确保数据库与缓存操作的原子性
- 读操作:
关于缓存三兄弟的定义和解决方法¶
- 缓存穿透
- 定义:指查询一个不存在的数据,由于缓存中没有该数据,会直接查询数据库,导致大量请求直接打到数据库上,可能会使数据库压力过大甚至崩溃。
- 解决方法:可以在缓存中设置一个空值或默认值,当查询不存在的数据时,将空值或默认值存入缓存,下次查询时直接从缓存中返回,避免再次查询数据库。也可以使用布隆过滤器,提前过滤掉不存在的数据,避免无效查询。
- 缓存空对象:实现简单,维护方便,不会对数据库造成额外压力。但是会造成额外的内存消耗,可能会造成短暂的数据不一致。
- 使用布隆过滤器:内存占用少,不需要多余的 key。但实现复杂,存在误判的可能。
- 缓存雪崩
- 定义:指在同一时刻,大量的缓存数据同时过期,导致大量请求同时访问数据库,使数据库压力骤增,甚至可能导致系统崩溃。
- 解决方法:可以将缓存的过期时间设置为随机值,避免大量数据同时过期。也可以使用分布式缓存,将数据分散到不同的节点上,避免单个节点上的数据集中过期。同时,可以增加缓存的冗余备份,当部分缓存节点失效时,从其他节点获取数据。
- 给不同的 key 的 TTL 添加随机值
- 利用 Redis 集群提高服务的可用性(哨兵机制)
- 给缓存业务添加降级限流策略
- 给业务增加多级缓存
- 缓存击穿
- 定义:指缓存中某个热点数据过期的瞬间,大量并发请求同时访问该数据,导致这些请求直接打到数据库上,对数据库造成较大压力。
- 解决方法:可以使用互斥锁或分布式锁,在缓存数据过期时,只允许一个请求去查询数据库并更新缓存,其他请求等待该请求完成后从缓存中获取数据。也可以将热点数据设置为永不过期,但需要定期在后台更新数据,或者使用逻辑过期,在查询数据时判断数据是否逻辑过期,如果过期则异步更新数据。
- 互斥锁:重建缓存前获取锁,写入缓存后释放锁。其他线程遇到锁后,休眠一段时间后再次尝试获取,直到缓存命中。没有额外内存消耗,保证了数据一致性,实现简单。但线程需要等待,影响性能,且可能有死锁风险。
- 逻辑过期:存储数据时,不设置 TTL,而是添加一个过期时间的字段。缓存命中后,如果已过期,尝试获取锁。获取到锁的线程,新开一个线程进行缓存重建,同时返回逻辑上过期的数据。未获取到锁的线程直接返回过期的数据,不需要管缓存重建的事情。线程无需等待,性能较好。但不能保证数据一致性,有额外内存消耗,实现复杂。
Redis 使用分布式锁如何避免网络分区导致的失效问题¶
- 使用 Redlock 算法:Redlock 是一种基于多个 Redis 节点的分布式锁算法。它通过在多个独立的 Redis 节点上获取锁来提高锁的可靠性。具体来说,客户端首先获取当前时间,然后依次向多个 Redis 节点发送获取锁的请求。如果客户端能够在大多数节点(超过一半的节点)上成功获取锁,并且获取锁的总时间小于锁的有效时间,那么就认为锁获取成功。在释放锁时,需要向所有获取锁的节点发送释放锁的请求。这样,即使在网络分区的情况下,只要大多数节点正常工作,分布式锁仍然能够正常工作,避免了因部分节点故障导致锁失效的问题。
- 设置合理的超时时间:在使用分布式锁时,要设置合理的超时时间。超时时间不能过长,否则可能会导致其他客户端长时间等待;也不能过短,否则可能会因为网络延迟等原因导致锁提前释放。可以根据实际业务场景和网络状况来调整超时时间,以保证在网络分区等异常情况下,锁能够在一定时间后自动释放,避免死锁的发生。
- 监控和重试机制:客户端可以定期监控分布式锁的状态,当发现锁出现异常(如获取锁失败或锁提前释放)时,可以根据具体情况进行重试。例如,在网络分区恢复后,客户端可以重新尝试获取锁,以保证业务的正常进行。同时,对于一些关键业务操作,可以在获取锁失败时进行适当的补偿操作,以避免数据不一致等问题。
统计日活量使用哪个数据结构,怎么设计¶
Redis 数据结构 Set
- 键名设计:以
day:yyyyMMdd:uv
作为键名,其中yyyyMMdd
表示具体的日期,例如day:20250409:uv
表示 2025 年 4 月 9 日的日活量统计。 - 数据存储:每当有一个用户在当天访问系统时,使用
SADD
命令将该用户的唯一标识(如用户 ID)添加到对应的Set
中。由于Set
中的元素是唯一的,所以无论同一个用户在当天访问多少次,都只会在Set
中存储一次。 - 统计日活量:通过
SCARD
命令获取Set
中元素的数量,即当天的日活量。例如,执行SCARD day:20250409:uv
就可以得到 2025 年 4 月 9 日的日活量。
MySQL 中有 2000w 条数据,Redis 存储 20w 条,如何保证 Redis 存储的都是热点数据?¶
可以考虑以下几种策略:
- 使用缓存淘汰策略: 设置合适的缓存淘汰策略,如 LRU(最近最少使用)、LFU(最不经常使用)等。这些策略可以自动将不常访问的数据从缓存中删除,保留访问频率较高的热点数据。
- 设置合适过期时间: 对于不需要长期缓存的数据,可以设置适当的过期时间,让 Redis 自动删除过期的数据。通过合理设置过期时间,可以保证缓存中的数据始终保持新鲜和热点。
- 使用缓存预热: 在应用启动时,可以预先将热点数据加载到 Redis 中,以确保 Redis 中的数据始终保持热点状态。可以通过定时任务或者在应用启动时加载数据到 Redis 中。
- 监控和调优: 定期监控 Redis 的使用情况和缓存命中率,根据实际情况进行调优。可以根据监控数据来优化缓存淘汰策略、调整过期时间等,以确保 Redis 中的数据都是热点数据。
- 使用自动化工具: 可以使用一些自动化工具来动态地识别和缓存热点数据。这些工具可以根据访问频率和访问模式来自动缓存和更新热点数据,从而保证 Redis 中的数据始终保持热点状态。
确保 Redis 中存储的数据始终都是热点数据,能够提高缓存命中率和系统性能。
你对 Redis 的操作是怎么实现的,通过注解吗?¶
在 Spring Boot 中,可以通过以下步骤实现对 Redis 的操作:
- 添加依赖:在
pom.xml
文件中添加 Spring Data Redis 和 Redis 客户端的依赖,例如:
XML | |
---|---|
- 配置 Redis 连接:在
application.properties
或application.yml
文件中配置 Redis 服务器的连接信息,如主机地址、端口号、密码等。例如:
- 使用 RedisTemplate 操作 Redis:在 Spring Boot 应用中,可以通过注入
RedisTemplate
来操作 Redis。RedisTemplate
提供了一系列方法来对不同的数据结构进行操作。例如,使用redisTemplate.opsForValue().set(key, value)
方法可以设置字符串类型的键值对,使用redisTemplate.opsForHash().put(hashKey, field, value)
方法可以在哈希中设置一个字段值等。 - 使用 StringRedisTemplate:如果只需要操作字符串类型的数据,也可以使用
StringRedisTemplate
,它是RedisTemplate
的子类,专门用于操作字符串类型的数据,其方法的参数和返回值都是字符串类型,使用起来更加方便。例如,stringRedisTemplate.opsForValue().set(key, value)
可以直接设置字符串键值对。
高并发场景下如何保证幂等性?¶
在高并发场景下,保证幂等性是非常重要的,可以通过以下几种方式来实现:
- 唯一标识符(ID): 在每次请求中包含一个唯一标识符(例如,UUID),服务端在接收到请求后首先检查该标识符是否已经处理过。如果已经处理过,则直接返回之前的结果,如果没有处理过,则进行正常处理并保存该标识符,以便下次检查。
- 版本号(Versioning): 在每个资源上添加一个版本号,客户端在更新资源时需要提供该版本号。服务端在更新资源时先比较客户端提供的版本号和当前资源的版本号是否一致,如果一致则执行更新操作,如果不一致则拒绝请求。
- 乐观锁(Optimistic Locking): 在更新资源时,先获取当前资源的版本号或者时间戳,在更新时比较该版本号或时间戳是否和之前一致。如果一致,则执行更新操作,否则拒绝请求或者重试更新操作。
- 幂等性接口设计: 设计幂等性的接口,确保多次调用接口对系统状态的影响是一致的。例如,对于增删改操作,可以设计为幂等性操作,多次执行效果是相同的。
- 幂等性校验逻辑: 在业务处理逻辑中添加幂等性校验逻辑,例如通过唯一索引、状态码等信息来检查是否已经处理过该请求,如果已经处理过则直接返回结果,避免重复处理。
- 使用分布式锁: 在关键业务操作前获取分布式锁,确保同一时刻只有一个线程能够执行该操作,避免多个线程并发执行导致的数据不一致问题。
综上所述,通过以上方法可以在高并发场景下保证系统的幂等性,避免重复操作和数据不一致问题,提高系统的可靠性和稳定性。选择合适的方式取决于具体的业务场景和系统架构设计。
介绍一下 Redis 发布订阅¶
Redis 发布订阅是一种消息通信模式,它允许客户端将消息发送到指定的频道,而其他订阅了该频道的客户端可以接收到相应的消息,实现了消息的异步通知和广播功能。
- 发布者:发布者是发送消息的客户端,它通过
PUBLISH
命令将消息发送到指定的频道。例如,PUBLISH channel_name message
,其中channel_name
是频道名称,message
是要发送的消息内容。 - 订阅者:订阅者是接收消息的客户端,它通过
SUBSCRIBE
命令来订阅一个或多个频道。例如,SUBSCRIBE channel1 channel2
,表示订阅channel1
和channel2
两个频道。当有消息发布到订阅的频道时,Redis 会将消息推送给所有订阅该频道的客户端。 - 应用场景:常用于实时消息通知、分布式系统中的事件广播、聊天应用等场景。例如,在一个实时股票交易系统中,服务器可以将股票价格的变化消息发布到相应的频道,而客户端可以订阅这些频道,及时获取股票价格的更新信息。它提供了一种简单、高效的消息通信方式,使得不同的客户端之间可以方便地进行信息交互。