第486集Redis 热点 key 怎么治理?
|字数总计:5.1k|阅读时长:22分钟|阅读量:
Redis 热点 key 怎么治理?
1. 概述
1.1 热点key的重要性
热点key是Redis性能优化的核心问题之一,热点key可能导致Redis单点压力过大,影响系统性能和稳定性。
热点key的影响:
- 单点压力:大量请求集中在单个key,导致Redis单点压力过大
- 性能下降:热点key可能导致Redis性能下降
- 系统不稳定:热点key可能导致系统不稳定
- 资源浪费:热点key可能导致资源浪费
1.2 热点key的定义
热点key:访问频率远高于其他key的key。
热点key的特征:
- 访问频率高:QPS远高于其他key
- 访问集中:大量请求集中在单个key
- 影响范围大:影响整个系统的性能
1.3 本文内容结构
本文将从以下几个方面全面解析Redis热点key治理:
- 热点key检测:如何检测热点key
- 本地缓存方案:使用本地缓存减少Redis压力
- 分片方案:将热点key分片到多个key
- 限流方案:对热点key进行限流
- 预热方案:预热热点key到本地缓存
- 其他方案:其他治理方案
- 实战案例:实际项目中的热点key治理
2. 热点key检测
2.1 检测方法
2.1.1 基于监控统计
方法:通过监控系统统计每个key的访问频率。
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Component public class HotKeyDetector { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private MeterRegistry meterRegistry;
public List<HotKey> detectHotKeys(int threshold) { Map<String, Long> keyAccessCount = getKeyAccessCount(); List<HotKey> hotKeys = new ArrayList<>(); for (Map.Entry<String, Long> entry : keyAccessCount.entrySet()) { if (entry.getValue() > threshold) { HotKey hotKey = new HotKey(); hotKey.setKey(entry.getKey()); hotKey.setAccessCount(entry.getValue()); hotKey.setQps(entry.getValue() / 60.0); hotKeys.add(hotKey); } } return hotKeys; }
public void recordKeyAccess(String key) { meterRegistry.counter("redis.key.access", "key", key).increment(); } }
|
2.1.2 基于Redis监控
方法:通过Redis的MONITOR命令或INFO命令监控key访问。
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @Component public class RedisHotKeyMonitor { @Autowired private RedisTemplate<String, String> redisTemplate; private final Map<String, AtomicLong> keyAccessCount = new ConcurrentHashMap<>();
@PostConstruct public void startMonitoring() { new Thread(() -> { Jedis jedis = new Jedis("localhost", 6379); jedis.monitor(new JedisMonitor() { @Override public void onCommand(String command) { String key = extractKey(command); if (key != null) { keyAccessCount.computeIfAbsent(key, k -> new AtomicLong(0)).incrementAndGet(); } } }); }).start(); }
public List<HotKey> getHotKeys(int threshold) { List<HotKey> hotKeys = new ArrayList<>(); for (Map.Entry<String, AtomicLong> entry : keyAccessCount.entrySet()) { if (entry.getValue().get() > threshold) { HotKey hotKey = new HotKey(); hotKey.setKey(entry.getKey()); hotKey.setAccessCount(entry.getValue().get()); hotKeys.add(hotKey); } } return hotKeys; } private String extractKey(String command) { return null; } }
|
2.2 实时检测
2.2.1 基于AOP拦截
方法:通过AOP拦截Redis操作,统计key访问频率。
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Aspect @Component public class RedisKeyAccessAspect { @Autowired private HotKeyDetector hotKeyDetector; private final Map<String, AtomicLong> keyAccessCount = new ConcurrentHashMap<>(); @Around("execution(* org.springframework.data.redis.core.RedisTemplate.*(..))") public Object interceptRedisOperation(ProceedingJoinPoint joinPoint) throws Throwable { String key = extractKey(joinPoint); if (key != null) { keyAccessCount.computeIfAbsent(key, k -> new AtomicLong(0)).incrementAndGet(); hotKeyDetector.recordKeyAccess(key); } return joinPoint.proceed(); } private String extractKey(ProceedingJoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); if (args.length > 0 && args[0] instanceof String) { return (String) args[0]; } return null; } }
|
3. 本地缓存方案
3.1 原理
3.1.1 基本思路
本地缓存方案:将热点key的数据缓存到应用本地,减少对Redis的访问。
优势:
- 减少Redis压力:减少对Redis的访问
- 提高响应速度:本地缓存访问速度更快
- 降低网络开销:减少网络请求
缺点:
- 内存占用:本地缓存占用应用内存
- 数据一致性:需要处理数据一致性问题
- 多实例问题:多实例环境下,每个实例都需要缓存
3.2 实现代码
3.2.1 Caffeine本地缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| @Service public class UserService { @Autowired private UserMapper userMapper; @Autowired private RedisTemplate<String, String> redisTemplate; private final Cache<String, User> localCache = Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(5, TimeUnit.MINUTES) .expireAfterAccess(3, TimeUnit.MINUTES) .recordStats() .build();
public User getUser(Long userId) { String cacheKey = "user:" + userId; User user = localCache.getIfPresent(cacheKey); if (user != null) { return user; } String userJson = redisTemplate.opsForValue().get(cacheKey); if (userJson != null) { user = JSON.parseObject(userJson, User.class); localCache.put(cacheKey, user); return user; } user = userMapper.selectById(userId); if (user != null) { redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user), 1, TimeUnit.HOURS); localCache.put(cacheKey, user); } return user; }
public void updateUser(User user) { String cacheKey = "user:" + user.getId(); userMapper.updateById(user); redisTemplate.delete(cacheKey); localCache.invalidate(cacheKey); notifyCacheInvalidation(cacheKey); } }
|
3.2.2 热点key自动识别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Service public class HotKeyLocalCache { @Autowired private RedisTemplate<String, String> redisTemplate; private final Cache<String, String> hotKeyCache = Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.MINUTES) .build(); private final Map<String, AtomicLong> keyAccessCount = new ConcurrentHashMap<>();
public String get(String key) { long accessCount = keyAccessCount.computeIfAbsent(key, k -> new AtomicLong(0)) .incrementAndGet(); if (accessCount > 100) { String value = hotKeyCache.getIfPresent(key); if (value != null) { return value; } value = redisTemplate.opsForValue().get(key); if (value != null) { hotKeyCache.put(key, value); } return value; } else { return redisTemplate.opsForValue().get(key); } } }
|
4. 分片方案
4.1 原理
4.1.1 基本思路
分片方案:将热点key分片到多个key,分散访问压力。
优势:
- 分散压力:将单个key的压力分散到多个key
- 提高并发:多个key可以并发访问
- 易于扩展:可以动态增加分片数量
缺点:
- 实现复杂:需要实现分片逻辑
- 数据一致性:需要保证分片数据的一致性
- 查询复杂:查询时需要聚合多个分片的数据
4.2 实现代码
4.2.1 简单分片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Service public class HotKeySharding { @Autowired private RedisTemplate<String, String> redisTemplate; private static final int SHARD_COUNT = 10;
public void set(String key, String value) { List<String> shardKeys = getShardKeys(key); for (String shardKey : shardKeys) { redisTemplate.opsForValue().set(shardKey, value, 1, TimeUnit.HOURS); } }
public String get(String key) { List<String> shardKeys = getShardKeys(key); String shardKey = shardKeys.get(new Random().nextInt(shardKeys.size())); return redisTemplate.opsForValue().get(shardKey); }
private List<String> getShardKeys(String key) { List<String> shardKeys = new ArrayList<>(); for (int i = 0; i < SHARD_COUNT; i++) { shardKeys.add(key + ":shard:" + i); } return shardKeys; } }
|
4.2.2 基于Hash分片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Service public class HotKeyHashSharding { @Autowired private RedisTemplate<String, String> redisTemplate; private static final int SHARD_COUNT = 10;
public void set(String key, String value) { int shardIndex = getShardIndex(key); String shardKey = key + ":shard:" + shardIndex; redisTemplate.opsForValue().set(shardKey, value, 1, TimeUnit.HOURS); }
public String get(String key) { int shardIndex = getShardIndex(key); String shardKey = key + ":shard:" + shardIndex; return redisTemplate.opsForValue().get(shardKey); }
private int getShardIndex(String key) { return Math.abs(key.hashCode()) % SHARD_COUNT; } }
|
4.2.3 动态分片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| @Service public class DynamicHotKeySharding { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private HotKeyDetector hotKeyDetector; private final Map<String, Integer> keyShardCount = new ConcurrentHashMap<>();
public String get(String key) { int shardCount = getShardCount(key); if (shardCount == 1) { return redisTemplate.opsForValue().get(key); } else { int shardIndex = getShardIndex(key, shardCount); String shardKey = key + ":shard:" + shardIndex; return redisTemplate.opsForValue().get(shardKey); } }
public void set(String key, String value) { int shardCount = getShardCount(key); if (shardCount == 1) { redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS); } else { for (int i = 0; i < shardCount; i++) { String shardKey = key + ":shard:" + i; redisTemplate.opsForValue().set(shardKey, value, 1, TimeUnit.HOURS); } } }
private int getShardCount(String key) { if (hotKeyDetector.isHotKey(key)) { return keyShardCount.computeIfAbsent(key, k -> 10); } else { return 1; } }
private int getShardIndex(String key, int shardCount) { return Math.abs(key.hashCode()) % shardCount; } }
|
5. 限流方案
5.1 原理
5.1.1 基本思路
限流方案:对热点key的访问进行限流,防止过度访问。
优势:
- 保护Redis:防止热点key过度访问Redis
- 保证稳定性:保证系统稳定性
- 资源保护:保护Redis资源
缺点:
- 可能影响业务:限流可能影响正常业务
- 需要合理设置:需要合理设置限流阈值
5.2 实现代码
5.2.1 基于令牌桶限流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Service public class HotKeyRateLimiter { @Autowired private RedisTemplate<String, String> redisTemplate; private final Map<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();
public String get(String key) { if (isHotKey(key)) { RateLimiter rateLimiter = getRateLimiter(key); if (!rateLimiter.tryAcquire()) { throw new BusinessException("热点key访问过于频繁,请稍后再试"); } } return redisTemplate.opsForValue().get(key); }
private RateLimiter getRateLimiter(String key) { return rateLimiters.computeIfAbsent(key, k -> { return RateLimiter.create(1000); }); } private boolean isHotKey(String key) { return hotKeyDetector.isHotKey(key); } }
|
5.2.2 基于Redis限流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Service public class RedisHotKeyRateLimiter { @Autowired private RedisTemplate<String, String> redisTemplate;
public String get(String key) { if (isHotKey(key)) { String limitKey = "rate_limit:hot_key:" + key; String countStr = redisTemplate.opsForValue().get(limitKey); int count = countStr == null ? 0 : Integer.parseInt(countStr); if (count >= 1000) { throw new BusinessException("热点key访问过于频繁,请稍后再试"); } redisTemplate.opsForValue().increment(limitKey); redisTemplate.expire(limitKey, 1, TimeUnit.SECONDS); } return redisTemplate.opsForValue().get(key); } }
|
6. 预热方案
6.1 原理
6.1.1 基本思路
预热方案:在系统启动或定时任务中,将热点key的数据预热到本地缓存。
优势:
- 减少冷启动:减少系统启动时的缓存未命中
- 提高性能:提前加载热点数据,提高响应速度
- 降低压力:减少对Redis的访问压力
6.2 实现代码
6.2.1 启动预热
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Component public class HotKeyPreloader { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private HotKeyDetector hotKeyDetector; @Autowired private Cache<String, String> localCache;
@PostConstruct public void preloadHotKeys() { List<HotKey> hotKeys = hotKeyDetector.getHotKeys(1000); for (HotKey hotKey : hotKeys) { String value = redisTemplate.opsForValue().get(hotKey.getKey()); if (value != null) { localCache.put(hotKey.getKey(), value); } } log.info("Preloaded {} hot keys", hotKeys.size()); } }
|
6.2.2 定时预热
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @Component public class HotKeyPreloader { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private HotKeyDetector hotKeyDetector; @Autowired private Cache<String, String> localCache;
@Scheduled(fixedDelay = 300000) public void preloadHotKeys() { List<HotKey> hotKeys = hotKeyDetector.getHotKeys(1000); for (HotKey hotKey : hotKeys) { try { String value = redisTemplate.opsForValue().get(hotKey.getKey()); if (value != null) { localCache.put(hotKey.getKey(), value); } } catch (Exception e) { log.error("Preload hot key failed: {}", hotKey.getKey(), e); } } log.info("Preloaded {} hot keys", hotKeys.size()); } }
|
7. 其他方案
7.1 互斥锁方案
7.1.1 原理
互斥锁方案:在缓存失效时,使用分布式锁确保只有一个线程进行缓存重建,防止缓存击穿。
优势:
- 防止缓存击穿:避免大量请求同时访问数据库
- 保证数据一致性:确保缓存重建的一致性
7.1.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| @Service public class HotKeyMutexLock { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private RedissonClient redissonClient; @Autowired private UserMapper userMapper;
public User getUser(Long userId) { String cacheKey = "user:" + userId; String userJson = redisTemplate.opsForValue().get(cacheKey); if (userJson != null) { return JSON.parseObject(userJson, User.class); } String lockKey = "lock:user:" + userId; RLock lock = redissonClient.getLock(lockKey); try { if (lock.tryLock(100, 10, TimeUnit.MILLISECONDS)) { try { userJson = redisTemplate.opsForValue().get(cacheKey); if (userJson != null) { return JSON.parseObject(userJson, User.class); } User user = userMapper.selectById(userId); if (user != null) { redisTemplate.opsForValue().set( cacheKey, JSON.toJSONString(user), 1, TimeUnit.HOURS ); } return user; } finally { lock.unlock(); } } else { Thread.sleep(50); return getUser(userId); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException("获取锁被中断", e); } } }
|
7.2 设置合理的过期时间
7.2.1 原理
设置合理的过期时间:为热点key设置适当的过期时间,防止缓存数据长期占用内存,同时避免缓存雪崩。
策略:
- 随机过期时间:避免大量key同时过期
- 分层过期时间:不同类型的数据设置不同的过期时间
- 动态调整:根据业务特点动态调整过期时间
7.2.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @Service public class HotKeyExpirationStrategy { @Autowired private RedisTemplate<String, String> redisTemplate;
public void set(String key, String value, int baseExpireSeconds) { int randomOffset = (int) (baseExpireSeconds * 0.2 * (Math.random() - 0.5)); int expireSeconds = baseExpireSeconds + randomOffset; redisTemplate.opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS); }
public void setHotKey(String key, String value, KeyType keyType) { int expireSeconds; switch (keyType) { case USER_INFO: expireSeconds = 3600; break; case PRODUCT_INFO: expireSeconds = 1800; break; case ORDER_INFO: expireSeconds = 7200; break; default: expireSeconds = 3600; } int randomOffset = (int) (expireSeconds * 0.1 * (Math.random() - 0.5)); expireSeconds += randomOffset; redisTemplate.opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS); } }
enum KeyType { USER_INFO, PRODUCT_INFO, ORDER_INFO }
|
7.3 读写分离
7.3.1 原理
读写分离:热点key的读操作分散到多个Redis实例。
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Service public class HotKeyReadWriteSplit { @Autowired private RedisTemplate<String, String> masterRedis; @Autowired private List<RedisTemplate<String, String>> slaveRedisList;
public String get(String key) { if (isHotKey(key)) { RedisTemplate<String, String> slaveRedis = slaveRedisList.get(new Random().nextInt(slaveRedisList.size())); return slaveRedis.opsForValue().get(key); } else { return masterRedis.opsForValue().get(key); } }
public void set(String key, String value) { masterRedis.opsForValue().set(key, value, 1, TimeUnit.HOURS); } }
|
7.4 异步更新
7.4.1 原理
异步更新:热点key的更新操作异步处理,减少对Redis的压力。
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Service public class HotKeyAsyncUpdate { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private ExecutorService asyncExecutor;
public void set(String key, String value) { if (isHotKey(key)) { asyncExecutor.submit(() -> { redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS); }); } else { redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS); } } }
|
8. 综合方案
8.1 完整治理方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
| @Service public class HotKeyGovernance { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private HotKeyDetector hotKeyDetector; @Autowired private Cache<String, String> localCache; @Autowired private RedissonClient redissonClient;
public String get(String key) { boolean isHotKey = hotKeyDetector.isHotKey(key); if (isHotKey) { return getHotKey(key); } else { return getNormalKey(key); } }
private String getHotKey(String key) { String value = localCache.getIfPresent(key); if (value != null) { return value; } if (!rateLimiter.tryAcquire(key)) { return getFallbackValue(key); } value = getFromRedis(key); if (value != null) { localCache.put(key, value); } else { value = getWithMutexLock(key); } return value; }
private String getNormalKey(String key) { String value = redisTemplate.opsForValue().get(key); if (value != null) { return value; } return getWithMutexLock(key); }
private String getWithMutexLock(String key) { String lockKey = "lock:" + key; RLock lock = redissonClient.getLock(lockKey); try { if (lock.tryLock(100, 10, TimeUnit.MILLISECONDS)) { try { String value = redisTemplate.opsForValue().get(key); if (value != null) { return value; } value = loadFromDatabase(key); if (value != null) { int expireSeconds = 3600 + (int)(Math.random() * 600); redisTemplate.opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS); } return value; } finally { lock.unlock(); } } else { Thread.sleep(50); return get(key); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusinessException("获取锁被中断", e); } }
private String getFromRedis(String key) { if (isSharded(key)) { return getFromShardedRedis(key); } else { return redisTemplate.opsForValue().get(key); } } private String loadFromDatabase(String key) { return null; } private String getFallbackValue(String key) { return null; } private boolean isSharded(String key) { return false; } private String getFromShardedRedis(String key) { return null; } }
|
9. 实战案例
9.1 案例:电商商品详情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| @Service public class ProductService { @Autowired private ProductMapper productMapper; @Autowired private RedisTemplate<String, String> redisTemplate; private final Cache<String, Product> localCache = Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(5, TimeUnit.MINUTES) .build();
public Product getProduct(Long productId) { String cacheKey = "product:" + productId; Product product = localCache.getIfPresent(cacheKey); if (product != null) { return product; } String productJson = redisTemplate.opsForValue().get(cacheKey); if (productJson != null) { product = JSON.parseObject(productJson, Product.class); if (isHotProduct(productId)) { localCache.put(cacheKey, product); } return product; } product = productMapper.selectById(productId); if (product != null) { redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(product), 1, TimeUnit.HOURS); if (isHotProduct(productId)) { localCache.put(cacheKey, product); } } return product; } private boolean isHotProduct(Long productId) { return hotKeyDetector.isHotKey("product:" + productId); } }
|
10. 总结
10.1 核心要点
- 热点key检测:通过监控、AOP等方式检测热点key
- 本地缓存:使用本地缓存减少Redis压力(最有效方案)
- 分片方案:将热点key分片到多个key,分散压力
- 限流方案:对热点key进行限流保护,防止系统过载
- 预热方案:预热热点key到本地缓存,减少冷启动
- 互斥锁:使用分布式锁防止缓存击穿
- 合理过期时间:设置随机过期时间,避免缓存雪崩
- 综合方案:组合使用多种方案,综合治理
10.2 关键理解
- 检测是基础:首先要能检测到热点key
- 本地缓存优先:本地缓存是最有效的方案,优先使用
- 分片分散压力:分片可以分散单个key的压力
- 限流保护系统:限流可以保护系统不被热点key压垮
- 互斥锁防击穿:使用分布式锁防止缓存击穿
- 随机过期时间:避免大量key同时过期导致缓存雪崩
10.3 最佳实践
- 实时检测:实时检测热点key,及时发现问题
- 本地缓存:热点key优先使用本地缓存,减少Redis压力
- 分片处理:超热点key使用分片,分散访问压力
- 限流保护:对热点key进行限流,保护系统稳定性
- 互斥锁:缓存失效时使用互斥锁,防止缓存击穿
- 随机过期:设置随机过期时间,避免缓存雪崩
- 监控告警:监控热点key的访问情况,及时告警
- 预热机制:系统启动或定时预热热点key,减少冷启动
相关文章: