在业务场景中,我们要防范高并发带来的,超卖,Redis操作时的原子性,死锁,锁失效等问题。
如有以下场景,电商业务中用户提交订单,此时后台扣减Redis中的商品id。
如果写个最简单的分布式锁可以使用Redis命令
/**
* 提交商品生成订单
* @param orderItemsDtos
* @return
*/
@PostMapping("/reduceInventory")
@SentinelResource(value = "order",blockHandler = "orderblockHandler") //资源名
public ApiResponse<List<ProductDto>> updateProduct(@RequestBody List<OrderItemsDto> orderItemsDtos){
String lockKey = "lock:product1";
String clientId = UUID.randomUUID().toString();
Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
if (!result){
return new ApiResponse(400,"当前库存紧张请重试",null);
}
try {
//比对是否有redis里的秒杀商品,如果有取出对应id值
for (OrderItemsDto orderItems : orderItemsDtos){
System.out.println(orderItems);
//根据传入的商品id去redis查询
String productStock = redisTemplate.opsForValue().get(String.valueOf(orderItems.getProductId()));
if (Integer.parseInt(productStock) >= orderItems.getQuantity()){
//传入对应商品的数量,去redis减少对应商品的秒杀数量
int setRedisProductStock = Integer.parseInt(productStock) - orderItems.getQuantity();
redisTemplate.opsForValue().set(String.valueOf(orderItems.getProductId()), String.valueOf(setRedisProductStock));
System.out.println( redisTemplate.opsForValue().get(String.valueOf(orderItems.getProductId())));
}else {
redisTemplate.delete(lockKey);
return new ApiResponse(400,"订单中的商品库存不足",orderItems.getProductId());
}
}
return new ApiResponse(200,"提交订单成功",null);
}finally {
//比较是否为自己的锁
if (clientId.equals(redisTemplate.opsForValue().get(lockKey))){
redisTemplate.delete(lockKey);
}
}
}
这个分布式锁是基于Redis命令完成的基础锁,有存在原子性的问题。
在释放锁时,可能会出现当前线程删除了别的线程的锁,因为线程处理业务逻辑的时间不一致,为了避免这种情况,可以开一个子线程,写定时任务去监听主线程的redis中的键,是否存在,如果存在就延期,不存在就结束子线程。
或者也可以使用Rdisson
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.23.0</version>
</dependency>
引入依赖后配置Redisson配置类
@Configuration
public class RedissonCogfiguratin {
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://youhost:6379").setPassword("youpassword");
return Redisson.create(config);
}
}
然后在controller中使用
/**
* 提交商品生成订单
* @param orderItemsDtos
* @return
*/
@PostMapping("/reduceInventory")
@SentinelResource(value = "order",blockHandler = "orderblockHandler") //资源名
public ApiResponse<List<ProductDto>> updateProduct(@RequestBody List<OrderItemsDto> orderItemsDtos){
// List<ProductDto> productDtos = productClient.updateProduct(orderItemsDtos);
String lockKey = "lock:product1";
RLock redissonLock = redissonClient.getLock(lockKey);
//加锁
redissonLock.lock();
try {
//比对是否有redis里的秒杀商品,如果有取出对应id值
for (OrderItemsDto orderItems : orderItemsDtos){
System.out.println(orderItems);
//根据传入的商品id去redis查询
String productStock = redisTemplate.opsForValue().get(String.valueOf(orderItems.getProductId()));
if (Integer.parseInt(productStock) >= orderItems.getQuantity()){
//传入对应商品的数量,去redis减少对应商品的秒杀数量
int setRedisProductStock = Integer.parseInt(productStock) - orderItems.getQuantity();
redisTemplate.opsForValue().set(String.valueOf(orderItems.getProductId()), String.valueOf(setRedisProductStock));
System.out.println( redisTemplate.opsForValue().get(String.valueOf(orderItems.getProductId())));
}else {
redisTemplate.delete(lockKey);
return new ApiResponse(400,"订单中的商品库存不足",orderItems.getProductId());
}
}
return new ApiResponse(200,"提交订单成功",null);
}finally {
//释放锁
redissonLock.unlock();
}
}
使用压力测试软件模拟100个线程,请求5秒,每秒20个并发,资源被处理的很好,没有死锁,锁失效等情况。
Rdisson生成锁时使用了lua脚本,保证原子性,向redis发送脚本。