Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.redkalex.cluster.consul.ConsulClusterAgent Maven / Gradle / Ivy
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.redkalex.cluster.consul;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.net.*;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.redkale.annotation.ResourceChanged;
import org.redkale.boot.*;
import org.redkale.cluster.spi.ClusterAgent;
import org.redkale.convert.json.JsonConvert;
import org.redkale.inject.ResourceEvent;
import org.redkale.service.Service;
import org.redkale.util.*;
/**
*
*
*
*
*
* <cluster type="consul" apiurl="http://localhost:8500/v1" ttls="10">
* </cluster>
*
*
*
*
* @author zhangjx
*/
public class ConsulClusterAgent extends ClusterAgent {
protected static final Map httpHeaders =
(Map) Utility.ofMap("Content-Type", "application/json", "Accept", "application/json");
protected static final Type MAP_STRING_ADDRESSENTRY = new TypeToken>() {}.getType();
protected static final Type MAP_STRING_SERVICEENTRY = new TypeToken>() {}.getType();
protected String apiUrl; // 不会以/结尾
protected HttpClient httpClient; // JDK11里面的HttpClient
protected int ttls = 10; // 定时检查的秒数
protected ScheduledThreadPoolExecutor scheduler;
protected ScheduledFuture taskFuture;
// 可能被HttpMessageClient用到的服务 key: serviceName
protected final ConcurrentHashMap> httpAddressMap = new ConcurrentHashMap<>();
// 可能被sncp用到的服务 key: serviceName
protected final ConcurrentHashMap> sncpAddressMap = new ConcurrentHashMap<>();
@Override
public void init(AnyValue config) {
super.init(config);
this.apiUrl = config.getValue("apiurl");
if (this.apiUrl.endsWith("/")) {
this.apiUrl = this.apiUrl.substring(0, this.apiUrl.length() - 1);
}
this.ttls = config.getIntValue("ttls", 10);
if (this.ttls < 5) {
this.ttls = 10;
}
this.httpClient = HttpClient.newHttpClient();
}
@Override
@ResourceChanged
public void onResourceChange(ResourceEvent[] events) {
StringBuilder sb = new StringBuilder();
int newTtls = this.ttls;
for (ResourceEvent event : events) {
if ("ttls".equals(event.name())) {
newTtls = Integer.parseInt(event.newValue().toString());
if (newTtls < 5) {
sb.append(ConsulClusterAgent.class.getSimpleName())
.append(" cannot change '")
.append(event.name())
.append("' to '")
.append(event.coverNewValue())
.append("'\r\n");
} else {
sb.append(ConsulClusterAgent.class.getSimpleName())
.append(" change '")
.append(event.name())
.append("' to '")
.append(event.coverNewValue())
.append("'\r\n");
}
} else {
sb.append(ConsulClusterAgent.class.getSimpleName())
.append(" skip change '")
.append(event.name())
.append("' to '")
.append(event.coverNewValue())
.append("'\r\n");
}
}
if (newTtls != this.ttls) {
this.ttls = newTtls;
start();
}
if (sb.length() > 0) {
logger.log(Level.INFO, sb.toString());
}
}
@Override
public void destroy(AnyValue config) {
if (scheduler != null) {
scheduler.shutdownNow();
}
}
@Override // ServiceLoader时判断配置是否符合当前实现类
public boolean acceptsConf(AnyValue config) {
if (config == null) {
return false;
}
if (!"consul".equalsIgnoreCase(config.getValue("type"))) {
return false;
}
return config.getValue("apiurl") != null;
}
@Override
public void start() {
if (this.scheduler == null) {
AtomicInteger counter = new AtomicInteger();
this.scheduler = new ScheduledThreadPoolExecutor(4, (Runnable r) -> {
final Thread t = new Thread(
r,
"Redkalex-" + ConsulClusterAgent.class.getSimpleName() + "-Task-Thread-"
+ counter.incrementAndGet());
t.setDaemon(true);
return t;
});
}
// delay为了错开请求
if (this.taskFuture != null) {
this.taskFuture.cancel(true);
}
this.taskFuture = this.scheduler.scheduleAtFixedRate(
() -> {
beatApplicationHealth();
localEntrys.values().stream().filter(e -> !e.canceled).forEach(entry -> {
beatLocalHealth(entry);
});
reloadSncpAddressHealth();
reloadHttpAddressHealth();
remoteEntrys.values().stream()
.filter(entry -> "SNCP".equalsIgnoreCase(entry.protocol))
.forEach(entry -> {
updateSncpAddress(entry);
});
},
18,
Math.max(2000, ttls * 1000 - 168),
TimeUnit.MILLISECONDS);
}
protected void reloadSncpAddressHealth() {
try {
String content = Utility.remoteHttpContent(
httpClient, "GET", this.apiUrl + "/agent/services", StandardCharsets.UTF_8, httpHeaders);
final Map map = JsonConvert.root().convertFrom(MAP_STRING_SERVICEENTRY, content);
Set sncpKeys = new HashSet<>();
map.forEach((key, en) -> {
if (en.Service.startsWith("sncp:")) {
sncpKeys.add(en.Service);
}
});
sncpKeys.forEach(serviceName -> {
try {
this.sncpAddressMap.put(
serviceName, queryAddress(serviceName).get(Math.max(2, ttls / 2), TimeUnit.SECONDS));
} catch (Exception e) {
logger.log(Level.SEVERE, "reloadSncpAddressHealth check " + serviceName + " error", e);
}
});
} catch (Exception ex) {
logger.log(Level.SEVERE, "reloadSncpAddressHealth check error", ex);
}
}
protected void reloadHttpAddressHealth() {
try {
this.httpAddressMap.keySet().stream().forEach(serviceName -> {
try {
this.httpAddressMap.put(
serviceName, queryAddress(serviceName).get(Math.max(2, ttls / 2), TimeUnit.SECONDS));
} catch (Exception e) {
logger.log(Level.SEVERE, "reloadHttpAddressHealth check " + serviceName + " error", e);
}
});
} catch (Exception ex) {
logger.log(Level.SEVERE, "reloadHttpAddressHealth check error", ex);
}
}
protected void beatLocalHealth(final ClusterEntry entry) {
String url = this.apiUrl + "/agent/check/pass/" + entry.checkId;
try {
String rs = Utility.remoteHttpContent(httpClient, "PUT", url, StandardCharsets.UTF_8, httpHeaders);
if (!rs.isEmpty()) {
logger.log(Level.SEVERE, entry.checkId + " check error: " + rs);
}
} catch (Exception ex) {
logger.log(Level.SEVERE, entry.checkId + " check error: " + url, ex);
}
}
@Override // 获取SNCP远程服务的可用ip列表
public CompletableFuture> querySncpAddress(String protocol, String module, String resname) {
final String serviceName = generateSncpServiceName(protocol, module, resname);
Set rs = sncpAddressMap.get(serviceName);
if (rs != null) {
return CompletableFuture.completedFuture(rs);
}
return queryAddress(serviceName).thenApply(t -> {
sncpAddressMap.put(serviceName, t);
return t;
});
}
@Override // 获取HTTP远程服务的可用ip列表
public CompletableFuture> queryHttpAddress(String protocol, String module, String resname) {
final String serviceName = generateHttpServiceName(protocol, module, resname);
Set rs = httpAddressMap.get(serviceName);
if (rs != null) {
return CompletableFuture.completedFuture(rs);
}
return queryAddress(serviceName).thenApply(t -> {
httpAddressMap.put(serviceName, t);
return t;
});
}
@Override
protected CompletableFuture> queryAddress(final ClusterEntry entry) {
return queryAddress(entry.serviceName);
}
private CompletableFuture> queryAddress(final String serviceName) {
final HttpClient client = httpClient;
String url = this.apiUrl + "/agent/services?filter="
+ URLEncoder.encode("Service==\"" + serviceName + "\"", StandardCharsets.UTF_8);
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url))
.expectContinue(true)
.timeout(Duration.ofMillis(6000));
httpHeaders.forEach((n, v) -> {
if (v instanceof Collection) {
for (Object val : (Collection) v) {
builder.header(n, val.toString());
}
} else if (v != null) {
builder.header(n, v.toString());
}
});
final Set set = new CopyOnWriteArraySet<>();
return client.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
.thenApply(HttpResponse::body)
.thenCompose(content -> {
final Map map =
JsonConvert.root().convertFrom(MAP_STRING_ADDRESSENTRY, (String) content);
if (map.isEmpty()) {
return CompletableFuture.completedFuture(set);
}
List> futures = new ArrayList<>();
for (Map.Entry en : map.entrySet()) {
String url0 = this.apiUrl + "/agent/health/service/id/" + en.getKey() + "?format=text";
HttpRequest.Builder builder0 = HttpRequest.newBuilder()
.uri(URI.create(url0))
.expectContinue(true)
.timeout(Duration.ofMillis(6000));
httpHeaders.forEach((n, v) -> {
if (v instanceof Collection) {
for (Object val : (Collection) v) {
builder0.header(n, val.toString());
}
} else if (v != null) {
builder0.header(n, v.toString());
}
});
futures.add(client.sendAsync(
builder0.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
.thenApply(HttpResponse::body)
.thenApply(irs -> {
if ("passing".equalsIgnoreCase(irs)) {
set.add(en.getValue().createSocketAddress());
} else {
logger.log(Level.INFO, en.getKey() + " (url=" + url0 + ") bad result: " + irs);
}
return null;
}));
}
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
.thenApply(v -> set);
});
}
protected boolean isApplicationHealth() {
String serviceId = generateApplicationServiceId();
try {
String irs = Utility.remoteHttpContent(
httpClient,
"GET",
this.apiUrl + "/agent/health/service/id/" + serviceId + "?format=text",
StandardCharsets.UTF_8,
httpHeaders);
return "passing".equalsIgnoreCase(irs);
} catch (java.io.FileNotFoundException ex) {
return false;
} catch (Exception e) {
logger.log(Level.SEVERE, serviceId + " health format=text error", e);
return true;
}
}
protected void beatApplicationHealth() {
String checkId = generateApplicationCheckId();
try {
String rs = Utility.remoteHttpContent(
httpClient,
"PUT",
this.apiUrl + "/agent/check/pass/" + checkId,
StandardCharsets.UTF_8,
httpHeaders);
if (!rs.isEmpty()) {
logger.log(Level.SEVERE, checkId + " check error: " + rs);
}
} catch (Exception ex) {
logger.log(Level.SEVERE, checkId + " check error", ex);
}
}
@Override
public void register(Application application) {
if (isApplicationHealth()) {
throw new RedkaleException("application.nodeid=" + nodeid + " exists in cluster");
}
deregister(application);
String serviceId = generateApplicationServiceId();
String serviceName = generateApplicationServiceName();
String host = this.appAddress.getHostString();
String json = "{\"ID\": \"" + serviceId + "\",\"Name\": \"" + serviceName
+ "\",\"Address\": \"" + host + "\",\"Port\": " + this.appAddress.getPort()
+ ",\"Check\":{\"CheckID\": \"" + generateApplicationCheckId()
+ "\",\"Name\": \"" + generateApplicationCheckName()
+ "\",\"TTL\":\"" + ttls + "s\",\"Notes\":\"Interval " + ttls + "s Check\"}}";
try {
String rs = Utility.remoteHttpContent(
httpClient,
"PUT",
this.apiUrl + "/agent/service/register",
StandardCharsets.UTF_8,
httpHeaders,
json);
if (!rs.isEmpty()) {
logger.log(Level.SEVERE, serviceId + " register error: " + rs);
}
} catch (Exception ex) {
logger.log(Level.SEVERE, serviceId + " register error", ex);
}
}
@Override
public void deregister(Application application) {
String serviceId = generateApplicationServiceId();
try {
String rs = Utility.remoteHttpContent(
httpClient,
"PUT",
this.apiUrl + "/agent/service/deregister/" + serviceId,
StandardCharsets.UTF_8,
httpHeaders);
if (!rs.isEmpty()) {
logger.log(Level.SEVERE, serviceId + " deregister error: " + rs);
}
} catch (Exception ex) {
logger.log(Level.SEVERE, serviceId + " deregister error", ex);
}
}
@Override
protected ClusterEntry register(NodeServer ns, String protocol, Service service) {
deregister(ns, protocol, service, false);
//
ClusterEntry clusterEntry = new ClusterEntry(ns, protocol, service);
String json = "{\"ID\": \"" + clusterEntry.serviceId + "\",\"Name\": \"" + clusterEntry.serviceName
+ "\",\"Address\": \"" + clusterEntry.address.getHostString() + "\",\"Port\": "
+ clusterEntry.address.getPort()
+ ",\"Check\":{\"CheckID\": \"" + generateCheckId(ns, protocol, service)
+ "\",\"Name\": \"" + generateCheckName(ns, protocol, service) + "\",\"TTL\":\"" + ttls
+ "s\",\"Notes\":\"Interval " + ttls + "s Check\"}}";
try {
String rs = Utility.remoteHttpContent(
httpClient,
"PUT",
this.apiUrl + "/agent/service/register",
StandardCharsets.UTF_8,
httpHeaders,
json);
if (rs.isEmpty()) {
// 需要立马执行下check,否则立即queryAddress可能会得到critical
Utility.remoteHttpContent(
httpClient,
"PUT",
this.apiUrl + "/agent/check/pass/" + generateCheckId(ns, protocol, service),
StandardCharsets.UTF_8,
httpHeaders);
} else {
logger.log(Level.SEVERE, clusterEntry.serviceId + " register error: " + rs);
}
} catch (Exception ex) {
logger.log(Level.SEVERE, clusterEntry.serviceId + " register error", ex);
return null;
}
return clusterEntry;
}
@Override
protected void deregister(NodeServer ns, String protocol, Service service) {
deregister(ns, protocol, service, true);
}
protected void deregister(NodeServer ns, String protocol, Service service, boolean realcanceled) {
String serviceid = generateServiceId(ns, protocol, service);
ClusterEntry currEntry = null;
for (final ClusterEntry entry : localEntrys.values()) {
if (entry.serviceId.equals(serviceid)) {
currEntry = entry;
break;
}
}
if (currEntry == null) {
for (final ClusterEntry entry : remoteEntrys.values()) {
if (entry.serviceId.equals(serviceid)) {
currEntry = entry;
break;
}
}
}
try {
String rs = Utility.remoteHttpContent(
httpClient,
"PUT",
this.apiUrl + "/agent/service/deregister/" + serviceid,
StandardCharsets.UTF_8,
httpHeaders);
if (realcanceled && currEntry != null) {
currEntry.canceled = true;
}
if (!rs.isEmpty()) {
logger.log(Level.SEVERE, serviceid + " deregister error: " + rs);
}
} catch (Exception ex) {
logger.log(
Level.SEVERE,
serviceid + " deregister error,protocol=" + protocol + ", service=" + service + ", currEntry="
+ currEntry,
ex);
}
}
public static final class ServiceEntry {
public String ID; // serviceId
public String Service; // serviceName
}
public static final class AddressEntry {
public String Address;
public int Port;
public InetSocketAddress createSocketAddress() {
return new InetSocketAddress(Address, Port);
}
}
}