1.はじめに
Schedulerx2.0のクライアントは、実行、こうしたログ統一されたフレームワークなどの作業、各種の分散、ユーザーがジャーパッケージschedulerx-労働者に頼ることができます提供し、コードの簡単な数行が提供するschedulerx2.0プログラミングモデルは、高を達成することができます信頼性の高い動作と保守が実行エンジンを配布することができます。
この記事では、私がこの記事を読んだ後、あなたが効率的な分散作業を書くことができると信じて、原則とベストプラクティスschedulerx2.0に基づく分散実行エンジンの導入に焦点を当てて、多分、数倍の速度を向上させることができます:)
2.スケーラブルな実行エンジン
ワーカー全体的なアーキテクチャ糸参照アーキテクチャはTaskMaster、コンテナ、プロセッサ3層に分かれています。
- TaskMaster:AppMasterの類似した糸、スケーラブルな分散実行フレームワーク、jobInstanceのライフサイクル全体の管理、コンテナのリソース管理だけでなく、フェイルオーバー機能をサポートしています。デフォルトの実装StandaloneTaskMaster(単一の実行)、BroadcastTaskMaster(放送実行)、MapTaskMaster(並列計算、メモリ、グリッド、グリッドコンピューティング)、MapReduceTaskMaster(並列計算、メモリ、グリッド、グリッドコンピューティング)。
- コンテナ:ようにビジネスロジックの実装のコンテナフレームワーク、サポートスレッド/プロセス/ドッキングウィンドウ/俳優と。
- プロセッサ:ビジネス・ロジック・フレームワーク、異なるプロセッサは、タスクの種類を表します。
MapTaskMasterに例えば、以下に示すように、原則について:
分散プログラミングモデルマップの3モデル
Schedulerx2.0は、分散プログラミングモデルをご用意しております(記事はより多くのビジネスシナリオのためのMapReduceモデルをご紹介します後)、この記事では、地図のモデルを紹介し、コードの簡単な数行は、複数の大規模なデータに配布することができます使用することは非常に簡単で、バッチを実行しているマシン上に分散。
異なるバッチ実行のシナリオでは、モデルの仕事は、並列コンピューティング、記憶グリッド、グリッドコンピューティングの3つのモードが提供マップ。
- 並列コンピューティング:以下サブタスク300、サブタスクのリストがあります。
- 内存网格:子任务5W以下,无子任务列表,速度快。
- 网格计算:子任务100W以下,无子任务列表。
4. 并行计算原理
因为并行任务具有子任务列表:
如上图,子任务列表可以看到每个子任务的状态、机器,还有重跑、查看日志等操作。
因为并行计算要做到子任务级别的可视化,并且worker挂了、重启还能支持手动重跑,就需要把task持久化到server端:
如上图所示:
- server触发jobInstance到某个worker,选中为master。
- MapTaskMaster选择某个worker执行root任务,当执行map方法时,会回调MapTaskMaster。
- MapTaskMaster收到map方法,会把task持久化到server端。
- 同时,MapTaskMaster还有个pull线程,不停拉取INIT状态的task,并派发给其他worker执行。
5. 网格计算原理
网格计算要支持百万级别的task,如果所有任务都往server回写,server肯定扛不住,所以网格计算的存储实际上是分布式在用户自己的机器上的:
如上图所示:
- server触发jobInstance到某个worker,选中为master。
- MapTaskMaster选择某个worker执行root任务,当执行map方法时,会回调MapTaskMaster。
- MapTaskMaster收到map方法,会把task持久化到本地h2数据库。
- 同时,MapTaskMaster还有个pull线程,不停拉取INIT状态的task,并派发给其他worker执行。
6. 最佳实践
6.1 需求
举个例子:
- 读取A表中status=0的数据。
- 处理这些数据,插入B表。
- 把A表中处理过的数据的修改status=1。
- 数据量有4亿+,希望缩短时间。
6.2 反面案例
我们先看下如下代码是否有问题?
public class ScanSingleTableProcessor extends MapJobProcessor {
private static int pageSize = 1000;
@Override
public ProcessResult process(JobContext context) {
String taskName = context.getTaskName();
Object task = context.getTask();
if (WorkerConstants.MAP_TASK_ROOT_NAME.equals(taskName)) {
int recordCount = queryRecordCount();
int pageAmount = recordCount / pageSize;//计算分页数量
for(int i = 0 ; i < pageAmount ; i ++) {
List<Record> recordList = queryRecord(i);//根据分页查询一页数据
map(recordList, "record记录");//把子任务分发出去并行处理
}
return new ProcessResult(true);//true表示执行成功,false表示失败
} else if ("record记录".equals(taskName)) {
//TODO
return new ProcessResult(true);
}
return new ProcessResult(false);
}
}
如上面的代码所示,在root任务中,会把数据库所有记录读取出来,每一行就是一个Record,然后分发出去,分布式到不同的worker上去执行。逻辑是没有问题的,但是实际上性能非常的差。结合网格计算原理,我们把上面的代码绘制成下面这幅图:
如上图所示,root任务一开始会全量的读取A表的数据,然后会全量的存到h2中,pull线程还会全量的从h2读取一次所有的task,还会分发给所有客户端。所以实际上对A表中的数据:
- 全量读2次
- ライトワンスの全額
- 送信時間の全額
この効率は非常に低いです。
6.3正例
ここでは、コードの肯定的な例です:
public class ScanSingleTableJobProcessor extends MapJobProcessor {
private static final int pageSize = 100;
static class PageTask {
private int startId;
private int endId;
public PageTask(int startId, int endId) {
this.startId = startId;
this.endId = endId;
}
public int getStartId() {
return startId;
}
public int getEndId() {
return endId;
}
}
@Override
public ProcessResult process(JobContext context) {
String taskName = context.getTaskName();
Object task = context.getTask();
if (taskName.equals(WorkerConstants.MAP_TASK_ROOT_NAME)) {
System.out.println("start root task");
Pair<Integer, Integer> idPair = queryMinAndMaxId();
int minId = idPair.getFirst();
int maxId = idPair.getSecond();
List<PageTask> taskList = Lists.newArrayList();
int step = (int) ((maxId - minId) / pageSize); //计算分页数量
for (int i = minId; i < maxId; i+=step) {
taskList.add(new PageTask(i, (i+step > maxId ? maxId : i+step)));
}
return map(taskList, "Level1Dispatch");
} else if (taskName.equals("Level1Dispatch")) {
PageTask record = (PageTask)task;
long startId = record.getStartId();
long endId = record.getEndId();
//TODO
return new ProcessResult(true);
}
return new ProcessResult(true);
}
@Override
public void postProcess(JobContext context) {
//TODO
System.out.println("all tasks is finished.");
}
private Pair<Integer, Integer> queryMinAndMaxId() {
//TODO select min(id),max(id) from xxx
return null;
}
}
上記のコードに示すように、
- 各タスクは、記録レコードの行全体ではなく、PageTask、内部の二つのフィールド、startIdとendId。
- ルートタスク、テーブル全体を読み取るのない量が、minIdとMAXIDテーブル全体を読み取り、次いでPageTaskページングを構築します。例えばTASK1がPageTask [1,1000]を表し、タスク2はPageTask [1001,2000]を表します。各処理タスク異なるデータテーブル。
- 次のタスクでは、GetがPageTaskである場合、テーブルにデータ処理部IDを記載。
上記のコードとグリッド原理は、この画像は、以下になる:
上記のように、
- 表全額は一度だけ読まれる必要があります。
- サブタスク千負例未満の数、何千回。
- サブタスクの体は、大規模なフィールドでrecod場合、非常に小さいですが、また何千、万回以下です。
時間は、H2メモリ上の倍以下の圧力、実行速度の数千人だけでなく、はるかに高速でなく、それを確実にするためにできるいくつかの少数の訪問は、地元のH2データベースがたむろ入れませんテーブルの上に、要約すると。