当前位置: 首页 > news >正文

Redis的大key和热key如何解决

文章目录

  • Redis大Key
    • 一、什么是Redis大Key
    • 二、大Key的产生原因
    • 三、大Key的影响
    • 四、大Key的解决方案
      • 1. 检测大Key
      • 2. 解决方案
        • (1) 数据拆分
        • (2) 使用压缩算法
        • (3) 使用合适的数据结构
        • (4) 设置合理的过期时间
        • (5) 合理清理
        • (6) 配置优化
    • 五、预防措施
    • 总结
  • Redis热key
    • 一、热Key问题的本质
      • 1. 什么是热Key
      • 2. 热Key的危害
    • 二、热Key的识别方法
      • 1. 使用Redis内置命令
      • 2. 使用slowlog分析
      • 3. 客户端埋点统计
      • 4. 使用第三方工具
    • 三、热Key问题的解决方案
      • 1. 本地缓存方案
      • 2. Key拆分方案
      • 3. 多级缓存方案
      • 4. 读写分离方案
      • 5. 数据分片方案
      • 6. 缓存永不过期方案
      • 7. 请求合并方案
    • 四、预防热Key的最佳实践
    • 五、云服务商的热Key解决方案
      • 阿里云Redis
      • AWS ElastiCache
    • 六、总结

Redis大Key

在这里插入图片描述

Redis大Key问题是Redis使用过程中常见的性能瓶颈之一,它会对Redis的性能、稳定性和资源利用率产生显著影响。下面我将从大Key的定义、产生原因、影响以及解决方案几个方面进行全面分析。

一、什么是Redis大Key

Redis大Key并没有统一的固定标准,通常根据业务场景而定,主要分为以下几种情况:

  1. 字符串类型(String):单个Key的Value特别大,一般认为在普通业务场景下,如果单个String类型的value大于1MB,或者在高并发低延迟场景中大于10KB,就可能被视为大Key

  2. 集合数据类型:如Hash、Set、ZSet、List等,其中的元素数量过多或总体数据量过大。例如,一个Hash类型Key的成员数量虽只有1000个,但这些成员的Value总大小达到100MB,或者一个ZSet类型的Key成员数量达到10000个

  3. 内存占用过高:比如阿里云Redis定义中,一个String类型的Key其值达到5MB,或一个ZSet类型的Key成员数量达到10000个,都被视为大Key

二、大Key的产生原因

大Key的产生通常有以下几种原因:

  1. 业务设计不合理:最常见的原因是在没有合理拆分的情况下,直接将大量数据(如大的JSON对象或二进制文件数据)存储在一个键中

例如:

  • 缓存大数据(图片和视频元数据)

  • 明星或网红粉丝列表

  • 商品页所有信息

  1. 未能处理Value动态增长问题:随着时间推移,如果持续向某个键的Value中添加数据,而又没有相应的定期删除机制、合理的过期策略或大小限制,Value的大小最终会增长到难以管理的程度

    • 不断累积的微博粉丝列表、热门评论或直播弹幕

    • 缓存时间设置不合理,导致数据不断累积

      5

  2. 程序Bug:软件开发中的错误可能导致某些键的生命周期超出预期,或者其包含的元素数量异常增长

    • 负责消费LIST类型键的业务代码发生故障,导致该Key的成员只增不减

    • 数据结构使用不当,如List中重复添加数据

  3. 存储大量数据的容器:如list、set等,随着业务增长,数据量不断增加

三、大Key的影响

大Key会对Redis系统产生多方面的负面影响:

  1. 读取成本高:大Key由于体积大,读取时会消耗更多的时间,增加延迟,尤其是在网络传输中会占用更多带宽

  2. 写操作易阻塞:Redis采用单线程模型处理请求,操作大Key会阻塞其他命令的执行,导致整个Redis服务响应变慢

  3. 慢查询与主从同步异常:大Key的读写操作时间长,可能触发Redis的慢查询日志记录;在主从复制场景下,大Key的同步也会比小Key慢

  4. 内存问题:大Key占据大量内存空间,容易触发Redis的内存淘汰策略,造成重要数据被意外移除;在极端情况下可能导致Redis实例因内存耗尽而崩溃(OOM)

  5. 集群架构下的内存资源不均衡:在Redis集群中,若某个分片上有大Key,该分片的内存使用率将远高于其他分片,打破集群间内存使用的均衡状态

  6. 持久化效率降低:AOF与RDB持久化操作会因大Key耗费更多时间

