Lambdaによるピット
3894 ワード
1.背景
先週、zk接続が遅いというパートナーのフィードバックがありました.zk接続を整理するキーロジックは次のとおりです.
上記のコードにより、
2.分析
ローカル再生後,
クライアントは実際にはすぐに
Javaにおけるlambdaの実現方法を知って、事は明らかになった.
簡単に言えば、jvmは、lambda式をクラスに変換するメソッド
もう一度整理します:ビジネススレッド:は、静的方法 マウント中に静的メンバーinstanceがマウントされ、 は、 が完了する.
zkイベントスレッド: しかし、この時点で トラフィックスレッドが
この過程で、2つのスレッドは互いに待機し(デッドロックに似ているがデッドロックではない)、ビジネススレッドがタイムアウトするまでこの局面を解消していることがわかる.
3.改善
修正
先週、zk接続が遅いというパートナーのフィードバックがありました.zk接続を整理するキーロジックは次のとおりです.
public class ClientZkAgent {
//
private static final ClientZkAgent instance = new ClientZkAgent();
private ZooKeeper zk; //zk
private ClientZkAgent() {
connect(); // zk
}
public static ClientZkAgent getInstance() {
return instance;
}
/**
* zk : zookeeper , zk ,
* , zookeeper EventThread
*/
private void connect() {
CountDownLatch semaphore = new CountDownLatch(1);
zk = new ZooKeeper(zkHost, timeout, watchEvent -> { // #_1
switch (e.getState()) {
case SyncConnected:
semaphore.countDown();
break;
// ....
}
});
semaphore.await(10000, TimeUnit.MILLISECONDS);
}
}
上記のコードにより、
ClientZkAgent.getInstance
が初めて呼び出される場合、10 sの時間がかかり、この時間はsemaphore
のタイムアウト時間に相当する.その間、世界全体が停滞したようだ.2.分析
ローカル再生後,
jstack
によりシステムの停滞期間のスレッドスタックが得られ,このときzookeeper
のEventThread
には比較的奇妙な現象があることが分かった."main-EventThread" #13 daemon prio=5 os_prio=0 tid=0x000000001fe36800 nid=0xf0c in Object.wait() [0x000000002032f000]
java.lang.Thread.State: RUNNABLE
at com.github.dapeng.registry.zookeeper.ClientZkAgent.lambda$connect$0(ClientZkAgent.java:154)
at com.github.dapeng.registry.zookeeper.ClientZkAgent$$Lambda$1/116211441.process(Unknown Source)
at org.apache.zookeeper.ClientCnxn$EventThread.processEvent(ClientCnxn.java:533)
at org.apache.zookeeper.ClientCnxn$EventThread.run(ClientCnxn.java:508)
Locked ownable synchronizers:
- None
クライアントは実際にはすぐに
zookeeper
に接続され、戻ってSyncConnected
イベントを生成し、EventThread
はすでにWatcher.process
メソッドをコールバックしているが、イベントスレッドは上の#_1
の位置でずっと下に行けないようだ.同時に、lambda式はClientZkAgent
のメソッドになった.lambda$connect$0
.Javaにおけるlambdaの実現方法を知って、事は明らかになった.
簡単に言えば、jvmは、lambda式をクラスに変換するメソッド
lambda${method}${seq}
(methodは、上記のconnectメソッドなどのlambdaが存在するメソッド名)と、動的エージェントによってlambda式が表す特定のインタフェースを実現するエージェントクラスを生成し、エージェントクラスでlambda${method}${seq}
を呼び出す.上記の例では、生成されたエージェントクラスは次のようになります.final class ClientZkAgent$$Lambda$1 implements Watcher {
final ClientZkAgent clientZkAgent;
public void process(WatchedEvent event) {
clientZkAgent.lambda$connect$0(event);
}
}
もう一度整理します:ビジネススレッド:
ClientZkAgent.getInstance()
によってインスタンスを取得し、最初のアクセス時にクラスClientZkAgent
のマウントがトリガーされる.ClientZkAgent
オブジェクトが作成されます.ClientZkAgent
の構造関数にzkを接続し、CountdownLatch
を介して閉塞状態に入る.注意この時点でクラスのマウントはまだ完了していません.CountdownLatch
タイムアウト後にオブジェクトの初期化とクラス全体のロードzkイベントスレッド:
SyncConnected
イベントがトリガーされた後、ClientZkAgent.lambda$connect$0(event)
が呼び出され、ビジネススレッド(lambdaにおける起動ロジック)を起動しようとする.ClientZkAgent
はまだロードが完了していないため、イベントスレッドはクラスロードプロセスの終了を待つしかない.ClientZkAgent
をロードした後、イベントスレッドはイベントの処理を完了する.この過程で、2つのスレッドは互いに待機し(デッドロックに似ているがデッドロックではない)、ビジネススレッドがタイムアウトするまでこの局面を解消していることがわかる.
3.改善
修正
ClientZkAgent
の初期化ロジックは次のとおりです.public class ClientZkAgent {
//
private static final ClientZkAgent instance = new ClientZkAgent();
private ZooKeeper zk; //zk
private ClientZkAgent() {
}
public static ClientZkAgent getInstance() {
if (instance.zk == null) {
synchronized(ClientZkAgent.class) {
if (instance.zk == null) {
instance.connect();
}
}
}
return instance;
}