在分布式系统中,各系统同步访问共同的资源是很常见的。所以咱们经常须要协调他们的动做。 若是不一样的系统或是同一个系统的不一样主机之间共享了一个或一组资源,那么访问这些资源的时候,每每须要互斥来防止彼此干扰来保证一致性,在这种状况下,便须要使用到分布式锁。java
一个好的分布式锁经常须要如下特性:redis
所以须要建立一张锁表sql
CREATE TABLE `methodLock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
`cust_id` varchar(1024) NOT NULL DEFAULT '客户端惟一编码',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
复制代码
添加锁数据库
insert into methodLock(method_name,cust_id) values (‘method_name’,‘cust_id’)
复制代码
这里cust_id 能够是机器的mac地址+线程编号, 确保一个线程只有惟一的一个编号。经过这个编号, 能够有效的判断是否为锁的建立者,从而进行锁的释放以及重入锁判断缓存
释放锁分布式
delete from methodLock where method_name ='method_name' and cust_id = 'cust_id'
复制代码
重入锁判断memcached
select 1 from methodLock where method_name ='method_name' and cust_id = 'cust_id'
复制代码
加锁以及释放锁的代码示例性能
/** * 获取锁 */
public boolean lock(String methodName){
boolean success = false;
//获取客户惟一识别码,例如:mac+线程信息
String custId = getCustId();
try{
//添加锁
success = insertLock(methodName, custId);
} catch(Exception e) {
//如添加失败
}
return success;
}
/** * 释放锁 */
public boolean unlock(String methodName) {
boolean success = false;
//获取客户惟一识别码,例如:mac+线程信息
String custId = getCustId();
try{
//添加锁
success = deleteLock(methodName, custId);
} catch(Exception e) {
//如添加失败
}
return success;
}
复制代码
完整流程ui
public void test() {
String methodName = "methodName";
//判断是否重入锁
if (!checkReentrantLock(methodName)) {
//非重入锁
while (!lock(methodName)) {
//获取锁失败, 则阻塞至获取锁
try{
Thread.sleep(100)
} catch(Exception e) {
}
}
}
//TODO 业务处理
//释放锁
unlock(methodName);
}
复制代码
以上代码还存在一些问题:编码
代码示例
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
// Redis客户端
private Jedis jedis;
/** * 尝试获取分布式锁 * @param lockKey 锁 * @param expireTime 超期时间 * @return 是否获取成功 */
public boolean lock(String lockKey, int expireTime) {
//获取客户惟一识别码,例如:mac+线程信息
String custId = getCustId();
String result = jedis.set(lockKey, custId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/** * 释放分布式锁 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */
public boolean unlock(String lockKey,) {
//获取客户惟一识别码,例如:mac+线程信息
String custId = getCustId();
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(custId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
/** * 获取锁信息 * @param lockKey 锁 * @return 是否重入锁 */
public boolean checkReentrantLock(String lockKey){
//获取客户惟一识别码,例如:mac+线程信息
String custId = getCustId();
//获取当前锁的客户惟一表示码
String currentCustId = redis.get(lockKey);
if (custId.equals(currentCustId)) {
return true;
}
return false;
}
复制代码
完整流程
public void test() {
String lockKey = "lockKey";
//判断是否重入锁
if (!checkReentrantLock(lockKey)) {
//非重入锁
while (!lock(lockKey)) {
//获取锁失败, 则阻塞至获取锁
try{
Thread.sleep(100)
} catch(Exception e) {
}
}
}
//TODO 业务处理
//释放锁
unlock(lockKey);
}
复制代码
memcached的实现方式和redis相似, 使用的是命令add(key, value, expireDate),注:仅当缓存中不存在键时,才会添加成功
代码示例
// Redis客户端
private MemCachedClient memCachedClient;
/** * 尝试获取分布式锁 * @param lockKey 锁 * @param expireTime 超期时间 * @return 是否获取成功 */
public boolean lock(String lockKey, Date expireDate) {
//获取客户惟一识别码,例如:mac+线程信息
String custId = getCustId();
Boolean result = false;
try {
result = memCachedClient.add(lockKey, custId,expireDate);
} catch(Excetion e) {
}
return result;
}
/** * 释放分布式锁 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */
public boolean unlock(String lockKey,) {
//获取客户惟一识别码,例如:mac+线程信息
//获取客户惟一识别码,例如:mac+线程信息
String custId = getCustId();
Boolean result = false;
try {
String currentCustId = memCachedClient.get(lockKey);
if (custId.equals(currentCustId)) {
result = memCachedClient.delete(lockKey, custId,expireDate);
}
} catch(Excetion e) {
}
return result;
}
/** * 获取锁信息 * @param lockKey 锁 * @return 是否重入锁 */
public boolean checkReentrantLock(String lockKey){
//获取客户惟一识别码,例如:mac+线程信息
String custId = getCustId();
//获取当前锁的客户惟一表示码
try {
String currentCustId = memCachedClient.get(lockKey);
if (custId.equals(currentCustId)) {
return true;
}
} catch(Excetion e) {
}
return false;
}
复制代码
完整流程
public void test() {
String lockKey = "lockKey";
//判断是否重入锁
if (!checkReentrantLock(lockKey)) {
//非重入锁
while (!lock(lockKey)) {
//获取锁失败, 则阻塞至获取锁
try{
Thread.sleep(100)
} catch(Exception e) {
}
}
}
//TODO 业务处理
//释放锁
unlock(lockKey);
}
复制代码
基于zookeeper临时有序节点能够实现的分布式锁。 大体思想即为:每一个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个惟一的瞬时有序节点。 判断是否获取锁的方式很简单,只须要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除便可。同时,其能够避免服务宕机致使的锁没法释放,而产生的死锁问题。
能够直接使用zookeeper第三方库Curator客户端,这个客户端中封装了一个可重入的锁服务。
完整流程
public void test() {
//Curator提供的InterProcessMutex是分布式锁的实现。经过acquire得到锁,并提供超时机制,release方法用于释放锁。
InterProcessMutex lock = new InterProcessMutex(client, ZK_LOCK_PATH);
try {
//获取锁
if (lock.acquire(10 * 1000, TimeUnit.SECONDS)) {
//TODO 业务处理
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//释放锁
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
欢迎长按下图关注公众号: 终身幼稚园