序文
スプリングブートを使用してnacosを統合するか、SpringCloudAlibabaを使用してnacosのみを統合または使用するか。結局、サービスインスタンスを登録または取得する特定の方法はnacos-clientパッケージです。nacos-apiはインターフェースのみを定義し、特定の操作はnacos-clientによって完了されます。私たちのプロジェクトに統合されたnacosは、nacosアクセス全体でクライアントの役割を果たし、すべてのデータの保存と保守は、別個のnacosサーバーによって実現されます。nacosクライアントとサーバーはhttpを介して通信します。この記事では、nacosの登録プロセス全体のソースコード分析のみを紹介します。
1.クライアントの実装
1.クライアント登録エントリオブジェクト
nacos -clientパッケージのネーミングディレクトリにクラスNacosNamingServiceがあり、nacos -apiのNamingServiceインターフェイスを実装しています。主な方法は次のとおりです。
- public NacosNamingService(String serverList)##コンストラクター、nacosサーバーアドレスを渡します
- public void registerInstance(String serviceName、String ip、int port)##サービスを登録します
- public void deregisterInstance(String serviceName、String ip、int port)##登録解除
- …その他については後の章で説明します
2.コンストラクターは何をしますか?
/** 构造函数 */
public NacosNamingService(String serverList) {
// nacos server地址
this.serverList = serverList;
// 初始化主要是读取系统的环境变量,读取namespace、日志名称、日志等级、缓存地址等信息
init();
// 订阅服务,当服务实例有变化时会触发通知
eventDispatcher = new EventDispatcher();
// 具体的与远程nacos server通讯的类
serverProxy = new NamingProxy(namespace, endpoint, serverList);
// 心跳服务,客户端要定时给nacos server发送心跳包
beatReactor = new BeatReactor(serverProxy);
// 更新服务的,获取服务的实例后,可以订阅服务,会定时拉取最新的实例列表
hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir);
}
3.登録サービス
登録方法は3つあり、主にオーバーロードされており、バージョンが異なるとわずかに異なる場合がありますが、通常は同じです。
/** 注册实例 */
/** 当前看的这个版本可能相对旧一点,最新的版本是还有一个参数,是否添加心跳的参数,可以选择是否对当前的实例一直与nacos服务端进行心跳检测 */
@Override
public void registerInstance(String serviceName, Instance instance) throws NacosException {
// 创建添加心跳的对象
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(serviceName);
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
// 添加心跳
beatReactor.addBeatInfo(serviceName, beatInfo);
// 具体实现注册
serverProxy.registerService(serviceName, instance);
}
4.ハートビートの実現
以下は、ハートビートを実装するオブジェクトです。その主な原則は、スレッドを開始し、初期化中にNamingProxyオブジェクトを介してnacosサーバーインターフェイスを定期的に要求し、http要求を送信することです。
class BeatProcessor implements Runnable {
@Override
public void run() {
try {
for (Map.Entry<String, BeatInfo> entry : dom2Beat.entrySet()) {
BeatInfo beatInfo = entry.getValue();
// 如果是健康的则不进行发送心跳
if (beatInfo.isScheduled()) {
continue;
}
// 如果不是健康的则会触发发送心跳
beatInfo.setScheduled(true);
executorService.schedule(new BeatTask(beatInfo), 0, TimeUnit.MILLISECONDS);
}
} catch (Exception e) {
LogUtils.LOG.error("CLIENT-BEAT", "Exception while scheduling beat.", e);
}
}
}
class BeatTask implements Runnable {
BeatInfo beatInfo;
public BeatTask(BeatInfo beatInfo) {
this.beatInfo = beatInfo;
}
@Override
public void run() {
// serverProxy就是NamingProxy
long result = serverProxy.sendBeat(beatInfo);
// 发送心跳后,当前的服务服务实例则置为不健康状态,这样才能触发下一次心跳发送
beatInfo.setScheduled(false);
if (result > 0) {
clientBeatInterval = result;
}
}
}
5.登録サービスインスタンス
NamingProxyには登録ロジックが含まれています。ここでの登録は実際には比較的単純です。これは主に現在のサービスインスタンスのデータをカプセル化し、それをnacosサーバーに要求します。
public void registerService(String serviceName, Instance instance) throws NacosException {
LogUtils.LOG.info("REGISTER-SERVICE", "{} registering service {} with instance: {}",
namespaceId, serviceName, instance);
final Map<String, String> params = new HashMap<String, String>(8);
params.put(Constants.REQUEST_PARAM_NAMESPACE_ID, namespaceId);
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("metadata", JSON.toJSONString(instance.getMetadata()));
params.put("serviceName", serviceName);
params.put("clusterName", instance.getClusterName());
//通用的发送请求
reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
}
6.一般的なhttpリクエストの送信
上記のハートビートは、実際にはNamingProxyオブジェクトに実装されています
public long sendBeat(BeatInfo beatInfo) {
try {
LogUtils.LOG.info("BEAT", "{} sending beat to server: {}", namespaceId, beatInfo.toString());
// 封装请求参数
Map<String, String> params = new HashMap<String, String>(4);
params.put("beat", JSON.toJSONString(beatInfo));
params.put(Constants.REQUEST_PARAM_NAMESPACE_ID, namespaceId);
params.put("serviceName", beatInfo.getServiceName());
// 请求远程接口
String result = reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, HttpMethod.PUT);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject != null) {
return jsonObject.getLong("clientBeatInterval");
}
} catch (Exception e) {
LogUtils.LOG.error("CLIENT-BEAT", "failed to send beat: " + JSON.toJSONString(beatInfo), e);
}
return 0L;
}
すべてのインターフェース要求はreqAPIメソッドで実行されます
/** 请求远程接口,传入请求接口方法、请求参数、nacos server服务地址,请求方式(post/get) */
public String reqAPI(String api, Map<String, String> params, List<String> servers, String method) {
params.put(Constants.REQUEST_PARAM_NAMESPACE_ID, getNamespaceId());
if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) {
throw new IllegalArgumentException("no server available");
}
// 获取nacos服务列表,随机获取地址进行请求
if (servers != null && !servers.isEmpty()) {
Random random = new Random(System.currentTimeMillis());
int index = random.nextInt(servers.size());
for (int i = 0; i < servers.size(); i++) {
String server = servers.get(index);
try {
return callServer(api, params, server, method);
} catch (Exception e) {
LogUtils.LOG.error("NA", "req api:" + api + " failed, server(" + server, e);
}
// 如果随机获取的服务地址没有请求成功,则尝试下一个地址
index = (index + 1) % servers.size();
}
throw new IllegalStateException("failed to req API:" + api + " after all servers(" + servers + ") tried");
}
for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {
try {
return callServer(api, params, nacosDomain);
} catch (Exception e) {
LogUtils.LOG.error("NA", "req api:" + api + " failed, server(" + nacosDomain, e);
}
}
throw new IllegalStateException("failed to req API:/api/" + api + " after all servers(" + servers + ") tried");
}
第二に、サーバーの実装
1.クライアント登録リクエストを受信します
/**
* Register new instance.
*
* @param request http request
* @return 'ok' if success
* @throws Exception any error during register
*/
@CanDistro
@PostMapping("/instance")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String registerInstance(@RequestParam(defaultValue = "v2", required = false) String ver,
HttpServletRequest request) throws Exception {
final String namespaceId = WebUtils
.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
NamingUtils.checkServiceNameFormat(serviceName);
final Instance instance = HttpRequestInstanceBuilder.newBuilder()
.setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();
// 选择版本,选择V1还是v2,做了兼容
getInstanceOperator(ver).registerInstance(namespaceId, serviceName, instance);
return "ok";
}
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
// 检测service是否存在,不存在则进行创建
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
// 获取service
Service service = getService(namespaceId, serviceName);
// 校验service是否存在,不存在则抛出异常
checkServiceIsNull(service, namespaceId, serviceName);
// 将传入的实例对象进行添加操作
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
throws NacosException {
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
// 获取service
Service service = getService(namespaceId, serviceName);
// 防止并发处理
synchronized (service) {
// 将实例添加到目标service的实例列表中,返回添加后的实例列表
List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
Instances instances = new Instances();
instances.setInstanceList(instanceList);
//处理一致性的实例数据,保证nacos server集群的数据一致性
consistencyService.put(key, instances);
}
}
ここでよりよく知られているのはいかだアルゴリズムです
3.まとめ
バージョンスパンは比較的大きく、実装方法も異なりますが、実装の一般的な方向はこれです。ソースコードを自分でフォローすることをお勧めします。