ETCD 各种使用场景demo

 ETCD 设计文档

 概述

 ETCD 是什么?

Etcd是一个分布式的,一致的key-value存储,主要用于共享配置和服务发现。Etcd是由CoreOS开发并维护,通过Raft一致性算法处理日志复制以保证强一致性。Raft是一个来自Stanford的新的一致性算法,适用于分布式系统的日志复制,Raft通过选举的方式来实现一致性,在Raft中,任何一个节点都可能成为leader。Google的容器集群管理系统Kubernetes、开源PaaS平台Cloud Foundry和CoreOS的Fleet都广泛使用了etcd。

 ETCD 的特性?

在分布式系统中,如何管理节点间的状态一直是一个难题,etcd像是专门为集群环境的服务发现和注册而设计,它提供了数据TTL失效、数据改变监视、多值、目录监听、分布式锁原子操作等功能,可以方便的跟踪并管理集群节点的状态。Etcd的特性如下:

- 简单: curl可访问的用户的API(HTTP+JSON) v3客户端中可以使用grpc调用

- 安全:可选的SSL客户端证书认证

- 快速:单例实例每秒1000次写操作

- 可靠:使用Raft算法保证一致性

 使用场景

- 服务发现

- 消息发布与订阅

- 负载均衡

- 分布式通知与协调

- 分布式锁

- 分布式队列

- 集群监控与Leader竞选

 ETCD 实现分布式锁:第一种方式

 一、设计思路

 1.客户端连接ETCD,以/lock/XXX为前缀创建全局唯一的KEY,假设第一个获取锁的客户端创建的KEY为'KEY=/lock/XXX/UUID1',第二个客户端创建的KEY为'KEY=、lock/XXX/UUID2';分别为自己的KEY设置租约 ,设置租约为15s

 2.创建定时任务作为租约的'心跳',当第一个客户端持有锁的期间,其他客户端只能等待,为了避免等待期间租约失效,客户端需创建一个定时任务作为'心跳'进行续约。如果客户端在持有锁的期间奔溃,'心跳'停止key将因租约到期而被删除,从而释放锁,避免死锁

 3.客户端将自己全局唯一的key写入etcd,将步骤1中获取到的租约id绑定到key上, 根据etcd的Revision机制,假设两个客户端put操作返回的Revision分别为1、2,客户端需记录Revision用以下来判断自己是否获得锁

4.客户端判断是否获得锁 客户端以前缀/lock/XXX读取keyvalue列表(keyValue中带有key对应的Revision),判断自己的key的Revision是否为当前列表中最小,如果是则认为获得锁;否则监听列表中前一个Revision比自己小的key的删除事件,一旦监听到删除时间或因租约失效而删除的事件,则自己获得锁.

 5.获得锁后,操作共享资源,执行业务代码.

 6.完成业务流程后,删除对应的key释放锁.

 二、流程图

 三、demo代码

