分布式锁背景介绍
在开发中,多线程场景,即多个线程共同去竞争同一个资源,我们为了避免出现线程不安全,常常采用Lock去锁住需要被线程共享的资源,常用的锁比如Synchronized和Reentrantlock。在分布式的场景中,多台机器上的程序,需要实现Lock锁这个功能。
常见的有下面解决方案,一是使用Redis的setnx,二是使用Zookeeper的持久节点下为每个客户端访问时创建临时顺序节点。直接使用在Java代码中使用Zookeeper不太方便。所以在开发中选择了使用Redis的方案。
业务场景介绍
在进行开发虚拟货币交易所时,由于不像股票交易,一支股票在哪个交易所上市,就只能在此上市交易所交易,显示价格等,如在上交所上市,就只需要调用上交所API,不会出现去深交所调用。而虚拟币不一样,虚拟币可以在全球各大交易所都有交易。所以在开发虚拟货币交易的时候,获取价格需要通过定时任务,调用多个交易所的API。而很多交易所,对API调用频率会进行限制,在分布式服务中,会产生多个节点同时访问,导致超过对方交易所的调用频率,从而被对方交易所封禁。所以我们需要运用Redis分布式锁,保证同一时刻,只有一个节点进行访问请求。代码如下
ThreadPoolTaskExecutor singleThreadExecutor = new ThreadPoolTaskExecutor();
public static final int INTERVAL = 30;
@PostConstruct
public void circleGetPrice() {
singleThreadExecutor = ThreadExecutorUtil.newSingleThreadExecutor("ZBPriceTaskThread-");
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
log.info("ZBPriceTask circleGetPrice running...");
while (true) {
try {
long setnx = CodisConnector.setnx(ZBConstans.GET_PRICE_TASK_NAME, INTERVAL, ZBConstans.GET_PRICE_TASK_NAME);
if (setnx == 1) {
getXlmPrice();
}
} catch (Exception e) {
log.error("ZBPriceTask circleGetPrice error", e);
}
try {
Thread.sleep(INTERVAL * 1000L);
} catch (InterruptedException e) {
}
}
}
});
}
这里用了,我们团队自己对jedis进行封装的工具类,底层其实也是jedis。这里进行重载了两个方法。
public static long setnx(String key, String value) {
Jedis jedis = pool.getResource();
try {
return jedis.setnx(key, value);
} catch (Exception e) {
logger.error(e);
} finally {
jedis.close();
}
return -1L;
}
public static long setnx(String key, int expire, String value) {
Jedis jedis = pool.getResource();
try {
if ("OK".equals(jedis.set(key, value, "NX", "EX", expire))) {
return 1;
}
return 0;
} catch (Exception e) {
logger.error("codis异常:", e);
} finally {
jedis.close();
}
return -1L;
}
解释
setnx(key, value)
如setnx(key, 1)当一个节点执行setnx返回value为1,则成功获得锁,执行代码完成后,再调用del方法。若返回!=1,则当前有其他节点执有锁。继续等待。
但这有一个致命的缺陷,若当前执有锁的节点,在执行代码时,死掉了,而没有来得及释放了锁。则其他所有节点,都会拿不到锁。造成死锁等待。所以redis又提供了另外一种方法。
set(key, value,expire,NX)
通过设置expire过期时间,当持有锁的节点,超时的时候,锁自动释放,从而避免死锁的现象。当然在此场景中,只有访问对方交易所价格API出现超时,这一种情况,所以对于原子性,没有太大的要求。