Skip to content

Redis  使用规范与常用优化方案

Redis 使用规范分布式缓存服务 DCS最佳实践_华为云 (huaweicloud.com)

阿里云 Redis 开发规范-阿里云开发者社区 (aliyun.com)

业务使用规范

原则原则说明级别备注
就近部署业务,避免时延过大如果部署位置过远(非同一个地区)或者时延较大(例如业务服务器与 Redis 实例通过公网连接),网络延迟将极大影响读写性能。强制-
冷热数据区分建议将热数据加载到 Redis 中。低频数据可存储在 Mysql 或者 ElasticSearch 中。建议Redis 将低频数据存入内存中,并不会加速访问,且占用 Redis 空间。
业务数据分离避免多个业务共用一个 Redis。强制一方面避免业务相互影响,另一方面避免单实例膨胀,并能在故障时降低影响面,快速恢复。
禁止使用 select 功能在单 Redis 实例做多 db 区分。强制Redis 单实例内多 DB 隔离性较差,Redis 开源社区已经不再发展多 DB 特性,后续不建议依赖该特性。
设置合理的内存淘汰(逐出)策略合理设置淘汰策略,可以在 Redis 内存意外写满的时候,仍然正常提供服务。强制默认值为volatile-lru。Redis 支持的数据逐出策略,参见:Redis 数据逐出策略
以缓存方式使用 RedisRedis 事务功能较弱,不建议过多使用。建议事务执行完后,不可回滚。
数据异常的情况下,支持清空缓存进行数据恢复。强制Redis 本身没有保障数据强一致的机制和协议,业务不能强依赖 Redis 数据的准确性。
以缓存方式使用 Redis 时,所有的 key 需设置过期时间,不可把 Redis 作为数据库使用。强制失效时间并非越长越好,需要根据业务性质进行设置。
防止缓存击穿推荐搭配本地缓存使用 Redis,对于热点数据建立本地缓存。本地缓存数据使用异步方式进行刷新。建议-
防止缓存穿透非关键路径透传数据库,建议对访问数据库进行限流。建议-
从 Redis 获取数据未命中时,访问只读数据库实例。可通过域名等方式对接多个只读实例。建议核心是未命中的缓存数据不会打到主库上。
用域名对接多个只读数据库实例,一旦出现问题,可以增加只读实例应急。
不用作消息队列发布订阅场景下,不建议作为消息队列使用。强制如没有非常特殊的需求,不建议将 Redis 当作消息队列使用。
Redis 当作消息队列使用,会有容量、网络、效率、功能方面的多种问题。
如需要消息队列,可使用高吞吐的 Kafka 或者高可靠的 RocketMQ。
合理选择规格如果业务增长会带来 Redis 请求增长,请选择集群实例(Proxy 集群和 Cluster 集群)强制单机和主备扩容只能实现内存、带宽的扩容,无法实现计算性能扩容。
生产实例需要选择主备或者集群实例,不能选用单机实例强制-
主备实例,不建议使用过大的规格。建议Redis 在执行 RewriteAOF 和 BGSAVE 的时候,会 fork 一个进程,过大的内存会导致卡顿
具备降级或容灾措施缓存访问失败时,具备降级措施,从 DB 获取数据;或者具备容灾措施,自动切换到另一个 Redis 使用。建议-

数据设计规范

分类原则原则说明级别备注
Key 相关规范使用统一的命名规范。一般使用业务名(或数据库名)为前缀,用冒号分隔。Key 的名称保证语义清晰。建议例如,业务名:子业务名:id
控制 Key 名称的长度。在保证语义清晰的情况下,尽量减少 Key 的长度。
有些常用单词可使用缩写,例如,user 缩写为 u,messages 缩写为 msg。
建议建议不要超过 128 字节(越短越好)。
禁止包含特殊字符(大括号“{}”除外)。禁止包含特殊字符,如空格、换行、单双引号以及其他转义字符。强制由于大括号“{}”为 Redis 的 hash tag 语义,如果使用的是集群实例,Key 名称需要正确地使用大括号避免分片不均的情况。
Value 相关规范设计合理的 Value 大小。设计合理的 Key 中 Value 的大小,string 类型推荐小于 10 KB。建议过大的 Value 会引发分片不均、热点 Key、实例流量或 CPU 使用率冲高等问题,还可能导致变更规格和迁移失败。应从设计源头上避免此类问题带来的影响。
设计合理的 Key 中元素的数量。对于集合和列表类的数据结构(例如 Hash,Set,List 等),避免其中包含过多元素,建议单 Key 中的元素不要超过 5000 个。建议由于某些命令(例如 HGETALL)的时间复杂度直接与 Key 中的元素数量相关。如果频繁执行时间复杂度为 O(N)及以上的命令,且 Key 中的子 Key 数量过多容易引发慢请求、分片流量不均或热点 Key 问题。
选择合适的数据类型。合理地选择数据结构能够节省内存和带宽。建议例如存储用户的信息,可用使用多个 key,使用 set u:1:name "X"、set u:1:age 20 存储,也可以使用 hash 数据结构,存储成 1 个 key,设置用户属性时使用 hmset 一次设置多个,同时这样存储也能节省内存。
设置合理的过期时间。合理设置 Key 的过期时间,将过期时间打散,避免大量 Key 在同一时间点过期。建议设置过期时间时,可以在基础值上增减一个随机偏移值,避免在同一个时间点大量 Key 过期。大量 Key 过期会导致 CPU 使用率冲高。

