疫情当下,大环境不好,自己又去了一家令人非常失望的单位,一直在996加班忙于业务代码,技术方面几乎等于零成长。但是,作为一个Coder,必须要挤出时间去学习与总结,不然就会被无情的淘汰。Coder加油吧!
本篇文章主要是介绍分别使用MySQL、Redis、Zookeeper实现分布式锁的思路与代码写法,以及底层依赖框架源码原理,例如,本篇文章使用Redis实现分布式是基于Redisson框架,我会找到Redisson框架中,分布式锁的相关源码,对它进行注释和总结,方面大家理解和学习相关知识。其次,为了方便大家使用不同种类的分布式锁,我把这几种分布式锁实现集成到一个架包中,集成时,我运用了多个设计模式、例如,工厂模式、策略模式、单例模式、构造模式等。目前企业中大多数都是基于Springboot框架进行发开,我又把架包改成一个Starter,免去各自各种配置,直接引入就可以使用。下面将分别介绍不同种类分布式锁的实现与相关原理以及架包运用的设计模式等:
MySQL实现分布式锁是采用在InnoDB引擎下,使用for update排他锁完成的。 for update具有只允许获取锁的事务对数据进行更新或删除操作,其他事务想要对相同的数据再次加锁的时,会进行到阻塞状态的特点。在数据量不大的情况下,可以使用MySQL实现的分布式锁。
使用MySQL实现的分布式锁,需要创建如下表,并且lock_key字段要有索引,不然for update 会从行锁升级成表锁:
CREATE TABLE `distribute_lock` (
`id` bigint NOT NULL AUTO_INCREMENT,
`lock_key` varchar(100) NOT NULL,
`create_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `lock_key` (`lock_key`)
) ENGINE=InnoDB;
具体实现如下(这里只展示,lock()与unlock()):
public MysqlDistributedLock(String lockKey, DataSource dataSource) {
super(lockKey);
this.dataSource = dataSource;
}
@Override
public void lock() throws DistributedLockException {
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
for (; ; ) {
connection = dataSource.getConnection();
connection.setAutoCommit(false);
statement = connection.prepareStatement(SELECT_SQL);
statement.setString(1, lockKey);
resultSet = statement.executeQuery();
// 存在加锁记录,并且首次执行for update,则直接return
if (resultSet.next()) {
return;
}
close(resultSet, statement, connection);
// 锁记录不存在创建
Connection insertConnection = dataSource.getConnection();
PreparedStatement insertStatement = null;
try {
insertStatement = insertConnection.prepareStatement(INSERT_SQL);
insertStatement.setString(1, lockKey);
insertStatement.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
if (insertStatement.executeUpdate() == 1) {
log.info(lockKey + "的锁记录创建成功!");
}
} catch (Exception e) {
throw new DistributedLockException("lockKey是 " + lockKey + " 加锁时,创建锁记录异常: ", e);
} finally {
close(insertStatement, insertConnection);
}
}
} catch (Exception e) {
throw new DistributedLockException("lockKey是 " + lockKey + " 加锁异常: ", e);
} finally {
close(resultSet, statement);
}
}
@Override
public void unlock() throws DistributedLockException {
try {
connection.commit();
close(connection);
} catch (Exception e) {
throw new DistributedLockException("lockKey是 " + lockKey + " 解锁异常: ", e);
}
}
Redis实现分布式锁是基于Redisson框架完成的,Redisson框架在实现加锁与解锁功能都是基于Lua脚本完成的,Lua脚本在同一台Redis服务端中具有原子性的特点,可以保障脚本中所有指令原则执行。Redisson中还巧妙的运用了Redis的发布与订阅功能,可以在当前拿锁的线程解锁时,广播通知到其他抢锁线程去抢锁。Redisson还解决了加锁有效期过期的问题,引入了看门狗机制,一直为加锁线程续期。
我具体是使用Redisson框架的RedissonLock锁,它主要用的数据结构是Hash结构,其中key是加锁key名称,field是id+":"+threadId ->UUID:threadId,value是记录可重入次数。再具体的加锁解锁原理,我日后再介绍。
具体实现如下(这里只展示,lock()与unlock()):
public RedisDistributedLock(String lockKey, RedissonClient client) {
super.lockKey = lockKey;
this.client = client;
}
@Override
public void lock() throws DistributedLockException {
try {
client.getLock(lockKey).lock();
} catch (Exception e) {
throw new DistributedLockException("lockKey是 " + lockKey + " 加锁异常: ", e);
}
}
@Override
public void unlock() throws DistributedLockException {
try {
client.getLock(lockKey).unlock();
} catch (Exception e) {
throw new DistributedLockException("lockKey是 " + lockKey + " 释放锁异常: ", e);
}
}
Zookeeper实现分布式锁是基于Curator框架完成的,Curator框架的实现的分布式锁主要应用到Zookeeper的临时顺序节点、Watch模式功能。以及Java线程安全的原子类、容器和synchronized下的线程通信机制等。
此外,Zookeeper不允许在临时节点下,创建子节点,InterProcessMutex工具类会根据加锁时,传入path的创建一个持久节点,然后在这个持久节点下创建顺序临时节点。如果,不定期清理,就会导致Zk节点数量将会急速递增。所以,启动该框架时,我会启动一个后台线程,定时去无效的节点。
具体实现如下(这里只展示,lock()与unlock()):
public ZkDistributedLock(String baseLockPath, String lockKey, CuratorFramework client) {
super(lockKey);
this.client = client;
this.lock = getInterProcessMutex(client, getZkLockPath(baseLockPath, lockKey));
}
@Override
public void lock() throws DistributedLockException {
try {
lock.acquire();
} catch (Exception e) {
throw new DistributedLockException("lockKey是 " + lockKey + " 加锁异常: ", e);
}
}
@Override
public void unlock() throws DistributedLockException {
try {
lock.release();
} catch (Exception e) {
throw new DistributedLockException("lockKey是 " + lockKey + "释放锁异常: ", e);
}
}
整理的代码设计主要使用了抽象类,模板模式和工厂模式,可以通过以下类图体现:
在启动配置架包时,运用到了建造模式,方便配置项目的相关参数。在使用具体分布式锁实现时,运用到了单例模式,获取相关配置类。
为了在Springboot体系下运行,我还将本项目改造成一个Springboot Starter,可以自动获取配置,自动将DistributedLockFactory Bean 加载到Spring容器中。Springboot Starter主要是利用Spring SPI的机制,进行加载相关配置类,进行配置的自动注入。创建Springboot Starter可以参考《SpringBoot之自定义发送异常邮件Starter》
https://gitee.com/hanxiaozhang2018/distributed-lock.git