作为后台开发,相信大家都对 Redis 并不陌生了。Redis
有三个客户端 Jedis
、Redisson
、Lettuce
。也就是提供基本的驱动来连接操作 Redis 数据库的。我们先简单介绍下这几个客户端的异同。
比较全面的 Redis 命令
的支持。分布式
和可扩展的 Java 数据结构。高级
Redis 客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。优点:
操作特性
。分布式锁
,分布式集合
,可通过 Redis 支持延迟队列。分布式缓存
框架上使用比较多。对比后不难发现,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();
}
}
我这里只是简单写了一个 RedisLockController 来模拟高并发场景实现Redis分布式锁,实际中的项目业务远远复杂的多哈。
不要小看以上几行代码,包含了很多场景在里面。
1、synchronized
我们通常都喜欢用 synchronized 来加锁,这是 jvm 在单机上才能这样操作,分布式中行不通,高并发场景依旧会出现两个线程共抢到这把锁。所以要用我上面实现的 Redis分布式锁。
2、finally 语句块的代码
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
这个是为了防止不是同一线程操作删除的,不这样加个 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);
还有这里为什么要设置 30s 的超时呢?
因为怕整个应用宕机了,像有的时候运维直接 kill -9 或者应用没有做容灾突然断电了,导致应用挂掉。这里设置了 30s 后锁直接过期,这样可以解决应用挂掉导致没有这个锁,后面的其他请求都直接是这个锁导致程序出Bug。
所以对于高并发的业务一定要考虑到各种场景,不然一不小心就会出Bug。
下面我来介绍下基于 Redisson 实现 Redis 分布式锁。Redisson 底层封装了 Luna 脚本来实现的分布式锁,直接拿来用,放心,很多大厂用了那么多年了,Redisson 有的 Bug 基本修复的基本没有了。这样一来,也让我们开发人员省心了很多,也不至于一不小心写了有 Bug 的分布式锁业务。一旦涉及到了客户的投诉、对公司造成了影响,轻则 KPI 没有了,重则直接叫你走人。
Talk is cheap, show me the code.
1、Maven 依赖
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.12.3version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
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);
}
}
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 的几行代码就可以实现我们前面所担心的那些问题了。