``` java

package com.jtzy.aisteel.etcd;

import java.util.List;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.TimeoutException;

import com.coreos.jetcd.Client;

import com.coreos.jetcd.KV;

import com.coreos.jetcd.Lease;

import com.coreos.jetcd.Watch.Watcher;

import com.coreos.jetcd.options.GetOption;

import com.coreos.jetcd.options.GetOption.SortTarget;

import com.coreos.jetcd.options.PutOption;

import com.coreos.jetcd.watch.WatchEvent;

import com.coreos.jetcd.watch.WatchResponse;

import com.coreos.jetcd.data.ByteSequence;

import com.coreos.jetcd.data.KeyValue;

import com.coreos.jetcd.kv.PutResponse;

import java.util.UUID;

/**

* EtcdLock

*

* @date 2020/3/17 14:14

* @Version 1.0

*/

public class EtcdLock {

    public static void main(String[] args) throws InterruptedException, ExecutionException,

            TimeoutException, ClassNotFoundException

    {

        // 创建Etcd客户端,Etcd服务端为单机模式

        Client client = EtcdUtil.getInstance();

        // 对于某共享资源制定的锁名

        String lockName = "/lock/mylock";

        // 模拟分布式场景下,多个进程“抢锁”

        for (int i = 0; i < 3; i++)

        {

            new MyThread(lockName, client).start();

        }

    }

    /**

    * 加锁方法,返回值为加锁操作中实际存储于Etcd中的key,即:lockName+UUID,

    * 根据返回的key,可删除存储于Etcd中的键值对,达到释放锁的目的。

    *

    * @param lockName

    * @param client

    * @param leaseId

    * @return

    */

    public static String lock(String lockName, Client client, long leaseId)

    {

        // lockName作为实际存储在Etcd的中的key的前缀,后缀是一个全局唯一的ID,从而确保:对于同一个锁,不同进程存储的key具有相同的前缀,不同的后缀

        StringBuffer strBufOfRealKey = new StringBuffer();

        strBufOfRealKey.append(lockName);

        strBufOfRealKey.append("/");

        strBufOfRealKey.append(UUID.randomUUID().toString());

        // 加锁操作实际上是一个put操作,每一次put操作都会使revision增加1,因此,对于任何一次操作,这都是唯一的。(get,delete也一样)

        // 可以通过revision的大小确定进行抢锁操作的时序,先进行抢锁的,revision较小,后面依次增加。

        // 用于记录自己“抢锁”的Revision,初始值为0L

        long revisionOfMyself = 0L;

        KV kvClient = client.getKVClient();

        // lock,尝试加锁,加锁只关注key,value不为空即可。

        // 注意:这里没有考虑可靠性和重试机制,实际应用中应考虑put操作而重试

        try

        {

            PutResponse putResponse = kvClient

                    .put(ByteSequence.fromString(strBufOfRealKey.toString()),

                            ByteSequence.fromString("value"),

                            PutOption.newBuilder().withLeaseId(leaseId).build())

                    .get(10, TimeUnit.SECONDS);

            // 获取自己加锁操作的Revision号

            revisionOfMyself = putResponse.getHeader().getRevision();

        }

        catch (InterruptedException | ExecutionException | TimeoutException e1)

        {

            System.out.println("[error]: lock operation failed:" + e1);

        }

        try

        {

            // lockName作为前缀,取出所有键值对,并且根据Revision进行升序排列,版本号小的在前

            List kvList = kvClient.get(ByteSequence.fromString(lockName),

                    GetOption.newBuilder().withPrefix(ByteSequence.fromString(lockName))

                            .withSortField(SortTarget.MOD).build())

                    .get().getKvs();

            // 如果自己的版本号最小,则表明自己持有锁成功,否则进入监听流程,等待锁释放

            if (revisionOfMyself == kvList.get(0).getModRevision())

            {

                System.out.println("[lock]: lock successfully. [ThreadName]"+ Thread.currentThread().getName()+" [revision]:" + revisionOfMyself);

                // 加锁成功,返回实际存储于Etcd中的key

                return strBufOfRealKey.toString();

            }

            else

            {

                // 记录自己加锁操作的前一个加锁操作的索引,因为只有前一个加锁操作完成并释放,自己才能获得锁

                int preIndex = 0;

                for (int index = 0; index < kvList.size(); index++)

                {

                    if (kvList.get(index).getModRevision() == revisionOfMyself)

                    {

                        preIndex = index - 1;// 前一个加锁操作,故比自己的索引小1

                    }

                }

                // 根据索引,获得前一个加锁操作对应的key

                ByteSequence preKeyBS = kvList.get(preIndex).getKey();

                // 创建一个Watcher,用于监听前一个key

                Watcher watcher = client.getWatchClient().watch(preKeyBS);

                WatchResponse res = null;

                // 监听前一个key,将处于阻塞状态,直到前一个key发生delete事件

                // 需要注意的是,一个key对应的事件不只有delete,不过,对于分布式锁来说,除了加锁就是释放锁

                // 因此,这里只要监听到事件,必然是delete事件或者key因租约过期而失效删除,结果都是锁被释放

                try

                {

                    System.out.println("[lock]: keep waiting until the lock is released.");

                    res = watcher.listen();

                }

                catch (InterruptedException e)

                {

                    System.out.println("[error]: failed to listen key.");

                }

                // 为了便于读者理解,此处写一点冗余代码,判断监听事件是否为DELETE,即释放锁

                List eventlist = res.getEvents();

                for (WatchEvent event : eventlist)

                {

                    // 如果监听到DELETE事件,说明前一个加锁操作完成并已经释放,自己获得锁,返回

                    if (event.getEventType().toString().equals("DELETE"))

                    {

                        System.out.println("[lock]: lock successfully. [ThreadName]"+ Thread.currentThread().getName()+"[revision]:" + revisionOfMyself);

                        return strBufOfRealKey.toString();

                    }

                }

            }

        }

        catch (InterruptedException | ExecutionException e)

        {

            System.out.println("[error]: lock operation failed:" + e);

        }

        return strBufOfRealKey.toString();

    }

    /**

    * 释放锁方法,本质上就是删除实际存储于Etcd中的key

    *

    * @param lockName

    * @param client

    */

    public static void unLock(String realLockName, Client client)

    {

        try

        {

            client.getKVClient().delete(ByteSequence.fromString(realLockName)).get(10,

                    TimeUnit.SECONDS);

            System.out.println("[unLock]: unlock successfully.[lockName]:" + realLockName);

        }

        catch (InterruptedException | ExecutionException | TimeoutException e)

        {

            System.out.println("[error]: unlock failed:" + e);

        }

    }

    /**

    * 自定义一个线程类,模拟分布式场景下多个进程 "抢锁"

    */

    public static class MyThread extends Thread

    {

        private String lockName;

        private Client client;

        MyThread(String lockName, Client client)

        {

            this.client = client;

            this.lockName = lockName;

        }

        @Override

        public void run()

        {

            // 创建一个租约,有效期15s

            Lease leaseClient = client.getLeaseClient();

            Long leaseId = null;

            try

            {

                leaseId = leaseClient.grant(15).get(10, TimeUnit.SECONDS).getID();

            }

            catch (InterruptedException | ExecutionException | TimeoutException e1)

            {

                System.out.println("[error]: create lease failed:" + e1);

                return;

            }

            // 创建一个定时任务作为“心跳”,保证等待锁释放期间,租约不失效;

            // 同时,一旦客户端发生故障,心跳便会中断,锁也会应租约过期而被动释放,避免死锁

            ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

            // 续约心跳为12s,仅作为举例

            service.scheduleAtFixedRate(new KeepAliveTask(leaseClient, leaseId), 1, 12, TimeUnit.SECONDS);

            // 1. try to lock

            String realLoclName = lock(lockName, client, leaseId);

            // 2. to do something

            try

            {

                Thread.sleep(6000);

            }

            catch (InterruptedException e2)

            {

                System.out.println("[error]:" + e2);

            }

            // 3. unlock

            service.shutdown();// 关闭续约的定时任务

            unLock(realLoclName, client);

        }

    }

    /**

    * 在等待其它客户端释放锁期间,通过心跳续约,保证自己的key-value不会失效

    *

    */

    public static class KeepAliveTask implements Runnable

    {

        private Lease leaseClient;

        private long leaseId;

        KeepAliveTask(Lease leaseClient, long leaseId)

        {

            this.leaseClient = leaseClient;

            this.leaseId = leaseId;

        }

        @Override

        public void run()

        {

            leaseClient.keepAliveOnce(leaseId);

        }

    }

}

```

