基于Mysql数据库(RDBMS)的分布式锁

【订阅专栏合集,作者所有付费文章都能看(持续更新)】

推荐【Kafka教程https://bigbird.blog.csdn.net/article/details/108770504
推荐【rabbitmq教程https://bigbird.blog.csdn.net/article/details/81436980
推荐【Flink教程https://blog.csdn.net/hellozpc/article/details/109413465
推荐【JVM面试与调优教程https://bigbird.blog.csdn.net/article/details/113888604
推荐【SpringBoot全套教程https://blog.csdn.net/hellozpc/article/details/107095951
推荐【SpringCloud教程https://blog.csdn.net/hellozpc/article/details/83692496
推荐【Mybatis教程https://blog.csdn.net/hellozpc/article/details/80878563
推荐【SnowFlake教程https://blog.csdn.net/hellozpc/article/details/108248227
推荐【并发限流教程https://blog.csdn.net/hellozpc/article/details/107582771
推荐【Redis教程https://bigbird.blog.csdn.net/article/details/81267030
推荐【Netty教程https://blog.csdn.net/hellozpc/category_10945233.html

  • 实现分布式锁有很多方案,常用的比如redisZooKeeper
    但是在简单场景(非高并发)下,我们不妨使用基于关系数据库的锁,实现简单,使用方便。

  • 锁本质上是一个标记。

  • 把这个标记放在关系数据库(RDBMS)中,我们就可以使用数据库的方式实现锁机制。比如设计一张锁表,表中有个字段state,state有两个值,分别表示锁定/未锁定状态;

  • 把这个标记放在zookeeper的节点中,我们就可以使用zk实现分布式锁;把这个标记放在内存中,比如设置一个 volatile变量state保存锁定/未锁定状态,就可以在java进程级别实现锁。

/**
 * 基于RDBMS的简单分布式锁
 *
 * @Author: zhoupengcheng
 */
public interface LockService {
    public String lock(String lockName);

    public boolean unlock(String lockName,String lockNo);
}
/**
 * @Author: zhoupengcheng
 */
@Service
public class LockServiceImpl implements LockService {

    @Resource
    SyncJobLockMapper syncJobLockMapper;

    @Override
    public String lock(String lockName) {
        SyncJobLock syncJobLock = new SyncJobLock();
        syncJobLock.setName(lockName);
        String lockNo = UuidUtil.genUuid();
        syncJobLock.setLockNo(lockNo);
        int i = syncJobLockMapper.updateByGetLock(syncJobLock);
        if (i == 1) {
            return lockNo;
        }
        return null;
    }

    @Override
    public boolean unlock(String lockName, String lockNo) {
        SyncJobLock syncJobLock = new SyncJobLock();
        syncJobLock.setName(lockName);
        syncJobLock.setLockNo(lockNo);
        int i = syncJobLockMapper.updateByUnLock(syncJobLock);
        if (i == 1) {
            return true;
        }
        return false;
    }
}

数据库添加一张锁表:

create table t_locks
(
	id int unsigned auto_increment comment '自增id'
		primary key,
	name varchar(64) null comment '锁名',
	status char default 'R' null comment '上锁标记,L=已锁,R=已释放',
	remark varchar(64) null comment '备注',
	lockNo varchar(64) null comment '锁序列号',
	create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
	update_time timestamp default CURRENT_TIMESTAMP not null comment '更新时间'
)
comment '分布式锁,job触发前先获取锁' charset=utf8;

create index idx_name_status
	on t_locks (name, status);


对应Java代码的Lock实体:


public class SyncJobLock {
    private Integer id;

    /**
     * 锁名
     */
    private String name;

    /**
     * 锁状态:L=已上锁,R=已释放
     */
    private String status;

    /**
     * 备注
     */
    private String remark;

    private Date createTime;

    private Date updateTime;

    /**
     * 锁序列号,用于解锁时判断(谁上的锁谁才能解)
     */
    private String lockNo;
}

Mapper接口:

public interface SyncJobLockMapper {

    int updateByGetLock(SyncJobLock syncJobLock);

    int updateByUnLock(SyncJobLock syncJobLock);
}

Mapper.xml文件:

<!--超过设置的最大锁定时间(比如1分钟)认为锁失效-->
    <update id="updateByGetLock" parameterType="com.zpc.entity.SyncJobLock">
        UPDATE t_locks
        SET update_time = sysdate(),
            status = 'L',
            lockNo = #{lockNo}
        where name = #{name}
          and (status = 'R' or
               (status = 'L' and (TIME_TO_SEC(SYSDATE()) - TIME_TO_SEC(update_time)) > 60))
    </update>

    <update id="updateByUnLock" parameterType="com.zpc.entity.SyncJobLock" >
        update t_locks t
        set t.status = 'R' , update_time = sysdate()
        where t.name = #{name}
          and t.status = 'L'
          and t.lockNo = #{lockNo}
    </update>

LockName 枚举,不同的业务可以定义不同的锁:

	
/**
 * 分布式环境下,每个job先获取锁再执行调度
 *
 * @Author: zhoupengcheng
 */
public enum LockName {

    /**
     * 业务锁A
     */
    SYNC_SCHEDULE_LOCK("rollbackScheduleLock"),

    /**
     * 锁B
     */
    SYNC_DRIVERANDCAR_LOCK("syncDriverAndCarLock");

    private String name;

    LockName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

使用场景:比如分布式环境下的调度,防止调度没有执行完,另一台服务器又起了同样的调度,造成数据重复处理等异常情况。

@EnableScheduling
	//业务使用
    @Scheduled(fixedDelay = 60000)
    private void startTransportationCapacity() {
        log.info("*************  startJob *************");
        long start = System.currentTimeMillis();
        boolean unlockable = true;//是否是有效的释放锁操作
        String lockNo = "";
        try {
            lockNo = lockService.lock(LockName.SYNC_DRIVERANDCAR_LOCK.getName());
            if (lockNo != null) {
                log.info("获取锁成功,开始执行调度,lockname:{},lockNo:{}", LockName.SYNC_DRIVERANDCAR_LOCK.getName(), lockNo);
                //这里写业务处理逻辑。。。。
                //Thread.sleep(10000);
            } else {
                log.info("获取锁失败,lockname:{}", LockName.SYNC_DRIVERANDCAR_LOCK.getName());
                unlockable = false;
            }
        } catch (Exception e) {
            log.error("业务处理异常", e);
        } finally {
            if (unlockable) {
                boolean unlockOk = lockService.unlock(LockName.SYNC_DRIVERANDCAR_LOCK.getName(), lockNo);
                if (unlockOk) {
                    log.info("释放锁成功,lockname:{},lockNo:{}", LockName.SYNC_DRIVERANDCAR_LOCK.getName(), lockNo);
                } else {
                    log.info("释放锁失败,lockname:{},lockNo:{}", LockName.SYNC_DRIVERANDCAR_LOCK.getName(), lockNo);
                }
            }
        }

        long end = System.currentTimeMillis();
        log.info("*************  endJob,cost:{}ms *************", end - start);
    }

锁释放失败不用处理,在获取锁时加了过期时间判断(见sql)。

**欢迎关注公众号**
**微信扫一扫**
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程猿薇茑

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值