redisson分布式锁和自定义

文章介绍了在高并发查询商品信息时,如何利用Redis作为缓存避免数据不一致。首先提出了先查缓存再查数据库的基本逻辑,然后指出在并发情况下可能出现的问题。为了解决这个问题,文章提出使用Redis的setnx命令和lua脚本来实现分布式锁,确保数据操作的原子性。同时,通过引入ThreadLocal来处理锁的重入性和线程安全。最后,提到了使用Redisson库可以简化分布式锁的实现,提高代码的简洁性。
摘要由CSDN通过智能技术生成

案列

在查询商品信息时,往往要把商品信息放在缓存中,很容易就想到了redis 。
基本逻辑:先查缓存,缓存中没有,就查数据库,并放到缓存中返回,有就走缓存。

问题

在高并发情况下,这样做的数据是有误的比如你发了一千次查询请求,实际上可能只有几百次请求。

解决

应用redis分布式锁解决 :

  1. setnx命令 : 将 key 的值设为 value ,当且仅当 key 不存在。
    若给定的 key 已经存在,则 SETNX 不做任何动作。高并发下本地锁都是不同的,所有设置分布式锁。

  2. 应用lua脚本,解决不具备原子性的问题,如 处理完业务需要释放锁,释放时也要判断锁是否是自己的,判断和释放不具备统一的原子操作,所以要引入lua脚本

  3. 自旋:其他的线程进来没有拿到锁, 就自旋拿锁 while循环

  4. 引入本地线程 :前面的的锁不具备重入性,自旋出来又会加锁,导致锁上锁,锁死,所以引入本地线程 ,ThreadLocal 底层实际上是map集合, 添加一个map保存拿到锁的状态 把token放到第一轮map中,用完之后删除第一轮的token,

  5. 代码如下:

在这//根据skuId查询商品的基本信息 先走redis 没有再走数据库
    @Override
    public SkuInfo getSkuInfo(Long skuId) {
        SkuInfo skuInfo = getSkuInfoFromRedis(skuId);
        return skuInfo;
    }

    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    private SkuInfo getSkuInfoFromRedis(Long skuId) {
        //细粒度锁
        String lockKey="lock-"+skuId;
        String cacheKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        SkuInfo skuInfoRedis = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (skuInfoRedis == null) {
            String token = threadLocal.get();
            boolean accquireLock=false;
            if(!StringUtils.isEmpty(token)){
                //锁已经拿到过了
                accquireLock=true;
            }else{
                //做一个标记
                token = UUID.randomUUID().toString();
                accquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, token,3, TimeUnit.SECONDS);
            }
            if(accquireLock){
                SkuInfo skuInfoDb = getSkuInfoFromDb(skuId);
                //把数据放到redis中
                redisTemplate.opsForValue().set(cacheKey, skuInfoDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);

                //添加一个lua脚本
                String luaScript="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                //把脚本放到script对象当中
                redisScript.setScriptText(luaScript);
                //设置脚本返回类型
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(lockKey),token);
                threadLocal.remove();
                return skuInfoDb;
            }else{
                //自旋 目的是为了去拿锁
                while(true){
                    SleepUtils.millis(50);
                    boolean retryAccquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, token,3, TimeUnit.SECONDS);
                    if(retryAccquireLock){
                        threadLocal.set(token);
                        break;
                    }
                }
                return getSkuInfoFromRedis(skuId);
            }
        }
        return skuInfoRedis;
/**
     * 数据库查询
     * @param skuId
     * @return
     */
    private SkuInfo getSkuInfoFromDb(Long skuId) {
        SkuInfo skuInfo = this.getById(skuId);
        if (skuInfo!=null){
            LambdaQueryWrapper<SkuImage> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SkuImage::getSkuId, skuId);
            List<SkuImage> imageList = skuImageService.list(wrapper);
            skuInfo.setSkuImageList(imageList);
        }
        return skuInfo;
    }

前面都是不用redisson的依赖情况下,自己设置的分布式锁,引入redisson会更加简单,底层也是lua脚本等实现,可以更加方便,使用也很简单

redisson使用

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.11.2</version>
</dependency>

配置类编写

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://10.211.55.25:6389");
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