ETCD 实现分布式锁的:第二种方式

 一、设计思路

 1.创建jetcd客户端 进行获取lease客户端获取一个15s的租约的id 并且创建一个定时任务作为心跳定时给id心跳以保障key的存货 并且当程序遇到问题时也不会死锁(通过一个公式进行心跳时间的控制 TTL-TTL/5)

 2.获取lock客户端进行加锁,此时如果客户端未获得锁会自动阻塞,直到获得锁。

 3.执行业务代码操作完成后释放锁

 二、流程图


 三、demo代码

``` java

package com.jtzy.aisteel.etcd;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;

import com.coreos.jetcd.Client;

import com.coreos.jetcd.Lease;

import com.coreos.jetcd.Lock;

import com.coreos.jetcd.data.ByteSequence;

public class DistributedLock

{

    private static DistributedLock lockProvider = null;

    private static Object mutex = new Object();

    private Client client;

    private Lock lockClient;

    private Lease leaseClient;

    private DistributedLock()

    {

        super();

        // 创建Etcd客户端,本例中Etcd集群只有一个节点

        this.client = EtcdUtil.getInstance();

        this.lockClient = client.getLockClient();

        this.leaseClient = client.getLeaseClient();

    }

    public static DistributedLock getInstance()

    {

        synchronized (mutex)

        {

            if (null == lockProvider)

            {

                lockProvider = new DistributedLock();

            }

        }

        return lockProvider;

    }

    /**

    * 加锁操作,需要注意的是,本例中没有加入重试机制,加锁失败将直接返回。

    *

    * @param lockName: 针对某一共享资源(数据、文件等)制定的锁名

    * @param TTL : Time To Live,租约有效期,一旦客户端崩溃,可在租约到期后自动释放锁

    * @return LockResult

    */

    public LockResult lock(String lockName, long TTL)

    {

        LockResult lockResult = new LockResult();

        /*1.准备阶段*/

        // 创建一个定时任务作为“心跳”,保证等待锁释放期间,租约不失效;

        // 同时,一旦客户端发生故障,心跳便会停止,锁也会因租约过期而被动释放,避免死锁

        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

        // 初始化返回值lockResult

        lockResult.setIsLockSuccess(false);

        lockResult.setService(service);

        // 记录租约ID,初始值设为 0L

        Long leaseId = 0L;

        /*2.创建租约*/

        // 创建一个租约,租约有效期为TTL,实际应用中根据具体业务确定。

        try

        {

            leaseId = leaseClient.grant(TTL).get().getID();

            lockResult.setLeaseId(leaseId);

            // 启动定时任务续约,心跳周期和初次启动延时计算公式如下,可根据实际业务制定。

            long period = TTL - TTL / 5;

            service.scheduleAtFixedRate(new KeepAliveTask(leaseClient, leaseId), period, period,

                    TimeUnit.SECONDS);

        }

        catch (InterruptedException | ExecutionException e)

        {

            System.out.println("[error]: Create lease failed:" + e);

            return lockResult;

        }

        System.out.println("[lock]: start to lock." + Thread.currentThread().getName());

        /*3.加锁操作*/

        // 执行加锁操作,并为锁对应的key绑定租约

        try

        {

            lockClient.lock(ByteSequence.fromString(lockName), leaseId).get();

        }

        catch (InterruptedException | ExecutionException e1)

        {

            System.out.println("[error]: lock failed:" + e1);

            return lockResult;

        }

        System.out.println("[lock]: lock successfully." + Thread.currentThread().getName());

        lockResult.setIsLockSuccess(true);

        return lockResult;

    }

    /**

    * 解锁操作,释放锁、关闭定时任务、解除租约

    *

    * @param lockName:锁名

    * @param lockResult:加锁操作返回的结果

    */

    public void unLock(String lockName, LockResult lockResult)

    {

        System.out.println("[unlock]: start to unlock." + Thread.currentThread().getName());

        try

        {

            // 释放锁 

            lockClient.unlock(ByteSequence.fromString(lockName)).get();

            // 关闭定时任务

            lockResult.getService().shutdown();

            // 删除租约

            if (lockResult.getLeaseId() != 0L)

            {

                leaseClient.revoke(lockResult.getLeaseId());

            }

        }

        catch (InterruptedException | ExecutionException e)

        {

            System.out.println("[error]: unlock failed: " + e);

        }

        System.out.println("[unlock]: unlock successfully." + Thread.currentThread().getName());

    }

    /**

    * 在等待其它客户端释放锁期间,通过心跳续约,保证自己的锁对应租约不会失效

    *

    */

    public static class KeepAliveTask implements Runnable

    {

        private Lease leaseClient;

        private long leaseId;

        KeepAliveTask(Lease leaseClient, long leaseId)

        {

            this.leaseClient = leaseClient;

            this.leaseId = leaseId;

        }

        @Override

        public void run()

        {

            // 续约一次

            leaseClient.keepAliveOnce(leaseId);

        }

    }

    /**

    * 该class用于描述加锁的结果,同时携带解锁操作所需参数

    *

    */

    public static class LockResult

    {

        private boolean isLockSuccess;

        private long leaseId;

        private ScheduledExecutorService service;

        LockResult()

        {

            super();

        }

        public void setIsLockSuccess(boolean isLockSuccess)

        {

            this.isLockSuccess = isLockSuccess;

        }

        public void setLeaseId(long leaseId)

        {

            this.leaseId = leaseId;

        }

        public void setService(ScheduledExecutorService service)

        {

            this.service = service;

        }

        public boolean getIsLockSuccess()

        {

            return this.isLockSuccess;

        }

        public long getLeaseId()

        {

            return this.leaseId;

        }

        public ScheduledExecutorService getService()

        {

            return this.service;

        }

    }

}

```

 etcd 服务注册发现设计

