Redis 雪崩
雪崩就是指缓存中大批量热点数据过期后系统涌入大量查询请求,因为大部分数据在Redis层已经失效,请求渗透到数据库层,大批量请求犹如洪水一般涌入,引起数据库压力造成查询堵塞甚至宕机。
解决办法:
- 将缓存失效时间分散开,比如每个key的过期时间是随机,防止同一时间大量数据过期现象发生,这样不会出现同一时间全部请求都落在数据库层,如果缓存数据库是分布式部署,将热点数据均匀分布在不同Redis和数据库中,有效分担压力,别一个人扛。
- 简单粗暴,让Redis数据永不过期(如果业务准许,比如不用更新的名单类)。当然,如果业务数据准许的情况下可以,比如中奖名单用户,每期用户开奖后,名单不可能会变了,无需更新。
缓存穿透
黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。
缓存击穿
缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
解决方式也很简单,
- 可以将热点数据设置为永远不过期;
- 基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 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
| import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration public class RedisConfig { @Bean public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<String, String> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(RedisSerializer.string()); template.setHashKeySerializer(RedisSerializer.string()); template.setValueSerializer(RedisSerializer.string()); template.setHashValueSerializer(RedisSerializer.string()); return template; } }
|
添加锁方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
private Boolean tryLock(String key) { Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); return BooleanUtils.isTrue(flag); }
private void unLock(String key) { stringRedisTemplate.delete(key); }
|
获取互斥锁和释放锁的传参都应传城市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 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
private City queryCityWithMutex(String key, String cityCode) {
City city = null; String cityJson = stringRedisTemplate.opsForValue().get(key); if (StringUtils.isNotBlank(cityJson)) { city = JSONObject.parseObject(cityJson, City.class); return city; } String lockKey = RedisConstants.LOCK_CITY_KEY + cityCode; Boolean isLock = tryLock(lockKey); try { if (!isLock) { Thread.sleep(100); return queryCityWithMutex(key, cityCode); } city = baseMapper.getByCode(cityCode); if (city == null) { stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES); return null; } stringRedisTemplate.opsForValue() .set(key, JSONObject.toJSONString(city), RedisConstants.CACHE_CITY_TTL, TimeUnit.MINUTES); } catch (Exception e) { throw new RuntimeException(e); } finally { unLock(lockKey); } return city; }
|
其中常量
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
|
public interface RedisConstants {
Long CACHE_NULL_TTL = 2L;
String CACHE_CITY_KEY = "cache:city:";
Long CACHE_CITY_TTL = 30L;
String LOCK_CITY_KEY = "lock:city:"; }
|