I.はじめに
この章は主に CompletableFuture に関する前回の記事の続きです。さらに詳しく知りたい場合は、まずケースにアクセスしてください:
https://juejin.cn/post/7091132240574283813
CompletableFuture は、シリアルやパラレルなどの一般的な非同期オーケストレーション ソリューションを提供しています、しかし、コールバックや複雑なシーケンスなど、細部にはまだ多くの欠陥があり、拡張されています。
私は以前、Gitee で多くのスターを獲得したオープン ソース ツールである AsyncTool に注目しました。
https://gitee.com/jd-platform-opensource/asyncTool
は Jingdong Retail の上級エンジニアによって書かれており、非常に豊富な機能を提供します。非同期配置関数であり、JD.com の内部テストに合格しており、CompletableFuture のカプセル化と補完であり、試用したところ非常に優れています。
2、使い方
1. はじめに
1) 推奨されません: Maven の導入、これはかなり残念です、客観的な理由により依存関係のダウンロードが失敗することが多いため、推奨されません; 2)、推奨: コードの量が少なく、コアが少ないため、ソース コードを直接ダウンロードします
。クラスとテストクラス。
以下の図に示すように、ダウンロードしたソース コードをコピーし、コア コードを java ディレクトリに配置し、テスト コードを test ディレクトリに配置するだけです。
2. ワーカーを作成する
1) ワーカーは AsyncTool のアイデアであり、クエリ、RPC 呼び出し、その他の時間のかかる操作などのタスクを処理するために特別に使用されます。タスクはワーカーです。2) ワーカーの構築は非常に簡単で、実装する必要があるだけです。 IWorker インターフェイスと ICallback
インターフェイス ;
3)、ここでは、前の記事のケースを取り上げ、二十四節気と星座をそれぞれクエリするためのワーカーを作成します; 4)、構築の開始時に begin メソッドが実行され
、 result メソッドは結果が取得された後に実行され、action メソッドは特定のタスクが処理される場所であり、一般的なビジネスがここに記述されます。defaultValue メソッドはタイムアウト例外が発生したときに返されるデフォルト値を提供します。
1)、二十四節気の働き者
package com.example.async.worker;
import cn.hutool.http.HttpUtil;
import com.jd.platform.async.callback.ICallback;
import com.jd.platform.async.callback.IWorker;
import com.jd.platform.async.executor.timer.SystemClock;
import com.jd.platform.async.worker.WorkResult;
import com.jd.platform.async.wrapper.WorkerWrapper;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 二十四节气worker
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-27 18:01
*/
@Slf4j
public class TwentyFourWorker implements IWorker<String, String>, ICallback<String, String> {
public static final String APPKEY = "xxxxxx";// 你的appkey
public static final String URL = "https://api.jisuapi.com/jieqi/query";
@Override
public void begin() {
// System.out.println(Thread.currentThread().getName() + "- start --" + System.currentTimeMillis());
}
@Override
public void result(boolean success, String param, WorkResult<String> workResult) {
if (success) {
System.out.println("callback twentyFourWorker success--" + SystemClock.now() + "----" + workResult.getResult()
+ "-threadName:" +Thread.currentThread().getName());
} else {
System.err.println("callback twentyFourWorker failure--" + SystemClock.now() + "----" + workResult.getResult()
+ "-threadName:" +Thread.currentThread().getName());
}
}
/**
* 查询二十四节气
*/
@Override
public String action(String object, Map<String, WorkerWrapper> allWrappers) {
String url = URL + "?appkey=" + APPKEY;
String result = HttpUtil.get(url);
// 模拟时长
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
log.error("[二十四节气]>>>> 异常: {}", e.getMessage(), e);
}
return result;
}
@Override
public String defaultValue() {
return "twentyFourWorker";
}
}
2)、コンステレーションワーカー
package com.example.async.worker;
import cn.hutool.http.HttpUtil;
import com.jd.platform.async.callback.ICallback;
import com.jd.platform.async.callback.IWorker;
import com.jd.platform.async.executor.timer.SystemClock;
import com.jd.platform.async.worker.WorkResult;
import com.jd.platform.async.wrapper.WorkerWrapper;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 星座worker
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-27 18:01
*/
@Slf4j
public class ConstellationWorker implements IWorker<String, String>, ICallback<String, String> {
public static final String APPKEY = "xxxxxx";// 你的appkey
public static final String URL = "https://api.jisuapi.com/astro/all";
@Override
public void begin() {
// System.out.println(Thread.currentThread().getName() + "- start --" + System.currentTimeMillis());
}
@Override
public void result(boolean success, String param, WorkResult<String> workResult) {
if (success) {
System.out.println("callback constellationWorker success--" + SystemClock.now() + "----" + workResult.getResult()
+ "-threadName:" +Thread.currentThread().getName());
} else {
System.err.println("callback constellationWorker failure--" + SystemClock.now() + "----" + workResult.getResult()
+ "-threadName:" +Thread.currentThread().getName());
}
}
/**
* 查询星座
*/
@Override
public String action(String object, Map<String, WorkerWrapper> allWrappers) {
String url = URL + "?appkey=" + APPKEY;
String result = HttpUtil.get(url);
// 模拟异常
// int i = 1/0;
// 模拟时长
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
log.error("[星座]>>>> 异常: {}", e.getMessage(), e);
}
return result;
}
@Override
public String defaultValue() {
return "constellationWorker";
}
}
3. 非同期オーケストレーション
1)、新しい AsyncToolService を作成し、その中でワーカーを宣言、ビルド、配置します;
2)、Async.beginWork は非同期タスクを実行するためのもので、パラメーターはタイムアウト時間とワーカーであり、タイムアウト時間を確認するために短く設定できます。効果; 3
) 最後に、カプセル化の結果を返すことができますが、ここでは、デモの時間を節約するために、マップを使用して直接返します。
package com.example.async.service;
import com.example.async.worker.ConstellationWorker;
import com.example.async.worker.TwentyFourWorker;
import com.jd.platform.async.executor.Async;
import com.jd.platform.async.wrapper.WorkerWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
/**
* <p>
* AsyncTools服务
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-27 17:56
*/
@Service
@Slf4j
public class AsyncToolService {
/**
* 异步返回结果
* ---- 方式:AsyncTool并行处理
*
* @return 结果
*/
public Map<String, Object> queryAsync() throws ExecutionException, InterruptedException {
// 声明worker
TwentyFourWorker twentyFourWorker = new TwentyFourWorker();
ConstellationWorker constellationWorker = new ConstellationWorker();
// 构建二十四节气worker
WorkerWrapper<String, String> twentyFourWrapper = new WorkerWrapper.Builder<String, String>()
.worker(twentyFourWorker)
.callback(twentyFourWorker)
.param("0")
.build();
// 构建星座worker
WorkerWrapper<String, String> constellationWrapper = new WorkerWrapper.Builder<String, String>()
.worker(constellationWorker)
.callback(constellationWorker)
.param("1")
.build();
// 开始工作,这里设定超时时间10s,测试时可以设短一点看效果。
Async.beginWork(10000, twentyFourWrapper, constellationWrapper);
// 打印当前线程数
log.debug("----------------- 当前线程数 ----------------");
log.debug(Async.getThreadCount());
// 打印结果
log.debug("----------------- 二十四节气 ----------------");
log.debug("结果: {}", twentyFourWrapper.getWorkResult());
log.debug("----------------- 星座 ----------------");
log.debug("结果: {}", constellationWrapper.getWorkResult());
// 返回
Map<String, Object> map = new HashMap<>();
map.put("twentyFour", twentyFourWrapper.getWorkResult());
map.put("constellation", constellationWrapper.getWorkResult());
// 关闭(spring web类应用不用关闭,否则第二次执行会报线程池异常。)
// Async.shutDown();
return map;
}
}
4. テスト効果
前のケースでは、同期実行の結果は約 10 秒で示されましたが、CompletableFuture の結果は 5 秒以上かかりました。
ここでテストした結果、AsyncTool の結果も約 5 秒で CompletableFuture と同様ですが、AsyncTool の方がリッチなアレンジが可能であることがわかります。
タイムアウトの影響をシミュレートするために、コンスタレーション ワーカーの 1 つの時間のかかるタスクを増やしてみましょう。AsyncTool は、上記の defaultValue メソッドで設定されたデフォルト値を直接返すことがわかります。
3. 共通の取り決め
AsyncTool は実際には、より複雑なものを含む多くの非同期オーケストレーション方法を提供しますが、私が働いてきた中小企業を例に取ると、複雑なオーケストレーションはほとんどなく、最も一般的に使用されるものはパラレルおよびシリアル + パラレルです。 。
AsyncTool の QuickStart.md に簡潔な説明があります: https://gitee.com/jd-platform-opensource/asyncTool/blob/master/QuickStart.md
1)、タスクの並列性
つまり、この記事で使用したレイアウトは、私が普段最もよく使用しているレイアウトです。
2)、シリアル+パラレル
これは実際には next() を介したシリアルおよびパラレル接続であり、一部のシナリオでも使用されます。
3) 他のタスクの結果に依存する
これも非常に一般的なシナリオで、タスク B はタスク A の結果に依存してビジネスを達成し、最終的に戻ります。
AsyncTool は非常に便利な方法も提供します:
1) サービス内でワーカー A を構築するときに ID 名を設定します;
2) ワーカーのアクション メソッドの 2 番目の入力パラメータがすべてのラッパーを含むマップであることがわかります;
3)、ワーカーBのアクションメソッドで、このIDを取得してラッパーを取得し、Aの結果を取得します。
4. ピット回避体験
1. スレッドプールを閉じないでください。
AsyncTool にはすべてのレイアウト メソッドが含まれるテスト クラスが多数用意されており、それらを 1 つずつ表示して検証することができますが、使用時に 1 点注意する必要があります。springboot などの Spring-Web プロジェクトの場合は、Async を手動で実行します。 shutdown() 処理は必要ありませんが、そうでない場合は、一度実行された後、スレッド プールが閉じられるため、多くの人はテスト コードを直接コピーして無視します。
2. カスタムスレッドプール
この問題は AsyncTool の問題で見ることができます. 作成者は Jingdong Retail のビジネスに基づいてどのスレッド プールを使用するかを決定します. 彼らが使用するデフォルトのスレッド プールは newCachedThreadPool, 無制限の長さのスレッド プールであり、再利用の機能があります。著者によれば、Jingdong のシーンのほとんどは、時間がかからず (10 ミリ秒)、同時実行性が高く、瞬間的な影響を与えるシーンであるため、この種のスレッド プールが最も適しているとのことです。
私の経験によると、企業が異なれば事業やプロジェクトも異なります。中小企業は存続するために企業や機関に依存することが多く、サードパーティのメーカーも多数あります。rpc インターフェイスは時間がかかり、制御できないことがよくあります。 Jingdong Retail の要件を満たしていないため、同時実行の特性により、Integer.MAX_VALUE の無制限のコア スレッドを直接使用することは適切ではありません。
中小企業には、カスタム スレッド プールを使用し、自社のハードウェア レベルと圧力テストの結果に応じて最終的なコア スレッド数とタスク キューの長さを調整し、直接拒否やメインスレッドのほうが安全です。
5. コード例
完全なサンプル コードは全員に提供されます。これには、私の超高速データ API のキーが含まれています。1 日あたり 100 回の無料通話、アカウント登録の必要はありません。先着順のテストです。遅い場合は、明日を待つだけです。
リンク: https://pan.baidu.com/doc/share/kJyph2LX076okHVWv38tlw-159275174957933
抽出コード: yqms
元の記事は純粋に手書きです。役に立ったと思われる場合は、高評価をお願いします。
仕事の経験や面白い話を時々共有しますので、気に入ったら注目してください。