四、大Key的解决方案

1. 检测大Key

在解决问题前,首先需要检测和识别大Key:

  1. redis-cli --bigkeys:Redis自带的命令,可以查询当前Redis中所有key的信息,对整个数据库中的键值对大小情况进行统计分析

    redis-cli --bigkeys
    
  2. MEMORY USAGE命令:Redis 4.0后推出的命令,可以返回指定key的内存使用情况

    MEMORY USAGE keyname
    
  3. RDB Tools:使用Redis RDB Tools等第三方工具分析RDB文件,找出大Key

    rdb -c memory dump.rdb --bytes 10240 -f bigkeys.csv
    
  4. Lua脚本遍历:编写Lua脚本遍历所有Key并统计大Key

2. 解决方案

(1) 数据拆分

将大Key拆分成多个小Key是最常见的解决方案:

  • 按业务逻辑拆分:例如,对于一个包含全品类商品销售数据的大Key,可以按照品类拆分为多个小的键

  • 按时间范围拆分:对于时间序列数据,可以按照时间范围进行拆分

  • 分片存储:如将用户订单按用户ID分桶存储

示例代码:

// 原始大Key存储
RedisTemplate.opsForValue().set("bigKey", largeValue);// 拆分后的存储
for (int i = 0; i < largeValue.size(); i++) {RedisTemplate.opsForValue().set("smallKey_" + i, largeValue.get(i));
}
(2) 使用压缩算法

对于可以压缩的数据类型(如字符串),可以使用压缩算法来减少内存占用

Python示例:

import zlib
# 压缩Value
compressed_value = zlib.compress(large_value)
# 存储压缩后的Value
redis.set("compressedKey", compressed_value)
# 解压缩Value
original_value = zlib.decompress(redis.get("compressedKey"))
(3) 使用合适的数据结构
  • 选择合适的Redis数据结构:例如,用Bitmap代替String记录URL访问情况 ,用HyperLogLog统计UV

  • 考虑使用其他存储系统:对于不适合Redis存储的大数据,可考虑转移到分布式文件系统,只在Redis中保留元数据

(4) 设置合理的过期时间

如果大Key中的数据不是一直需要的,可以设置过期时间,让Redis在一定时间后自动删除该Key

(5) 合理清理
  • 异步删除:使用UNLINK命令代替DEL命令异步删除大Key

    UNLINK big_hash_key
    
  • 分批定时定量删除:在低峰期分批删除大Key,防止阻塞

(6) 配置优化

调整Redis配置参数优化内存使用

CONFIG SET hash-max-ziplist-entries 512
CONFIG SET hash-max-ziplist-value 64

五、预防措施

  1. 合理设计数据结构:在业务设计初期就应该避免生成大Key,仅缓存必要的数据字段

  2. 监控预警:建立对Redis的监控系统,实时监测大Key的出现和内存使用情况

  3. 版本控制:为每个Value加入版本号,以进行一致性检查

  4. 定期维护:定期检查并清理潜在的大Key

总结

Redis大Key问题会对系统性能产生多方面的影响,需要通过合理的检测方法识别问题Key,并根据业务场景选择合适的解决方案。最佳实践是在系统设计阶段就考虑数据规模的增长,避免大Key的产生,同时建立完善的监控机制,及时发现和处理潜在的大Key问题


Redis热key

热Key(Hot Key)是Redis使用过程中常见的一个性能问题,指在短时间内被大量访问的单个Key或多个Key,可能导致Redis服务器负载不均、性能下降甚至服务不可用。

一、热Key问题的本质

1. 什么是热Key

热Key是指那些访问频率显著高于其他Key的特定Key,通常表现为:

  • 单个Key的QPS(每秒查询量)远高于平均水平
  • 对某个Key的访问量占整体访问量的很大比例
  • 由于访问集中导致Redis单线程处理瓶颈

2. 热Key的危害

  1. 性能瓶颈:Redis单线程模型下,热Key可能导致其他请求排队
  2. CPU负载高:处理大量相同Key请求消耗CPU资源
  3. 网络带宽压力:大量相同数据的重复传输
  4. 数据倾斜:在集群模式下导致某些节点负载过高
  5. 缓存击穿:热Key突然失效可能导致大量请求直达数据库

二、热Key的识别方法

1. 使用Redis内置命令

