分布式架构下基于Redisson实现Redis分布式锁


原创riemann_ 最后发布于2020-03-10 00:17:54 阅读数 88  已收藏
展开
一、前言
作为后台开发,相信大家都对 Redis 并不陌生了。Redis 有三个客户端 Jedis、Redisson、Lettuce。也就是提供基本的驱动来连接操作 Redis 数据库的。我们先简单介绍下这几个客户端的异同。

Jedis:是Redis 的 Java 实现客户端,提供了比较全面的 Redis 命令的支持。
Redisson:实现了分布式和可扩展的 Java 数据结构。
Lettuce:高级 Redis 客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
优点:

Jedis:比较全面的提供了 Redis 的操作特性。
Redisson:促使使用者对 Redis 的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过 Redis 支持延迟队列。
Lettuce:主要在一些分布式缓存框架上使用比较多。
对比后不难发现,Redisson 实现 Redis 分布式锁是比较好的。那为啥不是其他两个呢?不着急,我们下面一一道来。小伙伴们应该接触 Jedis 会比较多吧,因为它出现的时间比较长了。

但是随着现代系统的多核和异步,为了不断提高的吞吐量,异步非阻塞线程模型大行其道,这里面非常热门的框架就是 Netty,Netty 因其设计优秀,应用面广,实际使用的场景广泛,很多大型框架比如 Hadoop、Dubbo 等许多的底层都是通过Netty来实现的通信。所以我们就专门针对异步的且基于 Netty 的 Redis 驱动来分析,Redisson 和 Lettuce 都是基于 Netty 的也就是说他俩都是异步非阻塞的,但是他们有什么区别呢?其实在使用语法上面有一些区别,Redisson对结果做了一层包装,通过包装类来进行一些额外的操作来达到异步操作,并且 Redisson 提供了额外的分布式锁功能,那我们接下来就来说说 Redisson 的使用方式吧。

二、代码实现
在大厂中,经常有高并发场景。我就以下单减库存场景为例来模拟下怎么实现 Redis 分布式锁。

@Controller
public class RedisLockController {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockController.class);

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deduct_stock")
    @Transactional
    public ApiResult deductStock() {
        String lockKey = "lockKey";
        String clientId = UUID.randomUUID().toString();
        try {
            Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
            if (!flag) return new ApiResult(ReturnEnum.FAILED, "Not the same lock");
            int stockNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stockNum > 0) {
                int realStockNum = stockNum - 1;
                stringRedisTemplate.opsForValue().set("stock", realStockNum + "");
                LOGGER.info("扣减成功,剩余库存:{}", realStockNum);
            } else {
                LOGGER.info("扣减失败,库存不足");
            }
        } finally {
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }
        }
        return new ApiResult();
    }
}
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
我这里只是简单写了一个 RedisLockController 来模拟高并发场景实现Redis分布式锁,实际中的项目业务远远复杂的多哈。

不要小看以上几行代码,包含了很多场景在里面。

1、synchronized

我们通常都喜欢用 synchronized 来加锁,这是 jvm 在单机上才能这样操作,分布式中行不通,高并发场景依旧会出现两个线程共抢到这把锁。所以要用我上面实现的 Redis分布式锁。

2、finally 语句块的代码

if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
    stringRedisTemplate.delete(lockKey);
}
1
2
3
这个是为了防止不是同一线程操作删除的,不这样加个 clientId 唯一标识那条线程的话,那个锁永远不会生效。

3、Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);

其实这行代码包含了以下两行代码:

Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "riemann");
stringRedisTemplate.expire(lockKey, 30, TimeUnit.SECONDS);
1
2
还有这里为什么要设置 30s 的超时呢?

因为怕整个应用宕机了,像有的时候运维直接 kill -9 或者应用没有做容灾突然断电了,导致应用挂掉。这里设置了 30s 后锁直接过期,这样可以解决应用挂掉导致没有这个锁,后面的其他请求都直接是这个锁导致程序出Bug。

所以对于高并发的业务一定要考虑到各种场景,不然一不小心就会出Bug。

下面我来介绍下基于 Redisson 实现 Redis 分布式锁。Redisson 底层封装了 Luna 脚本来实现的分布式锁,直接拿来用,放心,很多大厂用了那么多年了,Redisson 有的 Bug 基本修复的基本没有了。这样一来,也让我们开发人员省心了很多,也不至于一不小心写了有 Bug 的分布式锁业务。一旦涉及到了客户的投诉、对公司造成了影响,轻则 KPI 没有了,重则直接叫你走人。

三、基于Redisson实现Redis分布式锁
Talk is cheap, show me the code.

1、Maven 依赖



    org.redisson
    redisson
    3.12.3



    org.springframework.boot
    spring-boot-starter-data-redis

1
2
3
4
5
6
7
8
9
10
11
2、RedissonConfig.java

@Configuration
public class RedissonConfig {
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException {
        Config config = new Config();
        // 此为单机模式
        config.useSingleServer().setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }
}
1
2
3
4
5
6
7
8
9
10
3、RedisLockController.java

@Controller
public class RedisLockController {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockController.class);

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deduct_stock_2")
    @Transactional
    public ApiResult deductStock() {
        String lockKey = "lockKey";
        RLock rLock = redissonClient.getLock(lockKey);
        try {
            rLock.lock();
            int stockNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stockNum > 0) {
                int realStockNum = stockNum - 1;
                stringRedisTemplate.opsForValue().set("stock", realStockNum + "");
                LOGGER.info("扣减成功,剩余库存:{}", realStockNum);
            } else {
                LOGGER.info("扣减失败,库存不足");
            }
        } finally {
            rLock.unlock();
        }
        return new ApiResult();
    }
}

这样我们用的 Redisson 的几行代码就可以实现我们前面所担心的那些问题了。


————————————————
版权声明:本文为CSDN博主「riemann_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/riemann_/article/details/104763755

你可能感兴趣的:(flink,java基础/js)