ETCD是一种高度一致的分布式键值(KV)存储,值接口仅有字符串类型。它提供了一种可靠的方式来存储需要由分布式系统或机器集群访问的数据。它在网络分区期间优雅地处理领导者选举,并且可以容忍机器故障,即使在主节点中也是如此。
ETCD3单节点默认使用2379端口提供HTTP API服务,多节点默认使用2380端口进行节点间的通信。ETCD在启动时采用raft算法,实现分布式系统数据的可用性和一致性。进行Leader的选举。
引入jar包
<dependency>
<groupId>com.ibm.etcd</groupId>
<artifactId>etcd-java</artifactId>
<version>0.0.18</version>
</dependency>
etcd接口
import java.util.List;
public interface IEtcdClient {
/**
* 存入key,value
*/
void put(String key, String value);
/**
* 存入key、value,和租约id
*/
void put(String key, String value, long leaseId);
/**
* 存入key、value,和过期时间,单位是秒
*/
void putAndGrant(String key, String value, long ttl);
/**
* 删除key
*/
void delete(String key);
/**
* 根据key,获取value
*/
String get(String key);
/**
* 获取指定前缀的所有key-value
*/
List<KeyValue> getPrefix(String key);
/**
* 监听key
*/
KvClient.WatchIterator watch(String key);
/**
* 监听前缀为key的
*/
KvClient.WatchIterator watchPrefix(String key);
/**
* 自动续约
* @param frequencySecs 续约频率,最小是4秒,默认是5秒
* @param minTtl 最小存活时间,最小是2秒,默认是10秒
* @return 返回leaseId
*/
long keepAlive(String key, String value, int frequencySecs, int minTtl) throws Exception;
/**
* 判断剩余的过期时间
*/
long timeToLive(long leaseId);
/**
* 获取分布式锁
*/
void lock(String key);
/**
* 锁释放
*/
void unlock(String key);
}
etcd操作类
package com.etcdtest.demo.service;
import com.google.protobuf.ByteString;
import com.ibm.etcd.api.*;
import com.ibm.etcd.client.EtcdClient;
import com.ibm.etcd.client.KvStoreClient;
import com.ibm.etcd.client.kv.KvClient;
import com.ibm.etcd.client.lease.LeaseClient;
import com.ibm.etcd.client.lease.PersistentLease;
import com.ibm.etcd.client.lock.LockClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* etcd操作类
*/
@Service
public class EtcdClientManager implements IEtcdClient {
/**
* 节点配置的值
*/
private String endPoints = "http://172.20.35.185";
//初始化连接
private KvStoreClient kvStoreClient;
//对数据操作的连接
private KvClient kvClient;
//对租期等时间操作的连接
private LeaseClient leaseClient;
//对分布式锁操作的连接
private LockClient lockClient;
/**
* 在Servlet容器初始化前执行
*/
@PostConstruct
private void init() {
kvStoreClient = EtcdClient.forEndpoints(endPoints).withPlainText().build();
this.kvClient = kvStoreClient.getKvClient();
this.leaseClient = kvStoreClient.getLeaseClient();
this.lockClient = kvStoreClient.getLockClient();
}
/**
* 添加操作,永不过期
* 形式,同步
*
* @param key
* @param value
*/
@Override
public void put(String key, String value) {
kvClient.put(ByteString.copyFromUtf8(key), ByteString.copyFromUtf8(value)).sync();
}
/**
* 添加操作,设置租期ID
*
* @param key
* @param value
* @param leaseId
*/
@Override
public void put(String key, String value, long leaseId) {
kvClient.put(ByteString.copyFromUtf8(key), ByteString.copyFromUtf8(value), leaseId).sync();
}
@Override
public void putAndGrant(String key, String value, long ttl) {
LeaseGrantResponse lease = leaseClient.grant(ttl).sync();
put(key, value, lease.getID());
}
@Override
public void delete(String key) {
kvClient.delete(ByteString.copyFromUtf8(key)).sync();
}
@Override
public String get(String key) {
RangeResponse rangeResponse = kvClient.get(ByteString.copyFromUtf8(key)).sync();
List<KeyValue> keyValues = rangeResponse.getKvsList();
if (CollectionUtils.isEmpty(keyValues)) {
return null;
}
return keyValues.get(0).getValue().toStringUtf8();
}
@Override
public List<KeyValue> getPrefix(String key) {
RangeResponse rangeResponse = kvClient.get(ByteString.copyFromUtf8(key)).asPrefix().sync();
return rangeResponse.getKvsList();
}
@Override
public KvClient.WatchIterator watch(String key) {
return kvClient.watch(ByteString.copyFromUtf8(key)).start();
}
@Override
public KvClient.WatchIterator watchPrefix(String key) {
return kvClient.watch(ByteString.copyFromUtf8(key)).asPrefix().start();
}
@Override
public long keepAlive(String key, String value, int frequencySecs, int minTtl) throws Exception {
//minTtl秒租期,每frequencySecs秒续约一下
PersistentLease lease = leaseClient.maintain().leaseId(System.currentTimeMillis()).keepAliveFreq(frequencySecs).minTtl(minTtl).start();
long newId = lease.get(3L, SECONDS);
put(key, value, newId);
return newId;
}
@Override
public long timeToLive(long leaseId) {
try {
return leaseClient.ttl(leaseId).get().getTTL();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return 0L;
}
}
@Override
public void lock(String key) {
lockClient.lock(ByteString.copyFromUtf8(key));
}
@Override
public void unlock(String key) {
lockClient.unlock(ByteString.copyFromUtf8(key));
}
@PreDestroy
public void close() {
try {
kvStoreClient.close();
System.out.println("close");
} catch (IOException e) {
e.printStackTrace();
}
}
}
示例代码
/**
* 示例代码
*/
@RestController
public class TestEtcd {
@Resource
private IEtcdClient etcdClient;
/**
* 存
*/
@RequestMapping("/put/{key}/{value}")
public String put(@PathVariable String key, @PathVariable String value) {
String setKey = key;
String setValue = value;
etcdClient.put(setKey, setValue);
return "success";
}
/**
* 取数据
*
* @return
*/
@RequestMapping("/get/{key}")
public String get(@PathVariable String key) {
String getValue = etcdClient.get(key);
System.out.println(getValue);
return getValue;
}
/**
* 设置key过期时间
*/
@RequestMapping("/put/{key}/{value}/{ttl}")
public String putSetTtl(@PathVariable String key, @PathVariable String value, @PathVariable Long ttl) {
String setKey = key;
String setValue = value;
Long setTtl = ttl;
etcdClient.putAndGrant(setKey, setValue, ttl);
return "success";
}
/**
* 前缀匹配
*
* @return
*/
@RequestMapping("test/get/{key}")
public String getPre(@PathVariable String key) {
List<KeyValue> prefix = etcdClient.getPrefix(key);
return "success";
}