記事ディレクトリ
前提
Nacos はスタンドアロン展開とクラスター展開をサポートします
- スタンドアロン モードの場合、Nacos はそれ自体と通信するだけです。
- クラスター モードの場合、クラスター内の各 Nacos メンバーは相互に通信する必要があります。
したがって、クラスター内の Nacos メンバー ノードの情報をどのように管理するかという疑問が生じます。これが Nacos の内部アドレス指定メカニズムです。
デザイン
スタンドアロンモードでもクラスターモードでも、基本的な違いはNacosメンバーノードの数が単一か複数かです。
-
ノードが増加したか減少したかなど、ノードの変化を認識できる必要があります。
-
現在の最新のメンバーリスト情報は何ですか?
-
会員名簿情報の管理方法
-
より優れた新しいメンバー リスト管理モードを迅速にサポートする方法など。
メンバー検索
上記の要件を考慮して、 MemberLookupインターフェイスは抽象化されています。
package com.alibaba.nacos.core.cluster;
import com.alibaba.nacos.api.exception.NacosException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* Member node addressing mode.
*
* @author <a href="mailto:[email protected]">liaochuntao</a>
*/
public interface MemberLookup {
/**
* start.
*
* @throws NacosException NacosException
*/
void start() throws NacosException;
/**
* is using address server.
*
* @return using address server or not.
*/
boolean useAddressServer();
/**
* Inject the ServerMemberManager property.
*
* @param memberManager {@link ServerMemberManager}
*/
void injectMemberManager(ServerMemberManager memberManager);
/**
* The addressing pattern finds cluster nodes.
*
* @param members {@link Collection}
*/
void afterLookup(Collection<Member> members);
/**
* Addressing mode closed.
*
* @throws NacosException NacosException
*/
void destroy() throws NacosException;
/**
* Some data information about the addressing pattern.
*
* @return {@link Map}
*/
default Map<String, Object> info() {
return Collections.emptyMap();
}
}
-
ServerMemberManager
ノードが認識しているすべてのメンバー ノード リスト情報を保存し、メンバー ノードの追加、削除、変更、およびクエリ操作を提供し、メンバー ノードのアドレス指定方法の動的な切り替えを容易にするために MemberLookup リストを維持します。 -
MemberLookup
インターフェイスは非常にシンプルで、コア インターフェイスは と の 2 つだけでinjectMemberManager
、前者はストレージとクエリ機能の使用を容易にするためにに注入するafterLookup
ために使用され、後者はイベント インターフェイスです。現在の最新のノードのメンバーノードリスト情報はこの関数を通じて通知され、特定のノード管理方法は特定の実装に隠蔽されます。ServerMemberManager
MemberLookup
ServerMemberManager
afterLookup
MemberLookup
ServerMemberManager
MemberLookup
内部実装
スタンドアロンのアドレス指定 StandaloneMemberLookup
スタンドアロン モードのアドレッシング モードは非常に単純で、実際には、独自の IP:PORT の組み合わせ情報を見つけてノード情報としてフォーマットし、afterLookup を呼び出し、その情報を ServerMemberManager に保存します。
package com.alibaba.nacos.core.cluster.lookup;
import com.alibaba.nacos.core.cluster.AbstractMemberLookup;
import com.alibaba.nacos.core.cluster.MemberUtil;
import com.alibaba.nacos.sys.env.EnvUtil;
import com.alibaba.nacos.sys.utils.InetUtils;
import java.util.Collections;
/**
* Member node addressing mode in stand-alone mode.
*
* @author <a href="mailto:[email protected]">liaochuntao</a>
*/
public class StandaloneMemberLookup extends AbstractMemberLookup {
@Override
public void doStart() {
String url = InetUtils.getSelfIP() + ":" + EnvUtil.getPort();
afterLookup(MemberUtil.readServerConf(Collections.singletonList(url)));
}
@Override
public boolean useAddressServer() {
return false;
}
}
ファイルのアドレス指定 FileConfigMemberLookup
ファイル アドレッシング モードは、Nacos クラスタ モードのデフォルトのアドレッシング実装です。
ファイル アドレッシング モードは非常に単純で、実際、各 Nacos ノードは、cluster.conf というファイルを維持する必要があります。
192.168.16.101:8847
192.168.16.102
192.168.16.103
デフォルトでは、このファイルは各メンバー ノードの IP 情報を入力するだけでよく、ポートは Nacos のデフォルト ポート 8848 を自動的に選択します。特別なニーズのために Nacos のポート情報を変更した場合は、完全な IP 情報を追加する必要があります。ネットワーク 完全なルート アドレス情報 (IP:PORT)。
Nacos ノードが起動すると、ファイルの内容を読み取り、ファイル内の IP を解析してノードのリストを作成し、afterLookup を呼び出して ServerMemberManager に保存します。
/**
* Cluster.conf file managed cluster member node addressing pattern.
*
* @author <a href="mailto:[email protected]">liaochuntao</a>
*/
public class FileConfigMemberLookup extends AbstractMemberLookup {
private static final String DEFAULT_SEARCH_SEQ = "cluster.conf";
private FileWatcher watcher = new FileWatcher() {
@Override
public void onChange(FileChangeEvent event) {
readClusterConfFromDisk();
}
@Override
public boolean interest(String context) {
return StringUtils.contains(context, DEFAULT_SEARCH_SEQ);
}
};
@Override
public void doStart() throws NacosException {
readClusterConfFromDisk();
// Use the inotify mechanism to monitor file changes and automatically
// trigger the reading of cluster.conf
try {
WatchFileCenter.registerWatcher(EnvUtil.getConfPath(), watcher);
} catch (Throwable e) {
Loggers.CLUSTER.error("An exception occurred in the launch file monitor : {}", e.getMessage());
}
}
@Override
public boolean useAddressServer() {
return false;
}
@Override
public void destroy() throws NacosException {
WatchFileCenter.deregisterWatcher(EnvUtil.getConfPath(), watcher);
}
private void readClusterConfFromDisk() {
Collection<Member> tmpMembers = new ArrayList<>();
try {
List<String> tmp = EnvUtil.readClusterConf();
tmpMembers = MemberUtil.readServerConf(tmp);
} catch (Throwable e) {
Loggers.CLUSTER
.error("nacos-XXXX [serverlist] failed to get serverlist from disk!, error : {}", e.getMessage());
}
afterLookup(tmpMembers);
}
}
クラスターが拡大および縮小していることが判明した場合は、各 Nacos ノードの下にあるcluster.conf ファイルを変更する必要があります。その後、Nacos 内のファイル変更監視センターが自動的にファイルの変更を検出し、ファイルの内容を再読み込みし、ロードします。 IPリスト情報を取得し、新しく追加したノードを更新します(FileWatcher)
ただし、このデフォルトのアドレッシング モードには、運用コストとメンテナンス コストが比較的高いという欠点があります。ご想像のとおり、新しい Nacos ノードを追加するときは、各 Nacos ノードの下にあるcluster.conf ファイルを手動で変更する必要があります。作業するか、もう少し高度な場合は、ansible などの自動デプロイメント ツールを使用していずれかのノードのファイルに障害が発生すると、クラスタ間のメンバー ノードのリストでデータの不整合が発生するため、別の新しいアドレッシング モード - アドレス サーバー アドレッシング モードが拡張されました。
アドレスサーバー検索AddressServerMemberLookup
アドレスサーバーアドレッシングモードは、Nacos が公式に推奨するクラスタメンバノードの情報管理の一種で、cluster.conf ファイルの内容情報を簡易 Web サーバーで管理することで、運用保守担当者のみが必要とするモードです。これを管理するには、各 Nacos メンバー ノードは、この Web ノードから最新のクラスター メンバー ノード リスト情報を定期的に要求するだけで済みます。
public class AddressServerMemberLookup extends AbstractMemberLookup {
private final GenericType<String> genericType = new GenericType<String>() {
};
public String domainName;
public String addressPort;
public String addressUrl;
public String envIdUrl;
public String addressServerUrl;
private volatile boolean isAddressServerHealth = true;
private int addressServerFailCount = 0;
private int maxFailCount = 12;
private final NacosRestTemplate restTemplate = HttpClientBeanHolder.getNacosRestTemplate(Loggers.CORE);
private volatile boolean shutdown = false;
private static final String HEALTH_CHECK_FAIL_COUNT_PROPERTY = "maxHealthCheckFailCount";
private static final String DEFAULT_HEALTH_CHECK_FAIL_COUNT = "12";
private static final String DEFAULT_SERVER_DOMAIN = "jmenv.tbsite.net";
private static final String DEFAULT_SERVER_POINT = "8080";
private static final int DEFAULT_SERVER_RETRY_TIME = 5;
private static final long DEFAULT_SYNC_TASK_DELAY_MS = 5_000L;
private static final String ADDRESS_SERVER_DOMAIN_ENV = "address_server_domain";
private static final String ADDRESS_SERVER_DOMAIN_PROPERTY = "address.server.domain";
private static final String ADDRESS_SERVER_PORT_ENV = "address_server_port";
private static final String ADDRESS_SERVER_PORT_PROPERTY = "address.server.port";
private static final String ADDRESS_SERVER_URL_ENV = "address_server_url";
private static final String ADDRESS_SERVER_URL_PROPERTY = "address.server.url";
private static final String ADDRESS_SERVER_RETRY_PROPERTY = "nacos.core.address-server.retry";
@Override
public void doStart() throws NacosException {
this.maxFailCount = Integer.parseInt(EnvUtil.getProperty(HEALTH_CHECK_FAIL_COUNT_PROPERTY, DEFAULT_HEALTH_CHECK_FAIL_COUNT));
initAddressSys();
run();
}
@Override
public boolean useAddressServer() {
return true;
}
private void initAddressSys() {
String envDomainName = System.getenv(ADDRESS_SERVER_DOMAIN_ENV);
if (StringUtils.isBlank(envDomainName)) {
domainName = EnvUtil.getProperty(ADDRESS_SERVER_DOMAIN_PROPERTY, DEFAULT_SERVER_DOMAIN);
} else {
domainName = envDomainName;
}
String envAddressPort = System.getenv(ADDRESS_SERVER_PORT_ENV);
if (StringUtils.isBlank(envAddressPort)) {
addressPort = EnvUtil.getProperty(ADDRESS_SERVER_PORT_PROPERTY, DEFAULT_SERVER_POINT);
} else {
addressPort = envAddressPort;
}
String envAddressUrl = System.getenv(ADDRESS_SERVER_URL_ENV);
if (StringUtils.isBlank(envAddressUrl)) {
addressUrl = EnvUtil.getProperty(ADDRESS_SERVER_URL_PROPERTY, EnvUtil.getContextPath() + "/" + "serverlist");
} else {
addressUrl = envAddressUrl;
}
addressServerUrl = "http://" + domainName + ":" + addressPort + addressUrl;
envIdUrl = "http://" + domainName + ":" + addressPort + "/env";
Loggers.CORE.info("ServerListService address-server port:" + addressPort);
Loggers.CORE.info("ADDRESS_SERVER_URL:" + addressServerUrl);
}
@SuppressWarnings("PMD.UndefineMagicConstantRule")
private void run() throws NacosException {
// With the address server, you need to perform a synchronous member node pull at startup
// Repeat three times, successfully jump out
boolean success = false;
Throwable ex = null;
int maxRetry = EnvUtil.getProperty(ADDRESS_SERVER_RETRY_PROPERTY, Integer.class, DEFAULT_SERVER_RETRY_TIME);
for (int i = 0; i < maxRetry; i++) {
try {
syncFromAddressUrl();
success = true;
break;
} catch (Throwable e) {
ex = e;
Loggers.CLUSTER.error("[serverlist] exception, error : {}", ExceptionUtil.getAllExceptionMsg(ex));
}
}
if (!success) {
throw new NacosException(NacosException.SERVER_ERROR, ex);
}
GlobalExecutor.scheduleByCommon(new AddressServerSyncTask(), DEFAULT_SYNC_TASK_DELAY_MS);
}
@Override
public void destroy() throws NacosException {
shutdown = true;
}
@Override
public Map<String, Object> info() {
Map<String, Object> info = new HashMap<>(4);
info.put("addressServerHealth", isAddressServerHealth);
info.put("addressServerUrl", addressServerUrl);
info.put("envIdUrl", envIdUrl);
info.put("addressServerFailCount", addressServerFailCount);
return info;
}
private void syncFromAddressUrl() throws Exception {
RestResult<String> result = restTemplate
.get(addressServerUrl, Header.EMPTY, Query.EMPTY, genericType.getType());
if (result.ok()) {
isAddressServerHealth = true;
Reader reader = new StringReader(result.getData());
try {
afterLookup(MemberUtil.readServerConf(EnvUtil.analyzeClusterConf(reader)));
} catch (Throwable e) {
Loggers.CLUSTER.error("[serverlist] exception for analyzeClusterConf, error : {}",
ExceptionUtil.getAllExceptionMsg(e));
}
addressServerFailCount = 0;
} else {
addressServerFailCount++;
if (addressServerFailCount >= maxFailCount) {
isAddressServerHealth = false;
}
Loggers.CLUSTER.error("[serverlist] failed to get serverlist, error code {}", result.getCode());
}
}
class AddressServerSyncTask implements Runnable {
@Override
public void run() {
if (shutdown) {
return;
}
try {
syncFromAddressUrl();
} catch (Throwable ex) {
addressServerFailCount++;
if (addressServerFailCount >= maxFailCount) {
isAddressServerHealth = false;
}
Loggers.CLUSTER.error("[serverlist] exception, error : {}", ExceptionUtil.getAllExceptionMsg(ex));
} finally {
GlobalExecutor.scheduleByCommon(this, DEFAULT_SYNC_TASK_DELAY_MS);
}
}
}
}
したがって、アドレス サーバー モードは、Nacos クラスター ノードの管理コストを大幅に簡素化すると同時に、アドレス サーバーは非常に単純な Web プログラムであり、そのプログラムの安定性は十分に保証されます。
今後の拡張ポイント
クラスタノードの自動拡張および縮小
現在、Nacos におけるクラスタノードの管理は依然として手動操作であるため、将来的には、アドレッシングモードに基づいてクラスタノードを自動管理する機能が実現されることが期待されています。ノード情報が 1 つだけあれば、一定期間内に元の Nacos クラスターに正常に参加できると同時に、生き残っていないノードを自分で発見し、自動的に削除することもできます。クラスター内の使用可能なノードのリストからそれらを選択します。この部分のロジック実装は、実際には Consul の Gossip プロトコルに似ています。