service代码


    @Autowired
    private RedissonClient redissonClient;

    private SkuInfo getSkuInfoFromRedis(Long skuId) {
        //减小锁的粒度   分布式锁过大,加上商品减小锁粒度,
        //以便别的商品不受正在查询的商品影响
        String lockKey="lock-"+skuId;
        String cacheKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        SkuInfo skuInfoRedis = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        RLock lock = redissonClient.getLock(lockKey);
        if (skuInfoRedis == null) {
            try {
                lock.lock();
                SkuInfo skuInfoDB = getSkuInfoFromDb(skuId);
                //把数据放入redis
                redisTemplate.opsForValue().set(cacheKey, skuInfoDB, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                return skuInfoDB;
            } finally {
                lock.unlock();
            }
        }
        return skuInfoRedis;
    }

会发现简单很多。因为其底层都帮我们实现了一些原子性问题等等判断,这样就不需要麻烦自己  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Redisson是一个基于Redis实现的Java驻内存数据网格(In-Memory Data Grid)和分布式锁(Distributed Lock)框架,它提供了一系列的分布式数据结构,其中包括分布式锁实现。 使用Redisson实现分布式锁非常简单,只需要遵循以下步骤: 1. 引入Redisson的依赖包: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.6</version> </dependency> ``` 2. 创建Redisson客户端对象: ```java Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); ``` 3. 获取对象: ```java RLock lock = redisson.getLock("myLock"); ``` 4. 加: ```java lock.lock(); ``` 5. 执行业务逻辑: ```java try { // 执行业务逻辑 } finally { // 释放 lock.unlock(); } ``` 完整的示例代码如下: ```java public class MyService { private RedissonClient redisson; public MyService() { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); redisson = Redisson.create(config); } public void myMethod() { RLock lock = redisson.getLock("myLock"); try { lock.lock(); // 执行业务逻辑 } finally { lock.unlock(); } } } ``` 需要注意的是,在执行业务逻辑的过程中,一定要放在try...finally块中,并在finally块中释放,以确保在出现异常时能够正确地被释放。 ### 回答2: Redisson是一种基于Redis实现分布式锁框架。以下是Redisson分布式锁的使用说明: 1. 引入Redisson依赖:首先需要在项目中引入Redisson的依赖,可以通过Maven或者Gradle等构建工具来管理依赖。 2. 创建RedissonClient:使用RedissonClient可以连接到Redis服务器,并获取一个分布式锁对象。 3. 加:使用分布式锁对象可以通过lock()方法来获取,该方法默认的超时时间是30秒,超过该时间会自动释放。也可以使用自定义超时时间。 4. 解:在加的代码块执行完毕后,需要调用unlock()方法手动释放,确保的释放,避免死的产生。 5. 的可重入性:Redisson分布式锁支持可重入性,即同一个线程可以多次获取同一个,在释放的时候需要调用相应次数的unlock()方法来释放。 6. 的异步执行:Redisson分布式锁也支持异步执行,即lock()方法可以通过异步方式获取。 7. 的公平性:Redisson分布式锁可以选择是否公平,默认为非公平,即不保障获取的顺序。可以通过配置参数来设置公平。 8. 监控:Redisson提供了监控分布式锁的功能,可以通过调用getLock("/lock")方法来监控名为"lock"的的情况。 总结来说,Redisson分布式锁的使用非常简单,只需要引入依赖、创建客户端、加和解即可。同时,Redisson还提供了可重入性、异步执行、公平监控等功能,可以根据实际需求进行配置和使用。通过使用Redisson分布式锁,可以有效地控制多线程环境下共享资源的访问,避免数据不一致和竞态条件的发生。 ### 回答3: Redisson是一个基于Redis的Java实现,提供了一系列分布式相关的功能,其中包括分布式锁的使用。 使用Redisson实现分布式锁,首先需要创建一个RedissonClient实例,通过该实例可以获取一个RLock对象,RLock提供了加和释放的方法。 在加方面,Redisson支持公平和非公平。公平会按照请求的顺序依次加,而非公平则允许插队,谁先抢到就谁先执行。通过调用RLock对象的lock()方法可以获取,该方法会一直阻塞直到获取到为止。如果不希望一直阻塞,可以使用tryLock()方法,该方法会尝试获取一段时间,如果超过指定的等待时间仍未获取到,则返回false。 在释放方面,可以使用unlock()方法来释放,只有加方才能释放。可以通过判断当前线程是否持有,再决定是否释放。 使用Redisson分布式锁还有一些其他的特性,比如的自动续约、可重入、可中断等,这些特性都可以通过相应的方法来使用。 总的来说,Redisson分布式锁使用起来非常方便,只需要简单的几行代码就可以实现分布式环境下的功能。但需要注意的是,在使用分布式锁时要考虑并发情况、死问题以及的粒度等,以确保代码的正确性和性能。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值