命令使用规范

原则原则说明级别备注
谨慎使用 O(N)复杂度的命令时间复杂度为 O(N)的命令,需要特别注意 N 的值。避免 N 过大,造成 Redis 阻塞以及 CPU 使用率冲高。强制例如:hgetall、lrange、smembers、zrange、sinter 这些命令都是做全集操作,如果元素很多,会消耗大量 CPU 资源。可使用 hscan、sscan、zscan 这些分批扫描的命令替代。
禁用高危命令禁止使用 flushall、keys、hgetall 等命令,或对命令进行重命名限制使用。强制修改启动配置文件 [SECURITY] 项中 增加 rename-command oldName newName
慎重使用 selectRedis 多数据库支持较弱,多业务用多数据库实际还是单线程处理,会有干扰。最好是拆分使用多个 Redis。建议-
使用批量操作提高效率如果有批量操作,可使用 mget、mset 或 pipeline,提高效率,但要注意控制一次批量操作的元素个数。建议mget、mset 和 pipeline 的区别如下:
1. mget 和 mset 是原子操作,pipeline 是非原子操作。
2. pipeline 可以打包不同的命令,mget 和 mset 做不到。
3. 使用 pipeline,需要客户端和服务端同时支持。
避免在 lua 脚本中使用耗时代码lua 脚本的执行超时时间为 5 秒钟,建议不要在 lua 脚本中使用比较耗时的代码。强制比如长时间的 sleep、大的循环等语句。
避免在 lua 脚本中使用随机函数调用 lua 脚本时,建议不要使用随机函数去指定 key,否则在主备节点上执行结果不一致,从而导致主备节点数据不一致。强制-
遵循集群实例使用 lua 的限制遵循集群实例使用 lua 的限制。强制使用 EVAL 和 EVALSHA 命令时,所有 key,必须在 1 个 slot 上。
对 mget,hmget 等批量命令做并行和异步 IO 优化某些客户端对于 MGET,HMGET 这些命令没有做特殊处理,串行执行再合并返回,效率较低,建议做并行优化。建议例如 Jedis 对于 MGET 命令在集群中执行的场景就没有特殊优化,串行执行,比起 lettuce 中并行 pipeline,异步 IO 的实现,性能差距可达到数十倍,该场景建议使用 Jedis 的客户端自行实现 slot 分组和 pipeline 的功能。
禁止使用 del 命令直接删除大 Key使用 del 命令直接删除大 Key(主要是集合类型)会导致节点阻塞,影响后续请求强制Redis 4.0 后的版本可以通过 UNLINK 命令安全地删除大 Key,该命令是异步非阻塞的。
对于 Redis 4.0 之前的版本:
1. 如果是 Hash 类型的大 Key,推荐使用 hscan + hdel
2. 如果是 List 类型的大 Key,推荐使用 ltrim
3. 如果是 Set 类型的大 Key,推荐使用 sscan + srem
4. 如果是 SortedSet 类型的大 Key,推荐使用 zscan + zrem
同时要注意防止 bigkey 过期时间自动删除问题。
必要情况下使用 monitor 命令时,要注意不要长时间使用必要情况下使用 monitor 命令时,要注意不要长时间使用建议1. monitor 命令在高并发条件下,会存在内存暴增和影响 Redis 性能的隐患,所以此种方法适合在短时间内使用。
2. 只能统计一个 Redis 节点的热点 key,对于 Redis 集群需要进行汇总统计。

SDK 使用规范