一、设计思路

1.向etcd 将/service/coreb/服务名称+uuid 作为key键 ip+port作为value进行,为了保证服务可以使用 创建lease 30s 的id 启动定时任务向etcd 进行心跳保持 保证服务没有崩溃

 2.获取向etcd 进行注册的任务的前缀进行塞选服务 进行转发 比如获取coreb 的服务 则以/service/coreb为前缀进行获取服务注册的列表 进行随机筛选 给予value 进行请求转发

 二、流程图


三、demo代码

- 注册端

``` java

package com.jtzy.aisteel.etcd;

import com.coreos.jetcd.Client;

import com.coreos.jetcd.Lease;

import com.coreos.jetcd.data.ByteSequence;

import com.coreos.jetcd.options.PutOption;

import java.net.InetAddress;

import java.net.UnknownHostException;

import java.util.UUID;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;

/**

* EtcdProxy

*

* @date 2020/3/11 14:31

* @Version 1.0

*/

public class EtcdProxy {

    /**

    * 服务名称

    */

    private String serviceName;

    /**

    * 端口号

    */

    private String port;

    /**

    * 本地ip地址

    */

    private String ipAddress;

    /**

    * etcd客户端

    */

    private Client etcdClient;

    /**

    * etcdlease 客户端

    */

    private Lease leaseClient;

    /**

    * 服务类型

    */

    private String classification;

    /**

    * 全局leaseId

    */

    private long leaseId;

    /**

    * 超时时间

    */

    private long ttl;

    /**

    * 全局服务唯一标识

    */

    private String uuid;

    public EtcdProxy() {

        try {

            this.etcdClient = EtcdUtil.getInstance();

            this.serviceName = "bums-etcd";

            this.ttl = 30L;

            this.port = "20000";

            this.classification = "coreb";

            this.uuid = UUID.randomUUID().toString();

            this.ipAddress = InetAddress.getLocalHost().getHostAddress();

        } catch (UnknownHostException e) {

            e.printStackTrace();

            System.out.println("获取本机ip失败");

        }

    }

    private void getTtl() {

        //获取lease客户端

        this.leaseClient = etcdClient.getLeaseClient();

        try {

            //进行超时时间设置

            this.leaseId = leaseClient.grant(ttl).get().getID();

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }

    public void regist() {

        //先调用获取lease id

        getTtl();

        try {

            //put服务相关信息到etcd

            etcdClient.getKVClient().put(ByteSequence.fromString(String.format("/service/%s/%s", classification, serviceName + uuid)), ByteSequence.fromString(ipAddress + ":" + port),

                    PutOption.newBuilder().withPrevKV().withLeaseId(leaseId).build()).get();

            System.out.println(String.format("注册etcd成功!服务名称:%s 端口号:%s ip地址:%s", serviceName, port, ipAddress));

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

        //创建线程池 保持心跳

        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

        long delay=ttl-ttl/5;

        service.scheduleWithFixedDelay(new GuardeEtcd(), delay, delay, TimeUnit.SECONDS);

    }

    private class GuardeEtcd implements Runnable {

        @Override

        public void run() {

            leaseClient.keepAliveOnce(leaseId);

        }

    }

}

```

