Curator实现分布式全局计数器
单机环境下,多线程之间如何实现线程安全自增(计数)的实现方法及原理,那么如果是在分布式环境中呢?自然synchronized,lock,atomicInteger等基于Java的方法不能满足,因为这些都只能在当前JVM环境中生效,而分布式环境中多个JVM实例是很正常的事
下面介绍Curator基于Zookeeper实现的分布式计数器
Curator recipes包下实现了DistributedAtomicInteger,DistributedAtomicLong等分布式原子自增计数器
简单应用
public class CuratorAtomicInteger {
/** zookeeper地址 */
static final String CONNECT_ADDR = "172.16.158.11:2181,"
+ "172.16.158.12:2181,"
+ "172.16.158.13:2181";
/** session超时时间 */
static final int SESSION_OUTTIME = 5000;//ms
public static void main(String[] args) throws Exception {
//1 重试策略:初试时间为1s 重试10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通过工厂创建连接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build();
//3 开启连接
cf.start();
//cf.delete().forPath("/super");
//4 使用DistributedAtomicInteger
DistributedAtomicInteger atomicIntger =
new DistributedAtomicInteger(cf, "/super", new RetryNTimes(3, 1000));
AtomicValue<Integer> value = atomicIntger.add(1);
System.out.println(value.succeeded());
System.out.println(value.postValue()); //最新值
System.out.println(value.preValue()); //原始值
}
}
结果正常返回,没有任何问题
高级应用(多线程环境下)
那么在多线程情况下,是否正确呢?有待验证
public class CuratorAtomicInteger2 {
static CountDownLatch countDownLatch = new CountDownLatch(10);
public static void main(String[] args) throws Exception {
CuratorFramework zkClient = getZkClient();
//指定计数器存放路径 及重试策略
DistributedAtomicInteger distributedAtomicInteger = new DistributedAtomicInteger(zkClient, "/counter", new ExponentialBackoffRetry(1000, 3));
//多线程自增10*100次
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 100; j++) {
try {
//调用add方法自增
AtomicValue<Integer> result = distributedAtomicInteger.add(1);
} catch (Exception e) {
e.printStackTrace();
}
}
countDownLatch.countDown();
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//查看结果
System.out.println("多线程自增结果" + distributedAtomicInteger.get().postValue());
}
private static CuratorFramework getZkClient() {
String zkServerAddress = "172.16.158.11:2181,"
+ "172.16.158.12:2181,"
+ "172.16.158.13:2181";
ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3, 5000);
CuratorFramework zkClient = CuratorFrameworkFactory.builder()
.connectString(zkServerAddress)
.sessionTimeoutMs(5000)
.connectionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
zkClient.start();
return zkClient;
}
}
按道理说,自增完后,结果是不是应该是10*100?,道友们运行几次后会发现实际值不全是1000。为什么呢?这玩意是假的?
我们看下AtomicValue result = distributedAtomicInteger.add(1)这行代码中,add()方法源码
/**
* Add delta to the current value and return the new value information. Remember to always
* check {@link AtomicValue#succeeded()}.
*
* @param delta amount to add
* @return value info
* @throws Exception ZooKeeper errors
*/
@Override
public AtomicValue<Integer> add(Integer delta) throws Exception
{
return worker(delta);
}
其中写道 Remember to always check {@link AtomicValue#succeeded()}. 也就是说,这个方法的自增是不一定会成功的,在前面初始化分布式机器数对象时,传入了重试策略,如果分布式环境中出现了并发自增的情况,会不断重试,如果重试后还失败,则结果返回失败