Dubbo優雅なダウンタイムの進化の道
12621 ワード
一、前言
『ShutdownHook-Javaエレガントダウンタイムソリューション』では、Javaがエレガントダウンタイムを実現する原理について説明します.次に、上記の知識点に基づいて、Dubboの内部に深く入り込み、Dubboがどのように優雅な停止を実現するかを理解します.
二、Dubboの優雅な停止問題解決すべき問題
優雅なダウンタイムを実現するために、Dubboはいくつかの問題を解決する必要があります.新しいリクエストは、ダウンタイム中のDubboサービスプロバイダに送信できません. サービスプロバイダを閉鎖し、サービス要求を受信した場合、サービスをオフラインにするには、処理が完了する必要があります. サービス消費者が閉鎖されると、すでに発行されたサービス要求は、応答の返信を待つ必要がある.
以上の3つの問題を解決してこそ、ダウンタイムが業務に与える影響を最小限に抑え、優雅なダウンタイムを実現することができます.
三、2.5.X
Dubboの優雅な停止は2.5.Xバージョンで比較的完全に実現され、このバージョンの実現は比較的簡単で、比較的理解しやすい.まずDubbo 2.5.Xバージョンのソースコードをベースに、Dubboが優雅なダウンタイムを実現する方法を見てみましょう.
3.1、優雅なダウンタイムの全体実現方案
エレガントダウンタイムエントリクラスは
ここでは、
上から見ると、Dubboの優雅な停止は主に2つのステップに分かれています.登録抹消センター すべての をログアウト
3.2、登録センターを抹消する
登録抹消センターのソースコードは以下の通りです.
このメソッドは、内部生成レジストリ・センター・サービスをログアウトします.登録センターの内部ロジックをログアウトするのは簡単で、ここではソースコードに深く入り込まず、直接画像で展示します.
ps:ソースコード:
ZKの場合、Dubboは対応するサービスノードを削除し、サブスクリプションをキャンセルします.ZKノード情報の変更により、ZKサービス側はdubbo消費者にこのサービスノードをオフラインするように通知し、最後にサービスを閉じてZKに接続する.
登録センターを通じて、Dubboは直ちに消費者にオフラインサービスを通知することができて、新しい要求もオフラインのノードに送らないで、上述の第1の問題を解決します:新しい要求は更に停止しているDubboサービスプロバイダに送ることができません.
しかし、ここでは、ネットワークの分離により、ZKサービス側とDubboとの接続に一定の遅延が生じる可能性があり、ZK通知は消費側に最初の時間に通知できない可能性があるという弊害がある.このような状況を考慮すると、登録センターをログアウトした後、待機進数を追加します.コードは次のとおりです.
デフォルト待ち時間は10000 msであり、
3.3、Protocolを抹消する
ここでは主に
Dubboのデフォルトでは、Nettyが最下位の通信フレームワークとして使用され、
上記のソースコードでは、まず
3.4、サーバーを閉じる
まず、
ここでは、
次に、ハートビート検出をオフにし、下位通信フレームワークNettyServerをオフにします.ここでは
ここでは、まずビジネス・スレッド・プールを閉じます.このプロセスでは、スレッド・プール内のタスクをできるだけ実行し、スレッド・プールを閉じ、最後にNetty通信の最下位サーバを閉じます.
Dubboのデフォルトでは、ビジネス・スレッド・プールにリクエスト/ハートビートなどのリクエストが送信されます.
サーバを閉じ、スレッドプールが閉じるのを優雅に待つことで、上記の2つ目の問題を解決しました.サービスプロバイダを閉じると、サービスリクエストが受信され、処理が完了してからサービスをオフにする必要があります.
Dubboサービスプロバイダのクローズプロセスを図に示す.
ps:ソースコードのデバッグを容易にするために、Serverクローズコールを添付します.
3.5 Clientを閉じる
Clientクローズ方式はほぼサーバと同じで、ここでは主に処理が要求ロジックを発行したことを紹介し、コードは
Clientを閉じると、応答を受信していない情報リクエストがまだ存在する場合は、すべてのリクエストが応答を受信したことを確認するまで一定時間待機するか、タイムアウト時間を超えるまで待機します.
ps:Dubboリクエストが一時的に存在する
この点で、サービス消費者を閉鎖すると、すでに発行されたサービス要求に応答が戻るのを待つ必要があるという3つ目の問題を解決しました.
Dubboエレガントなダウンタイムの全体的な流れを図に示します.
ps:Clientクローズコールチェーンは以下の通りです.
四、2.7.1 X
Dubboは一般的にSpringフレームワークとともに使用され、2.5.Xバージョンのダウンタイムプロセスは優雅なダウンタイムを失効させる可能性があります.これはSpringフレームワークが閉じると、対応するShutdownHookイベントがトリガーされ、関連するBeanがログアウトされるためです.このプロセスはSpringが率先してダウンタイムを実行すると、関連するBeanをログアウトします.このときDubboクローズイベントでSpringのBeanに引用されると、ダウンタイム中に異常が発生し、優雅なダウンタイムが失効します.
この問題を解決するために、Dubboは2.6.Xバージョンでこのロジックの再構築を開始し、2.7.1.Xバージョンまで反復し続けた.
新しいバージョンでは
Springフレームワークが初期化を開始すると、
五、最後
エレガントなダウンタイムは実現が難しくないように見えますが、中には細部の設計が非常に多く、1つの点で実現に問題があり、エレガントなダウンタイムが失効します.もしあなたも優雅なダウンタイムを実現しているなら、Dubboの実現ロジックを参考にしてみてください.
Dubboシリーズ記事のおすすめ
1.Dubboに登録センターの動作原理を聞かれたら、この文章を彼にあげます.2.サービスの動的発見をどのように実現するか分かりませんか.Dubboがどのようにして3.Dubbo Zkデータ構造を実現したかを見てみましょう.4.Dubboから、Spring XML Schema拡張メカニズムについて話します.
ヘルプ記事
1、kirito大神文章を読むことを強くお勧めします:Dubbo優雅な停止
私の公衆番号に注目してください:プログラムが通じて、日常の乾物のプッシュを獲得します.私のテーマの内容に興味があれば、私のブログにも注目してください:studyidea.cn
『ShutdownHook-Javaエレガントダウンタイムソリューション』では、Javaがエレガントダウンタイムを実現する原理について説明します.次に、上記の知識点に基づいて、Dubboの内部に深く入り込み、Dubboがどのように優雅な停止を実現するかを理解します.
二、Dubboの優雅な停止問題解決すべき問題
優雅なダウンタイムを実現するために、Dubboはいくつかの問題を解決する必要があります.
以上の3つの問題を解決してこそ、ダウンタイムが業務に与える影響を最小限に抑え、優雅なダウンタイムを実現することができます.
三、2.5.X
Dubboの優雅な停止は2.5.Xバージョンで比較的完全に実現され、このバージョンの実現は比較的簡単で、比較的理解しやすい.まずDubbo 2.5.Xバージョンのソースコードをベースに、Dubboが優雅なダウンタイムを実現する方法を見てみましょう.
3.1、優雅なダウンタイムの全体実現方案
エレガントダウンタイムエントリクラスは
AbstractConfig
静的コードにあり、ソースコードは以下の通りである.static {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
if (logger.isInfoEnabled()) {
logger.info("Run shutdown hook now.");
}
ProtocolConfig.destroyAll();
}
}, "DubboShutdownHook"));
}
ここでは、
ShutdownHook
が登録され、ダウンタイムが適用されると、ProtocolConfig.destroyAll()
が呼び出されます.ProtocolConfig.destroyAll()
ソースコードは次のとおりです.public static void destroyAll() {
//
if (!destroyed.compareAndSet(false, true)) {
return;
}
//
AbstractRegistryFactory.destroyAll();
// Wait for registry notification
try {
Thread.sleep(ConfigUtils.getServerShutdownTimeout());
} catch (InterruptedException e) {
logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!");
}
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Protocol.class);
// Protocol
for (String protocolName : loader.getLoadedExtensions()) {
try {
Protocol protocol = loader.getLoadedExtension(protocolName);
if (protocol != null) {
protocol.destroy();
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
上から見ると、Dubboの優雅な停止は主に2つのステップに分かれています.
Protocol
3.2、登録センターを抹消する
登録抹消センターのソースコードは以下の通りです.
public static void destroyAll() {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Close all registries " + getRegistries());
}
// Lock up the registry shutdown process
LOCK.lock();
try {
for (Registry registry : getRegistries()) {
try {
registry.destroy();
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
}
}
REGISTRIES.clear();
} finally {
// Release the lock
LOCK.unlock();
}
}
このメソッドは、内部生成レジストリ・センター・サービスをログアウトします.登録センターの内部ロジックをログアウトするのは簡単で、ここではソースコードに深く入り込まず、直接画像で展示します.
ps:ソースコード:
AbstractRegistry
ZKの場合、Dubboは対応するサービスノードを削除し、サブスクリプションをキャンセルします.ZKノード情報の変更により、ZKサービス側はdubbo消費者にこのサービスノードをオフラインするように通知し、最後にサービスを閉じてZKに接続する.
登録センターを通じて、Dubboは直ちに消費者にオフラインサービスを通知することができて、新しい要求もオフラインのノードに送らないで、上述の第1の問題を解決します:新しい要求は更に停止しているDubboサービスプロバイダに送ることができません.
しかし、ここでは、ネットワークの分離により、ZKサービス側とDubboとの接続に一定の遅延が生じる可能性があり、ZK通知は消費側に最初の時間に通知できない可能性があるという弊害がある.このような状況を考慮すると、登録センターをログアウトした後、待機進数を追加します.コードは次のとおりです.
// Wait for registry notification
try {
Thread.sleep(ConfigUtils.getServerShutdownTimeout());
} catch (InterruptedException e) {
logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!");
}
デフォルト待ち時間は10000 msであり、
dubbo.service.shutdown.wait
を設定することでデフォルトパラメータを上書きできます.10 sはただの経験値で、実際の状況に応じて設定することができます.しかし、この待ち時間の設定にはこだわりがあり、短すぎると消費側がZKの通知を受け取っていないため、プロバイダが停止します.あまり長く設定することもできません.長すぎると、アプリケーションを停止する時間が長くなり、パブリケーション体験に影響します.3.3、Protocolを抹消する
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Protocol.class);
for (String protocolName : loader.getLoadedExtensions()) {
try {
Protocol protocol = loader.getLoadedExtension(protocolName);
if (protocol != null) {
protocol.destroy();
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
loader#getLoadedExtensions
は、Protocol
とDubboProtocol
の2つのサブクラスを返します.InjvmProtocol
はサービス側要求と対話し、DubboProtocol
は内部要求対話に用いられる.アプリケーション呼び出しが自分でDubboサービスを提供する場合、ネットワーク呼び出しは実行されず、内部メソッドを直接実行します.ここでは主に
InjvmProtocol
の内部論理を分析します.DubboProtocol
ソース:public void destroy() {
// Server
for (String key : new ArrayList(serverMap.keySet())) {
ExchangeServer server = serverMap.remove(key);
if (server != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close dubbo server: " + server.getLocalAddress());
}
server.close(ConfigUtils.getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
// Client
for (String key : new ArrayList(referenceClientMap.keySet())) {
ExchangeClient client = referenceClientMap.remove(key);
if (client != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close dubbo connect: " + client.getLocalAddress() + "-->" + client.getRemoteAddress());
}
client.close(ConfigUtils.getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
for (String key : new ArrayList(ghostClientMap.keySet())) {
ExchangeClient client = ghostClientMap.remove(key);
if (client != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close dubbo connect: " + client.getLocalAddress() + "-->" + client.getRemoteAddress());
}
client.close(ConfigUtils.getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
stubServiceMethodsMap.clear();
super.destroy();
}
Dubboのデフォルトでは、Nettyが最下位の通信フレームワークとして使用され、
DubboProtocol#destroy
とServer
に分かれています.Client
は、他の消費者Server
からの要求を受信するために使用される.上記のソースコードでは、まず
Client
を閉じ、新しい要求の受信を停止した後、Server
を閉じる.これにより、サービスが消費者に呼び出される可能性が低減される.3.4、サーバーを閉じる
まず、
Client
が呼び出されます.ソースコードは次のとおりです.public void close(final int timeout) {
startClose();
if (timeout > 0) {
final long max = (long) timeout;
final long start = System.currentTimeMillis();
if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) {
// READ_ONLY
sendChannelReadOnlyEvent();
}
while (HeaderExchangeServer.this.isRunning()
&& System.currentTimeMillis() - start < max) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
}
//
doClose();
server.close(timeout);
}
private void doClose() {
if (!closed.compareAndSet(false, true)) {
return;
}
stopHeartbeatTimer();
try {
scheduled.shutdown();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
ここでは、
HeaderExchangeServer#close
イベントがサービス消費者に送信される.消費者が受け入れた後、このノードを自発的に排除し、他の正常なノードに要求を送信します.これにより、登録センターの通知遅延による影響がさらに低減される.次に、ハートビート検出をオフにし、下位通信フレームワークNettyServerをオフにします.ここでは
READ_ONLY
メソッドが呼び出され、このメソッドは実際にNettyServer#close
で実装される.AbstractServer
ソースコードは次のとおりです.public void close(int timeout) {
ExecutorUtil.gracefulShutdown(executor, timeout);
close();
}
ここでは、まずビジネス・スレッド・プールを閉じます.このプロセスでは、スレッド・プール内のタスクをできるだけ実行し、スレッド・プールを閉じ、最後にNetty通信の最下位サーバを閉じます.
Dubboのデフォルトでは、ビジネス・スレッド・プールにリクエスト/ハートビートなどのリクエストが送信されます.
サーバを閉じ、スレッドプールが閉じるのを優雅に待つことで、上記の2つ目の問題を解決しました.サービスプロバイダを閉じると、サービスリクエストが受信され、処理が完了してからサービスをオフにする必要があります.
Dubboサービスプロバイダのクローズプロセスを図に示す.
ps:ソースコードのデバッグを容易にするために、Serverクローズコールを添付します.
DubboProtocol#destroy
->HeaderExchangeServer#close
->AbstractServer#close
->NettyServer#doClose
3.5 Clientを閉じる
Clientクローズ方式はほぼサーバと同じで、ここでは主に処理が要求ロジックを発行したことを紹介し、コードは
AbstractServer#close
にある.// graceful close
public void close(int timeout) {
if (closed) {
return;
}
closed = true;
if (timeout > 0) {
long start = System.currentTimeMillis();
//
while (DefaultFuture.hasFuture(channel)
&& System.currentTimeMillis() - start < timeout) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
}
close();
}
Clientを閉じると、応答を受信していない情報リクエストがまだ存在する場合は、すべてのリクエストが応答を受信したことを確認するまで一定時間待機するか、タイムアウト時間を超えるまで待機します.
ps:Dubboリクエストが一時的に存在する
HeaderExchangeChannel#close
Map中なので、Mapを簡単に判断すればリクエストに応答があるかどうかを知ることができます.この点で、サービス消費者を閉鎖すると、すでに発行されたサービス要求に応答が戻るのを待つ必要があるという3つ目の問題を解決しました.
Dubboエレガントなダウンタイムの全体的な流れを図に示します.
ps:Clientクローズコールチェーンは以下の通りです.
DubboProtocol#close
->ReferenceCountExchangeClient#close
->HeaderExchangeChannel#close
->AbstractClient#close
四、2.7.1 X
Dubboは一般的にSpringフレームワークとともに使用され、2.5.Xバージョンのダウンタイムプロセスは優雅なダウンタイムを失効させる可能性があります.これはSpringフレームワークが閉じると、対応するShutdownHookイベントがトリガーされ、関連するBeanがログアウトされるためです.このプロセスはSpringが率先してダウンタイムを実行すると、関連するBeanをログアウトします.このときDubboクローズイベントでSpringのBeanに引用されると、ダウンタイム中に異常が発生し、優雅なダウンタイムが失効します.
この問題を解決するために、Dubboは2.6.Xバージョンでこのロジックの再構築を開始し、2.7.1.Xバージョンまで反復し続けた.
新しいバージョンでは
DefaultFuture
が追加され、Spring ShutdownHookListener
インタフェースを継承し、Spring関連イベントをリスニングします.ここでApplicationListener
はSpringクローズイベントのみをリスニングし、Springがクローズを開始するとShutdownHookListener
の内部ロジックがトリガーされる.
public class SpringExtensionFactory implements ExtensionFactory {
private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);
private static final Set CONTEXTS = new ConcurrentHashSet();
private static final ApplicationListener SHUTDOWN_HOOK_LISTENER = new ShutdownHookListener();
public static void addApplicationContext(ApplicationContext context) {
CONTEXTS.add(context);
if (context instanceof ConfigurableApplicationContext) {
// ShutdownHook
((ConfigurableApplicationContext) context).registerShutdownHook();
// AbstractConfig ShutdownHook
DubboShutdownHook.getDubboShutdownHook().unregister();
}
BeanFactoryUtils.addApplicationListener(context, SHUTDOWN_HOOK_LISTENER);
}
// ApplicationListener,
private static class ShutdownHookListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextClosedEvent) {
DubboShutdownHook shutdownHook = DubboShutdownHook.getDubboShutdownHook();
shutdownHook.doDestroy();
}
}
}
}
Springフレームワークが初期化を開始すると、
ShutdownHookListener
ロジックがトリガーされ、SpringExtensionFactory
登録AbstractConfig
がログアウトされ、ShutdownHook
が追加されます.これで上の『ダブルホック』問題を完璧に解決します.五、最後
エレガントなダウンタイムは実現が難しくないように見えますが、中には細部の設計が非常に多く、1つの点で実現に問題があり、エレガントなダウンタイムが失効します.もしあなたも優雅なダウンタイムを実現しているなら、Dubboの実現ロジックを参考にしてみてください.
Dubboシリーズ記事のおすすめ
1.Dubboに登録センターの動作原理を聞かれたら、この文章を彼にあげます.2.サービスの動的発見をどのように実現するか分かりませんか.Dubboがどのようにして3.Dubbo Zkデータ構造を実現したかを見てみましょう.4.Dubboから、Spring XML Schema拡張メカニズムについて話します.
ヘルプ記事
1、kirito大神文章を読むことを強くお勧めします:Dubbo優雅な停止
私の公衆番号に注目してください:プログラムが通じて、日常の乾物のプッシュを獲得します.私のテーマの内容に興味があれば、私のブログにも注目してください:studyidea.cn