package com.ctriposs.baiji.rpc.server.registry;
import com.ctriposs.etcd.CEtcdClient;
import com.ctriposs.etcd.CEtcdClientException;
import com.ctriposs.etcd.CEtcdResult;
import java.net.URI;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class EtcdServiceRegistry implements ServiceRegistry {
private static final int DEFAULT_HEARTBEAT_INTERVAL = 30 * 1000; // In milliseconds
private static final int HEARTBEAT_MISS_TOLERANCE = 3;
private final List<ServiceInfo> _services = new LinkedList<ServiceInfo>();
private final CEtcdClient _client;
private final Timer _heartbeatTimer = new Timer("EtcdServiceRegistry_Heartbeat", true);
private int _heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL;
private int _registrationTtl = _heartbeatInterval * HEARTBEAT_MISS_TOLERANCE / 1000;
private boolean _running;
public EtcdServiceRegistry(String serviceUrl) {
if (serviceUrl == null || serviceUrl.isEmpty()) {
throw new IllegalArgumentException("serviceUrl can't be null or empty.");
}
_client = new CEtcdClient(URI.create(serviceUrl));
}
@Override
public void addService(ServiceInfo serviceInfo) {
if (serviceInfo == null) {
throw new IllegalArgumentException("serviceInfo can't be null.");
}
_services.add(serviceInfo);
}
@Override
public int getHeartbeatInterval() {
return _heartbeatInterval;
}
@Override
public void setHeartbeatInterval(int interval) {
if (interval > 0) {
_heartbeatInterval = interval;
_registrationTtl = interval * HEARTBEAT_MISS_TOLERANCE / 1000;
}
}
@Override
public void run() {
if (_running) {
return;
}
_running = true;
_heartbeatTimer.scheduleAtFixedRate(new HeartbeatTask(), 0, _heartbeatInterval);
}
@Override
public void stop() {
if (!_running) {
return;
}
_running = false;
_heartbeatTimer.cancel();
}
private class HeartbeatTask extends TimerTask {
private static final String BASE_KEY = "/soa4j";
private static final String URL_KEY = "/url";
private static final String UP_KEY = "/up";
private final Map<ServiceInfo, Boolean> _registrationFlags = new ConcurrentHashMap<>();
private final Map<ServiceInfo, Boolean> _lastStatus = new ConcurrentHashMap<>();
@Override
public void run() {
List<ServiceInfo> services = new ArrayList<ServiceInfo>(_services);
for (ServiceInfo service : services) {
try {
Boolean registrationFlag = _registrationFlags.get(service);
if (!Boolean.TRUE.equals(registrationFlag) || service.isDirty()) {
if (register(service)) {
service.setDirty(false);
_registrationFlags.put(service, Boolean.TRUE);
}
} else {
if (!heartbeat(service)) {
register(service);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private boolean register(ServiceInfo service) {
try {
CEtcdResult result;
String serviceKey = getServiceKey(service);
result = _client.get(serviceKey);
if (result == null || result.node == null) {
try {
_client.createDir(serviceKey);
} catch (CEtcdClientException ex) {
ex.printStackTrace();
return false;
}
}
String instanceKey = getInstanceKey(service);
result = _client.get(instanceKey);
boolean nodeExisted =result != null && result.node != null;
_client.createDir(instanceKey, _registrationTtl, nodeExisted);
_client.set(instanceKey + URL_KEY, service.getServiceUrl());
Boolean status = getStatus(service);
_client.set(instanceKey + UP_KEY, String.valueOf(status));
_lastStatus.put(service, status);
for (Map.Entry<String, String> metadata : service.getMetadata().entrySet()) {
_client.set(metadata.getKey(), metadata.getValue());
}
return true;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
private boolean heartbeat(ServiceInfo service) throws CEtcdClientException {
try {
String instanceKey = getInstanceKey(service);
CEtcdResult result = _client.createDir(instanceKey, _registrationTtl, true);
if (result.isError()) {
return false;
}
Boolean status = getStatus(service);
Boolean lastStatus = _lastStatus.get(service);
if (!status.equals(lastStatus)) {
_client.set(instanceKey + UP_KEY, String.valueOf(status));
_lastStatus.put(service, status);
}
return true;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
private boolean getStatus(ServiceInfo service) {
boolean isHealthy = true;
try {
HealthCheckHandler handler = service.getHealthCheckHandler();
if (handler != null) {
isHealthy = handler.isHealthy();
}
} catch (Exception ex) {
ex.printStackTrace();
isHealthy = false;
}
return isHealthy;
}
private String getServiceKey(ServiceInfo service) {
String key = String.format(BASE_KEY + "/%s/%s",
normalizeKey(service.getServiceName()),
normalizeKey(service.getServiceNamespace()));
if (service.getSubEnv() != null && !service.getSubEnv().isEmpty()) {
key += "/" + normalizeKey(service.getSubEnv());
}
return key;
}
private String getInstanceKey(ServiceInfo service) {
return getServiceKey(service) + "/" + normalizeKey(service.getServiceUrl());
}
}
private static String normalizeKey(String key) {
return key.replace('/', '_');
}
}