Redis问题画像图

按照“问题 --> 主线 --> 技术点”的方式梳理出来,后续可以不断完善这张图,形成自己的Redis问题画像图

缓存与数据库不一致

写回策略

  • 同步直写策略:写缓存时,也同步写数据库,缓存和数据库中的数据一致。使用这种策略时,需要使用事务机制,来保证缓存和数据库的更新具有原子性,两者要不一起更新,要不都不更新,返回错误信息,进行重试。

  • 异步写回策略:写缓存时不同步写数据库,等到数据从缓存中淘汰时,再写回数据库。 使用这种策略时,如果数据还没有写回数据库,缓存就发生了故障,那么,此时,数据库就没有最新的数据了。

常用策略

  1. 缓存过期策略 设置适当的过期时间(TTL):为缓存中的数据设置一个合理的生存时间(TTL),让旧数据自动过期。这是最简单也是最常用的一种方式,虽然不能完全避免数据不一致,但可以在一定程度上减少数据不一致的时间窗口。

  2. 写入时更新缓存策略

  • 写入数据库后立即更新缓存:在数据更新操作后立即更新缓存中的数据。这种策略能够保证缓存数据的新鲜度,但在高并发场景下可能导致缓存击穿问题。

  • 先更新数据库,再删除缓存:这种方式简单有效,通过删除缓存来避免复杂的缓存更新逻辑,确保下一次读取时能从数据库加载最新数据。但需要注意,如果删除缓存和数据库写入之间有时间差,可能会读到旧的缓存数据。

  1. 使用事务或锁保证原子性 数据库和缓存操作原子性:如果可能,可以通过分布式锁或事务机制来保证更新数据库和更新(或删除)缓存的操作是原子性的,避免中间状态对数据一致性的影响。

  2. 异步处理 消息队列:使用消息队列异步更新缓存,当数据库更新后,发布一个消息到消息队列,再由另一个服务消费消息来更新或清除缓存。这种方式可以解耦数据库操作和缓存操作,降低系统耦合度,但需要处理好消息的可靠性和消费顺序。

写入时更新对比

雪崩/击穿/穿透

缓存污染

问题:缓存污染问题指的是留存在缓存中的数据,实际不会被再次访问了,但是又占据了缓存空间。如果这样的数据体量很大,甚至占满了缓存,每次有新数据写入缓存时,还需要把这些数据逐步淘汰出缓存,就会增加缓存操作的时间开销。

解决:LFU

脑裂

问题:脑裂是指在主从集群中,同时有两 个主库都能接收写请求。在 Redis 的主从切换过程中,如果发生了脑裂,客户端数据就会 写入到原主库,如果原主库被降为从库,这些新写入的数据就丢失了。

原因:

  1. 和主库部署在同一台服务器上的其他程序临时占用了大量资源(例如 CPU 资源),导致 主库资源使用受限,短时间内无法响应心跳。其它程序不再使用资源时,主库又恢复正 常。

  2. 主库自身遇到了阻塞的情况,例如,处理 bigkey 或是发生内存 swap,短时间内无法响应心跳,等主库阻塞解除 后,又恢复正常的请求处理了。

解决:通过合理地配置参数 min-slaves-to-write 和 min-slaves-max-lag,来预防脑裂的发生。

  • min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量;

  • min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟(以秒为单位)

假设从库有 K 个,可以将 min-slaves-to-write 设置为 K/2+1(如果 K 等于 1,就设为 1),将 min-slaves-max-lag 设置为十几秒(例如 10~ 20s),在这个配置下,如果有一半以上的从库和主库进行的 ACK 消息延迟超过十几秒, 我们就禁止主库接收客户端写请求。

数据倾斜

数据量倾斜

当数据量倾斜发生时,数据在切片集群的多个实例上分布不均衡,大量数据集中到了一个或几个实例上,如下图所示:

数据访问倾斜

发生数据访问倾斜的根本原因,就是实例上存在热点数据(比如新闻应用中的热点新闻内容、电商促销活动中的热门商品信息,等等)。

一旦热点数据被存在了某个实例中,那么,这个实例的请求访问量就会远高于其它实例,面临巨大的访问压力,如下图所示:

通常来说,热点数据以服务读操作为主,在这种情况下,我们可以采用热点数据多副本的 方法来应对。

这个方法的具体做法是,我们把热点数据复制多份,在每一个数据副本的 key 中增加一个 随机前缀,让它和其它副本数据不会被映射到同一个 Slot 中。这样一来,热点数据既有多 个副本可以同时服务请求,同时,这些副本数据的 key 又不一样,会被映射到不同的 Slot 中。在给这些 Slot 分配实例时,我们也要注意把它们分配到不同的实例上,那么,热点数 据的访问压力就被分散到不同的实例上了。

