前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >3 Redis的缓存问题 & Redisson & 超卖的解决方案

3 Redis的缓存问题 & Redisson & 超卖的解决方案

作者头像
收心
发布2022-01-17 14:18:49
1.2K0
发布2022-01-17 14:18:49
举报
文章被收录于专栏:Java实战博客

既然,面试常问此类问题。我们就认真的分析一下。

缓存三连问

缓存穿透(没有key):

  • 用户请求在redis中没有数据,又去找数据库中,数据库也没有。现在的情况就相当于直接访问数据库,一旦请求量变大,就数据库扛不住,就挂了。
  • 解决办法:在用户的请求,对请求参数判断,不合法,就直接return。或者数据库找不到,那就再redis弄个虚假的数据,使用布隆过滤器

缓存击穿(一个key失效):

  • 用户访问一个热点的redis的key,但是这个key突然失效,失效瞬间,大量请求数据库就打进来了,直接打崩数据库。
  • 解决办法:设置永不过期,或者互斥锁。

缓存雪崩(多个key失效):

  • 用户大量的请求到redis中读取数据,但是redis中的key,设置了相同的失效时间,一旦到了失效时间,大量的key失效,请求直接打在数据库中,将数据库打崩。
  • 解决办法:设置不同key的失效时间,避免同时key失效,瞬间数据库访问大量并发,方式二:设置不失效,一旦数据户有变动,就刷新缓存。

如何避免 Redis 缓存崩溃的三个问题?

  • 事前:部署集群
  • 事中:使用本地 ehcache缓存 + hystrix 限流、降级
  • 事后:redis持久化存储(RDB+AOF),一旦重启,自动读取硬盘上的数据,快速恢复。

什么是RDB AOF?

  • RDB半持久化,通过默认配置,设置时间进行持久化新的文件,然后完成替换老的文件存储。会存在数据丢失问题,因为设置60s存储一次,如果存储时间没到,就挂了,那么上一次的存储时间到崩溃时间的数据就会丢失
  • AOF持久化方案 通过简单的脚本启动自己AOF文件就行,默认每秒同步一次,就不会丢失很多的数据。
  • RDB与AOF同时存在:优先听AOF的。
  • 但是RDB性能更好,但是存在丢失的概率更大
  • AOF持续性的 IO性能 差一点,但是丢失数据的概率更小
  • 官方建议同时存在:RDB做备用,AOF重写时间长一点,减少性能消耗。

Redis键过期的策略有(常用)?

  • 定时删除:设置个过期时间,到时间自动删除
  • 惰性删除:如果过期了不管,一旦调用的时候,再判断,过期了再删除
  • 定期删除:(外部手段干扰)定时间对数据库进行检查,删除里面的过期值

Redis 的超卖问题 & 简单分布式锁

