项目场景:
使用ETCD服务,在Java中用springboot+jetcd实现分布式管理的服务的注册、发现和选主
代码展示:
直接上干货,添加MAVEN依赖,代码复制到自己的工程里面,配置下文件即可使用。
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.17.RELEASE</version>
</dependency>
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-core</artifactId>
<version>0.5.7</version>
</dependency>
package jetcd;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.Election;
import io.etcd.jetcd.KV;
import io.etcd.jetcd.Lease;
import io.etcd.jetcd.Lock;
import io.etcd.jetcd.Watch;
/**
* jectd 构建与etcd的操作客户端
* @author luzl
*
*/
@Component
@PropertySource(value= {"file:./config/jetcd.properties"},ignoreResourceNotFound=true)
public class JetcdOperatorClient {
@Value("${endpoints:http://127.0.0.1:2379}")
private String endpoints;
private Client client;
private KV kv;
private Lock lock;
private Lease lease;
private Watch watch;
private Election election;
private final String DOUHAO = ",";
@PostConstruct
private void init() {
client=Client.builder().endpoints(endpoints.split(DOUHAO)).build();
kv=client.getKVClient();
lock=client.getLockClient();
lease=client.getLeaseClient();
watch=client.getWatchClient();
election=client.getElectionClient();
}
@PreDestroy
private void destroy() {
election.close();
kv.close();
lease.close();
watch.close();
lock.close();
client.close();
}
public KV getKv() {
return kv;
}
public void setKv(KV kv) {
this.kv = kv;
}
public Lock getLock() {
return lock;
}
public void setLock(Lock lock) {
this.lock = lock;
}
public Lease getLease() {
return lease;
}
public void setLease(Lease lease) {
this.lease = lease;
}
public Watch getWatch() {
return watch;
}
public void setWatch(Watch watch) {
this.watch = watch;
}
public Election getElection() {
return election;
}
public void setElection(Election election) {
this.election = election;
}
}
package jetcd.registry;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Election;
import io.etcd.jetcd.KV;
import io.etcd.jetcd.KeyValue;
import io.etcd.jetcd.Lease;
import io.etcd.jetcd.Watch;
import io.etcd.jetcd.Watch.Watcher;
import io.etcd.jetcd.election.LeaderResponse;
import io.etcd.jetcd.kv.DeleteResponse;
import io.etcd.jetcd.kv.GetResponse;
import io.etcd.jetcd.lease.LeaseKeepAliveResponse;
import io.etcd.jetcd.options.DeleteOption;
import io.etcd.jetcd.options.GetOption;
import io.etcd.jetcd.options.PutOption;
import io.etcd.jetcd.options.WatchOption;
import io.etcd.jetcd.watch.WatchResponse;
import io.grpc.stub.StreamObserver;
import io.netty.util.internal.StringUtil;
import jetcd.JetcdOperatorClient;
/**
* 注册管理中心
* @author luzl
*
*/
@Service
public class EtcdRegistryCenter {
@Autowired
private JetcdOperatorClient jetcdOperatorClient;
@Value("${root:root}")
private String ETCD_REGISTRY_DIR;
@Value("${namespace:namespace}")
private String NAMESPACE;
@Value("${group:group}")
private String GROUP;
@Value("${server_name:serverName}")
private String SERVER_NAME;
public String SERVER_REGISTRY_PATH;
public String GROUP_REGISTRY_PATH;
private String XIE_XIAN="/";
@PostConstruct
private void init() {
GROUP_REGISTRY_PATH=ETCD_REGISTRY_DIR+XIE_XIAN+NAMESPACE+XIE_XIAN+GROUP+XIE_XIAN;
SERVER_REGISTRY_PATH=GROUP_REGISTRY_PATH+SERVER_NAME;
//服务注册
registerInstance(SERVER_REGISTRY_PATH, "127.0.0.1:2379", null);
//服务监听
watchInstance(GROUP_REGISTRY_PATH, onNext->{
onNext.getEvents().forEach(action->{
switch (action.getEventType()) {
case PUT:{
System.out.println("服务注册"+action.getKeyValue().getKey().toString(StandardCharsets.UTF_8));
}
break;
case DELETE:{
System.out.println("服务注销"+action.getKeyValue().getKey().toString(StandardCharsets.UTF_8));
}
break;
default:
break;
}
});
});
}
@PreDestroy
private void destroy() {
deregisterInstance(SERVER_REGISTRY_PATH);
}
/***
*
* @param serverNamePath 服务记录的KEY
* @param metadata 需要保留的元数据
* @param electionName 选举范围定义
*/
public void registerInstance(String serverNamePath,String metadata,String electionName) {
ByteSequence servreKey=ByteSequence.from(serverNamePath, StandardCharsets.UTF_8);
ByteSequence metadataValue=ByteSequence.from(metadata, StandardCharsets.UTF_8);
ByteSequence electionNameKey=ByteSequence.from(electionName, StandardCharsets.UTF_8);
Lease lease=jetcdOperatorClient.getLease();
long leaseId=0;
try {
//租约时间
leaseId = lease.grant(60,60,TimeUnit.SECONDS).get().getID();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
StreamObserver<LeaseKeepAliveResponse> observer=new StreamObserver<LeaseKeepAliveResponse>() {
@Override
public void onNext(LeaseKeepAliveResponse arg0) {
System.out.println("心跳");
}
@Override
public void onError(Throwable arg0) {
}
@Override
public void onCompleted() {
}
};
//续约
lease.keepAlive(leaseId, observer);
KV kv=jetcdOperatorClient.getKv();
//绑定租约
PutOption option=PutOption.newBuilder().withLeaseId(leaseId).build();
//注册
kv.put(servreKey, metadataValue, option);
//参与选举
if(!StringUtil.isNullOrEmpty(electionName)) {
Election election=jetcdOperatorClient.getElection();
election.campaign(electionNameKey, leaseId, servreKey);
}
}
/**
* 服务注销
* @param serverNamePath
*/
public void deregisterInstance(String serverNamePath) {
ByteSequence servreKey=ByteSequence.from(serverNamePath, StandardCharsets.UTF_8);
KV kv=jetcdOperatorClient.getKv();
Lease lease=jetcdOperatorClient.getLease();
DeleteOption option=DeleteOption.newBuilder().withPrevKV(true).build();
CompletableFuture<DeleteResponse> deleteFuture = kv.delete(servreKey, option);
try {
long leaseId= deleteFuture.get().getPrevKvs().get(0).getLease();
lease.revoke(leaseId);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
/**
* 获取分组下所有实例
* @param groupRegistryPath
* @return
*/
public List<KeyValue> getGroupAllInstance(String groupRegistryPath){
KV kv=jetcdOperatorClient.getKv();
ByteSequence groupKey=ByteSequence.from(groupRegistryPath, StandardCharsets.UTF_8);
GetOption option=GetOption.newBuilder().withPrefix(groupKey).build();
CompletableFuture<GetResponse> getfuture = kv.get(groupKey, option);
List<KeyValue> listInstance=new ArrayList<KeyValue>();
try {
listInstance= getfuture.get().getKvs();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return listInstance;
}
/**
* 获取注册实例
* @param serverPath
* @return
*/
public KeyValue getInstance(String serverPath) {
KV kv=jetcdOperatorClient.getKv();
ByteSequence servreKey=ByteSequence.from(serverPath, StandardCharsets.UTF_8);
CompletableFuture<GetResponse> getfuture = kv.get(servreKey);
KeyValue keyValue=null;
try {
List<KeyValue> listInstance= getfuture.get().getKvs();
keyValue=listInstance.size()>0? listInstance.get(0):null;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return keyValue;
}
/**
* 注册监听
* @param watchPath
* @param onNext
* @return
*/
public Watcher watchInstance(String watchPath,Consumer<WatchResponse> onNext) {
ByteSequence watchKey=ByteSequence.from(watchPath, StandardCharsets.UTF_8);
Watch watch=jetcdOperatorClient.getWatch();
WatchOption option=WatchOption.newBuilder().withPrefix(watchKey).build();
return watch.watch(watchKey, option, onNext);
}
/**
* 获取领导节点信息
* @param electionName
* @return
*/
public KeyValue getLeader(String electionName) {
ByteSequence electionKey=ByteSequence.from(electionName, StandardCharsets.UTF_8);
Election election=jetcdOperatorClient.getElection();
CompletableFuture<LeaderResponse> leadFuture = election.leader(electionKey);
KeyValue keyValue=null;
try {
keyValue=leadFuture.get().getKv();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return keyValue;
}
}
后续的发布到gitee:https://gitee.com/luzl1991/etcd-use?_from=gitee_search