原则原则说明级别备注
使用连接池和长连接短连接性能差,推荐使用带有连接池的客户端。建议连接的频繁创建和销毁,会浪费大量的系统资源,极限情况会造成宿主机宕机。请确保使用了正确的 Redis 客户端连接池配置。
客户端需要对可能的故障和慢请求做容错处理由于 Redis 服务可能因网络波动或基础设置故障的影响,引发主备倒换,命令超时或慢请求等现象,需要在客户端内设计合理的容错重试机制。建议参考:Redis 客户端重试指南
合理设置重试时间和次数合理设置容错处理的重试时间,根据业务要求设置,避免过短或者过长。强制如果超时重试时间设置的非常短(例如 200 毫秒以下),可能引发重试风暴,极易引发业务层雪崩。
如果重试时间设置得较长或者重试次数设置得较大,则可能导致在主备倒换情况下业务恢复较慢。
避免使用 Lettuce 客户端Lettuce 客户端在默认配置下有一定性能优势,并且是 spring 的默认客户端,但是 Jedis 客户端在面对连接异常,网络抖动等场景下的异常处理和检测能力明显强于 Lettuce,可靠性更强,建议使用 Jedis。建议Lettuce 存在几个方面的问题:
1. Lettuce 客户端在请求多次超时后,不再发起自动重连,当发生主备倒换后,可能出现连接超时导致无法重连。
2.Lettuce 默认未配置集群拓补刷新的配置,会导致 Cluster 集群在发生拓补信息变化(主备倒换,扩容缩容)时,无法识别新的节点信息,导致业务失败。可参考:使用 Lettuce 连接 Cluster 集群实例时的扩容异常处理
3. Lettuce 没有连接池校验的功能,无法检测连接池中的连接是否仍然有效,获取失效连接之后会导致业务失败。

运维管理规范

原则原则说明级别备注
生产开启密码保护生产系统中需要开启 Redis 密码保护机制。强制-
现网操作安全禁止开发人员私自连到线上 Redis 服务。强制-
验证业务的故障处理能力或容灾逻辑在测试环境或者预生产环境中组织演练,验证在 Redis 主备倒换、宕机或者扩缩容场景下业务的可靠性。建议-
监控实践关注 Redis 负载,在过载前提前扩容。强制-
日常巡检例行检查各个节点的内存使用率,查看主节点内存使用率是否有不均衡的状态。建议内存使用率不均衡说明存在大 Key 问题,需要进行大 Key 拆分及优化。参见:如何发现和处理大 Key、热 Key
开启热 Key 例行分析,并分析是否有 Key 频繁调用。建议-
例行诊断 Redis 实例的命令,分析 O(N)类命令是否存在隐患。建议针对 O(N)命令,即使耗时很小,建议开发分析业务增长,N 是否会增长。
例行巡检 Redis 慢日志命令。建议针对慢日志分析隐患,并尽快从业务上进行修复。

补充资料

Redis 数据逐出策略

Overview of Redis key eviction policies

Redis 实例支持通过修改配置参数(maxmemory-policy),修改数据逐出策略。

在达到内存上限(maxmemory)时,Redis 支持选择以下 8 种数据逐出策略:

  • noeviction:在这种策略下,如果缓存达到了配置的上限,实例将不再处理客户端任何增加缓存数据的请求,比如写命令,实例直接返回错误给客户端。缓存达到上限后,实例只处理删除和少数几个例外请求。
  • allkeys-lru:根据 LRU(Least recently used,最近最少使用)算法尝试回收最少使用的键,使得新添加的数据有空间存放。
  • volatile-lru:根据 LRU(Least recently used,最近最少使用)算法尝试回收最少使用的键,但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random:回收随机的键使得新添加的数据有空间存放。
  • volatile-random:回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl:回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
  • allkeys-lfu (Redis4.0 新增):从所有键中驱逐最不常用的键。
  • volatile-lfu (Redis4.0 新增):从具有“expire”字段集的所有键中驱逐最不常用的键。

Redis 客户端 重试指南

Redis 客户端重试指南

引发 Redis 操作失败的场景

场景说明
故障触发了主备倒换因 Redis 底层硬件或其他原因导致主节点故障后,会触发主备倒换。
慢查询引起了请求堵塞执行时间复杂度为 O(N)的操作,引发慢查询和请求的堵塞,此时,客户端发起的其他请求可能出现暂时性失败。
复杂的网络环境由于客户端与 Redis 服务器之间复杂网络环境引起,可能出现偶发的网络抖动、数据重传等问题,此时,客户端发起的请求可能会出现暂时性失败。
复杂的硬件问题由于客户端所在的硬件偶发性故障引起,例如虚拟机 HA,磁盘时延抖动等场景,此时,客户端发起的请求可能会出现暂时性失败。

