分布式栅栏-Barrier
Barrier的作用是堵塞所以用正在运行的进程直到某一时刻,锁释放所有进程同时执行。就比如赛马场中的赛马来到起跑线 等待枪声发号施令之后,栅栏释放之后所有赛马都一同飞奔而去。
简单说下我在项目中的使用场景。在做支付账户系统结算客户有两个账户:1、现金账户、2、待清算账户。两个账户客户都可以提现。不同的是待清算账户记录的是每天的交易的发生额,到凌晨的时候支付公司需要把客户前一天的交易发生额从待清算账户转到现金账户, 但是有可能在资金结转的时候可能出现客户进行提现把前一天的资金使用了。可能造成后面的结转有问题,这时候在定时任务发生结转的时候限制客户待清算账户不能发生提现交易但是又不能影响接口交易的实时交易。所以这里考虑在定时任务发生结转的时候利用栅栏控制实时交易将其进行堵塞直到任务结转完成栅栏释放,通知实时交易正常进行。
栅栏Barrier
DistributedBarrier类实现了栅栏的功能。通过构造方法将Zkclient进行初始化,Zkclient是可以多次重复使用的可以在项目启动的时候初始化进行持有。
@PostConstruct
private void initZkClient(){
zkClient = new ZkClient(serverAddress, sessionTimeout,connectTimeout);
}
三个方法实现栅栏过程:
1、设置栅栏,在Zk上建立持久节点,因为在上面的使用场景里面是客户没有结转成功是不允许资金结转,所以考虑了下用持久节点更加合适。这里说明下一般栅栏都是使用临时节点。
setBarrier();
2、所有线程在执行的时候到Zk上检查节点是否存在,存在则等待直到节点释放才通过,不存在则直接通过。
public void waitOnBarrier();
3、当满足条件(也就是说结转完成),删除栅栏。所有线程开始继续执行。
removeBarrier();
下面是完整实现过程:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.apache.zookeeper.WatchedEvent;
[@Slf4j](https://my.oschina.net/slf4j)
public class DistributedBarrier{
@Setter
private String serverAddress;
@Setter
private int sessionTimeout;
@Setter
private int connectTimeout;
@Setter
private String barrierRootPath;
@Setter
private int awaitTime;
private ZkClient zkClient;
@PostConstruct
private void initZkClient(){
zkClient = new ZkClient(serverAddress, sessionTimeout,connectTimeout);
}
//创建目录
public void setBarrier(String barrierPath){
if(!zkClient.exists(barrierRootPath+barrierPath)){
zkClient.createPersistent(barrierRootPath+barrierPath,true);//创建持久节点,递归调用父节点
}
}
//等待释放
public String waitOnBarrier(String dataPath){
final CountDownLatch cdl = new CountDownLatch(1);
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataDeleted(String dataPath) throws Exception {
log.info("收到节点被删除了{}",dataPath);
cdl.countDown();
}
public void handleDataChange(String dataPath, Object data)
throws Exception {
}
};
zkClient.subscribeDataChanges(barrierRootPath+dataPath, listener);
String msg = "";
if (this.zkClient.exists(barrierRootPath+dataPath)) {
try {
cdl.await(awaitTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.info("线程被中断InterruptedException",e);
}
boolean stat = zkClient.exists(barrierRootPath+dataPath);
if (stat){
msg= barrierRootPath+dataPath+" exist";
}
}
// 取消注册
zkClient.unsubscribeDataChanges(barrierRootPath+dataPath, listener);
return msg;
}
//移除目录
public void removeBarrier(String barrierPath){
zkClient.delete(barrierRootPath+barrierPath);
}
}