由于锁不能夸跨jvm,在分布式服务中单体锁就会失效,所以需要借助第三方来实现锁的效果。
一、基于数据库悲观加锁来实现分布式锁
关于使用到数据库 select * from ... for update (悲观锁),会锁住查询出来的行数据,直到commit。具体请自行查阅,以下给出命令参考。
先开一个会话,关闭会话的自动提交,选一个数据select * from ... for update 加锁,
在此期间,在开一个会话,使用select * from ... for update给同一条数据加锁,由于一开始的会话已经给此数据加锁,所以会一直等待,直到最初的会话手动commit,新会话才能再次给同一条数据加锁。
-- 查看当前会话是自动提交还是手动提交(1 自动提交,0 手动提交)
select @@autocommit;
-- 设置当前会话为手动提交
set @@autocommit = 0;
-- select * from ... for update (悲观锁)
SELECT * FROM `distribute_lock` where business_code = 'demo' for update
-- 手动提交
commit;
利用select * from ... for update借助数据库,实现分布式锁
1、新建springboot项目,写一个dao和对应的xml(就是要使用for update),内容如下(自行根据表内容更改)
<select id="selectByCodeLock" resultType="com.xpf.distributelock.pojo.DistributeLock">
SELECT * FROM `distribute_lock` where business_code = #{businessCode} for update
</select>
2、新建controller类demo
@RestController
@Slf4j
@RequestMapping("demoController")
public class DBLockController {
@Resource
private DistributeLockMapper distributeLockMapper;
//开始事务,让整个方法执行完在提交,而不是数据库自动的在上锁后提交
@Transactional(rollbackFor = Exception.class)
@PostMapping("singleLock")
public String singleLock(){
log.info("我进入了方法");
DistributeLock distributeLock = distributeLockMapper.selectByCodeLock("demo");
if (distributeLock == null){
throw new RuntimeException("分布式找不到锁");
}
try {
log.info("我进入了锁");
Thread.sleep(15000);
} catch (Exception e) {
log.info(e.getMessage());
}
return "我已经执行完毕!";
}
}
3、启动项目,然后复制配置在多启动一个相同的项目,端口改成8081。模拟分布式服务。
4、用postman分别访问8080和8081这两个服务。
一开始访问8080,控制台输出如下
获得了锁
然后访问8081,控制台输出如下
分析,在8080给行数据锁之后,8081就需要等待8080业务执行完毕(模拟业务耗时,休眠了15s),并且事务提交后(必须开启事务或者说必须关闭数据库会话的自动提交,不然数据库上锁之后就自动提交了,不会等到下面的业务代码执行),才能给行数据加锁。
继续等待15s,控制台内容如下
结果符合预期