ナコスクライアントの構成メカニズム
バネ仕掛けのリモート設定
ナコスのクライアント構成を見る前に、方法について私たちは、ばね荷重リモート設定を見てみましょう。スプリングは、拡張インターフェースPropertySourceLocator負荷リモート構成を提供します。簡単な例を見てみましょう:
PropertySourceLocatorを達成
public class GreizPropertySourceLocator implements PropertySourceLocator {
@Override
public PropertySource<?> locate(Environment environment) {
// 自定义配置,来源可以从任何地方
Map<String, Object> source = new HashMap<>();
source.put("userName", "Greiz");
source.put("userAge", 18);
return new MapPropertySource(GreizPropertySource.PROPERTY_NAME, source);
}
}
PropertySourceLocator 1つのインターフェースのみ、我々は、データベース構成ファイルから取得したり設定を取得するなど、インターフェイスにロードされたカスタム構成を実装することができます。
springbootスタートアップコンフィギュレーションクラス
@Configuration
public class GreizConfigBootstrapConfiguration {
@Bean
public GreizPropertySourceLocator greizPropertySourceLocator() {
return new GreizPropertySourceLocator();
}
}
META-INF / spring.factoriesは、指定されたロード・クラスがスタート追加で
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.greiz.demo.config.GreizConfigBootstrapConfiguration
使用
@Component
public class Greiz {
@Value("${userName}")
private String name;
@Value("${userAge}")
private Integer age;
// 省getter/setter
}
地元の構成と同様。
春には、リモートコンフィギュレーション・プロセスを開始するためにロード
スタートprepareContextフェーズは春にカスタム設定をロードするすべてのPropertySourceLocator実装クラス、環境管理への最終的な追加を行います。
ナコスクライアント
リモート設定を引いて
ナコスクライアントの起動時にリモート設定をロードし、上記の方法を使用することです。ここでは、ソースに応じて、特定のプロセスを見てください。NacosPropertySourceLocatorは春の起動時にそれが見つけメソッドを呼び出します、PropertySourceLocatorを実現しました。
public PropertySource<?> locate(Environment env) {
// 1. 创建一个跟远程打交道的对象NacosConfigService
ConfigService configService = nacosConfigProperties.configServiceInstance();
... 省略代码
// 2. 操作NacosPropertySource对象,下面三个方法最终都会调用该对象build
nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
// 3.
String name = nacosConfigProperties.getName();
String dataIdPrefix = nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
// 从远程获取的properties会存放到该类,最终放到Environment中
CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);
// 加载公共模块配置
loadSharedConfiguration(composite);
// 加载扩展配置
loadExtConfiguration(composite);
// 加载独有配置
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
1時 - ConfigServiceを作成し、オブジェクトのインスタンスは、反射NacosConfigServiceによって作成されます。このクラスは、ナコスサーバーバット大切な人とのナコスクライアントです。教室の後ろの周りに詳細になります。
2 - キャッシュがリフレッシュされるところNacosPropertySourceBuilderインスタンスを作成し、NacosPropertySourceを構築してキャッシュするために使用、使用されます。
3時 - 負荷設定、一般的な構成のため - >拡張構成 - >プライベートコンフィギュレーション、フロントカバーと同じキーの背後にある場合。データID、デフォルトのルール$ {} spring.application.name .propertiesファイルを生成します。
ロード3つの構成は、最終的にNacosPropertySourceBuilder.build()メソッドを呼び出します。
NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {
// 加载配置
Properties p = loadNacosData(dataId, group, fileExtension);
NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId, propertiesToMap(p), new Date(), isRefreshable);
// 缓存nacosPropertySource
NacosPropertySourceRepository.collectNacosPropertySources(nacosPropertySource);
return nacosPropertySource;
}
構成パッケージnacosPropertySource、およびキャッシュをロードした後。
メインロジックNacosPropertySourceBuilder.loadNacosData()です。
private Properties loadNacosData(String dataId, String group, String fileExtension) {
// 获取配置
String data = configService.getConfig(dataId, group, timeout);
... 省略代码
// .properties扩展名
if (fileExtension.equalsIgnoreCase("properties")) {
Properties properties = new Properties();
properties.load(new StringReader(data));
return properties;
} else if (fileExtension.equalsIgnoreCase("yaml") || fileExtension.equalsIgnoreCase("yml")) {// .yaml或者.yml扩展名
YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
yamlFactory.setResources(new ByteArrayResource(data.getBytes()));
return yamlFactory.getObject();
}
return EMPTY_PROPERTIES;
}
拡張に応じて統一的特性へのデータ分析へのリモートアクセス。ナコスコンソール設定サポートのプロパティと2つの拡張YAML。
本当にリモート設定がNacosConfigService.getConfigで取得する()、()getConfigInnerを呼び出します。
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
group = null2defaultGroup(group);
ParamUtils.checkKeyParam(dataId, group);
ConfigResponse cr = new ConfigResponse();
cr.setDataId(dataId);
cr.setTenant(tenant);
cr.setGroup(group);
// 1. 优先使用failvoer配置
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
if (content != null) {
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
}
try {
// 2. 服务器获取配置
content = worker.getServerConfig(dataId, group, tenant, timeoutMs);
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
} catch (NacosException ioe) {
if (NacosException.NO_RIGHT == ioe.getErrCode()) {
throw ioe;
}
}
// 3. 当服务器挂了就拿本地快照
content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
}
1時 - failvoerから設定を取得するための優先順位、追加するために背中を知ってもらう、文書が生成する方法である、と私は非常に明確でありません。
2時 - ナコスサービスから設定を取得します。
それはローカルスナップショット・ファイルから取得するために失敗した場合は2から3時。このファイルは、更新プログラムは、ファイルの更新に対応している場合は、最初、リモートコンフィギュレーションファイルを読み取り、その後、構成の更新のポーリングによって生成されます。
ダーティ・アクセス・サービス・インターフェースは確かにクライアントワーカーClientWorkerを必要とし、次はNacosConfigService.getConfig()ClientWorker.getServerConfig()を呼び出します。
public String getServerConfig(String dataId, String group, String tenant, long readTimeout)
throws NacosException {
// 就是这么简单http请求获取的配置
HttpResult result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
... 省略代码
// 写本地文件快照
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.content);
...省略代码
return result.content;
}
上記のコードは** kのfを叫ぶしようとしていないリモート設定を見て取得し、どのように簡単なことです!!!はい、httpリクエストのhttpで:// IP:ポート/ V1 / CS /のconfigsインタフェース、ナコスページビューとコンソールと同じです。
このナコスクライアントリモート設定およびカプセル化された環境のエンドの読み取りを開始します。
ロングポーリングは、更新プログラムを入手します
前のセクションは、プロジェクトナコスクライアントのリモートコンフィギュレーション・プロセス分析の開始にロードされ、このセクションでは、分析でプロジェクトを実行しますナコスクライアントの設定の変更はどのような種類の通知を保持しています。
前述のNacosConfigServiceは、更新プロセスの動作を設定する方法には以下のクラスを見て、ナコスクライアントナコスサーバードッキングブリッジです。NacosConfigServiceコンストラクタを見てください。
public NacosConfigService(Properties properties) throws NacosException {
... 省略代码
// 初始化 namespace
initNamespace(properties);
// 查询服务列表变化情况
agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
agent.start();
// 配置更新解决方案在这里面
worker = new ClientWorker(agent, configFilterChainManager, properties);
}
コンストラクタで初期化エンコード、名前空間、HttpAgentとClientWorker。
HttpAgentは、http、アドレスリストとクライアントがローカルに合意されたメンテナンスサービスを介してアドレスサービスプロキシクラスのリストを取得することです。
ClientWorkerは、サーバー構成とクライアント構成一貫性のある労働者を維持することです。前の初期化は、リモート設定も対象になってしまいます。
内部ClientWorkerはそれを更新し、クライアントの特性を維持する方法ですか?何をしたClientWorkerコンストラクタを見て。
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) {
...省略代码
executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
...省略代码
});
executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
...省略代码
});
// 每10毫秒检查一遍配置
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
checkConfigInfo();
} catch (Throwable e) {
LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
}
}
}, 1L, 10L, TimeUnit.MILLISECONDS);
}
ClientWorkerコンストラクタは2つのスレッド・プールを作成します。エグゼキュータは、一度10ミリ秒ごとcheckConfigInfo()、スケジュールされたタスクを作成します。私たちは読みExecutorServiceのの役割はどのようなものです。
public void checkConfigInfo() {
// 分任务 向上取整为批数
int listenerSize = cacheMap.get().size();
int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
if (longingTaskCount > currentLongingTaskCount) {
for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
executorService.execute(new LongPollingRunnable(i));
}
currentLongingTaskCount = longingTaskCount;
}
}
段階的にタスクにExecutorServiceのデフォルト設定3000にタスク分割を実行します。エグゼキュータとExecutorServiceのネッティーはブーイングと労働者のようではないでしょうか?原子炉モード、労働者の明確な区分。
LongPollingRunnable ClientWorkerは、Runnableインタフェースを実装するクラスのメンバーです。run()メソッドを見てください。
public void run() {
List<CacheData> cacheDatas = new ArrayList<CacheData>();
List<String> inInitializingCacheList = new ArrayList<String>();
try {
// 1. 只处理该任务中的配置并且检查failover配置
for (CacheData cacheData : cacheMap.get().values()) {
if (cacheData.getTaskId() == taskId) {
cacheDatas.add(cacheData);
try {
checkLocalConfig(cacheData);
if (cacheData.isUseLocalConfigInfo()) {
cacheData.checkListenerMd5();
}
} catch (Exception e) {
LOGGER.error("get local config info error", e);
}
}
}
// 2. 把客户端的MD5值跟服务端的MD5比较,把不一样的配置以 "example.properties+DEFAULT_GROUP"方式返回
List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
// 3. 把有更新的配置重新从服务端拉取配置内容
for (String groupKey : changedGroupKeys) {
String[] key = GroupKey.parseKey(groupKey);
String dataId = key[0];
String group = key[1];
String tenant = null;
if (key.length == 3) {
tenant = key[2];
}
try {
String content = getServerConfig(dataId, group, tenant, 3000L);
CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
// 修改客户端本地值并且重新计算该对象的md5值
cache.setContent(content);
} catch (NacosException ioe) {
...省略代码
}
}
for (CacheData cacheData : cacheDatas) {
if (!cacheData.isInitializing() || inInitializingCacheList.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
// 4. 根据md5值检查是否更新,如果更新通知listener
cacheData.checkListenerMd5();
cacheData.setInitializing(false);
}
}
inInitializingCacheList.clear();
// 5. 又把this放进线程池中,形成一个长轮询检查客户端和服务端配置一致性
executorService.execute(this);
} catch (Throwable e) {
executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
}
}
1--スクリーニングでは、タスクの構成に属し、フェイルオーバー構成を確認してください。
2で - パラメータサーバのHTTPなどの構成 "データIDグループMD5テナントの\ r \ n" スプライシング要求後:// IP:ポート/ V1 / CS /コンフィグ/リスナーインターフェース。サーバが返すように「example.properties + DEFAULT_GROUP」道に新しいコンフィギュレーションを返します
3 - リストに従って、サーバーのHTTPへの要求を横断2で返さ:// IP:ポート/ V1 / CS /コンフィグ・インターフェースを最新の設定のために。その後CacheDataコンテンツ値を更新し、MD5値を更新します。
4 - 新しいMD5値CacheData前と比較して、リスナーの更新された値と同じでない場合には、気づきます。次のセクションでは、内部で説明します。
5時 - バックスレッドプールにRunnableオブジェクトは、ロングポーリングを形成します。
このセクションでは、ナコスクライアント構成はほぼリアルタイム同期サーバとの接触を維持する方法である分析します。短いロングポーリング+ http接続することによって。
リフレッシュ値
このセクションの開始前に、クラス構造をCacheData、我々は時間を見て上に表示されます。
public class CacheData {
private final String name;
private final ConfigFilterChainManager configFilterChainManager;
public final String dataId;
public final String group;
public final String tenant;
// 监听列表
private final CopyOnWriteArrayList<ManagerListenerWrap> listeners;
// 内容md5值
private volatile String md5;
// 是否使用本地配置
private volatile boolean isUseLocalConfig = false;
// 本地版本号
private volatile long localConfigLastModified;
private volatile String content;
// 长轮询中分段任务ID
private int taskId;
private volatile boolean isInitializing = true;
...省略代码
}
名前によると、CacheData構成データキャッシュオブジェクトを学習することができます。オブジェクトのMD5の変更がリスナーを横断することにより、誰リスナーを通知する際にリスナーは、リスニングリストBOで、より興味深い属性。
サーバから取得した後、MD5チェックが更新された設定を持つようにする前に、CacheData.checkListenerMd5()メソッドを呼び出します。
void checkListenerMd5() {
for (ManagerListenerWrap wrap : listeners) {
if (!md5.equals(wrap.lastCallMd5)) {
safeNotifyListener(dataId, group, content, md5, wrap);
}
}
}
class ManagerListenerWrap {
final Listener listener;
String lastCallMd5 = CacheData.getMd5String(null);
... 省略代码
}
lastCallMd5のManagerListenerWrapのMD5値は、lastCallMd5値MD5のCacheDataとManagerListenerWrap同じでない場合は、構成の更新古い構成です。私たちは、リスナーが更新されません通知する必要があります。
private void safeNotifyListener(final String dataId, final String group, final String content, final String md5, final ManagerListenerWrap listenerWrap) {
final Listener listener = listenerWrap.listener;
Runnable job = new Runnable() {
@Override
public void run() {
... 省略代码
// 调用监听者的方法
listener.receiveConfigInfo(contentTmp);
listenerWrap.lastCallMd5 = md5;
... 省略代码
}
};
try {
if (null != listener.getExecutor()) {
listener.getExecutor().execute(job);
} else {
job.run();
}
} catch (Throwable t) {
}
}
receiveConfigInfoリスナーの()メソッドを呼び出し、その後、ManagerListenerWrapのlastCallMd5値を変更します。
このセクションでは、サーバーの設定から更新を取得するために戻ってリスナーを設定するには、ここに通知を分析します。しかし、リスナーは時に登録されていますか?次に、我々はCacheDataプロセスに登録されたリスナーを分析し続けます。
NacosContextRefresherはApplicationListenerを実現しました
private void registerNacosListener(final String group, final String dataId) {
Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() {
// 通知监听者调用的就是这个方法啦
@Override
public void receiveConfigInfo(String configInfo) {
refreshCountIncrement();
String md5 = "";
if (!StringUtils.isEmpty(configInfo)) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md5 = new BigInteger(1, md.digest(configInfo.getBytes("UTF-8"))).toString(16);
}
catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e);
}
}
refreshHistory.add(dataId, md5);
// spring的刷新事件通知,刷新监听者会被执行
applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Nacos config"));
}
@Override
public Executor getExecutor() {
return null;
}
});
// 注册本监听者
configService.addListener(dataId, group, listener);
...省略代码
}
NacosConfigService.addListener()によってリスナーを登録。
NacosConfigService.addListener():
public void addListener(String dataId, String group, Listener listener) throws NacosException {
worker.addTenantListeners(dataId, group, Arrays.asList(listener));
}
またはClientWorkerに引き渡さ
ClientWorker.addTenantListeners()
public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners) throws NacosException {
group = null2defaultGroup(group);
String tenant = agent.getTenant();
CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);
for (Listener listener : listeners) {
cache.addListener(listener);
}
}
CacheDataへClientWorkerリスナーが登録されて。
要約で実行しているシステムのプロセス構成を更新します。
- あなたは、ローカル更新CacheDataに登録し、リスナーを起動すると。
- ClientWorkerロングポーリング同期サーバーの構成を更新します。
- 図2は、更新された内容CacheDataリセットを取得します。
- Listener.receiveConfigInfo(最大登録CacheDataコールバック1)
- リスナー最終イベント通知スプリングリフレッシュ、完全なコンテキストプロパティ値を更新します。
概要
長いアクセス設定の更新のポーリングタイミングを使用してナコスコンフィグクライアントとナコスコンフィグサーバのhttpリクエスト、このデザインはシンプルで、コンフィグクライアントナコス構成サーバー構造。Serverクライアントは、もはや接続モードあまりにも多くの圧力ではありません。