推荐的重试准则

重试准则说明
仅重试幂等的操作由于超时可能发生在下述任一阶段:
1. 该命令由客户端发送成功,但尚未到达 Redis。
2. 命令到达 Redis,但执行超时。
3. 命令在 Redis 中执行结束,但结果返回给客户端时发生超时。
执行重试可能导致某个操作在 Redis 中被重复执行,因此不是所有操作均适合设计重试机制。通常推荐仅重试幂等的操作,例如 SET 操作,即多次执行 SET a b 命令,那么 a 的值只可能是 b 或执行失败;如果执行 LPUSH mylist a 则不是幂等的操作,可能导致 mylist 中包含多个 a 元素。
适当的重试次数与间隔根据业务需求和实际场景调整适当的重试次数与间隔,否则可能引发下述问题:
1. 如果重试次数不足或间隔太长,应用程序可能无法完成操作而导致失败。
2. 如果重试次数过大或间隔过短,应用程序可能会占用过多的系统资源,且可能因请求过多而堵塞在服务器上无法恢复。
常见的重试间隔方式包括立即重试、固定时间重试、指数增加时间重试、随机时间重试等。
避免重试嵌套重试嵌套可能导致重试时间被指数级放大。
记录重试异常并打印失败报告在重试过程中,建议在 WARN 级别上打印重试错误日志,同时,仅在重试失败时打印异常信息。

如何发现和处理大 Key、热 Key

如何发现和处理大 Key、热 Key

大 Key 和热 Key 的定义

  • 大 Key 和热 Key 场景较多,没有非常明确的边界,需要根据实际业务判断
名词定义
大 Key大 Key 可以分为两种情况:
1. Key 的 Value 较大,例如一个 String 类型的 Key 大小达到 10MB,或者一个集合类型(Hash,List,Set 等)的元素总大小达到了 100MB。一般我们定义单个 String 类型的 Key 大小达到 10KB,或者集合类型的 Key 总大小达到 50MB,则定义其为大 Key。
2. Key 的元素较多,例如一个 Hash 类型的 Key,其元素数量达到了 10000。一般我们定义集合类型的 Key 中元素超过 5000 个,则认为其为大 Key。
热 Key通常以一个 Key 被操作的频率和占用的资源来判定其是否为热 Key,例如:
1. 某个集群实例一个分片每秒处理 10000 次请求,其中有 3000 次都是操作同一个 Key。
2. 某个集群实例一个分片的总带宽使用(入带宽+出带宽)为 100Mbits/s,其中 80Mbits 是由于对某个 Hash 类型的 Key 执行 HGETALL 所占用。

大 Key 和热 Key 的影响

类别影响
大 Key造成数据迁移失败:
1. Redis 集群变更规格过程中会进行数据 rebalance(节点间迁移数据),单个 Key 过大的时候会触发 Redis 内核对于单 Key 的迁移限制,造成数据迁移超时失败,Key 越大失败的概率越高,大于 512MB 的 Key 可能会触发该问题。
2. 数据迁移过程中,如果一个大 Key 的元素过多,则会阻塞后续 Key 的迁移,后续 Key 的数据会放到迁移机的内存 Buffer 中,如果阻塞时间太久,则会导致迁移失败。
容易造成集群分片不均的情况:
1. 各分片内存使用不均。例如某个分片占用内存较高甚至首先使用满,导致该分片 Key 被逐出,同时也会造成其他分片的资源浪费。
2. 各分片的带宽使用不均。例如某个分片被频繁流控,其他分片则没有这种情况。
客户端执行命令的时延变大:
1. 对大 Key 进行的慢操作会导致后续的命令被阻塞,从而导致一系列慢查询。
导致主备倒换:
1. 对大 Key 执行危险的 DEL 操作可能会导致主节点长时间阻塞,从而导致主备倒换。
热 Key容易造成集群分片不均的情况:
1. 造成热 Key 所在的分片有大量业务访问而同时其他的分片压力较低。这样不仅会容易产生单分片性能瓶颈,还会浪费其他分片的计算资源。
使得 CPU 冲高:
1. 对热 Key 的大量操作可能会使得 CPU 冲高,如果表现在集群单分片中就可以明显地看到热 Key 所在的分片 CPU 使用率较高。这样会导致其他请求受到影响,产生慢查询,同时影响整体性能。业务量突增场景下甚至会导致主备切换。
易造成缓存击穿:
1. 热 Key 的请求压力过大,超出 Redis 的承受能力易造成缓存击穿,即大量请求将被直接指向后端的数据库,导致数据库访问量激增甚至宕机,从而影响其他业务。

