美文网首页
分布式锁解决方案

分布式锁解决方案

作者: sunpy | 来源:发表于2022-08-30 15:14 被阅读0次

    为什么需要分布式锁


    单机多线程访问同一资源,需要加锁,同理多台机器情况下,访问同一资源,为了保证访问数据互斥,那么同样需要锁,而这个锁就是分布式锁。

    分布式锁理论


    • 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

    相关文章

      网友评论

          本文标题:分布式锁解决方案

          本文链接:https://www.haomeiwen.com/subject/qgvpgrtx.html