单机部署的项目,简单的加jvm锁(也就是传说中的(synchronized)就行了。但是目前是分布式项目,加锁只能保证单机服务问题,就无法保证多机的数据安全问题。我们就需要一个 分布式锁

我们采用redis 来做分布式锁,当然分布式锁有很多方式,redis比较方便一点。

调用的是Redis的 API setnx key value 也就是K 不在 在设置V,K在,不设置V

代码语言:javascript
复制
setnx key value   
# 当key 不存在的时候 才会set 值
在 stringRedisTemplate 中 调用的是.opsForValue().setIfAbsent("key",value); 返回一个布尔值

如果单纯这样涉及,就会有所谓的死锁情况,比如项目加锁后,停电,嘎,没有释放锁,锁的key永久都在,所以务必加上key的失效时间

在刁钻一点
加上失效时间 如果写法不对也不能保证100%的没问题

流程图图下

有个问题,服务器挂了,标记未删除

如果 在没有删除 标记之前,服务器挂了,重启后,所有的 请求都去设置标记,但每次都是获得,已经存在标记了,就会带来新的问题。

我们需要对一个key 设置一下 超时时间

代码语言:javascript
复制
// 格式:   stringRedisTemplate.expire(key,value,time,时间单位)
// 设置 10 秒 失效时间
stringRedisTemplate.expire("flag",10,TimeUtils.Second);

上面的也有问题,如果 添加key 之后 在设置失效时间,就会带来新的问题,添加的时候,没设置失效之前挂了。我们就需要在设置key的时候,同时 指定时间

代码语言:javascript
复制
超高并发依旧有问题!!!!


超高并发依旧有问题!!!!



超高并发依旧有问题!!!!


        // 添加标记10秒失效
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("key",value,10, TimeUtils.Second);

        if(!result){
            return "当前有其他线程正在操作";
        }else{
            //TODO 正常的减少库存
        }finally{
            // 删除key标记
            stringRedisTemplate.delete("key")
        }

还有个问题 如果 redis 设置key后 网络超时了,10秒后,网络正常了 或者 业务 响应了 10秒 即 分布式锁永久失效(自己加的锁,被别人释放了)

我们设置标记的value 为我们当前操作的 UUID,我们删除的时候,都要去读取匹配 我们的UUID ,匹配成功,我们才取删除!

代码语言:javascript
复制

还有问题 查到自己的UUID,匹配成功,去删除的时候,10秒失效时间到了,其他线程,一样拿到线程,霍霍数据 (锁续命问题)

开辟新线程,去检验 主线程 是否持有锁,如果有所,重新设置 锁时间,手写太要命,用Redis 官方推荐的 Redisson

Redissonhttps://github.com/redisson/redisson

他是对 jedis 增强,虽然没有直接关系!!! redis son 即 redis的儿子 ,被官方承认的!

用途:

Redisson 里的lock 机制,是开启定时器 (根据存储键的时间/3),重新设置 redis 中键的失效时间并嵌套调用

用法:

Maven依赖

代码语言:javascript
复制
        <!-- Redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.15.0</version>
        </dependency>

注入一个 Bean

代码语言:javascript
复制
    @Bean
    public Redisson redisson() {
        Config config = new Config();
        // Redis是单机服务
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

快速使用 伪代码

代码语言:javascript
复制
    @Autowired
    Redisson redisson;


    String lockKey = "自定义一个key(一般是用户Id锁自己的操作)";
    RLock redissonLock = redisson.getLock(lockKey);
    try{
        redissonLock.lock();
        //TODO 执行操作 
    }finally{
        redissonLock.unlock();
    }

完成!!!

更详细的代码

代码语言:javascript
复制
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

@Configuration
public class RedissLockUtil {


    /* 尝试获取锁
     *
     * @param lockKey   锁名
     * @param unit      时间单位
     * @param waitTime  最多等待时间
     * @param leaseTime 上锁后自动释放锁时间
     * @return
     */
    public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
        RedissonClient redissonClient = Redisson.create(config);
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }


    /*
     * 释放锁
     *
     * @param lockKey 锁名
     */
    public static void unlock(String lockKey) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
        RedissonClient redissonClient = Redisson.create(config);
        RLock lock = redissonClient.getLock(lockKey);
        // 在解锁之前先判断要解锁的key是否已被锁定并且是否被当前线程保持。 如果满足条件时才解锁
        if (lock.isLocked()) {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    
    // 常规的业务逻辑代码
    @Transactional
    public String RedissonForDemo() throws Exception {
        boolean flag = false;
        // 定义一个锁
        String LockName = "所有线程拿这个锁(比如商品数量)";
        // 通过flag判断加锁是否成功
        try {
            flag = RedissLockUtil.tryLock(LockName, TimeUnit.SECONDS, 3, 10);
            if (flag) {
                //TODO 执行自己的业务逻辑 先查询订单数量,再下单。
                return "下单成功!";
            } else {
                return "没有加锁成功,下单失败!";
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 一旦加锁成功,方法结束必须要释放锁
            if (flag) {
                RedissLockUtil.unlock(LockName);
            }
        }
        return "方法结束";
    }

}

Redis 主从架构锁失效问题

Redlock

Redis的多级缓存一致性问题

Redis与数据库缓存不一致问题

特殊说明: 解决问题的光鲜,藏着磕Bug的痛苦。 万物皆入轮回,谁也躲不掉! 以上文章,均是我实际操作,写出来的笔记资料,不会出现全文盗用别人文章!烦请各位,请勿直接盗用!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 缓存三连问
  • 如何避免 Redis 缓存崩溃的三个问题?
    • 什么是RDB AOF?
    • Redis键过期的策略有(常用)?
    • Redis 的超卖问题 & 简单分布式锁
      • 有个问题,服务器挂了,标记未删除
        • 还有个问题 如果 redis 设置key后 网络超时了,10秒后,网络正常了 或者 业务 响应了 10秒 即 分布式锁永久失效(自己加的锁,被别人释放了)
          • 还有问题 查到自己的UUID,匹配成功,去删除的时候,10秒失效时间到了,其他线程,一样拿到线程,霍霍数据 (锁续命问题)
          • Redisson :https://github.com/redisson/redisson
            • 用途:
              • 用法:
                • Redis 主从架构锁失效问题
                  • Redis的多级缓存一致性问题
                    • Redis与数据库缓存不一致问题
                    相关产品与服务
                    云数据库 Redis®
                    腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档