【推荐】zookeeper典型应用场景: 分布式计数器

一、技术介绍

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() 来查看此次加数是否成功。
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值