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
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
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
try {
GetResponse getResponse = getResponseCompletableFuture.get();
List
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
CompletableFuture
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
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);
}
```