为什么需要分布式锁
单机多线程访问同一资源,需要加锁,同理多台机器情况下,访问同一资源,为了保证访问数据互斥,那么同样需要锁,而这个锁就是分布式锁。
分布式锁理论
-
CAP理论:分区容错性、一致性、高可用三者最多只能满足其中两项。
往往我们都是牺牲强一致性,提供最终一致性,而最终一致性的实现采用分布式锁、分布式事务实现。 -
jvm中多线程竞争和多机竞争的区别:
多线程竞争可以共享堆内存、而多机的策略是从同一台服务器上共同存取。
分布式锁使用场景
网图
- 业务层面:在医疗系统中,多机部署,多名患者都想在网上挂某个名医下面3点到4点的号,那么就需要竞争,需要上锁排队。实际上只要业务中多机情况,竞争同一个资源就需要分布式锁。
- 技术层面:当使用quartz定时器做任务调度的时候,多台机器集群实现了quartz任务,为了防止quartz的定时任务被执行多次,那么就利用了数据库锁方案实现。
主流分布式锁的解决方案
- 数据库分布式锁
- zookeeper分布式锁
- redission分布式锁
数据库锁实现分布式锁
mysql的锁机制传送门:
https://www.jianshu.com/p/1ac605679658
数据库悲观锁实现分布式锁(不推荐)
-
思路:利用mysql中的范围锁,select xxxxxxx for update语句
-
实现:
表结构:
# 分布式锁表
CREATE TABLE `sys_lock` (
`lock_id` int NOT NULL AUTO_INCREMENT,
`lock_key` varchar(32) DEFAULT NULL,
`last_thread` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`lock_id`) USING BTREE,
UNIQUE KEY `lock_key_idx` (`lock_key`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
#操作锁日志表
CREATE TABLE `sys_lock_log` (
`log_id` int NOT NULL AUTO_INCREMENT,
`opt_thread` varchar(128) NOT NULL,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`log_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb3;
mapper层实现:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sunpy.adminservice.dao.LockMapper">
<select id="openTrans">
start transaction;
</select>
<select id="selectLockForUpdate" resultType="com.sunpy.adminservice.po.Lock">
select lock_id,
lock_key,
last_thread,
create_time,
update_time from sys_lock where lock_key = "lock_key" for update;
</select>
<update id="updateLastThread">
update sys_lock set last_thread = #{lastThread}
where lock_key = "lock_key";
</update>
<select id="releaseLock">
commit;
</select>
<select id="countLockKey" resultType="java.lang.Integer">
select count(lock_key) from sys_lock where lock_key = "lock_key";
</select>
</mapper>
@Mapper
public interface LockMapper extends BaseMapper<Lock> {
Lock selectLockForUpdate() throws CommonException;
int updateLastThread(@Param("lastThread") String lastThread) throws CommonException;
void releaseLock() throws CommonException;
void openTrans() throws CommonException;
Integer countLockKey() throws CommonException;
}
锁服务层实现:
@Slf4j
@Service
public class LockServiceImpl extends ServiceImpl<LockMapper, Lock> implements ILockService {
@Autowired
private LockMapper lockMapper;
@Autowired
private LockLogMapper lockLogMapper;
private boolean tryLock() {
return lockMapper.countLockKey() > 0;
}
@Transactional
@Override
public ResultModel<String> lock() {
ResultModel<String> resultModel = new ResultModel<>();
if (tryLock()) {
lockMapper.openTrans();
Lock lock = lockMapper.selectLockForUpdate();
if (lock != null) {
String nowThread = Thread.currentThread().getName();
log.info(nowThread + ":释放分布式锁成功");
lockMapper.updateLastThread(nowThread);
// lock获取的日志
LockLog lockLog = new LockLog();
lockLog.setOptThread(nowThread);
lockLogMapper.insert(lockLog);
resultModel.setTime(TimeUtil.getNowTime());
resultModel.setMsg(nowThread + ":获取分布式锁成功");
return resultModel;
}
}
resultModel.setSuccess(false);
resultModel.setCode(500);
resultModel.setTime(TimeUtil.getNowTime());
resultModel.setMsg("分布式锁不存在");
return resultModel;
}
@Transactional
@Override
public void unlock() {
String nowThread = Thread.currentThread().getName();
log.info(nowThread + ":释放分布式锁成功");
lockMapper.releaseLock();
}
}
jmeter测试结果:
为什么不推荐使用数据库悲观锁实现分布式锁?
我们通过hikari连接池实现的mysql的分布式锁,那么hikari连接池对于数据库的最大连接数是有限的,如果连接不够用就会抛出java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available异常。而且我们将压力都转移到后端数据库,这样也是不合理的。
zookeeper实现分布式锁
思路:
1> 创建持久根节点
2> 一台机器进来获取锁,在根节点下面创建临时顺序节点
3> 获取根节点下面所有子节点,放入List中排序。
4> 判断当前节点是否为最小节点。
4.1> 判断方法1:最小节点判断不需要考虑每个比较,在有序从小到大队列中,只要节点还有节点,说明当前节点就不是最小的。
4.2> 判断方法2:或者当前节点不是有序从小到大数组队列中第一个节点,说明当前节点就不是最小的。
5> 如果当前节点是最小节点,那么就获取锁。
6> 如果当前节点不是最小节点,那么就将当前操作请求的线程阻塞,排队等待。
zookeeper实现分布式锁代码实现(很早以前已经实现过了):
https://www.jianshu.com/p/f65304d860ff
网友评论