Redis缓存设计与性能优化

前言

  • redis在项目中是经常会使用的一种缓存数据库,了解其一些配置和设计思路有利于我们更有效安全的使用它
  • 这次介绍一下关于缓存设计优化和性能优化。

缓存设计解析

缓存穿透
  • 缓存穿透是指查询一个根本不存在的数据,在缓存和数据库层都无法命中。
  • 导致每次查询不存在的数据的请求最后都要到数据库层中查询,数据库查不出数据也无法写入缓存,失去了缓存过滤请求保护DB的意义。
  • 而造成缓存穿透的原因基本有两个:
    • 自身业务代码或者数据出现问题
    • 一些恶意攻击,爬虫等造成大量空数据命中
  • 缓存穿透的几种解决方案:
  1. 缓存空值对象
/**
 * @AUTHOR ZRH
 * @DATE 2021/7/31
 */
@Service
public class RedisServiceImpl {

    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private StorageMapper storageMapper;
    /**
     * 缓存过期时间
     */
    private final static Integer EXPIRE = 60000;
    private final static Object DEFAULT_VALUE = new Object();

    public String redisCache(String key) {
        // 在缓存中获取数据
        String value = redisUtils.get(key);
        if (null == value) {
            // 在数据库层获取数据
            value = storageMapper.getDataToDB(key);
            if (null == value) {
                // 数据库查询数据为空,保存默认数据到缓存层,并设置较短的过期时间
                redisUtils.set(key, DEFAULT_VALUE, 1000, TimeUnit.MILLISECONDS);
            } else {
                redisUtils.set(key, value, EXPIRE, TimeUnit.MILLISECONDS);
            }
        }
        return value;
    }
}
  1. 布隆过滤器
  • 对于恶意攻击,向服务器中请求大量不存在的数据造成缓存穿透(比如查询用户ID为-1的数据),可以用布隆过滤器先做一次过滤
  • 当布隆过滤器说某个值存在,这个值可能不存在。当它说不存在,那就一定不存在
  • 布隆过滤器具体实现方式可以参考文章# 分布式布隆过滤器实践
缓存击穿(失效)
  • 某一热点数据(比如秒杀活动的某一热销商品信息)缓存时间过期了,这时正好有大量请求无法命中这一数据从而直达数据库,可能数据库压力瞬间增大导致机器宕机等
  • 缓存击穿的几种解决方案:
  1. 设置热点数据永不过期(不推荐)
  2. 加互斥锁
/**
 * @AUTHOR ZRH
 * @DATE 2021/7/31
 */
@Service
public class RedisServiceImpl {

    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private StorageMapper storageMapper;
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 缓存过期时间
     */
    private final static Integer EXPIRE = 60000;
    private final static Object DEFAULT_VALUE = new Object();

    public String redisCache(String key) {
        // 在缓存中获取数据
        String value = redisUtils.get(key);
        if (null == value) {
            // 使用redisson实现分布式锁
            RLock lock = redissonClient.getLock(key);
            try {
                lock.lock();
                // 在数据库层获取数据
                value = storageMapper.getDataToDB(key);
                if (null == value) {
                    // 数据库查询数据为空,保存默认数据到缓存层,并设置较短的过期时间
                    redisUtils.set(key, DEFAULT_VALUE, 1000, TimeUnit.MILLISECONDS);
                } else {
                    redisUtils.set(key, value, EXPIRE, TimeUnit.MILLISECONDS);
                }
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        return value;
    }
}
缓存雪崩
  • 缓存雪崩指在某一时刻,缓存中有大量的key同时失效,导致请求直接命中数据库,导致数据库压力瞬间增大甚至宕机
  • 缓存雪崩的几种解决方案:
  1. 设置热点数据永不过期(不推荐,一直占有资源)
  2. 使用互斥锁(不推荐,阻塞效率慢)
  3. 缓存标记(对热点数据进行监控,如果缓存过期会触发更新缓存数据)
  4. 针对不同key通过随机数设置不同的过期时间
int nextInt = new Random().nextInt(60000);
redisUtils.set(key, value, EXPIRE + nextInt, TimeUnit.MILLISECONDS);
缓存与数据库数据一致性问题
  • 在高并发情况下,同时操作数据库和缓存会存在数据一致性问题
  1. 双写导致数据不一致

1627670596(1).png

  1. 读写导致数据不一致

1627670657(1).png

  • 数据一致问题的几种解决方式
  1. 对于并发量小或者能容忍短时间内数据不一致的业务场景,基本不用考虑这个问题
  2. 对于并发量高且无法容忍数据不一致,可以通过加读写锁保证并发读写和写写的顺序执行,读读相当于无锁
  3. 可以使用阿里开源的canal通过监听mysql的binlog日志及时去修改缓存数据,需要引入新的中间件

性能优化、

键值设计
  • key名设计尽量具有可读性、可管理性、简洁性、不要包含特殊字符
  1. 以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
  2. 保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视
  • value设计拒绝bigkey(防止网卡流量,慢查询),在Redis中一个字符串最大512M,一个二级缓存数据结构(hash,list,set,zset)可以存储2^32-1个元素。但在下列情况也会认为是一个bigkey
  1. 字符串类型:它的big体现在单个value值很大,一般认为超过10KB就是bigkey
  2. 非字符串类型:哈希、列表、集合、有序集合,它们的big体现在元素个数太多。一般来说,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000
  • 如何优化bigkey
  1. 拆分
    • big list: list1、list2、…listN
    • big hash:可以讲数据分段存储,比如一个大的key,假设存了1百万的用户数据,可以拆分成200个key,每个key下面存放5000个用户数据
  2. 如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要hmget,而不是hgetall),删除也是一样,尽量使用优雅的方式来处理。
过期策略
  • Redis对于过期键有三种过期策略
1. 被动删除:当读/写一个过期的数据时,会触发惰性删除,直接删除当前数据
2. 主动删除:被动删除无法保证冷数据被及时删除,所以Redis会定期删除一些过期数据
3. 当前已用内存超过maxmemory限定时,会触发主动清理策略
  • 主动清理策略在Redis 4.0之前有6种内存淘汰策略,4.0后新增了两种lfu淘汰策略,共有8种:
1. volatile-ttl:针对设置了过期时间的键值对,根据过期时间的先后顺序进行删除,越早过期越先删除
2. volatile-random:对设置了过期时间的键值对,进行随机删除
3. volatile-lru:使用lru算法在设置了过期时间的键值对筛选删除
4. volatile-lfu:使用lfu算法在设置了过期时间的键值对筛选删除
5. allkeys-random:从所以键值对中随机删除
6. allkeys-lru:使用lru算法在所有数据的键值对筛选删除
7. allkeys-lfu:使用lfu算法在所有数据的键值对筛选删除
8. noeviction:不删除数据,内存满了后拒绝写入操作并返回客户端错误信息
  • 当存在热点数据,lru效率很好,但偶发性、周期性的批量操作会导致lru命中率下降,缓存污染验证。这时使用lfu会更好。
  • 想了解lru算法可以看这篇文章# LRU算法记录一下
  • 想了解lfu算法可以看这篇文章# LFU算法代码实现记录一下

最后

  • 虚心学习,共同进步 -_-
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值