【实战】mysql 实现分布式锁

mysql锁表创建语句如下:

create table mysql_lock
(
    id          bigint auto_increment comment '主键'
        primary key,
    lock_key    varchar(100) default '0'               not null comment '分布式锁key',
    end_time    timestamp    default CURRENT_TIMESTAMP not null comment '失效时间',
    client_id   varchar(100) default ''                not null comment '获取锁的ip + threadId',
    version     int(4)       default 1                 not null comment '版本号',
    times       int(4)       default 0                 not null comment '重入次数',
    create_time timestamp    default CURRENT_TIMESTAMP not null comment '创建时间',
    update_time timestamp    default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
    constraint uniq_lock_key
        unique (lock_key)
)
    comment '分布式锁表';

java代码工具类

服务侧

public class MysqlLocker {

    @Resource
    private MLockDao mLockDao;

    /**
     * 加锁
     * @param key
     * @param secondes
     * @return 成功/失败
     */
    @Transactional(rollbackFor = Throwable.class)
    public Boolean tryLock(String key,Long secondes){
        MLock lock = mLockDao.selectByKey(key);
        String clientId = currentId();
        Date endTime = new Date(System.currentTimeMillis() + secondes * 1000);
        Boolean result = Boolean.FALSE;
        if(Objects.isNull(lock)){
            // 无冲突 直接插入
            lock = new MLock();
            lock.setLockKey(key);
            lock.setEndTime(endTime);
            lock.setClientId(clientId);
            // 防止极端情况 初始版本号一致 导致误解锁
            lock.setVersion(new Random().nextInt(100));
            try{
                int rst = mLockDao.insert(lock);
                // 插入成功 无碰撞获取锁成功
                result = rst > 0;
            }catch (Exception e){
                // 唯一索引冲突 枷锁失败
                result = Boolean.FALSE;
            }
        }else {
            // 锁过期判定
            if(System.currentTimeMillis() >= lock.getEndTime().getTime()){
                int rst = mLockDao.updateByKey(key,endTime,clientId, lock.getVersion(), lock.getVersion() + 1,0);
                if(rst > 0){
                    result = Boolean.TRUE;
                }
            }
        }
        return result;
    }

    /**
     * 解锁
     * @param key
     */
    @Transactional(rollbackFor = Throwable.class)
    public void unLock(String key){
        MLock lock = mLockDao.selectByKey(key);
        if(Objects.nonNull(lock)){
            // 判断是否当前线程拥有锁
            String clientId = currentId();
            if(!clientId.equals(lock.getClientId())){
                // 非当前线程拥有 不可解锁
                return;
            }
            // 当前线程拥有 则直接删除记录
            int rst = mLockDao.deleteByKey(key,lock.getVersion());
            if(rst > 0){
                return;
            }
        }

    }

    private String currentId(){
        String ip = null;
        try {
            ip = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            // 获取本地ip异常
            ip = "";
        }
        return ip + "_" + Thread.currentThread().getId();
    }

}

dao层

public interface MLockDao {
    int insert(MLock lock);
    int updateByKey(@Param("key") String key,
                    @Param("endTime") Date endTime,
                    @Param("clientId") String clientId,
                    @Param("version") int version,
                    @Param("newVersion") int newVersion,
                    @Param("times") int times);
    MLock selectByKey(@Param("key") String key);

    int deleteByKey(@Param("key") String key,
                    @Param("version") int version);
}

mapper

<mapper namespace="com.example.demo.dao.primary.MLockDao">
    <sql id="fields">
        id,
        lock_key as lockKey,
        end_time as endTime,
        client_id as clientId,
        version,
        times,
        create_time as createTime,
        update_time as updateTime
    </sql>
    <insert id="insert" parameterType="com.example.demo.utils.Lock.MLock"
            useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into m_lock (
            lock_key,
            end_time,
            client_id,
            version
        )
        values(
                  #{lockKey},
                  #{endTime},
                  #{clientId},
                  #{version}
              )
    </insert>

    <select id="selectByKey" resultType="com.example.demo.utils.Lock.MLock">
        select
        <include refid="fields"/>
        from m_lock
        where lock_key = #{key}
    </select>

    <update id="updateByKey" >
        update m_lock
        set
            end_time = #{endTime},
            client_id = #{clientId},
            version = #{newVersion},
            times = #{times}
        where lock_key = #{key} and version = #{version}
    </update>

    <delete id="deleteByKey">
        delete from m_lock
        where lock_key = #{key} and version = #{version}
    </delete>

</mapper>

最顶层工具类

public class LockUtil implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(LockUtil.class);

    @Resource
    private MysqlLocker mLock;

    private static MysqlLocker LOCK;

    /**
     * 初始化
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        LockUtil.LOCK = mLock;
    }

    /**
     * 加锁
     * @param key
     * @param secondes
     * @return
     */
    public static boolean tryLock(String key, Long secondes){
        return LOCK.tryLock(key,secondes);
    }

    /**
     * 解锁
     * @param key
     */
    public static void unLock(String key){
        LOCK.unLock(key);
    }

    /**
     * 加锁执行 并获取结果
     * @param key
     * @param secondes
     * @param function
     * @return
     * @param <T>
     */
    public static <T> T execute(String key, Long secondes, Supplier<T> function){
        Boolean loked = Boolean.FALSE;
        try {
            if(loked = LOCK.tryLock(key,secondes)){
                if(logger.isDebugEnabled()){
                    logger.debug("分布式加锁成功,key:{} expir:{}",key,secondes);
                }
                return function.get();
            }
            throw new BizException("操作频繁,请稍后再试");
        } catch (Exception e) {
            logger.error("分布式加锁操作失败,key:{} ,error:{}",key,e.getMessage(),e);
            throw new BizException("操作频繁,请稍后再试", e);
        }finally {
            if(loked){
                LOCK.unLock(key);
                if(logger.isDebugEnabled()){
                    logger.debug("分布式解锁成功,key:{}",key);
                }
            }
        }

    }

}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

荼白z

感谢老板请我喝咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值