サービスの登録と発見
マイクロサービスアーキテクチャでは、ビジネスは複数のマイクロサービスに分割され、各サービスは相互に通信して全体的な機能を完了します。単一障害点を回避するために、マイクロサービスはクラスターベースの高可用性デプロイメントを採用し、サービスコンシューマーは複数のサービスプロバイダーで構成されるクラスターを呼び出す必要があります。サービスプロバイダークラスターでノードのダウンタイムまたはオフラインが発生した場合、サービスコンシューマーのローカル構成漢方薬は、ノードの関連する構成情報を削除します。このとき、サービスアドレス管理、サービス登録、サービス動的認識を主に担当するサービス登録センターを導入する必要があります。
アリババナコス
Nacosは、マイクロサービスでの統合構成、サービス登録、および検出の問題を解決するために使用されます。開発者が動的なサービス検出、サービス構成、サービスメタデータ、およびトラフィック管理を迅速に実現するのに役立つ、シンプルで使いやすい機能セットのセットを提供します。その主な機能は次のとおりです。[nacos-1.1.4による説明]
サービス登録の場合、提供される外部サービスインターフェイスアドレスはnacos / v1 / ns / instance、対応するソースコード:com.alibaba.nacos.naming.controllers.InstanceControllerです。
- サービス登録:register()
@CanDistro
@PostMapping
public String register(HttpServletRequest request) throws Exception {
String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
serviceManager.registerInstance(namespaceId, serviceName, parseInstance(request));
return "ok";
}
ポジショニング:registerInstance()メソッドは、主に次の操作を実行します。
- 空のサービスを作成し、serviceMapを初期化します[基本的に:ConcurrentHashMap ]
- サービスオブジェクトを取得しますから、ルート虎namespaceIdとでserviceNameをserviceMap
- addInstance()を呼び出して、サービスインスタンスを追加します
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
Service service = getService(namespaceId, serviceName);
if (service == null) {
throw new NacosException(NacosException.INVALID_PARAM,
"service not found, namespace: " + namespaceId + ", service: " + serviceName);
}
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
ポジショニング:createEmptyService()、つまり:createServiceIfAbsent()メソッド。主に以下を実装します。
- namespaceIdとserviceNameを介してキャッシュからサービスインスタンスを取得します。インスタンスが空の場合、インスタンスが作成され、キャッシュに保存されます。
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster) throws NacosException {
Service service = getService(namespaceId, serviceName);
if (service == null) {
Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
service = new Service();
service.setName(serviceName);
service.setNamespaceId(namespaceId);
service.setGroupName(NamingUtils.getGroupName(serviceName));
// now validate the service. if failed, exception will be thrown
service.setLastModifiedMillis(System.currentTimeMillis());
service.recalculateChecksum();
if (cluster != null) {
cluster.setService(service);
service.getClusterMap().put(cluster.getName(), cluster);
}
service.validate();
putServiceAndInit(service);
if (!local) {
addOrReplaceService(service);
}
}
}
以下に注目しましょう:putServiceAndInit()メソッド。これは以下を実装します。
- putServiceメソッドを介してサービスをメモリにキャッシュします
- service.init()を介してハートビート検出メカニズムを確立します
- 整合性サービス .listenはデータ整合性監視を実装します
private void putServiceAndInit(Service service) throws NacosException {
// 将服务缓存到内存,将service保存到serviceMap中
putService(service);
// 建立服务下包含实例心跳检测机制,每5s发送一次心跳包,不断检测当前服务下所有实例的状态,若请求超时,表明服务不健康,触发服务变更事件
service.init();
// 实现数据一致性监听
consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJSON());
}
- サービスアドレス管理クエリ:list()、リクエストパラメータの解析、doSrvIPXTを介したサービスリストデータの返送
@GetMapping("/list")
public JSONObject list(HttpServletRequest request) throws Exception {
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
Constants.DEFAULT_NAMESPACE_ID);
String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
String agent = WebUtils.getUserAgent(request);
String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY);
String clientIP = WebUtils.optional(request, "clientIP", StringUtils.EMPTY);
Integer udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0"));
String env = WebUtils.optional(request, "env", StringUtils.EMPTY);
boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, "isCheck", "false"));
String app = WebUtils.optional(request, "app", StringUtils.EMPTY);
String tenant = WebUtils.optional(request, "tid", StringUtils.EMPTY);
boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, "healthyOnly", "false"));
// 主要实现方法
return doSrvIPXT(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant,
healthyOnly);
}
主にnamespaceIdとserviceNameに基づいてServiceインスタンスを取得し、インスタンスsrvIPに基づいてすべてのサービスプロバイダーのインスタンス情報を取得するdoSrvIPXT()メソッドに注目しましょう。
public JSONObject doSrvIPXT(String namespaceId, String serviceName, String agent, String clusters, String clientIP,
int udpPort,
String env, boolean isCheck, String app, String tid, boolean healthyOnly) throws Exception {
ClientInfo clientInfo = new ClientInfo(agent);
JSONObject result = new JSONObject();
Service service = serviceManager.getService(namespaceId, serviceName);
List<Instance> srvedIPs;
// 获取指定服务下的所有实例IP
srvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters, ",")));
Map<Boolean, List<Instance>> ipMap = new HashMap<>(2);
ipMap.put(Boolean.TRUE, new ArrayList<>());
ipMap.put(Boolean.FALSE, new ArrayList<>());
for (Instance ip : srvedIPs) {
ipMap.get(ip.isHealthy()).add(ip);
}
for (Map.Entry<Boolean, List<Instance>> entry : ipMap.entrySet()) {
List<Instance> ips = entry.getValue();
if (healthyOnly && !entry.getKey()) {
continue;
}
for (Instance instance : ips) {
// remove disabled instance:
if (!instance.isEnabled()) {
continue;
}
JSONObject ipObj = new JSONObject();
ipObj.put("ip", instance.getIp());
ipObj.put("port", instance.getPort());
// deprecated since nacos 1.0.0:
ipObj.put("valid", entry.getKey());
ipObj.put("healthy", entry.getKey());
ipObj.put("marked", instance.isMarked());
ipObj.put("instanceId", instance.getInstanceId());
ipObj.put("metadata", instance.getMetadata());
ipObj.put("enabled", instance.isEnabled());
ipObj.put("weight", instance.getWeight());
ipObj.put("clusterName", instance.getClusterName());
if (clientInfo.type == ClientInfo.ClientType.JAVA &&
clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {
ipObj.put("serviceName", instance.getServiceName());
} else {
ipObj.put("serviceName", NamingUtils.getServiceName(instance.getServiceName()));
}
ipObj.put("ephemeral", instance.isEphemeral());
hosts.add(ipObj);
}
}
result.put("hosts", hosts);
if (clientInfo.type == ClientInfo.ClientType.JAVA &&
clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {
result.put("dom", serviceName);
} else {
result.put("dom", NamingUtils.getServiceName(serviceName));
}
result.put("name", serviceName);
result.put("cacheMillis", cacheMillis);
result.put("lastRefTime", System.currentTimeMillis());
result.put("checksum", service.getChecksum());
result.put("useSpecifiedURL", false);
result.put("clusters", clusters);
result.put("env", env);
result.put("metadata", service.getMetadata());
return result;
}
- サービスの動的認識
- クライアントがイベントサブスクリプションを開始した後、NacosクライアントにHostReactorクラスがあります。これは、サービスの動的更新を実装するために使用されます。これには、サーバー側のサービスアドレスリストを取得するために10秒ごとにプルリクエストを送信するUpdateTaskサイトが含まれています。
- Naocs Serverの場合、ハートビート検出はサーバーとサービスプロバイダーのインスタンスの間で維持されます。サービスプロバイダーのインスタンスが異常になると、サービスコンシューマーであるNacosクライアントに通知されます。
- リクエストを受信した後、Nacosクライアントサービスコンシューマーは、HostReactorで提供されるprocessServiceJSONを使用して情報を解析し、サーバーサービスのアドレスリストをタイムリーに更新します。
Nacos実装アーキテクチャ図
アーキテクチャ図から、次のことがわかります。
- プロバイダーAPP:サービスプロバイダー
- コンシューマーアプリ:サービスコンシューマー
- ネームサーバー:VritualIPまたはDNSによって実装されたNacos高可用性クラスターサービスルーティング
- Nacosサーバー:機能アクセスエントリとしてOpen APIを含むNacosサービスプロバイダー。ConfigServiceとNamingServiceは、Nacosが提供する構成および命名サービスモジュールです。ConsistencyProtocolは、Nacosクラスターノード間のデータ同期を実現するために使用される一貫性プロトコルです。
- Nacosコンソール:コンソールアプリケーション
レジストリの機能は主に以下に反映されます。
- サービスインスタンスは、起動時にサービスレジストリに登録され、シャットダウン時に登録解除されます。
- サービスコンシューマーは、サービスレジストリにクエリを実行して、使用可能なインスタンスを取得します。
- サービスレジストリは、サービスインスタンスのヘルスチェックAPIを呼び出して、リクエストを処理できるかどうかを確認する必要があります。特定のサービス登録と検出のケースコードについては、Githubを参照してください。