- 服务端

``` java

package com.jtzy.aisteel.etcd;

import com.coreos.jetcd.KV;

import com.coreos.jetcd.data.ByteSequence;

import com.coreos.jetcd.data.KeyValue;

import com.coreos.jetcd.kv.GetResponse;

import com.coreos.jetcd.options.GetOption;

import java.util.List;

import java.util.Random;

import java.util.concurrent.CompletableFuture;

import java.util.concurrent.ExecutionException;

import java.util.stream.IntStream;

/**

* EtcdService

*

* @date 2020/3/18 15:36

* @Version 1.0

*/

public class EtcdService {

    /**

    * etcd kv客户端

    */

    private KV kvClient;

    /**

    * 服务目录前缀

    */

    private String serviceDir;

    public EtcdService() {

        this.kvClient = EtcdUtil.getInstance().getKVClient();

        this.serviceDir="/service/";

    }

    /**

    * 获取服务

    * @date 2020/3/18 15:49

    */

    public String getServiceIP(String classification) throws EtcdException {

        String prefix= serviceDir+classification;

        CompletableFuture getResponseCompletableFuture = kvClient.get(ByteSequence.fromString(prefix), GetOption.newBuilder().withPrefix(ByteSequence.fromString(prefix)).build());

        try {

            GetResponse getResponse = getResponseCompletableFuture.get();

            List kvs = getResponse.getKvs();

            int randomService = getRandomService(kvs.size());

            KeyValue keyValue = kvs.get(randomService);

            return keyValue.getValue().toStringUtf8();

        } catch (Exception e) {

            throw new EtcdException("获取服务出错!可能服务未注册成功,请检查!");

        }

    }

    /**

    * 随机选择服务

    * @date 2020/3/18 15:49

    */

    private int getRandomService(int num){

        if(num<=1){

            return 0;

        }

        Random random = new Random();

        return random.nextInt(num);

    }

    /**

    * 获取当前注册中心注册的所有服务

    * @date 2020/3/18 15:53

    */

    public List getAllService() throws EtcdException {

        CompletableFuture getResponseCompletableFuture = kvClient.get(ByteSequence.fromString(serviceDir), GetOption.newBuilder().withPrefix(ByteSequence.fromString(serviceDir)).build());

        try {

            return getResponseCompletableFuture.get().getKvs();

        } catch (Exception e) {

            throw new EtcdException("获取服务出错");

        }

    }

}

```

