redis的大key和热key问题解决方案

1. Redis 大 Key 问题

原因
  • 内存占用:大 Key 占用大量内存,可能导致 Redis 服务器内存不足。
  • 性能影响:
    • 删除操作:删除大 Key 时,Redis 需要释放大量内存,这可能阻塞其他操作。
    • 过期操作:大 Key 过期时,同样会阻塞 Redis 服务器。
    • 持久化操作:在进行 RDB 或 AOF 持久化时,大 Key 会增加持久化文件的大小,延长持久化时间。
  • 网络传输:大 Key 在网络传输时会占用大量带宽,影响其他数据的传输效率。
解决方案
  1. 拆分数据:将大 Key 拆分成多个小 Key,减少单个 Key 的内存占用。

  2. 使用过期时间:为大 Key 设置合理的过期时间,避免长时间占用内存。

  3. 优化数据结构 :选择合适的数据结构来存储数据,例如使用 zset 替代 list,使用 hash 替代多个 string。

  4. 定期清理:定期检查和清理不再需要的大 Key。

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.UUID;

    @Service
    public class BigKeySplitterService {

    复制代码
     @Autowired
     private StringRedisTemplate stringRedisTemplate;
    
     private static final int CHUNK_SIZE = 1000; // 每个分片的最大大小
    
     /**
      * 将大 Key 拆分成多个小 Key
      *
      * @param bigKey 大 Key
      * @param values 大 Key 对应的值列表
      */
     public void splitBigKey(String bigKey, List<String> values) {
         int totalChunks = (int) Math.ceil((double) values.size() / CHUNK_SIZE);
         for (int i = 0; i < totalChunks; i++) {
             int start = i * CHUNK_SIZE;
             int end = Math.min(start + CHUNK_SIZE, values.size());
             List<String> chunk = values.subList(start, end);
    
             String chunkKey = bigKey + ":chunk:" + i;
             stringRedisTemplate.opsForList().rightPushAll(chunkKey, chunk);
         }
     }
    
     /**
      * 合并多个小 Key 为一个大 Key
      *
      * @param bigKey 大 Key
      * @return 合并后的值列表
      */
     public List<String> mergeChunks(String bigKey) {
         List<String> result = new ArrayList<>();
    
         int i = 0;
         while (true) {
             String chunkKey = bigKey + ":chunk:" + i;
             List<String> chunk = stringRedisTemplate.opsForList().range(chunkKey, 0, -1);
             if (chunk.isEmpty()) {
                 break;
             }
             result.addAll(chunk);
             i++;
         }
    
         return result;
     }

    }

2. Redis 热 Key 问题

原因
  • 高并发访问:某些 Key 被大量请求访问,导致 Redis 服务器负载过高。
  • 性能瓶颈:热 Key 可能成为性能瓶颈,影响整个系统的响应速度。
解决方案
  1. 数据分片:将数据分布在多个 Redis 实例上,减轻单个实例的负载。
  2. 缓存层:在 Redis 前面增加一层缓存,例如使用本地缓存或 CDN 缓存。
  3. 限流:对热 Key 的访问进行限流,避免过多请求同时打到 Redis 上。
  4. 预热:提前加载热 Key,避免冷启动时的高负载。

数据分片:将数据分布在多个 Redis 实例上,减轻单个实例的负载。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class DataShardingService {

    @Autowired
    private Map<Integer, StringRedisTemplate> redisTemplates;

    /**
     * 初始化 Redis 实例映射
     */
    @Autowired
    public void initRedisTemplates() {
        redisTemplates = new HashMap<>();
        // 假设我们有3个 Redis 实例
        redisTemplates.put(0, new StringRedisTemplate()); // 配置 Redis 实例1
        redisTemplates.put(1, new StringRedisTemplate()); // 配置 Redis 实例2
        redisTemplates.put(2, new StringRedisTemplate()); // 配置 Redis 实例3
    }

    /**
     * 根据 Key 的哈希值选择 Redis 实例
     *
     * @param key 键
     * @return 选择的 Redis 实例
     */
    public StringRedisTemplate selectRedisInstance(String key) {
        int hash = key.hashCode();
        int instanceIndex = Math.abs(hash % redisTemplates.size());
        return redisTemplates.get(instanceIndex);
    }

    /**
     * 存储数据
     *
     * @param key   键
     * @param value 值
     */
    public void setData(String key, String value) {
        StringRedisTemplate redisTemplate = selectRedisInstance(key);
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 获取数据
     *
     * @param key 键
     * @return 值
     */
    public String getData(String key) {
        StringRedisTemplate redisTemplate = selectRedisInstance(key);
        return redisTemplate.opsForValue().get(key);
    }
}

使用 Redis 的原子递增操作来实现限流。

复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class RateLimiterService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 限流逻辑
     *
     * @param key      限流的键
     * @param limit    限流阈值
     * @param duration 限流时间窗口(秒)
     * @return 是否超过限制
     */
    public boolean isRateLimited(String key, int limit, int duration) {
        String rateLimitKey = "rate_limit:" + key;
        long count = stringRedisTemplate.opsForValue().increment(rateLimitKey, 1);

        if (count == 1) {
            stringRedisTemplate.expire(rateLimitKey, duration, TimeUnit.SECONDS);
        }

        return count > limit;
    }
}
相关推荐
Decay__33 分钟前
VMware 安装 SQL Server 双节点 AlwaysOn 可用性组教程 —— WSFC、AG 与侦听器完整部署(系列核心篇)
运维·数据库·sql·mysql·架构
茶栀(*´I`*)1 小时前
SQL 性能分析:从慢查询日志到执行计划的完整定位方法
数据库·sql
艺杯羹2 小时前
1Panel 装好后 ufw 仍是 inactive:一次服务器安全盲区复盘
服务器·数据库·安全·网络安全·系统架构·实战·1panel
code2cat3 小时前
【随笔】MCP工具重试:任务已经创建,响应丢了怎么办
数据库·mcp
Star(up up up)3 小时前
Redis Windows 安装教程:使用Windows 的 Redis 压缩包
redis·笔记·学习·程序人生·云原生·学习方法·改行学it
wuyk5554 小时前
Spider-第10讲-SQLite持久化与去重
数据库·爬虫·sqlite
乌暮6 小时前
Spring AOP 与 Spring 事务:用大白话彻底理解原理与实践
java·数据库·spring boot·后端·sql·学习·spring
小此方7 小时前
Re:Mysql数据库基础篇(十二):什么是事务、事务解决了什么问题、事务的四大属性、事务的操作方式
数据库·mysql
flash俊杰8 小时前
v0.5-01 让桌面 AI 助手记得住:预算组装、递归摘要、长期记忆与评估基线
数据库·安全·架构
2501_933923258 小时前
redis:string类型常见命令
数据库·redis·缓存