这里,有个地方需要注意下,热点数据多副本方法只能针对只读的热点数据。如果热点数 据是有读有写的话,就不适合采用多副本方法了,因为要保证多副本间的数据一致性,会 带来额外的开销。

对于有读有写的热点数据,我们就要给实例本身增加资源了,例如使用配置更高的机器,来应对大量的访问压力。

redis变慢

  1. 使用复杂度过高的命令或一次查询全量数据;

  2. 操作 bigkey;

  3. 大量 key 集中过期;

  4. 内存达到 maxmemory;

  5. 客户端使用短连接和 Redis 相连;

  6. 当 Redis 实例的数据量大时,无论是生成 RDB,还是 AOF 重写,都会导致 fork 耗时严重;

  7. AOF 的写回策略为 always,导致每个操作都要同步刷回磁盘;

  8. Redis 实例运行机器的内存不足,导致 swap 发生,Redis 需要到 swap 分区读取数据;

  9. 进程绑定 CPU 不合理;

  10. Redis 实例运行机器上开启了透明内存大页机制;

  11. 网卡压力过大。

慢查询和latency monitor

slowlog-log-slower-than:这个参数表示,慢查询日志对执行时间大于多少微秒的命 令进行记录。

slowlog-max-len:这个参数表示,慢查询日志最多能记录多少条命令记录。慢查询日 志的底层实现是一个具有预定大小的先进先出队列,一旦记录的命令数量超过了队列长 度,最先记录的命令操作就会被删除。这个值默认是 128。但是,如果慢查询命令较多 的话,日志里就存不下了;如果这个值太大了,又会占用一定的内存空间。所以,一般 建议设置为 1000 左右,这样既可以多记录些慢查询命令,方便排查,也可以避免内存 开销。

我们可以使用 SLOWLOG GET 命令,来查看慢查询日志中记录的命令操作,例如,我们执 行如下命令,可以查看最近的一条慢查询的日志信息。

SLOWLOG GET 1

可以看到,KEYS "abc*"这条命令的执行时间是 20906 微秒,大约 20 毫秒,的确是一条 执行较慢的命令操作。如果我们想查看更多的慢日志,只要把 SLOWLOG GET 后面的数字.

一个命令的实际执行时长超过该阈值时,就会被 latency monitor 监控到。比如, 我们可以把 latency monitor 监控的命令执行时长阈值设为 1000 微秒,如下所示:

config set latency-monitor-threshold 1000

bigkey

使用指令查询单个key的大小

MEMORY USAGE user:info 2

(integer) 315663239

使用指令统计key的数量

List 类型:LLEN 命令;

Hash 类型:HLEN 命令;

Set 类型:SCARD 命令;

Sorted Set 类型:ZCARD 命令;

附录

服务熔断和降级

服务熔断和服务降级是分布式系统中常用的两种容错机制,它们虽然目标相似——都旨在保护系统在面对故障时仍能正常运行,但是它们的工作方式和触发条件有所不同。

服务熔断(Circuit Breaker) 服务熔断机制的概念类似于电路中的断路器,旨在防止连锁故障。当某个服务的失败率超过预定阈值时(比如连续多次请求失败),熔断器会被触发,后续的请求会被自动拒绝,避免继续对这个可能已经不稳定或无响应的服务进行调用,从而保护系统的整体稳定性和性能。在熔断状态持续一段时间后,系统会自动尝试恢复连接,检查服务是否已恢复正常。

  • 目标:防止系统过载,快速失败以保护系统。

  • 触发条件:服务调用的错误率或超时率超过一定阈值。

  • 恢复机制:熔断后,会在一定时间内自动尝试部分请求,检查服务是否恢复。

服务降级(Service Degradation) 服务降级是指在系统资源紧张或某个服务响应缓慢/不可用时,主动放弃一部分功能或服务质量,以确保核心服务的正常运行。服务降级通常需要开发者提前定义好备选方案或简化的服务逻辑。比如,当推荐服务不可用时,系统可以展示静态的推荐列表或隐藏推荐模块,以保证主体功能的可用性。

  • 目标:在服务不可用或资源紧张时保证核心服务的可用性。

  • 触发条件:系统资源紧张(如CPU、内存过高),或依赖的外部服务响应缓慢或不可用。

  • 恢复机制:通常需要人工干预,评估系统状态和服务状态后手动恢复全量服务。

区分要点

  • 触发点不同:熔断通常是由于服务错误率过高自动触发,而降级则可能是由于外部服务不可用或系统资源紧张而人工或自动触发。

  • 目的不同:熔断的目的是防止错误雪崩,快速失败以保护系统;服务降级则是通过牺牲非核心功能的质量来保证核心服务的可用性。

  • 恢复方式:熔断有自动尝试恢复机制,而服务降级的恢复往往需要根据系统和服务的实际状态决定是否恢复全量服务。