如何发现大 Key 和热 Key

方法说明
通过 redis-cli 的 bigkeys 和 hotkeys 参数查找大 Key 和热 Key1. Redis-cli 提供了 bigkeys 参数,能够使 redis-cli 以遍历的方式分析 Redis 实例中的所有 Key,并返回 Key 的整体统计信息与每个数据类型中 Top1 的大 Key,bigkeys 仅能分析并输入六种数据类型(STRING、LIST、HASH、SET、ZSET、STREAM),命令示例为:redis-cli -h <实例的连接地址> -p <端口> -a <密码> --bigkeys。
2. 自 Redis 4.0 版本起,redis-cli 提供了 hotkeys 参数,可以快速帮您找出业务中的热 Key,该命令需要在业务实际运行期间执行,以统计运行期间的热 Key。命令示例为:redis-cli -h <实例的连接地址> -p <端口> -a <密码> --hotkeys。热 Key 的详情可以在结果中的 summary 部分获取到。
通过 Redis 命令查找大 Key如果有已知的大 Key 模式,例如知道其前缀为 u:plus:detail,那么可以通过一个程序,SCAN 符合该前缀的 Key,然后通过查询成员数量和查询 Key 大小的相关命令,来判断具体的大 Key。
查询成员数量的相关命令:LLEN,HLEN,XLEN,ZCARD,SCARD
查询 Key 占用内存大小的命令:DEBUG OBJECT,MEMORY USAGE
注意:该方法会大量消耗计算资源,不要在业务压力较大的实例使用该方法,否则可能会对正常业务造成影响。
通过 redis-rdb-tools 工具找出大 Keyredis-rdb-tools是分析 Redis RDB 快照文件的开源工具。可以根据需求自定义分析 Redis 实例中所有 Key 的内存占用情况。
注意:使用此方法需要导出 rdb 文件。时效性相较于在线分析来说较差,优势在于完全不影响现有业务

如何优化大 Key 和热 Key

类别方法
大 Key进行大 Key 拆分,分为以下几种场景:
1. 该对象为 String 类型的大 Key:可以尝试将对象分拆成几个 Key-Value, 使用 MGET 或者多个 GET 组成的 pipeline 获取值,分拆单次操作的压力,对于集群来说可以将操作压力平摊到多个分片上,降低对单个分片的影响。
2. 该对象为集合类型的大 Key,并且需要整存整取:在设计上严格禁止这种场景的出现,因为无法拆分。有效的方法是将该大 Key 从 Redis 去除,单独放到其余存储介质上。
3. 该对象为集合类型的大 Key,每次只需操作部分元素:将集合类型中的元素分拆。以 Hash 类型为例,可以在客户端定义一个分拆 Key 的数量 N,每次对 HGET 和 HSET 操作的 field 计算哈希值并取模 N,确定该 field 落在哪个 Key 上,实现上类似于 Redis Cluster 的计算 slot 的算法。
将大 Key 单独转移到其余存储介质:
1. 无法拆分的大 Key 建议使用此方法,将不适用 Redis 能力的数据存至其它存储介质,如 SFS 或者其余 NoSQL 数据库,并在 Redis 中删除该大 Key。
合理设置过期时间并对过期数据定期清理:
1. 合理设置过期时间,避免历史数据在 Redis 中大量堆积。可以配置数据删除策略为定期删除
热 Key使用读写分离:
如果热 Key 主要是读流量较大,则可以在客户端配置读写分离,降低对主节点的影响。
使用客户端缓存/本地缓存:
该方案需要提前了解业务的热点 Key 有哪些,设计客户端/本地和远端 Redis 的两级缓存架构,热点数据优先从本地缓存获取,写入时同时更新,这样能够分担热点数据的大部分读压力。缺点是需要修改客户端架构和代码,改造成本较高。
设计熔断/降级机制:
热 Key 极易造成缓存击穿,高峰期请求都直接透传到后端数据库上,从而导致业务雪崩。因此热 Key 的优化一定需要设计系统的熔断/降级机制,在发生击穿的场景下进行限流和服务降级,保护系统的可用性。