Redis 的雪崩、穿透和击穿

2023-04-01 10:57:50 浏览数 (1)

Redis 雪崩

  雪崩就是指缓存中大批量热点数据过期后系统涌入大量查询请求,因为大部分数据在Redis层已经失效,请求渗透到数据库层,大批量请求犹如洪水一般涌入,引起数据库压力造成查询堵塞甚至宕机。

解决办法:

  1. 将缓存失效时间分散开,比如每个key的过期时间是随机,防止同一时间大量数据过期现象发生,这样不会出现同一时间全部请求都落在数据库层,如果缓存数据库是分布式部署,将热点数据均匀分布在不同Redis和数据库中,有效分担压力,别一个人扛。
  2. 简单粗暴,让Redis数据永不过期(如果业务准许,比如不用更新的名单类)。当然,如果业务数据准许的情况下可以,比如中奖名单用户,每期用户开奖后,名单不可能会变了,无需更新。

缓存穿透

黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。

缓存击穿

缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

解决方式也很简单,

  • 可以将热点数据设置为永远不过期;
  • 基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。
代码语言:javascript复制
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对象
        RedisTemplate<String, String> template = new RedisTemplate<>();
        //设置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        //创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置KEY的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        //设置VALUE的序列化,不用jsonRedisSerializer
        //        template.setValueSerializer(jsonRedisSerializer);
        //        template.setHashValueSerializer(jsonRedisSerializer);
        template.setValueSerializer(RedisSerializer.string());
        template.setHashValueSerializer(RedisSerializer.string());
        //返回
        return template;
    }
}

添加锁方法

代码语言:javascript复制
/**
* 获取互斥锁
* @return
*/
private Boolean tryLock(String key) {
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    return BooleanUtils.isTrue(flag);
}

/**
* 释放锁
* @param key
*/
private void unLock(String key) {
    stringRedisTemplate.delete(key);
}

获取互斥锁和释放锁的传参都应传城市redis互斥锁key

然后编写通过互斥锁机制查询城市信息的方法:

代码语言:javascript复制
/**
* 通过互斥锁机制查询城市信息
* @param key
*/
private City queryCityWithMutex(String key, String cityCode) {

    City city = null;
    // 1.查询缓存
    String cityJson = stringRedisTemplate.opsForValue().get(key);
    // 2.判断缓存是否有数据
    if (StringUtils.isNotBlank(cityJson)) {
        // 3.有,则返回
        city = JSONObject.parseObject(cityJson, City.class);
        return city;
    }
    // 4.无,则获取互斥锁
    String lockKey = RedisConstants.LOCK_CITY_KEY   cityCode;
    Boolean isLock = tryLock(lockKey);
    // 5.判断获取锁是否成功
    try {
        if (!isLock) {
            // 6.获取失败, 休眠并重试
            Thread.sleep(100);
            return queryCityWithMutex(key, cityCode);
        }
        // 7.获取成功, 查询数据库
        city = baseMapper.getByCode(cityCode);
        // 8.判断数据库是否有数据
        if (city == null) {
            // 9.无,则将空数据写入redis
            stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }
        // 10.有,则将数据写入redis
        stringRedisTemplate.opsForValue()
            .set(key, JSONObject.toJSONString(city), RedisConstants.CACHE_CITY_TTL, TimeUnit.MINUTES);
    } catch (Exception e) {
        throw new RuntimeException(e);
    } finally {
        // 11.释放锁
        unLock(lockKey);
    }
    // 12.返回数据
    return city;
}

其中常量

代码语言:javascript复制
/**
 * redis常量
 * @author wl
 * @date 2022/3/17 16:09
 */
public interface RedisConstants {
    /**
     * 空值缓存过期时间(分钟)
     */
    Long CACHE_NULL_TTL = 2L;

    /**
     * 城市redis缓存key
     */
    String CACHE_CITY_KEY = "cache:city:";
    /**
     * 城市redis缓存过期时间(分钟)
     */
    Long CACHE_CITY_TTL = 30L;

    /**
     * 城市redis互斥锁key
     */
    String LOCK_CITY_KEY = "lock:city:";
}

0 人点赞