1、分布式锁的实现方式

大概有三种:

  1. 基于关系型数据库(本文讲解了基于mysql数据库的分布式锁)
  2. 基于缓存(基于redis的redisson实现分布式锁)
  3. 基于zookeeper(基于zookeeper实现分布式锁

2、思路

利用主键唯一的特性,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可(性能上依赖数据库)。

mysql分布式方案 mysql分布式有哪些实现_数据库

3、创建表

CREATE TABLE `data_base_lock` (
  `id` varchar(64) NOT NULL,
  `lock_key` varchar(500) NOT NULL DEFAULT '' COMMENT '锁名称',
  `time_out` int(11) NOT NULL DEFAULT '0' COMMENT '锁存活时间(单位:秒)',
  `remarks` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
  `update_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后更新时间',
  `create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_by` varchar(64) NOT NULL DEFAULT '',
  `create_by` varchar(64) NOT NULL DEFAULT '',
  `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '0正常,1删除',
  PRIMARY KEY (`lock_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='数据库分布式锁表';

当我们想要获得锁时,可以插入一条数据:

INSERT INTO data_base_lock ( id, lock_key, time_out, create_by, create_date, update_by, update_date, del_flag ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )

注意:在表data_base_lock中,lock_key字段做了唯一性约束,这样如果有多个请求同时提交到数据库的话,数据库可以保证只有一个操作可以成功(其它的会报错:### Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'lock-key' for key 'PRIMARY',那么我们就可以认为操作成功的那个请求获得了锁。

4、基于数据库乐观锁的分布式锁工具类实现如下

package com.example.mybatiesplus.utils;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.mybatiesplus.entity.DataBaseLock;
import com.example.mybatiesplus.mapper.DataBaseLockMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @DESCRIPTION 基于数据库 乐观锁的 分布式锁
 *
 *   利用主键唯一的特性,如果有多个请求同时提交到数据库的话,
 *   数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,
 *   当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。
 * @Author lst
 * @Date 2020-05-20 18:00
 */
@Slf4j
@Component
public class DataBaseLockUtil {

    @Autowired
    private DataBaseLockMapper dataBaseLockMapper;

    @Scheduled(cron = "0 10 * * * ?")
    private void checkSynTaskKeyIsExpire() {
        log.info("=========开始检查数据库分布式锁的过期时间=======");
        List<DataBaseLock> taskLocks = dataBaseLockMapper.selectList(null);
        //筛选出过期的key
        List<DataBaseLock> dataBaseLockList = taskLocks.stream().filter(dataBaseLock ->
                System.currentTimeMillis() - dataBaseLock.getUpdateDate().getTime() - dataBaseLock.getTimeOut() * 1000 > 0).collect(Collectors.toList());
        //删除过期的锁
        dataBaseLockList.forEach(dataBaseLock -> {
            dataBaseLockMapper.deleteById(dataBaseLock.getId());
        });
        log.info("=========trs_task_lock 删除{}条 过期的key=======",dataBaseLockList.size());
    }

    /**
      * 获取分布式锁
      * 根据插入语句中的主键冲突,相同主键的多次插入操作,只会有一次成功,成功的就获得分布式锁执行任务的权利
      * @author lst
      * @date 2020-5-21 8:53
      * @param key 锁名称
      * @param timeOut 锁超时时间
      * @return boolean
     */
    public boolean tryLock(String key,int timeOut){
        DataBaseLock dataBaseLock = new DataBaseLock();
        dataBaseLock.setLockKey(key);
        dataBaseLock.setTimeOut(timeOut);
        int flag = 0;
        try {
            flag = dataBaseLockMapper.insert(dataBaseLock);
        }catch (Exception e){
            log.info("{} 其他机器已经在执行",key);
            log.info("错误信息:{}",e.toString());
        }
        return flag == 1;
    }

    /**
      * 删除分布式锁
      * @author lst
      * @date 2020-5-21 8:59
      * @param key 锁名称
      * @return boolean
     */
    public boolean delLock(String key){
        int flag = 0;
        try{
            flag = dataBaseLockMapper.deleteAll(key);
        }catch (Exception e){
            log.info("错误信息:{}",e.toString());
        }
        return flag == 1;
    }
    
    /**
      * 获取锁对象
      * @author lst
      * @date 2020-5-21 9:05
      * @param key
      * @return com.example.mybatiesplus.entity.DataBaseLock
     */
    public DataBaseLock getLock(String key){
        return dataBaseLockMapper.selectOne(new QueryWrapper<DataBaseLock>().lambda().eq(DataBaseLock::getLockKey,key));
    }

}

5、DataBaseLock实体类

package com.example.mybatiesplus.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.mybaties.entity.BasePlusEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 数据库分布式锁表
 * </p>
 *
 * @Author lst
 * @Date 2020-05-20 18:00
 */
@TableName("data_base_lock")
@Data
public class DataBaseLock extends BasePlusEntity<DataBaseLock> {

    /**
     *锁名称
     */
    @TableField("lock_key")
    private String lockKey;

    /**
     *锁存活时间(单位:秒)  避免死锁
     */
    @TableField("time_out")
    private int timeOut;

}

6、DataBaseLockMapper和DataBaseLockMapper.xml

package com.example.mybatiesplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatiesplus.entity.DataBaseLock;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @DESCRIPTION
 * @Author lst
 * @Date 2020-05-20 18:00
 */
@Mapper
public interface DataBaseLockMapper  extends BaseMapper<DataBaseLock> {

    int deleteAll(@Param("lockKey") String lockKey);
}
<?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.example.mybatiesplus.mapper.DataBaseLockMapper">
    <delete id="deleteAll" parameterType="String">
        delete from data_base_lock
        where lock_key = #{lockKey}
    </delete>
</mapper>

7、写一个测试类

package com.example.mybatiesplus.controller;

import com.example.mybatiesplus.result.BaseResponse;
import com.example.mybatiesplus.result.ResultGenerator;
import com.example.mybatiesplus.utils.DataBaseLockUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * @DESCRIPTION 测试类
 * @Author lst
 * @Date 2020-05-24
 */
@RestController
@RequestMapping("/test")
@Api(value = "TestController", tags = "测试类")
@Slf4j
public class TestController {

    @Autowired
    private DataBaseLockUtil dataBaseLockUtil;

    public final static String LOCK_KEY = "lock-key";

    public final static int DEFAULT_TIME_OUT = 60 * 10;

    /**
      * 通过数据库分布式锁高并发测试
      * @author lst
      * @date 2020-5-24 17:32
      * @param
      * @return com.example.mybatiesplus.result.BaseResponse
     */
    @GetMapping(value = "/dataBaseLock", produces = "application/json; charset=utf-8")
    @ApiOperation(value = "通过数据库分布式锁高并发测试", notes = "通过数据库分布式锁高并发测试", code = 200, produces = "application/json")
    public BaseResponse dataBaseLock() {
        try{
            log.info("============={} 线程访问开始============",Thread.currentThread().getName());
            //TODO 获取分布式锁
            boolean lock = dataBaseLockUtil.tryLock(LOCK_KEY,DEFAULT_TIME_OUT);
            if (lock) {
                log.info("线程:{},获取到了锁",Thread.currentThread().getName());
                //TODO 获得锁之后可以进行相应的处理  睡一会
                Thread.sleep(100);
                log.info("======获得锁后进行相应的操作======" + Thread.currentThread().getName());
                dataBaseLockUtil.delLock(LOCK_KEY);
                log.info("============================={} 释放了锁" + Thread.currentThread().getName());
            }
        }catch (Exception e){
            log.info("错误信息:{}",e.toString());
            log.info("线程:{} 获取锁失败",Thread.currentThread().getName());
        }
        return ResultGenerator.genSuccessResult();
    }
}

8、使用jmeter测试

mysql分布式方案 mysql分布式有哪些实现_mysql分布式方案_02

9、测试数据

在dataBaseLockUtil.delLock(LOCK_KEY);锁未释放的测试下,可能看到后台日志只有线程51获取到了锁。

mysql分布式方案 mysql分布式有哪些实现_分布式锁_03

将dataBaseLockUtil.delLock(LOCK_KEY);释放开在测试,只要某个抢到锁的线程执行完毕并且释放了锁资源,其他的线程很快就会获取到锁。

mysql分布式方案 mysql分布式有哪些实现_数据库_04