# 使用redis-cli的hotkeys功能(Redis 4.0+)
redis-cli --hotkeys# 使用monitor命令临时监控(谨慎使用,影响性能)
redis-cli monitor | grep "GET\|HGET\|SMEMBERS"

2. 使用slowlog分析

# 配置slowlog
redis-cli config set slowlog-log-slower-than 1000  # 记录执行超过1ms的命令
redis-cli config set slowlog-max-len 1000         # 保留1000条记录# 查看slowlog
redis-cli slowlog get

3. 客户端埋点统计

// 示例:使用AOP统计Key访问频率
@Aspect
@Component
public class RedisKeyMonitorAspect {private ConcurrentHashMap<String, AtomicLong> keyAccessCount = new ConcurrentHashMap<>();@Around("execution(* com.xxx.RedisService.*(..))")public Object monitorKeyAccess(ProceedingJoinPoint pjp) throws Throwable {String key = parseRedisKey(pjp.getArgs());keyAccessCount.computeIfAbsent(key, k -> new AtomicLong(0)).incrementAndGet();return pjp.proceed();}// 定期输出热Key统计@Scheduled(fixedRate = 60000)public void reportHotKeys() {keyAccessCount.entrySet().stream().sorted((e1, e2) -> Long.compare(e2.getValue().get(), e1.getValue().get())).limit(10).forEach(e -> System.out.println("HotKey: " + e.getKey() + " - " + e.getValue()));}
}

4. 使用第三方工具

  • RedisInsight:Redis官方可视化工具
  • CacheCloud:搜狐开源的Redis监控平台
  • Prometheus + Grafana:配合Redis exporter监控

三、热Key问题的解决方案

1. 本地缓存方案

适用场景:读多写少、数据一致性要求不严格的场景

