All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redkalex.cluster.consul.ConsulClusterAgent Maven / Gradle / Ivy

There is a newer version: 2.7.7
Show newest version
/*
 * 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 org.redkale.cluster.ClusterAgent;
import java.lang.reflect.Type;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.*;
import org.redkale.boot.*;
import org.redkale.convert.json.JsonConvert;
import org.redkale.service.Service;
import org.redkale.util.*;

/**
 * 
 *  <cluster value="org.redkalex.cluster.consul.ConsulClusterAgent">
 *      <property name="apiurl" value="http://localhost:8500/v1"/>
 *      <property name="ttls" value="10"/>
 *  </cluster>
 * 
* * @author zhangjx */ public class ConsulClusterAgent extends ClusterAgent { protected static final Map httpHeaders = 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 Object httpClient; //JDK11里面的HttpClient protected int ttls = 10; //定时检查的秒数 protected ScheduledThreadPoolExecutor scheduler; //可能被HttpMessageClient用到的服务 key: servicename protected final ConcurrentHashMap> httpAddressMap = new ConcurrentHashMap<>(); //可能被mqtp用到的服务 key: servicename protected final ConcurrentHashMap> mqtpAddressMap = new ConcurrentHashMap<>(); @Override public void init(AnyValue config) { super.init(config); AnyValue[] properties = config.getAnyValues("property"); for (AnyValue property : properties) { if ("apiurl".equalsIgnoreCase(property.getValue("name"))) { this.apiurl = property.getValue("value", "").trim(); if (this.apiurl.endsWith("/")) this.apiurl = this.apiurl.substring(0, this.apiurl.length() - 1); } else if ("ttls".equalsIgnoreCase(property.getValue("name"))) { this.ttls = Integer.parseInt(property.getValue("value", "").trim()); if (this.ttls < 5) this.ttls = 10; } } try { Class httpClientClass = Class.forName("java.net.http.HttpClient"); this.httpClient = httpClientClass.getMethod("newHttpClient").invoke(null); } catch (Exception e) { //不是JDK11+ } } @Override public void destroy(AnyValue config) { if (scheduler != null) scheduler.shutdownNow(); } @Override //ServiceLoader时判断配置是否符合当前实现类 public boolean match(AnyValue config) { if (config == null) return false; AnyValue[] properties = config.getAnyValues("property"); if (properties == null || properties.length == 0) return false; for (AnyValue property : properties) { if ("apiurl".equalsIgnoreCase(property.getValue("name"))) return true; } return false; } @Override public void start() { if (this.scheduler == null) { this.scheduler = new ScheduledThreadPoolExecutor(4, (Runnable r) -> { final Thread t = new Thread(r, ConsulClusterAgent.class.getSimpleName() + "-Task-Thread"); t.setDaemon(true); return t; }); //delay为了错开请求 this.scheduler.scheduleAtFixedRate(() -> { checkApplicationHealth(); checkHttpAddressHealth(); }, 18, Math.max(2000, ttls * 1000 - 168), TimeUnit.MILLISECONDS); this.scheduler.scheduleAtFixedRate(() -> { loadMqtpAddressHealth(); }, 88 * 2, Math.max(2000, ttls * 1000 - 168), TimeUnit.MILLISECONDS); this.scheduler.scheduleAtFixedRate(() -> { localEntrys.values().stream().filter(e -> !e.canceled).forEach(entry -> { checkLocalHealth(entry); }); }, 128 * 3, Math.max(2000, ttls * 1000 - 168), TimeUnit.MILLISECONDS); this.scheduler.scheduleAtFixedRate(() -> { remoteEntrys.values().stream().filter(entry -> "SNCP".equalsIgnoreCase(entry.protocol)).forEach(entry -> { updateSncpTransport(entry); }); }, 188 * 4, Math.max(2000, ttls * 1000 - 168), TimeUnit.MILLISECONDS); } } protected void loadMqtpAddressHealth() { try { String content = Utility.remoteHttpContent("GET", this.apiurl + "/agent/services", httpHeaders, (String) null).toString(StandardCharsets.UTF_8); final Map map = JsonConvert.root().convertFrom(MAP_STRING_SERVICEENTRY, content); Set mqtpkeys = new HashSet<>(); map.forEach((key, en) -> { if (en.Service.startsWith("mqtp:")) mqtpkeys.add(en.Service); }); mqtpkeys.forEach(servicename -> { try { this.mqtpAddressMap.put(servicename, queryAddress(servicename).get(10, TimeUnit.SECONDS)); } catch (Exception e) { logger.log(Level.SEVERE, "loadMqtpAddressHealth check " + servicename + " error", e); } }); } catch (Exception ex) { logger.log(Level.SEVERE, "loadMqtpAddressHealth check error", ex); } } protected void checkHttpAddressHealth() { try { this.httpAddressMap.keySet().stream().forEach(servicename -> { try { this.httpAddressMap.put(servicename, queryAddress(servicename).get(10, TimeUnit.SECONDS)); } catch (Exception e) { logger.log(Level.SEVERE, "checkHttpAddressHealth check " + servicename + " error", e); } }); } catch (Exception ex) { logger.log(Level.SEVERE, "checkHttpAddressHealth check error", ex); } } protected void checkLocalHealth(final ClusterEntry entry) { String url = this.apiurl + "/agent/check/pass/" + entry.checkid; try { String rs = Utility.remoteHttpContent("PUT", url, httpHeaders, (String) null).toString(StandardCharsets.UTF_8); 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 //获取MQTP的HTTP远程服务的可用ip列表, key = servicename的后半段 public CompletableFuture>> queryMqtpAddress(String protocol, String module, String resname) { final Map> rsmap = new ConcurrentHashMap<>(); final String servicenamprefix = generateHttpServiceName(protocol, module, null) + ":"; mqtpAddressMap.keySet().stream().filter(k -> k.startsWith(servicenamprefix)) .forEach(sn -> rsmap.put(sn.substring(servicenamprefix.length()), mqtpAddressMap.get(sn))); return CompletableFuture.completedFuture(rsmap); } @Override //获取HTTP远程服务的可用ip列表 public CompletableFuture> queryHttpAddress(String protocol, String module, String resname) { final String servicename = generateHttpServiceName(protocol, module, resname); Collection 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) { return (httpClient != null) ? queryAddress11(servicename) : queryAddress8(servicename); } //JDK11+版本以上的纯异步方法 private CompletableFuture> queryAddress11(final String servicename) { final java.net.http.HttpClient client = (java.net.http.HttpClient) httpClient; String url = this.apiurl + "/agent/services?filter=" + URLEncoder.encode("Service==\"" + servicename + "\"", StandardCharsets.UTF_8); java.net.http.HttpRequest.Builder builder = java.net.http.HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofMillis(6000)); httpHeaders.forEach((n, v) -> builder.header(n, v)); final Set set = new CopyOnWriteArraySet<>(); return client.sendAsync(builder.build(), java.net.http.HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)).thenApply(resp -> resp.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"; java.net.http.HttpRequest.Builder builder0 = java.net.http.HttpRequest.newBuilder().uri(URI.create(url0)).timeout(Duration.ofMillis(6000)); httpHeaders.forEach((n, v) -> builder0.header(n, v)); futures.add(client.sendAsync(builder0.build(), java.net.http.HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)).thenApply(resp -> resp.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); }); } private CompletableFuture> queryAddress8(final String servicename) { final HashSet set = new HashSet<>(); String rs = null; String url = this.apiurl + "/agent/services?filter=" + URLEncoder.encode("Service==\"" + servicename + "\"", StandardCharsets.UTF_8); try { rs = Utility.remoteHttpContent("GET", url, httpHeaders, (String) null).toString(StandardCharsets.UTF_8); Map map = JsonConvert.root().convertFrom(MAP_STRING_ADDRESSENTRY, rs); map.forEach((serviceid, en) -> { try { String irs = Utility.remoteHttpContent("GET", this.apiurl + "/agent/health/service/id/" + serviceid + "?format=text", httpHeaders, (String) null).toString(StandardCharsets.UTF_8); if ("passing".equalsIgnoreCase(irs)) { set.add(en.createSocketAddress()); } else { logger.log(Level.INFO, serviceid + " (url=" + url + ") bad result: " + irs); } } catch (Exception e) { logger.log(Level.SEVERE, serviceid + " health format=text error", e); } }); } catch (Exception ex) { logger.log(Level.SEVERE, servicename + " queryAddress error, result=" + rs, ex); } return CompletableFuture.completedFuture(set); } protected boolean isApplicationHealth() { String serviceid = generateApplicationServiceId(); try { String irs = Utility.remoteHttpContent("GET", this.apiurl + "/agent/health/service/id/" + serviceid + "?format=text", httpHeaders, (String) null).toString(StandardCharsets.UTF_8); 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 checkApplicationHealth() { String checkid = generateApplicationCheckId(); try { String rs = Utility.remoteHttpContent("PUT", this.apiurl + "/agent/check/pass/" + checkid, httpHeaders, (String) null).toString(StandardCharsets.UTF_8); 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 RuntimeException("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("PUT", this.apiurl + "/agent/service/register", httpHeaders, json).toString(StandardCharsets.UTF_8); 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("PUT", this.apiurl + "/agent/service/deregister/" + serviceid, httpHeaders, (String) null).toString(StandardCharsets.UTF_8); if (!rs.isEmpty()) logger.log(Level.SEVERE, serviceid + " deregister error: " + rs); } catch (Exception ex) { logger.log(Level.SEVERE, serviceid + " deregister error", ex); } } @Override protected void register(NodeServer ns, String protocol, Service service) { deregister(ns, protocol, service, false); // String serviceid = generateServiceId(ns, protocol, service); String servicename = generateServiceName(ns, protocol, service); InetSocketAddress address = ns.isSNCP() ? ns.getSncpAddress() : ns.getServer().getSocketAddress(); String host = address.getHostString(); if ("0.0.0.0".equals(host)) host = appAddress.getHostString(); String json = "{\"ID\": \"" + serviceid + "\",\"Name\": \"" + servicename + "\",\"Address\": \"" + host + "\",\"Port\": " + 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("PUT", this.apiurl + "/agent/service/register", httpHeaders, json).toString(StandardCharsets.UTF_8); if (rs.isEmpty()) { //需要立马执行下check,否则立即queryAddress可能会得到critical Utility.remoteHttpContent("PUT", this.apiurl + "/agent/check/pass/" + generateCheckId(ns, protocol, service), httpHeaders, (String) null).toString(StandardCharsets.UTF_8); } else { logger.log(Level.SEVERE, serviceid + " register error: " + rs); } } catch (Exception ex) { logger.log(Level.SEVERE, serviceid + " register error", ex); } } @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("PUT", this.apiurl + "/agent/service/deregister/" + serviceid, httpHeaders, (String) null).toString(StandardCharsets.UTF_8); 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 class ServiceEntry { public String ID; //serviceid public String Service; //servicename } public static class AddressEntry { public String Address; public int Port; public InetSocketAddress createSocketAddress() { return new InetSocketAddress(Address, Port); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy