人间四月芳菲尽-基于数据库(RDBMS)的分布式锁

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/zpcandzhj/article/details/89054724

题记:人间四月芳菲尽,山寺桃花始盛开。

实现分布式锁有很多方案,常用的比如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)。

猜你喜欢

转载自blog.csdn.net/zpcandzhj/article/details/89054724