etcd 发布订阅模式和队列模式

一、设计思路

 1.发布订阅模式中因watch 只能 帮助阻塞监听一次所以 为了可以持续监听 需要启用一个线程服务去执行死阻塞操作 持续监听此键的变化并且不监听删除 并且 广播一次后不再保存

2.队列模式 队列模式中 获取全部key键消费后即删除 保证etcd中不存在重复数据 相当于 redis 中的list pop之后 即删除

二、流程图

- 普通队列流程图

- 发布订阅流程图


 三、demo 代码

- 普通队列、发布订阅客户端方法

```java

package com.jtzy.aisteel.etcd;

import com.coreos.jetcd.Client;

import com.coreos.jetcd.data.ByteSequence;

import com.coreos.jetcd.kv.PutResponse;

import java.util.UUID;

import java.util.concurrent.ExecutionException;

/**

* EtcdQueue

*

* @date 2020/3/12 15:06

* @Version 1.0

*/

public class EtcdQueue {

    /**

    * etcd客户端

    */

    private Client etcdclient;

    public EtcdQueue() {

        this.etcdclient = EtcdUtil.getInstance();

    }

    /**

    * 发布订阅

    * @date 2020/3/18 17:15

    */

    public long sendRelease(String queueName,String value) throws EtcdQueueException {

        try {

            PutResponse putResponse = etcdclient.getKVClient().put(ByteSequence.fromString(queueName), ByteSequence.fromString(value)).get();

            return 1;

        } catch (InterruptedException |ExecutionException e) {

            throw new EtcdQueueException("发布订阅失败!");

        }

    }

    /**

    * 发送普通队列

    * @date 2020/3/18 17:19

    */

    public long sendQueue(String queueDir,String value) throws EtcdQueueException {

        PutResponse putResponse = null;

        try {

            putResponse = etcdclient.getKVClient().put(ByteSequence.fromString(queueDir + UUID.randomUUID().toString()), ByteSequence.fromString(value)).get();

            return putResponse.getPrevKv().getCreateRevision();

        } catch (InterruptedException | ExecutionException e) {

            throw new EtcdQueueException("队列发送失败!");

        }

    }

}

```

- 普通队列、发布订阅消费端

