一、技术介绍
zookeeper有很多典型应用场景,应用在分布式系统中,这里介绍其分布式计数器应用。本文将讨论如何使用Curator来实现计数器。 顾名思义,计数器是用来计数的, 利用ZooKeeper可以实现一个集群共享的计数器。 只要使用相同的path就可以得到最新的计数器值, 这是由ZooKeeper的一致性保证的。
Curator有两个计数器:SharedCount计数器,DistributedAtomicLong计数器(如int等其它类型计数器类似)
-
SharedCount
这个类使用int类型来计数。 主要涉及三个类。
- SharedCount
- SharedCountReader
- SharedCountListener
SharedCount
代表计数器, 可以为它增加一个SharedCountListener,当计数器改变时此Listener可以监听到改变的事件,而SharedCountReader可以读取到最新的值, 包括字面值和带版本信息的值VersionedValue。
-
DistributedAtomicLong
再看一个Long类型的计数器。 除了计数的范围比SharedCount
大了之外, 它首先尝试使用乐观锁的方式设置计数器, 如果不成功(比如期间计数器已经被其它client更新了), 它使用InterProcessMutex
方式来更新计数值, 这和上面的计数器的实现有显著的不同。你必须检查返回结果的succeeded()
, 它代表此操作是否成功。 如果操作成功, preValue()
代表操作前的值,postValue()
代表操作后的值。
具体实现步骤:
二、maven依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
三、SharedCount计数器
SharedCount计数器在操作失败时,需要自己去重试,代码如下:
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.shared.SharedCount;
import org.apache.curator.framework.recipes.shared.SharedCountListener;
import org.apache.curator.framework.recipes.shared.SharedCountReader;
import org.apache.curator.framework.recipes.shared.VersionedValue;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;
public class ZookeeperSharedCountMain {
public static void main(String[] args) throws Exception {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new ExponentialBackoffRetry(1000, 3));
client.start();
//指定锁路径
String lockPath = "/zkLockRoot/lock_2";
//创建分布式计数器,初始为0
SharedCount sharedCount = new SharedCount(client, lockPath, 0);
//定义监听器
SharedCountListener sharedCountListener = new SharedCountListener() {
@Override
public void countHasChanged(SharedCountReader sharedCountReader, int i) throws Exception {
System.out.println("new cnt : " + i);
}
@Override
public void stateChanged(CuratorFramework curatorFramework, ConnectionState connectionState) {
System.out.println("stated change : " + connectionState);
}
};
//添加监听器
sharedCount.addListener(sharedCountListener);
sharedCount.start();
//生成线程池
ExecutorService executor = Executors.newCachedThreadPool();
Consumer<SharedCount> consumer = (SharedCount count) -> {
try {
List<Callable<Boolean>> callList = new ArrayList<>();
Callable<Boolean> call = () -> {
boolean result = false;
try {
//数据更新,若失败,则重试10次,每次间隔30毫秒
for(int i=0; i<10;i++){
//获取当前版本数据
VersionedValue<Integer> oldVersion = sharedCount.getVersionedValue();
int newCnt = oldVersion.getValue() + 1;
result = sharedCount.trySetCount(oldVersion, newCnt);
if(result){
System.out.println(Thread.currentThread().getName()
+ " oldVersion:"+oldVersion.getVersion()
+" oldCnt:"+oldVersion.getValue()
+" newCnt:"+sharedCount.getCount()
+" result:"+result);
break;
}
TimeUnit.MILLISECONDS.sleep(30);
}
} catch (Exception e) {
} finally {
}
return result;
};
//5个线程
for (int i = 0; i < 5; i++) {
callList.add(call);
}
List<Future<Boolean>> futures = executor.invokeAll(callList);
} catch (Exception e) {
}
};
//测试分布式int类型的计数器
consumer.accept(sharedCount);
System.out.println("final cnt : " + sharedCount.getCount());
sharedCount.close();
executor.shutdown();
}
}
输出:
stated change : CONNECTED
pool-3-thread-5 oldVersion:0 oldCnt:0 newCnt:1 result:true
new cnt : 1
pool-3-thread-3 oldVersion:1 oldCnt:1 newCnt:2 result:true
new cnt : 2
pool-3-thread-1 oldVersion:2 oldCnt:2 newCnt:3 result:true
new cnt : 3
pool-3-thread-2 oldVersion:3 oldCnt:3 newCnt:4 result:true
new cnt : 4
pool-3-thread-4 oldVersion:4 oldCnt:4 newCnt:5 result:true
final cnt : 5
四、DistributedAtomicLong计数器
DistributedAtomicLong计数器可以自己设置重试的次数与间隔,代码如下:
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.atomic.AtomicValue;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicLong;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Consumer;
public class ZookeeperDistributedAtomicLongMain {
public static void main(String[] args) throws Exception {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new ExponentialBackoffRetry(1000, 3));
client.start();
//指定锁路径
String lockPath = "/zkLockRoot/lock_3";
//分布式计数器,失败时重试10,每次间隔30毫秒
DistributedAtomicLong distributedAtomicLong = new DistributedAtomicLong(client, lockPath, new RetryNTimes(10, 30));
//生成线程池
ExecutorService executor = Executors.newCachedThreadPool();
Consumer<DistributedAtomicLong> consumer = (DistributedAtomicLong count) -> {
try {
List<Callable<Boolean>> callList = new ArrayList<>();
Callable<Boolean> call = () -> {
boolean result = false;
try {
AtomicValue<Long> val = count.increment();
System.out.println("old cnt: "+val.preValue()+" new cnt : "+ val.postValue()+" result:"+val.succeeded());
result = val.succeeded();
} catch (Exception e) {
} finally {
}
return result;
};
//5个线程
for (int i = 0; i < 5; i++) {
callList.add(call);
}
List<Future<Boolean>> futures = executor.invokeAll(callList);
} catch (Exception e) {
}
};
//测试计数器
consumer.accept(distributedAtomicLong);
System.out.println("final cnt : " + distributedAtomicLong.get().postValue());
executor.shutdown();
}
}
输出:
old cnt: 0 new cnt : 1 result:true
old cnt: 1 new cnt : 2 result:true
old cnt: 2 new cnt : 3 result:true
old cnt: 3 new cnt : 4 result:true
old cnt: 4 new cnt : 5 result:true
final cnt : 5
如果在DistributedAtomicLong的构造方法参数中,RetryNTimes重试次数不够,比如是3,你会发现并不一定每次加数都会成功。显然这里是用了乐观锁机制,它并不保证操作一定成功(它在重试这么多次中都没有成功获得锁,导致操作没有执行),所以我们有必要通过调用 av.succeeded() 来查看此次加数是否成功。