一、引言:
前两篇文章,介绍了:
《学会Zookeeper分布式锁--让面试官对你刮目相看》
《SpringBoot电商项目实战-Curator分布式锁实现》
细心的读者可能发现,号主在分布式锁这条路上“越走越黑”,已经发表了好几篇相关文章了,莫慌,再学完本章,你就可以在分布式锁场景下拿着倚天剑与屠龙刀叱咤分风云了
因为实际项目可能面临各种业务场景需求,需要不同的分布式锁方案,而这些文章并不是类似网上很多的教你ctrl c + ctrl +v,关键掌握真谛。还不会的建议先去看下第一篇哦,从实际高并发场景深入浅出,层层剖析...
1、分布式锁思路分析
锁特点:
排他性:同一时间,只有一个线程能获得;
阻塞性:其它未抢到的线程阻塞等待,直到锁被释放,再继续抢;
可重入性:线程获得锁后,后续是否可重复获取该锁(避免死锁)。
当然,还要考虑性能开销等问题。
2、常规的分布式锁解决方案有哪几种:
文件系统:同一个目录下,不能存在同名文件
数据库锁:主键 、 唯一约束 、for update
基于Redis的分布式锁:setnx、set、Redisson
基于ZooKeeper的分布式锁:类似文件系统
直接进入今天正题,就不多介绍分布式锁的背景、区别之类了,可以参考前两篇文章哦。
二、redis分布式锁简介
1、基于redis分布式锁
(1)基本锁
原理:利用redis的setnx,如果不存在某个key则设置值,设置成功则表示取得锁成功。
缺点:如果获取锁后的进程在没有执行完就挂了,则锁永远不会释放,产生死锁。
(2)改进型
改进:在基本锁上setnx后设置expire,保证超时后也能自动释放锁。
缺点:setnx与expire不是一个原子操作,可能执行完setnx,还没来得及执行expire设置过期时间,该进程就挂了。
(3)增强版
改进:利用Lua脚本,将setnx与expire变成一个原子操作,可解决一部分问题。
缺点:还是锁过期问题。
注意: Redis 从2.6.12版本开始 set 命令支持 NX 、 PX 这些参数来达到 setnx 、 setex 、 psetex 命令的效果。
2、Redisson简介
所以直接使用redis自己实现分布式锁,相对麻烦,还存在上面的问题。接下来主要分析Redisson的分布式锁的实现,一般提及到Redis的分布式锁我们更多的使用的是Redisson的分布式锁,Redis的官方也是建议我们这样去做的。Redisson点我可以直接跳转到Redisson的官方文档。
注:redisson 更新注意点
2018-02-26更新:增加tryLock方法,建议后面去掉DistributedLocker接口和其实现类,直接在RedissLockUtil中注入RedissonClient实现类(简单但会丢失接口带来的灵活性)。
2019-07-11更新:redisson官方发布了 redisson-spring-boot-starter
本文将引入最新的redisson和springboot的集成包:redisson-spring-boot-starter,网上的很多教程可能还是使用redisson老版本,互相copy。redisson介绍可参考官网,有中英文wiki 。
参考官网:
https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter#spring-boot-starter
注:本示例为方便演示,仅提供如何实现redisson分布式锁方案,库存直接读取数据库,自己可以改造放到redis等缓存中,这个不会的自己去脑补。
三、项目实战
1、添加依赖:
org.redisson
redisson-spring-boot-starter
3.11.2
2、application.yml配置
3、redissson.yml 配置
4、新建一个redisson配置,读取配置文件redisson.yml,并将RedissonClient注册到spring ioc容器
@Configuration
public class RedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
// 1. 创建Config对象,读取配置属性
// 2. 创建Redisson对象,传入Config对象
RedissonClient redisson = Redisson.create(Config.fromYAML(
new ClassPathResource("redisson.yml").getInputStream()));
return redisson;
}
}
5、新建一个商品服务实现类
@Service
public class ProductServiceImpl implements IProductService {
@Autowired
private ProductDao productDao;
@Autowired
private RedissonClient redissonClient;
/**
* 秒杀商品
* @param productId 商品ID
* @param number 数量
* @return
*/
@Override
public Boolean seckillProduct(Long productId, Integer number) {
String key = "seckill_stock_lock_" + productId;
RLock lock = redissonClient.getLock(key);
try {
//获取分布式锁
lock.lock();
Product product = productDao.selectById(productId);
if ( product!=null && product.getStocks() == 0) {
return false;
}
Integer stocks = product.getStocks() - 1;
productDao.updateStocksById(productId,stocks);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return true;
}
}
6、新建一个controller,并提供一个http接口:秒杀商品
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private IProductService productService;
/**
* 秒杀商品测试
* @return
*/
@GetMapping("seckillTest")
public String seckillProductTest() {
Boolean flag = productService.seckillProduct(1L, 1);
if(flag ==true){
return "创建订单成功";
} else{
return "库存不足";
}
}
}
7、数据库product商品表数据如下,id=1的库存为30个,如下:
8、使用并发测试工具类,如jmeter ,新建一个测试任务,并发线程设为50或其他,模拟多人同时并发秒杀某个商品,启动项目,然后测试:
9、全部执行成功后,查看数据库中数据,库存已经为0,没有出现负数,超卖的情况。
完结,分布式锁专题终于告一段落,你学会了吗
注:本示例为方便演示,仅提供如何实现redisson分布式锁方案,库存直接读取数据库,自己可以改造放到redis等缓存中,这个不会的自己去脑补。
●SpringBoot电商项目实战-Curator分布式锁实现
●学会Zookeeper分布式锁,让面试官对你刮目相看
●SpringBoot+Token实现接口幂等性|防止表单重复提交
一只 有深度 有灵魂 的公众号0.0 "阿甘正专"
来都来了,点个在看再走吧~~~