``` java

package com.jtzy.aisteel.etcd;

import com.coreos.jetcd.KV;

import com.coreos.jetcd.Watch;

import com.coreos.jetcd.data.ByteSequence;

import com.coreos.jetcd.data.KeyValue;

import com.coreos.jetcd.kv.GetResponse;

import com.coreos.jetcd.options.GetOption;

import com.coreos.jetcd.options.WatchOption;

import com.coreos.jetcd.watch.WatchEvent;

import java.util.List;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorCompletionService;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

/**

* EtcdConsumption

*

* @date 2020/3/18 17:21

* @Version 1.0

*/

public class EtcdConsumption {

    /**

    * kv客户端

    */

    private KV kvClient;

    /**

    * wacht客户端

    */

    private Watch watchClient;

    public EtcdConsumption() {

        this.kvClient = EtcdUtil.getInstance().getKVClient();

        this.watchClient = EtcdUtil.getInstance().getWatchClient();

    }

    /**

    * 获取订阅信息

    *

    * @date 2020/3/19 14:35

    */

    public void subscription(String queueName, EtcdSubscription etcdSubscription) throws EtcdQueueException, ExecutionException, InterruptedException {

        Watch.Watcher watch = watchClient.watch(ByteSequence.fromString(queueName),WatchOption.newBuilder().withNoDelete(true).build());

        ExecutorService executorService=Executors.newCachedThreadPool();

        //制造阻塞效果

        executorService.submit(new wacth(watch,etcdSubscription)).get();

    }

    /**

    * 轮训队列中的消息 使用后销毁

    * @date 2020/3/19 15:37

    */

    public void getQueue(String dirPrefix,EtcdQueueCom etcdQueueCom) throws ExecutionException, InterruptedException {

        ExecutorService executorService=Executors.newCachedThreadPool();

        //制造阻塞效果

        executorService.submit(new queue(dirPrefix,etcdQueueCom)).get();

    }

    //普通队列消费服务

    private class queue implements Runnable{

        private String dirPreFix;

        private EtcdQueueCom etcdQueueCom;

        public queue(String dirPreFix, EtcdQueueCom etcdQueueCom) {

            this.dirPreFix = dirPreFix;

            this.etcdQueueCom = etcdQueueCom;

        }

        @Override

        public void run() {

            while (true){

                GetResponse getResponse = null;

                try {

                    getResponse = kvClient.get(ByteSequence.fromString(dirPreFix), GetOption.newBuilder().withPrefix(ByteSequence.fromString(dirPreFix)).withSortField(GetOption.SortTarget.MOD).build()).get();

                    List kvs = getResponse.getKvs();

                    for (KeyValue kv : kvs) {

                        etcdQueueCom.getQueue(kv.getValue().toStringUtf8());

                        kvClient.delete(kv.getKey());

                    }

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                } catch (ExecutionException e) {

                    e.printStackTrace();

                }

            }

        }

    }

    /**

    * 单起线程进行监听

    * @date 2020/3/19 15:28

    */

    private class wacth implements Runnable{

        private Watch.Watcher watcher;

        private EtcdSubscription etcdSubscription;

        public wacth(Watch.Watcher watcher, EtcdSubscription etcdSubscription) {

            this.watcher = watcher;

            this.etcdSubscription=etcdSubscription;

        }

        @Override

        public void run() {

            while (true){

                System.out.println("轮询");

                try {

                    WatchEvent watchEvent = watcher.listen().getEvents().get(0);

                    etcdSubscription.getsubscription(watchEvent.getKeyValue().getValue().toStringUtf8());

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }

    }

}

```

- 普通队列实现接口

``` java

package com.jtzy.aisteel.etcd;

/**

* EtcdQueueCom

*

* @date 2020/3/19 15:37

* @Version 1.0

*/

public interface EtcdQueueCom {

    public void getQueue(String value);

}

```

- 发布订阅实现接口

``` java

package com.jtzy.aisteel.etcd;

/**

* EtcdSubscription

*

* @date 2020/3/19 14:46

* @Version 1.0

*/

public interface EtcdSubscription {

    public void getsubscription(String value);

}

```

你可能感兴趣的:(ETCD 各种使用场景demo)