// 使用Guava Cache实现本地缓存
LoadingCache<String, Object> localCache = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(10, TimeUnit.SECONDS)  // 10秒过期.build(new CacheLoader<String, Object>() {@Overridepublic Object load(String key) throws Exception {// 从Redis获取数据return redisTemplate.opsForValue().get(key);}});// 使用示例
public Object getData(String key) {try {return localCache.get(key);} catch (ExecutionException e) {// 异常处理return null;}
}

注意事项

  • 设置合理的过期时间,避免本地缓存与Redis数据不一致
  • 考虑使用消息队列通知本地缓存失效
  • 监控本地缓存命中率

2. Key拆分方案

适用场景:大Value的热Key

// 将一个大Key拆分为多个子Key
public void setBigData(String bigKey, BigData data) {// 拆分为多个子KeyMap<String, String> parts = splitBigData(data);// 使用pipeline批量写入redisTemplate.executePipelined((RedisCallback<Object>) connection -> {parts.forEach((subKey, value) -> {connection.set(("bigkey:"+bigKey+":"+subKey).getBytes(), value.getBytes());});return null;});
}public BigData getBigData(String bigKey) {// 获取所有子KeySet<String> subKeys = redisTemplate.keys("bigkey:"+bigKey+":*");// 批量获取List<Object> values = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {subKeys.forEach(key -> connection.get(key.getBytes()));return null;});// 合并结果return mergeValues(values);
}

3. 多级缓存方案

架构示例

客户端 → 本地缓存 → Redis集群 → 数据库

实现要点

  1. 第一级:客户端内存缓存(短时间,如1秒)
  2. 第二级:Redis集群
  3. 第三级:持久化存储

4. 读写分离方案

配置Redis主从复制

# 在从节点配置
replica-read-only yes

客户端实现

public class RedisReadWriteClient {private Jedis master;private List<Jedis> replicas;private AtomicInteger counter = new AtomicInteger(0);// 读操作路由到从节点public String get(String key) {Jedis replica = getNextReplica();return replica.get(key);}// 写操作使用主节点public void set(String key, String value) {master.set(key, value);}private Jedis getNextReplica() {int index = counter.incrementAndGet() % replicas.size();return replicas.get(index);}
}

5. 数据分片方案

Redis Cluster自动分片

# 创建集群(3主3从)
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1

客户端分片示例

public class ShardedRedisClient {private List<Jedis> nodes;public void set(String key, String value) {getNode(key).set(key, value);}public String get(String key) {return getNode(key).get(key);}private Jedis getNode(String key) {// 使用一致性哈希选择节点int hash = Math.abs(key.hashCode());return nodes.get(hash % nodes.size());}
}

6. 缓存永不过期方案

适用场景:配置类数据,极少变更

// 设置Key永不过期
redisTemplate.opsForValue().set("config:key", "value");// 后台更新逻辑
public void updateConfig(String key, Object value) {// 1. 更新数据库db.updateConfig(key, value);// 2. 更新RedisredisTemplate.opsForValue().set("config:"+key, value);// 3. 发送消息通知其他服务更新messageQueue.publish("config.update", key);
}

7. 请求合并方案

使用Google Guava的RateLimiter

private RateLimiter limiter = RateLimiter.create(1000); // 每秒1000个请求public Object getData(String key) {if (limiter.tryAcquire()) {return redisTemplate.opsForValue().get(key);} else {// 返回缓存中的旧数据或默认值return getCachedValue(key);}
}

四、预防热Key的最佳实践

  1. 设计阶段预防

    • 避免使用全局计数器等容易成为热点的设计
    • 对可能的热点数据提前规划拆分方案
  2. 监控报警

    # 监控单个Key的QPS
    while true; doredis-cli info stats | grep total_commands_processedsleep 1
    done
    
  3. 压力测试

    • 使用redis-benchmark模拟高并发
    redis-benchmark -t get,set -n 100000 -r 100000 -d 100
    
  4. Key命名规范

    • 业务前缀:数据类型:唯一标识 如 user:info:1001
    • 避免使用过长的Key名
  5. 数据过期策略

    • 对热Key设置合理的过期时间
    • 采用随机过期时间避免集中失效

五、云服务商的热Key解决方案

阿里云Redis

  1. 性能洞察功能:自动识别热Key
  2. 代理查询缓存:在Proxy层缓存热Key结果
  3. 读写分离版:自动将读请求路由到从节点

AWS ElastiCache

  1. Auto Scaling:根据负载自动扩展
  2. Enhanced Monitoring:提供详细的Key统计
  3. Read Replicas:配置多个只读副本

六、总结

解决Redis热Key问题需要综合考虑业务场景、数据特性和系统架构,主要解决方案包括:

  1. 本地缓存:减少对Redis的直接访问
  2. Key拆分:将大Key/热Key拆分为多个子Key
  3. 多级缓存:构建分层缓存体系
  4. 读写分离:利用从节点分担读压力
  5. 数据分片:将负载分散到不同节点
  6. 请求合并:减少重复请求

实际应用中,通常需要组合使用多种方案。例如,对商品详情页的热点数据可以同时采用:

  • 本地缓存(短时间)
  • Redis多级缓存
  • 读写分离
  • 监控报警

最后,热Key问题的解决不是一劳永逸的,需要持续监控并根据业务变化调整策略。建立完善的监控体系和应急预案,才能在热Key出现时快速响应,保障系统稳定运行。

相关文章:

  • 晨控CK-FR06与西门子PLC配置Modbus TCP通讯连接操作手册
  • React Native【详解】内置 API
  • 【攻防渗透】nps免杀
  • 【Datawhale组队学习202506】YOLO-Master task02 YOLO系列发展线
  • uniapp开发小程序,导出文件打开并保存,实现过程downloadFile下载,openDocument打开
  • 服务器手动安装并编译R环境库包:PROJ→RGDAL
  • JS入门——事件与事件绑定
  • Linux驱动第十章:SPI总线驱动子系统
  • 【unitrix】 3.5 类型级别的比较系统(cmp.rs)
  • RPC常见问题回答
  • 在 CI/CD 流程中使用 Jenkins 与 Docker 集成
  • 内存的代价:如何正确与 WASM 模块传值交互
  • 大内存对电脑性能有哪些提升
  • Redis ⑩-持久化 || RDB
  • 算法-每日一题(DAY11)每日温度
  • 【VUE】某时间某空间占用情况效果展示,vue2+element ui实现。场景:会议室占用、教室占用等。
  • MySQL基础多表查询
  • uniapp实现像qq消息列表左滑显示右侧操作栏效果
  • Qt—(Qt线程,Qt进程,,QT与sqlite数据库)
  • 学习华为 ensp 的学习心得体会
  • 佛山网站开发公司电话/品牌全案策划
  • 国家信用信息企业公示系统官网/windows优化大师靠谱吗
  • 苏州互联网公司工资/上海官网seo
  • 成都最好的网站建设公司/产品软文代写
  • 方维制网站/最好看免费观看高清大全
  • 西宁高端网站建设/千峰培训多少钱