-
Notifications
You must be signed in to change notification settings - Fork 10
/
atom.xml
552 lines (326 loc) · 888 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>芋道源码 —— 纯源码解析BLOG</title>
<subtitle>愿半生编码,如一生老友!</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://www.iocoder.cn/"/>
<updated>2017-09-17T18:56:22.000Z</updated>
<id>http://www.iocoder.cn/</id>
<author>
<name>王文斌</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>TCC-Transaction 源码分析 —— 项目实战</title>
<link href="http://www.iocoder.cn/TCC-Transaction/http-sample/"/>
<id>http://www.iocoder.cn/TCC-Transaction/http-sample/</id>
<published>2018-03-14T16:00:00.000Z</published>
<updated>2017-09-17T18:56:22.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p><ul><li><a href="#1-%E6%A6%82%E8%BF%B0">1. 概述</a></li><li><a href="#2-%E5%AE%9E%E4%BD%93%E7%BB%93%E6%9E%84">2. 实体结构</a><ul><li><a href="#21-%E5%95%86%E5%9F%8E%E6%9C%8D%E5%8A%A1">2.1 商城服务</a></li><li><a href="#22-%E8%B5%84%E9%87%91%E6%9C%8D%E5%8A%A1">2.2 资金服务</a></li><li><a href="#23-%E7%BA%A2%E5%8C%85%E6%9C%8D%E5%8A%A1">2.3 红包服务</a></li></ul></li><li><a href="#3-%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8">3. 服务调用</a></li><li><a href="#4-%E4%B8%8B%E5%8D%95%E6%94%AF%E4%BB%98%E6%B5%81%E7%A8%8B">4. 下单支付流程</a><ul><li><a href="#41-try-%E9%98%B6%E6%AE%B5">4.1 Try 阶段</a></li><li><a href="#42-confirm--cancel-%E9%98%B6%E6%AE%B5">4.2 Confirm / Cancel 阶段</a></li></ul></li><li><a href="#666-%E5%BD%A9%E8%9B%8B">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。</li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文分享 <strong>TCC 项目实战</strong>。以官方 Maven项目 <code>tcc-transaction-http-sample</code> 为例子( <code>tcc-transaction-dubbo-sample</code> 类似 )。</p><p>建议你已经成功启动了该项目。如果不知道如何启动,可以先查看<a href="http://www.iocoder.cn/TCC-Transaction/build-debugging-environment/?self">《TCC-Transaction 源码分析 —— 调试环境搭建》</a>。如果再碰到问题,欢迎加微信公众号( <strong>芋道源码</strong> ),我会一一仔细回复。</p><p>OK,首先我们简单了解下这个项目。</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_03_15/01.png" alt=""></p><ul><li>首页 => 商品列表 => 确认支付页 => 支付结果页</li><li>使用账户余额 + 红包余额<strong>联合</strong>支付购买商品,并账户之间<strong>转账</strong>。</li></ul><p>项目拆分三个子 Maven 项目:</p><ul><li><code>tcc-transaction-http-order</code> :商城服务,提供商品和商品订单逻辑。</li><li><code>tcc-transaction-http-capital</code> :资金服务,提供账户余额逻辑。</li><li><code>tcc-transaction-http-redpacket</code> :红包服务,提供红包余额逻辑。</li></ul><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_03_15/03.png" alt=""></p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 TCC-Transaction 点赞!<a href="https://github.com/changmingxie/tcc-transaction" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-实体结构"><a href="#2-实体结构" class="headerlink" title="2. 实体结构"></a>2. 实体结构</h1><h2 id="2-1-商城服务"><a href="#2-1-商城服务" class="headerlink" title="2.1 商城服务"></a>2.1 商城服务</h2><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_03_15/02.png" alt=""></p><ul><li><p>Shop,商店表。实体代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Shop</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 商店编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> id;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 所有者用户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> ownerUserId;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>Product,商品表。实体代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Product</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 商品编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> productId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 商店编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> shopId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 商品名</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String productName;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 单价</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> BigDecimal price;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>Order,订单表。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Order</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">5908730245224893590L</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 订单编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> id;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 支付( 下单 )用户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> payerUserId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 收款( 商店拥有者 )用户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> payeeUserId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 红包支付金额</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> BigDecimal redPacketPayAmount;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 账户余额支付金额</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> BigDecimal capitalPayAmount;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 订单状态</span></div><div class="line"><span class="comment"> * - DRAFT :草稿</span></div><div class="line"><span class="comment"> * - PAYING :支付中</span></div><div class="line"><span class="comment"> * - CONFIRMED :支付成功</span></div><div class="line"><span class="comment"> * - PAY_FAILED :支付失败</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String status = <span class="string">"DRAFT"</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 商户订单号,使用 UUID 生成</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String merchantOrderNo;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 订单明细数组</span></div><div class="line"><span class="comment"> * 非存储字段</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> List<OrderLine> orderLines = <span class="keyword">new</span> ArrayList<OrderLine>();</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><ul><li><p>OrderLine,订单明细。实体代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">OrderLine</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">2300754647209250837L</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 订单编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> id;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 商品编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> productId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 数量</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">int</span> quantity;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 单价</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> BigDecimal unitPrice;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><p><strong>业务逻辑</strong>:</p><p>下单时,插入订单状态为 <code>"DRAFT"</code> 的订单( Order )记录,并插入购买的商品订单明细( OrderLine )记录。支付时,更新订单状态为 <code>"PAYING"</code>。</p><ul><li>订单支付成功,更新订单状态为 <code>"CONFIRMED"</code>。</li><li>订单支付失败,更新订单状体为 <code>"PAY_FAILED"</code>。</li></ul><h2 id="2-2-资金服务"><a href="#2-2-资金服务" class="headerlink" title="2.2 资金服务"></a>2.2 资金服务</h2><p>关系较为简单,有两个实体:</p><ul><li><p>CapitalAccount,资金账户余额。实体代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CapitalAccount</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 账户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> id;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 用户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> userId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 余额</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> BigDecimal balanceAmount;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>TradeOrder,交易订单表。实体代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TradeOrder</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 交易订单编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> id;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 转出用户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> selfUserId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 转入用户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> oppositeUserId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 商户订单号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String merchantOrderNo;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 金额</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> BigDecimal amount;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 交易订单状态</span></div><div class="line"><span class="comment"> * - DRAFT :草稿</span></div><div class="line"><span class="comment"> * - CONFIRM :交易成功</span></div><div class="line"><span class="comment"> * - CANCEL :交易取消</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String status = <span class="string">"DRAFT"</span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><p><strong>业务逻辑</strong>:</p><p>订单支付支付中,插入交易订单状态为 <code>"DRAFT"</code> 的订单( TradeOrder )记录,并更新<strong>减少</strong>下单用户的资金账户余额。</p><ul><li>订单支付成功,更新交易订单状态为 <code>"CONFIRM"</code>,并更新<strong>增加</strong>商店拥有用户的资金账户余额。</li><li>订单支付失败,更新交易订单状态为 <code>"CANCEL"</code>,并更新<strong>增加( 恢复 )</strong>下单用户的资金账户余额。</li></ul><h2 id="2-3-红包服务"><a href="#2-3-红包服务" class="headerlink" title="2.3 红包服务"></a>2.3 红包服务</h2><p>关系较为简单,<strong>和资金服务 99.99% 相同</strong>,有两个实体:</p><ul><li><p>RedPacketAccount,红包账户余额。实体代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedPacketAccount</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 账户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> id;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 用户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> userId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 余额</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> BigDecimal balanceAmount;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>TradeOrder,交易订单表。实体代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TradeOrder</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 交易订单编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> id;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 转出用户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> selfUserId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 转入用户编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> oppositeUserId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 商户订单号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String merchantOrderNo;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 金额</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> BigDecimal amount;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 交易订单状态</span></div><div class="line"><span class="comment"> * - DRAFT :草稿</span></div><div class="line"><span class="comment"> * - CONFIRM :交易成功</span></div><div class="line"><span class="comment"> * - CANCEL :交易取消</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String status = <span class="string">"DRAFT"</span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><p><strong>业务逻辑</strong>:</p><p>订单支付支付中,插入交易订单状态为 <code>"DRAFT"</code> 的订单( TradeOrder )记录,并更新<strong>减少</strong>下单用户的红包账户余额。</p><ul><li>订单支付成功,更新交易订单状态为 <code>"CONFIRM"</code>,并更新<strong>增加</strong>商店拥有用户的红包账户余额。</li><li>订单支付失败,更新交易订单状态为 <code>"CANCEL"</code>,并更新<strong>增加( 恢复 )</strong>下单用户的红包账户余额。</li></ul><h1 id="3-服务调用"><a href="#3-服务调用" class="headerlink" title="3. 服务调用"></a>3. 服务调用</h1><p>服务之间,通过 <strong>HTTP</strong> 进行调用。</p><p><strong>红包服务和资金服务为商城服务提供调用( 以资金服务为例子 )</strong>:</p><ul><li><p>XML 配置如下 :</p> <figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// appcontext-service-provider.xml</div><div class="line"><?xml version="1.0" encoding="UTF-8"?></div><div class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></div><div class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xmlns:util</span>=<span class="string">"http://www.springframework.org/schema/util"</span></span></div><div class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">name</span>=<span class="string">"capitalAccountRepository"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.capital.domain.repository.CapitalAccountRepository"</span>/></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">name</span>=<span class="string">"tradeOrderRepository"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.capital.domain.repository.TradeOrderRepository"</span>/></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">name</span>=<span class="string">"capitalTradeOrderService"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.capital.service.CapitalTradeOrderServiceImpl"</span>/></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">name</span>=<span class="string">"capitalAccountService"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.capital.service.CapitalAccountServiceImpl"</span>/></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">name</span>=<span class="string">"capitalTradeOrderServiceExporter"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"service"</span> <span class="attr">ref</span>=<span class="string">"capitalTradeOrderService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceInterface"</span></span></div><div class="line"><span class="tag"> <span class="attr">value</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.capital.api.CapitalTradeOrderService"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">name</span>=<span class="string">"capitalAccountServiceExporter"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"service"</span> <span class="attr">ref</span>=<span class="string">"capitalAccountService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceInterface"</span></span></div><div class="line"><span class="tag"> <span class="attr">value</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.capital.api.CapitalAccountService"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"httpServer"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.springframework.remoting.support.SimpleHttpServerFactoryBean"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"contexts"</span>></span></div><div class="line"> <span class="tag"><<span class="name">util:map</span>></span></div><div class="line"> <span class="tag"><<span class="name">entry</span> <span class="attr">key</span>=<span class="string">"/remoting/CapitalTradeOrderService"</span> <span class="attr">value-ref</span>=<span class="string">"capitalTradeOrderServiceExporter"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">entry</span> <span class="attr">key</span>=<span class="string">"/remoting/CapitalAccountService"</span> <span class="attr">value-ref</span>=<span class="string">"capitalAccountServiceExporter"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">util:map</span>></span></div><div class="line"> <span class="tag"></<span class="name">property</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"port"</span> <span class="attr">value</span>=<span class="string">"8081"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"><span class="tag"></<span class="name">beans</span>></span></div></pre></td></tr></table></figure></li><li><p>Java 代码实现如下 :</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CapitalAccountServiceImpl</span> <span class="keyword">implements</span> <span class="title">CapitalAccountService</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> CapitalAccountRepository capitalAccountRepository;</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> BigDecimal <span class="title">getCapitalAccountByUserId</span><span class="params">(<span class="keyword">long</span> userId)</span> </span>{</div><div class="line"> <span class="keyword">return</span> capitalAccountRepository.findByUserId(userId).getBalanceAmount();</div><div class="line"> }</div><div class="line"></div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CapitalAccountServiceImpl</span> <span class="keyword">implements</span> <span class="title">CapitalAccountService</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> CapitalAccountRepository capitalAccountRepository;</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> BigDecimal <span class="title">getCapitalAccountByUserId</span><span class="params">(<span class="keyword">long</span> userId)</span> </span>{</div><div class="line"> <span class="keyword">return</span> capitalAccountRepository.findByUserId(userId).getBalanceAmount();</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure></li></ul><hr><p><strong>商城服务调用</strong></p><ul><li><p>XML 配置如下:</p> <figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// appcontext-service-consumer.xml</div><div class="line"><?xml version="1.0" encoding="UTF-8"?></div><div class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></div><div class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></div><div class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"httpInvokerRequestExecutor"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"httpClient"</span>></span></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"org.apache.commons.httpclient.HttpClient"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"httpConnectionManager"</span>></span></div><div class="line"> <span class="tag"><<span class="name">ref</span> <span class="attr">bean</span>=<span class="string">"multiThreadHttpConnectionManager"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">property</span>></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"> <span class="tag"></<span class="name">property</span>></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"multiThreadHttpConnectionManager"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"params"</span>></span></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"org.apache.commons.httpclient.params.HttpConnectionManagerParams"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"connectionTimeout"</span> <span class="attr">value</span>=<span class="string">"200000"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxTotalConnections"</span> <span class="attr">value</span>=<span class="string">"600"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"defaultMaxConnectionsPerHost"</span> <span class="attr">value</span>=<span class="string">"512"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"soTimeout"</span> <span class="attr">value</span>=<span class="string">"5000"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"> <span class="tag"></<span class="name">property</span>></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"captialTradeOrderService"</span> <span class="attr">class</span>=<span class="string">"org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceUrl"</span> <span class="attr">value</span>=<span class="string">"http://localhost:8081/remoting/CapitalTradeOrderService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceInterface"</span></span></div><div class="line"><span class="tag"> <span class="attr">value</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.capital.api.CapitalTradeOrderService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"httpInvokerRequestExecutor"</span> <span class="attr">ref</span>=<span class="string">"httpInvokerRequestExecutor"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"capitalAccountService"</span> <span class="attr">class</span>=<span class="string">"org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceUrl"</span> <span class="attr">value</span>=<span class="string">"http://localhost:8081/remoting/CapitalAccountService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceInterface"</span></span></div><div class="line"><span class="tag"> <span class="attr">value</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.capital.api.CapitalAccountService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"httpInvokerRequestExecutor"</span> <span class="attr">ref</span>=<span class="string">"httpInvokerRequestExecutor"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"redPacketAccountService"</span> <span class="attr">class</span>=<span class="string">"org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceUrl"</span> <span class="attr">value</span>=<span class="string">"http://localhost:8082/remoting/RedPacketAccountService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceInterface"</span></span></div><div class="line"><span class="tag"> <span class="attr">value</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.redpacket.api.RedPacketAccountService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"httpInvokerRequestExecutor"</span> <span class="attr">ref</span>=<span class="string">"httpInvokerRequestExecutor"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"redPacketTradeOrderService"</span> <span class="attr">class</span>=<span class="string">"org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceUrl"</span> <span class="attr">value</span>=<span class="string">"http://localhost:8082/remoting/RedPacketTradeOrderService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"serviceInterface"</span></span></div><div class="line"><span class="tag"> <span class="attr">value</span>=<span class="string">"org.mengyun.tcctransaction.sample.http.redpacket.api.RedPacketTradeOrderService"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"httpInvokerRequestExecutor"</span> <span class="attr">ref</span>=<span class="string">"httpInvokerRequestExecutor"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"><span class="tag"></<span class="name">beans</span>></span></div></pre></td></tr></table></figure></li><li><p>Java 接口接口如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CapitalAccountService</span> </span>{</div><div class="line"> <span class="function">BigDecimal <span class="title">getCapitalAccountByUserId</span><span class="params">(<span class="keyword">long</span> userId)</span></span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CapitalTradeOrderService</span> </span>{</div><div class="line"> <span class="function">String <span class="title">record</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span></span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">RedPacketAccountService</span> </span>{</div><div class="line"> <span class="function">BigDecimal <span class="title">getRedPacketAccountByUserId</span><span class="params">(<span class="keyword">long</span> userId)</span></span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">RedPacketTradeOrderService</span> </span>{</div><div class="line"> <span class="function">String <span class="title">record</span><span class="params">(TransactionContext transactionContext, RedPacketTradeOrderDto tradeOrderDto)</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><h1 id="4-下单支付流程"><a href="#4-下单支付流程" class="headerlink" title="4. 下单支付流程"></a>4. 下单支付流程</h1><p><strong>ps</strong>:数据访问的方法,请自己拉取代码,使用 IDE 查看。谢谢。🙂</p><p>下单支付流程,整体流程如下图( <a href="./../../images/TCC-Transaction/2018_03_15/04.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_03_15/04.png" alt=""></p><p>点击<strong>【支付】</strong>按钮,下单支付流程。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Controller</span></div><div class="line"><span class="meta">@RequestMapping</span>(<span class="string">""</span>)</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">OrderController</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"/placeorder"</span>, method = RequestMethod.POST)</div><div class="line"> <span class="function"><span class="keyword">public</span> ModelAndView <span class="title">placeOrder</span><span class="params">(@RequestParam String redPacketPayAmount,</span></span></div><div class="line"><span class="function"><span class="params"> @RequestParam <span class="keyword">long</span> shopId,</span></span></div><div class="line"><span class="function"><span class="params"> @RequestParam <span class="keyword">long</span> payerUserId,</span></span></div><div class="line"><span class="function"><span class="params"> @RequestParam <span class="keyword">long</span> productId)</span> </span>{</div><div class="line"> PlaceOrderRequest request = buildRequest(redPacketPayAmount, shopId, payerUserId, productId);</div><div class="line"> <span class="comment">// 下单并支付订单</span></div><div class="line"> String merchantOrderNo = placeOrderService.placeOrder(request.getPayerUserId(), request.getShopId(),</div><div class="line"> request.getProductQuantities(), request.getRedPacketPayAmount());</div><div class="line"> <span class="comment">// 返回</span></div><div class="line"> ModelAndView mv = <span class="keyword">new</span> ModelAndView(<span class="string">"pay_success"</span>);</div><div class="line"> <span class="comment">// 查询订单状态</span></div><div class="line"> String status = orderService.getOrderStatusByMerchantOrderNo(merchantOrderNo);</div><div class="line"> <span class="comment">// 支付结果提示</span></div><div class="line"> String payResultTip = <span class="keyword">null</span>;</div><div class="line"> <span class="keyword">if</span> (<span class="string">"CONFIRMED"</span>.equals(status)) {</div><div class="line"> payResultTip = <span class="string">"支付成功"</span>;</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"PAY_FAILED"</span>.equals(status)) {</div><div class="line"> payResultTip = <span class="string">"支付失败"</span>;</div><div class="line"> }</div><div class="line"> mv.addObject(<span class="string">"payResult"</span>, payResultTip);</div><div class="line"> <span class="comment">// 商品信息</span></div><div class="line"> mv.addObject(<span class="string">"product"</span>, productRepository.findById(productId));</div><div class="line"> <span class="comment">// 资金账户金额 和 红包账户金额</span></div><div class="line"> mv.addObject(<span class="string">"capitalAmount"</span>, accountService.getCapitalAccountByUserId(payerUserId));</div><div class="line"> mv.addObject(<span class="string">"redPacketAmount"</span>, accountService.getRedPacketAccountByUserId(payerUserId));</div><div class="line"> <span class="keyword">return</span> mv;</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>PlaceOrderService#placeOrder(...)</code> 方法,下单并支付订单。</li><li>调用 <a href="https://github.com/YunaiV/tcc-transaction/blob/ff443d9e6d39bd798ed042034bad123b83675922/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-order/src/main/java/org/mengyun/tcctransaction/sample/http/order/domain/service/OrderServiceImpl.java" rel="external nofollow noopener noreferrer" target="_blank"><code>OrderService#getOrderStatusByMerchantOrderNo(...)</code></a> 方法,查询订单状态。</li></ul><hr><p>调用 <code>PlaceOrderService#placeOrder(...)</code> 方法,下单并支付订单。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PlaceOrderServiceImpl</span> </span>{</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">placeOrder</span><span class="params">(<span class="keyword">long</span> payerUserId, <span class="keyword">long</span> shopId, List<Pair<Long, Integer>> productQuantities, BigDecimal redPacketPayAmount)</span> </span>{</div><div class="line"> <span class="comment">// 获取商店</span></div><div class="line"> Shop shop = shopRepository.findById(shopId);</div><div class="line"> <span class="comment">// 创建订单</span></div><div class="line"> Order order = orderService.createOrder(payerUserId, shop.getOwnerUserId(), productQuantities);</div><div class="line"> <span class="comment">// 发起支付</span></div><div class="line"> Boolean result = <span class="keyword">false</span>;</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> paymentService.makePayment(order, redPacketPayAmount, order.getTotalAmount().subtract(redPacketPayAmount));</div><div class="line"> } <span class="keyword">catch</span> (ConfirmingException confirmingException) {</div><div class="line"> <span class="comment">// exception throws with the tcc transaction status is CONFIRMING,</span></div><div class="line"> <span class="comment">// when tcc transaction is confirming status,</span></div><div class="line"> <span class="comment">// the tcc transaction recovery will try to confirm the whole transaction to ensure eventually consistent.</span></div><div class="line"> result = <span class="keyword">true</span>;</div><div class="line"> } <span class="keyword">catch</span> (CancellingException cancellingException) {</div><div class="line"> <span class="comment">// exception throws with the tcc transaction status is CANCELLING,</span></div><div class="line"> <span class="comment">// when tcc transaction is under CANCELLING status,</span></div><div class="line"> <span class="comment">// the tcc transaction recovery will try to cancel the whole transaction to ensure eventually consistent.</span></div><div class="line"> } <span class="keyword">catch</span> (Throwable e) {</div><div class="line"> <span class="comment">// other exceptions throws at TRYING stage.</span></div><div class="line"> <span class="comment">// you can retry or cancel the operation.</span></div><div class="line"> e.printStackTrace();</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> order.getMerchantOrderNo();</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>ShopRepository#findById(...)</code> 方法,查询商店。</li><li><p>调用 <code>OrderService#createOrder(...)</code> 方法,创建订单状态为 <code>"DRAFT"</code> 的<strong>商城</strong>订单。实际业务不会这么做,此处仅仅是例子,简化流程。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Service</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">OrderServiceImpl</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Transactional</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Order <span class="title">createOrder</span><span class="params">(<span class="keyword">long</span> payerUserId, <span class="keyword">long</span> payeeUserId, List<Pair<Long, Integer>> productQuantities)</span> </span>{</div><div class="line"> Order order = orderFactory.buildOrder(payerUserId, payeeUserId, productQuantities);</div><div class="line"> orderRepository.createOrder(order);</div><div class="line"> <span class="keyword">return</span> order;</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure></li></ul><ul><li>调用 <code>PaymentService#makePayment(...)</code> 方法,发起支付,<strong>TCC 流程</strong>。</li><li><strong>生产代码对于异常需要进一步处理</strong>。</li><li><strong>生产代码对于异常需要进一步处理</strong>。</li><li><strong>生产代码对于异常需要进一步处理</strong>。</li></ul><h2 id="4-1-Try-阶段"><a href="#4-1-Try-阶段" class="headerlink" title="4.1 Try 阶段"></a>4.1 Try 阶段</h2><p><strong>商城服务</strong></p><p>调用 <code>PaymentService#makePayment(...)</code> 方法,发起 Try 流程,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Compensable</span>(confirmMethod = <span class="string">"confirmMakePayment"</span>, cancelMethod = <span class="string">"cancelMakePayment"</span>)</div><div class="line"><span class="meta">@Transactional</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">makePayment</span><span class="params">(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount)</span> </span>{</div><div class="line"> System.out.println(<span class="string">"order try make payment called.time seq:"</span> + DateFormatUtils.format(Calendar.getInstance(), <span class="string">"yyyy-MM-dd HH:mm:ss"</span>));</div><div class="line"> <span class="comment">// 更新订单状态为支付中</span></div><div class="line"> order.pay(redPacketPayAmount, capitalPayAmount);</div><div class="line"> orderRepository.updateOrder(order);</div><div class="line"> <span class="comment">// 资金账户余额支付订单</span></div><div class="line"> String result = tradeOrderServiceProxy.record(<span class="keyword">null</span>, buildCapitalTradeOrderDto(order));</div><div class="line"> <span class="comment">// 红包账户余额支付订单</span></div><div class="line"> String result2 = tradeOrderServiceProxy.record(<span class="keyword">null</span>, buildRedPacketTradeOrderDto(order));</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>设置方法注解 @Compensable</p><ul><li>事务传播级别 Propagation.REQUIRED ( <strong>默认值</strong> )</li><li>设置 <code>confirmMethod</code> / <code>cancelMethod</code> 方法名</li><li>事务上下文编辑类 DefaultTransactionContextEditor ( <strong>默认值</strong> )</li></ul></li><li><p>设置方法注解 @Transactional,保证方法操作原子性。</p></li><li><p>调用 <code>OrderRepository#updateOrder(...)</code> 方法,更新订单状态为<strong>支付中</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// Order.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">pay</span><span class="params">(BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.redPacketPayAmount = redPacketPayAmount;</div><div class="line"> <span class="keyword">this</span>.capitalPayAmount = capitalPayAmount;</div><div class="line"> <span class="keyword">this</span>.status = <span class="string">"PAYING"</span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><ul><li><p>调用 <code>TradeOrderServiceProxy#record(...)</code> 方法,<strong>资金</strong>账户余额支付订单。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TradeOrderServiceProxy.java</span></div><div class="line"><span class="meta">@Compensable</span>(propagation = Propagation.SUPPORTS, confirmMethod = <span class="string">"record"</span>, cancelMethod = <span class="string">"record"</span>, transactionContextEditor = Compensable.DefaultTransactionContextEditor.class)</div><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">record</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>{</div><div class="line"> <span class="keyword">return</span> capitalTradeOrderService.record(transactionContext, tradeOrderDto);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// CapitalTradeOrderService.java</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CapitalTradeOrderService</span> </span>{</div><div class="line"> <span class="function">String <span class="title">record</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>设置方法注解 @Compensable</p><ul><li><code>propagation=Propagation.SUPPORTS</code> :支持当前事务,如果当前没有事务,就以非事务方式执行。<strong>为什么不使用 REQUIRED</strong> ?如果使用 REQUIRED 事务传播级别,事务恢复重试时,会发起新的事务。</li><li><code>confirmMethod</code>、<code>cancelMethod</code> 使用和 try 方法<strong>相同方法名</strong>:<strong>本地发起</strong>远程服务 TCC confirm / cancel 阶段,调用相同方法进行事务的提交或回滚。远程服务的 CompensableTransactionInterceptor 会根据事务的状态是 CONFIRMING / CANCELLING 来调用对应方法。</li></ul></li><li><p>调用 <code>CapitalTradeOrderService#record(...)</code> 方法,远程调用,发起<strong>资金</strong>账户余额支付订单。</p><ul><li>本地方法调用时,参数 <code>transactionContext</code> 传递 <code>null</code> 即可,TransactionContextEditor 会设置。在<a href="http://www.iocoder.cn/TCC-Transaction/tcc-core/?self">《TCC-Transaction 源码分析 —— TCC 实现》「6.3 资源协调者拦截器」</a>有详细解析。</li><li>远程方法调用时,参数 <code>transactionContext</code> 需要传递。Dubbo 远程方法调用实际也进行了传递,传递方式较为特殊,通过隐式船舱,在<a href="http://www.iocoder.cn/TCC-Transaction/dubbo-support/?self">《TCC-Transaction 源码分析 —— Dubbo 支持》「3. Dubbo 事务上下文编辑器」</a>有详细解析。</li></ul></li></ul></li><li><p>调用 <code>TradeOrderServiceProxy#record(...)</code> 方法,<strong>红包</strong>账户余额支付订单。和<strong>资金</strong>账户余额支付订单 99.99% 类似,不重复“复制粘贴”。</p></li></ul><hr><p><strong>资金服务</strong></p><p>调用 <code>CapitalTradeOrderServiceImpl#record(...)</code> 方法,<strong>红包</strong>账户余额支付订单。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="meta">@Compensable</span>(confirmMethod = <span class="string">"confirmRecord"</span>, cancelMethod = <span class="string">"cancelRecord"</span>, transactionContextEditor = Compensable.DefaultTransactionContextEditor.class)</div><div class="line"><span class="meta">@Transactional</span></div><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">record</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>{</div><div class="line"> <span class="comment">// 调试用</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> Thread.sleep(<span class="number">1000l</span>);</div><div class="line"><span class="comment">// Thread.sleep(10000000L);</span></div><div class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</div><div class="line"> }</div><div class="line"> System.out.println(<span class="string">"capital try record called. time seq:"</span> + DateFormatUtils.format(Calendar.getInstance(), <span class="string">"yyyy-MM-dd HH:mm:ss"</span>));</div><div class="line"> <span class="comment">// 生成交易订单</span></div><div class="line"> TradeOrder tradeOrder = <span class="keyword">new</span> TradeOrder(</div><div class="line"> tradeOrderDto.getSelfUserId(),</div><div class="line"> tradeOrderDto.getOppositeUserId(),</div><div class="line"> tradeOrderDto.getMerchantOrderNo(),</div><div class="line"> tradeOrderDto.getAmount()</div><div class="line"> );</div><div class="line"> tradeOrderRepository.insert(tradeOrder);</div><div class="line"> <span class="comment">// 更新减少下单用户的资金账户余额</span></div><div class="line"> CapitalAccount transferFromAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId());</div><div class="line"> transferFromAccount.transferFrom(tradeOrderDto.getAmount());</div><div class="line"> capitalAccountRepository.save(transferFromAccount);</div><div class="line"> <span class="keyword">return</span> <span class="string">"success"</span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>设置方法注解 @Compensable</p><ul><li>事务传播级别 Propagation.REQUIRED ( <strong>默认值</strong> )</li><li>设置 <code>confirmMethod</code> / <code>cancelMethod</code> 方法名</li><li>事务上下文编辑类 DefaultTransactionContextEditor ( <strong>默认值</strong> )</li></ul></li><li><p>设置方法注解 @Transactional,保证方法操作原子性。</p></li><li>调用 <a href="https://github.com/YunaiV/tcc-transaction/blob/ff443d9e6d39bd798ed042034bad123b83675922/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-capital/src/main/java/org/mengyun/tcctransaction/sample/http/capital/domain/repository/TradeOrderRepository.java" rel="external nofollow noopener noreferrer" target="_blank"><code>TradeOrderRepository#insert(...)</code></a> 方法,生成订单状态为 <code>"DRAFT"</code> 的交易订单。</li><li>调用 <a href="https://github.com/YunaiV/tcc-transaction/blob/ff443d9e6d39bd798ed042034bad123b83675922/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-capital/src/main/java/org/mengyun/tcctransaction/sample/http/capital/domain/repository/CapitalAccountRepository.java" rel="external nofollow noopener noreferrer" target="_blank"><code>CapitalAccountRepository#save(...)</code></a> 方法,更新减少下单用户的资金账户余额。<strong>Try 阶段锁定资源时,一定要先扣。TCC 是最终事务一致性,如果先添加,可能被使用</strong>。</li></ul><h2 id="4-2-Confirm-Cancel-阶段"><a href="#4-2-Confirm-Cancel-阶段" class="headerlink" title="4.2 Confirm / Cancel 阶段"></a>4.2 Confirm / Cancel 阶段</h2><p>当 Try 操作<strong>全部</strong>成功时,发起 Confirm 操作。<br>当 Try 操作存在<strong>任务</strong>失败时,发起 Cancel 操作。</p><h3 id="4-2-1-Confirm"><a href="#4-2-1-Confirm" class="headerlink" title="4.2.1 Confirm"></a>4.2.1 Confirm</h3><p><strong>商城服务</strong></p><p>调用 <code>PaymentServiceImpl#confirmMakePayment(...)</code> 方法,更新订单状态为支付<strong>成功</strong>。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">confirmMakePayment</span><span class="params">(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount)</span> </span>{</div><div class="line"> <span class="comment">// 调试用</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> Thread.sleep(<span class="number">1000l</span>);</div><div class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</div><div class="line"> }</div><div class="line"> System.out.println(<span class="string">"order confirm make payment called. time seq:"</span> + DateFormatUtils.format(Calendar.getInstance(), <span class="string">"yyyy-MM-dd HH:mm:ss"</span>));</div><div class="line"> <span class="comment">// 更新订单状态为支付成功</span></div><div class="line"> order.confirm();</div><div class="line"> orderRepository.updateOrder(order);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>生产代码该方法需要加下 @Transactional 注解,保证原子性</strong>。</li><li><p>调用 <code>OrderRepository#updateOrder(...)</code> 方法,更新订单状态为支付成功。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// Order.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">confirm</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">this</span>.status = <span class="string">"CONFIRMED"</span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><hr><p><strong>资金服务</strong></p><p>调用 <code>CapitalTradeOrderServiceImpl#confirmRecord(...)</code> 方法,更新交易订单状态为交易<strong>成功</strong>。</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Transactional</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">confirmRecord</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>{</div><div class="line"> <span class="comment">// 调试用</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> Thread.sleep(<span class="number">1000l</span>);</div><div class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</div><div class="line"> }</div><div class="line"> System.out.println(<span class="string">"capital confirm record called. time seq:"</span> + DateFormatUtils.format(Calendar.getInstance(), <span class="string">"yyyy-MM-dd HH:mm:ss"</span>));</div><div class="line"> <span class="comment">// 查询交易记录</span></div><div class="line"> TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo());</div><div class="line"> <span class="comment">// 判断交易记录状态。因为 `#record()` 方法,可能事务回滚,记录不存在 / 状态不对</span></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != tradeOrder && <span class="string">"DRAFT"</span>.equals(tradeOrder.getStatus())) {</div><div class="line"> <span class="comment">// 更新订单状态为交易成功</span></div><div class="line"> tradeOrder.confirm();</div><div class="line"> tradeOrderRepository.update(tradeOrder);</div><div class="line"> <span class="comment">// 更新增加商店拥有者用户的资金账户余额</span></div><div class="line"> CapitalAccount transferToAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getOppositeUserId());</div><div class="line"> transferToAccount.transferTo(tradeOrderDto.getAmount());</div><div class="line"> capitalAccountRepository.save(transferToAccount);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>设置方法注解 @Transactional,保证方法操作原子性。</li><li><strong>判断交易记录状态</strong>。因为 <code>#record()</code> 方法,可能事务回滚,记录不存在 / 状态不对。</li><li>调用 <a href="https://github.com/YunaiV/tcc-transaction/blob/ff443d9e6d39bd798ed042034bad123b83675922/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-capital/src/main/java/org/mengyun/tcctransaction/sample/http/capital/domain/repository/TradeOrderRepository.java" rel="external nofollow noopener noreferrer" target="_blank"><code>TradeOrderRepository#update(...)</code></a> 方法,更新交易订单状态为交易<strong>成功</strong>。</li><li><p>调用 <a href="https://github.com/YunaiV/tcc-transaction/blob/ff443d9e6d39bd798ed042034bad123b83675922/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-capital/src/main/java/org/mengyun/tcctransaction/sample/http/capital/domain/repository/CapitalAccountRepository.java" rel="external nofollow noopener noreferrer" target="_blank"><code>CapitalAccountRepository#save(...)</code></a> 方法,更新增加商店拥有者用户的资金账户余额。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CapitalAccount.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">transferTo</span><span class="params">(BigDecimal amount)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.balanceAmount = <span class="keyword">this</span>.balanceAmount.add(amount);</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><hr><p><strong>红包服务</strong></p><p>和<strong>资源服务</strong> 99.99% 相同,不重复“复制粘贴”。</p><h3 id="4-2-2-Cancel"><a href="#4-2-2-Cancel" class="headerlink" title="4.2.2 Cancel"></a>4.2.2 Cancel</h3><p><strong>商城服务</strong></p><p>调用 <code>PaymentServiceImpl#cancelMakePayment(...)</code> 方法,更新订单状态为支付<strong>失败</strong>。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancelMakePayment</span><span class="params">(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount)</span> </span>{</div><div class="line"> <span class="comment">// 调试用</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> Thread.sleep(<span class="number">1000l</span>);</div><div class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</div><div class="line"> }</div><div class="line"> System.out.println(<span class="string">"order cancel make payment called.time seq:"</span> + DateFormatUtils.format(Calendar.getInstance(), <span class="string">"yyyy-MM-dd HH:mm:ss"</span>));</div><div class="line"> <span class="comment">// 更新订单状态为支付失败</span></div><div class="line"> order.cancelPayment();</div><div class="line"> orderRepository.updateOrder(order);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>生产代码该方法需要加下 @Transactional 注解,保证原子性</strong>。</li><li><p>调用 <code>OrderRepository#updateOrder(...)</code> 方法,更新订单状态为支付失败。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// Order.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancelPayment</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">this</span>.status = <span class="string">"PAY_FAILED"</span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><hr><p><strong>资金服务</strong></p><p>调用 <code>CapitalTradeOrderServiceImpl#cancelRecord(...)</code> 方法,更新交易订单状态为交易<strong>失败</strong>。</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Transactional</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancelRecord</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>{</div><div class="line"> <span class="comment">// 调试用</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> Thread.sleep(<span class="number">1000l</span>);</div><div class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</div><div class="line"> }</div><div class="line"> System.out.println(<span class="string">"capital cancel record called. time seq:"</span> + DateFormatUtils.format(Calendar.getInstance(), <span class="string">"yyyy-MM-dd HH:mm:ss"</span>));</div><div class="line"> <span class="comment">// 查询交易记录</span></div><div class="line"> TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo());</div><div class="line"> <span class="comment">// 判断交易记录状态。因为 `#record()` 方法,可能事务回滚,记录不存在 / 状态不对</span></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != tradeOrder && <span class="string">"DRAFT"</span>.equals(tradeOrder.getStatus())) {</div><div class="line"> <span class="comment">// / 更新订单状态为交易失败</span></div><div class="line"> tradeOrder.cancel();</div><div class="line"> tradeOrderRepository.update(tradeOrder);</div><div class="line"> <span class="comment">// 更新增加( 恢复 )下单用户的资金账户余额</span></div><div class="line"> CapitalAccount capitalAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId());</div><div class="line"> capitalAccount.cancelTransfer(tradeOrderDto.getAmount());</div><div class="line"> capitalAccountRepository.save(capitalAccount);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>设置方法注解 @Transactional,保证方法操作原子性。</li><li><strong>判断交易记录状态</strong>。因为 <code>#record()</code> 方法,可能事务回滚,记录不存在 / 状态不对。</li><li>调用 <a href="https://github.com/YunaiV/tcc-transaction/blob/ff443d9e6d39bd798ed042034bad123b83675922/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-capital/src/main/java/org/mengyun/tcctransaction/sample/http/capital/domain/repository/TradeOrderRepository.java" rel="external nofollow noopener noreferrer" target="_blank"><code>TradeOrderRepository#update(...)</code></a> 方法,更新交易订单状态为交易<strong>失败</strong>。</li><li><p>调用 <a href="https://github.com/YunaiV/tcc-transaction/blob/ff443d9e6d39bd798ed042034bad123b83675922/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-capital/src/main/java/org/mengyun/tcctransaction/sample/http/capital/domain/repository/CapitalAccountRepository.java" rel="external nofollow noopener noreferrer" target="_blank"><code>CapitalAccountRepository#save(...)</code></a> 方法,更新增加( 恢复 )下单用户的资金账户余额。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CapitalAccount.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancelTransfer</span><span class="params">(BigDecimal amount)</span> </span>{</div><div class="line"> transferTo(amount);</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><hr><p><strong>红包服务</strong></p><p>和<strong>资源服务</strong> 99.99% 相同,不重复“复制粘贴”。</p><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>嘿嘿,代码只是看起来比较多,实际不对。</p><p>本系列 EOF ~撒花</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_03_15/05.png" alt=""></p><p>胖友,分享个朋友圈,可好?!</p>]]></content>
<summary type="html">
<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p>
<ul>
<li><a href="#1-%E6%A6%82%E8%BF%B0">1. 概述</a></li>
<li><a href="#2-%E5%AE%
</summary>
<category term="TCC-Transaction" scheme="http://www.iocoder.cn/categories/TCC-Transaction/"/>
</entry>
<entry>
<title>TCC-Transaction 源码分析 —— 运维平台</title>
<link href="http://www.iocoder.cn/TCC-Transaction/console/"/>
<id>http://www.iocoder.cn/TCC-Transaction/console/</id>
<published>2018-02-27T16:00:00.000Z</published>
<updated>2017-09-17T11:06:42.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. 数据访问层</a><ul><li><a href="#">2.1 JDBC 事务 DAO</a></li><li><a href="#">2.2 Redis 事务 DAO</a></li></ul></li><li><a href="#">3. 控制层</a><ul><li><a href="#">3.1 查看未完成的事务列表</a></li><li><a href="#">3.2 重置事务恢复重试次数</a></li></ul></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。</li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文分享 <strong>运维平台</strong>。TCC-Transaction 提供了相对精简的运维平台,用于查看在<a href="http://www.iocoder.cn/TCC-Transaction/transaction-repository/?self">《TCC-Transaction 源码分析 —— 事务存储器》</a>提到的<strong>事务存储</strong>。目前暂时只有两个功能:</p><ul><li>查看未完成的事务列表</li><li>重置事务恢复重试次数</li></ul><p>运维平台( Maven 项目 <code>tcc-transaction-server</code> ) 整体代码结构如下:</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_28/01.png" alt=""></p><p>本文自下而上,Dao => Controller => UI 的顺序进行解析实现。</p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 TCC-Transaction 点赞!<a href="https://github.com/changmingxie/tcc-transaction" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><p>ps:笔者假设你已经阅读过<a href="https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x" rel="external nofollow noopener noreferrer" target="_blank">《tcc-transaction 官方文档 —— 使用指南1.2.x》</a>。</p><h1 id="2-数据访问层"><a href="#2-数据访问层" class="headerlink" title="2. 数据访问层"></a>2. 数据访问层</h1><p><code>org.mengyun.tcctransaction.server.dao.TransactionDao</code>,事务Dao <strong>接口</strong>,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">TransactionDao</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 获得事务 VO 数组</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> domain 领域</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> pageNum 第几页</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> pageSize 分页大小</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 事务 VO 数组</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">List<TransactionVo> <span class="title">findTransactions</span><span class="params">(String domain, Integer pageNum, <span class="keyword">int</span> pageSize)</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 获得事务总数量</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> domain 领域</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 数量</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">Integer <span class="title">countOfFindTransactions</span><span class="params">(String domain)</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 重置事务重试次数</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> domain 领域</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> globalTxId 全局事务编号</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> branchQualifier 分支事务编号</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 是否重置成功</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">boolean</span> <span class="title">resetRetryCount</span><span class="params">(String domain, <span class="keyword">byte</span>[] globalTxId, <span class="keyword">byte</span>[] branchQualifier)</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><p>TCC-Transaction 提供了四种事务存储器,但是目前只支持两种数据访问层的实现:</p><ul><li>JDBC 事务 DAO</li><li>Redis 事务 DAO</li></ul><h2 id="2-1-JDBC-事务-DAO"><a href="#2-1-JDBC-事务-DAO" class="headerlink" title="2.1 JDBC 事务 DAO"></a>2.1 JDBC 事务 DAO</h2><p><code>org.mengyun.tcctransaction.server.dao.JdbcTransactionDao</code>,JDBC 事务 DAO 实现。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Repository</span>(<span class="string">"jdbcTransactionDao"</span>)</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcTransactionDao</span> <span class="keyword">implements</span> <span class="title">TransactionDao</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TABLE_NAME_PREFIX = <span class="string">"TCC_TRANSACTION"</span>;</div><div class="line"></div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> DataSource dataSource;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 读取 jdbc-domain-suffix.properties</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Value</span>(<span class="string">"#{jdbcDomainSuffix}"</span>)</div><div class="line"> <span class="keyword">private</span> Properties domainSuffix;</div><div class="line"> </div><div class="line"> <span class="comment">// ... 省略代码</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p><code>dataSource</code>,数据源。配置方式如下:</p> <figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// appcontext-server-dao.xml </div><div class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"dataSource"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.apache.commons.dbcp.BasicDataSource"</span></span></div><div class="line"><span class="tag"> <span class="attr">destroy-method</span>=<span class="string">"close"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"driverClassName"</span> <span class="attr">value</span>=<span class="string">"com.mysql.jdbc.Driver"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"url"</span> <span class="attr">value</span>=<span class="string">"${jdbc.url}"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"username"</span> <span class="attr">value</span>=<span class="string">"${jdbc.username}"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"password"</span> <span class="attr">value</span>=<span class="string">"${jdbc.password}"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxActive"</span> <span class="attr">value</span>=<span class="string">"50"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"minIdle"</span> <span class="attr">value</span>=<span class="string">"5"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxIdle"</span> <span class="attr">value</span>=<span class="string">"20"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"initialSize"</span> <span class="attr">value</span>=<span class="string">"30"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"logAbandoned"</span> <span class="attr">value</span>=<span class="string">"true"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"removeAbandoned"</span> <span class="attr">value</span>=<span class="string">"true"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"removeAbandonedTimeout"</span> <span class="attr">value</span>=<span class="string">"10"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxWait"</span> <span class="attr">value</span>=<span class="string">"1000"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"timeBetweenEvictionRunsMillis"</span> <span class="attr">value</span>=<span class="string">"10000"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"numTestsPerEvictionRun"</span> <span class="attr">value</span>=<span class="string">"10"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"minEvictableIdleTimeMillis"</span> <span class="attr">value</span>=<span class="string">"10000"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"validationQuery"</span> <span class="attr">value</span>=<span class="string">"SELECT NOW() FROM DUAL"</span>/></span></div><div class="line"><span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line">// tcc-transaction-server.properties</div><div class="line">jdbc.url=jdbc:mysql://127.0.0.1:33061/TCC?useUnicode=true&characterEncoding=UTF-8</div><div class="line">jdbc.username=root</div><div class="line">jdbc.password=123456</div></pre></td></tr></table></figure><ul><li>在 <code>appcontext-server-dao.xml</code>,配置数据源 Bean 对象。</li><li>在 <code>tcc-transaction-server.properties</code>,配置数据源属性。</li></ul></li><li><p><code>domainSuffix</code>,<code>domian</code> 和 表后缀( <code>suffix</code> ) 的映射关系。配置方式如下:</p> <figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// jdbc-domain-suffix.properties</div><div class="line">CAPITAL=_CAP</div><div class="line">ORDER=_ORD</div><div class="line">REDPACKET=_RED</div></pre></td></tr></table></figure><ul><li>键 :domain。</li><li>值 :suffix。</li></ul></li></ul><p>JdbcTransactionDao 代码实现上比较易懂,点击<a href="https://github.com/YunaiV/tcc-transaction/blob/e54c3e43a2e47a7765bdb18a485860cb31acbb72/tcc-transaction-server/src/main/java/org/mengyun/tcctransaction/server/dao/JdbcTransactionDao.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>查看,已经添加中文注释。</p><h2 id="2-2-Redis-事务-DAO"><a href="#2-2-Redis-事务-DAO" class="headerlink" title="2.2 Redis 事务 DAO"></a>2.2 Redis 事务 DAO</h2><p><code>org.mengyun.tcctransaction.server.dao.RedisTransactionDao</code>,Redis 事务 DAO。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Repository</span>(<span class="string">"redisTransactionDao"</span>)</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisTransactionDao</span> <span class="keyword">implements</span> <span class="title">TransactionDao</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * redis pool</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="keyword">private</span> JedisPool jedisPool;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 序列化</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> ObjectSerializer serializer = <span class="keyword">new</span> JdkSerializationSerializer();</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 读取 redis-domain-key-prefix.properties</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Value</span>(<span class="string">"#{redisDomainKeyPrefix}"</span>)</div><div class="line"> <span class="keyword">private</span> Properties domainKeyPrefix;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p><code>jedisPool</code>,Redis 连接池。配置方式如下:</p> <figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// appcontext-server-dao.xml</div><div class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"jedisPoolConfig"</span> <span class="attr">class</span>=<span class="string">"redis.clients.jedis.JedisPoolConfig"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxTotal"</span> <span class="attr">value</span>=<span class="string">"300"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxIdle"</span> <span class="attr">value</span>=<span class="string">"100"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"minIdle"</span> <span class="attr">value</span>=<span class="string">"10"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxWaitMillis"</span> <span class="attr">value</span>=<span class="string">"3000"</span>/></span></div><div class="line"><span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"jedisPool"</span> <span class="attr">class</span>=<span class="string">"redis.clients.jedis.JedisPool"</span>></span></div><div class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">index</span>=<span class="string">"0"</span> <span class="attr">ref</span>=<span class="string">"jedisPoolConfig"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">index</span>=<span class="string">"1"</span> <span class="attr">value</span>=<span class="string">"${redis.host}"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">index</span>=<span class="string">"2"</span> <span class="attr">value</span>=<span class="string">"${redis.port}"</span> <span class="attr">type</span>=<span class="string">"int"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">index</span>=<span class="string">"3"</span> <span class="attr">value</span>=<span class="string">"6000"</span> <span class="attr">type</span>=<span class="string">"int"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">index</span>=<span class="string">"4"</span> <span class="attr">type</span>=<span class="string">"java.lang.String"</span>></span></div><div class="line"> <span class="tag"><<span class="name">null</span>/></span></div><div class="line"> <span class="tag"></<span class="name">constructor-arg</span>></span></div><div class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">index</span>=<span class="string">"5"</span> <span class="attr">value</span>=<span class="string">"${redis.db}"</span> <span class="attr">type</span>=<span class="string">"int"</span>/></span></div><div class="line"><span class="tag"></<span class="name">bean</span>></span></div><div class="line"></div><div class="line">// tcc-transaction-server.properties</div><div class="line">redis.host=127.0.0.1</div><div class="line">redis.port=6379</div><div class="line">redis.password=</div><div class="line">redis.db=0</div></pre></td></tr></table></figure><ul><li>在 <code>appcontext-server-dao.xml</code>,配置 Redis 连接池 Bean 对象。</li><li>在 <code>tcc-transaction-server.properties</code>,配置 Redis 连接池属性。</li></ul></li><li><p><code>domainKeyPrefix</code>,domain 和 Redis Key 前缀( <code>prefix</code> )的映射。配置方式如下:</p> <figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">CAPITAL=TCC:CAP:</div><div class="line">ORDER=TCC:ORD:</div><div class="line">REDPACKET=TCC:RED:</div></pre></td></tr></table></figure><ul><li>键 :domain。</li><li>值 :suffix。</li></ul></li></ul><p>RedisTransactionDao 代码实现上比较易懂,点击[链接]<a href="https://github.com/YunaiV/tcc-transaction/blob/e54c3e43a2e47a7765bdb18a485860cb31acbb72/tcc-transaction-server/src/main/java/org/mengyun/tcctransaction/server/dao/RedisTransactionDao.java)查看,已经添加中文注释。" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/YunaiV/tcc-transaction/blob/e54c3e43a2e47a7765bdb18a485860cb31acbb72/tcc-transaction-server/src/main/java/org/mengyun/tcctransaction/server/dao/RedisTransactionDao.java)查看,已经添加中文注释。</a></p><h1 id="3-控制层"><a href="#3-控制层" class="headerlink" title="3. 控制层"></a>3. 控制层</h1><p><code>org.mengyun.tcctransaction.server.controller.TransactionController</code>,事务 Controller。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Controller</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TransactionController</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> Integer DEFAULT_PAGE_NUM = <span class="number">1</span>;</div><div class="line"></div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_PAGE_SIZE = <span class="number">10</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 数据访问对象</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Autowired</span></div><div class="line"> <span class="meta">@Qualifier</span>(<span class="string">"jdbcTransactionDao"</span>)</div><div class="line"> <span class="keyword">private</span> TransactionDao transactionDao;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 项目访问根目录</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Value</span>(<span class="string">"${tcc_domain}"</span>)</div><div class="line"> <span class="keyword">private</span> String tccDomain;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p><code>transactionDao</code>,数据访问对象。配置方式如下:</p> <figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// appcontext-server-dao.xml</div><div class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"transactionDao"</span> <span class="attr">class</span>=<span class="string">"org.mengyun.tcctransaction.server.dao.JdbcTransactionDao"</span>/></span></div></pre></td></tr></table></figure><ul><li>目前运维平台只能读取一个数据源,如果你的数据源是多个,需要对运维平台做一定的改造,或启动多个项目。</li></ul></li><li><p><code>tccDomain</code>,项目访问根目录。配置方式如下:</p> <figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// tcc-transaction-server.properties</div><div class="line">tcc_domain=</div></pre></td></tr></table></figure><ul><li>一般情况下不用配置,如果你放在 Tomcat 根目录。</li></ul></li></ul><h2 id="3-1-查看未完成的事务列表"><a href="#3-1-查看未完成的事务列表" class="headerlink" title="3.1 查看未完成的事务列表"></a>3.1 查看未完成的事务列表</h2><p>调用 <code>TransactionController#manager(...)</code> 方法,查看事务列表。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@RequestMapping</span>(value = <span class="string">"/management"</span>, method = RequestMethod.GET)</div><div class="line"><span class="function"><span class="keyword">public</span> ModelAndView <span class="title">manager</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ModelAndView(<span class="string">"manager"</span>);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@RequestMapping</span>(value = <span class="string">"/management/domain/{domain}"</span>, method = RequestMethod.GET)</div><div class="line"><span class="function"><span class="keyword">public</span> ModelAndView <span class="title">manager</span><span class="params">(@PathVariable String domain)</span> </span>{</div><div class="line"> <span class="keyword">return</span> manager(domain, DEFAULT_PAGE_NUM);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@RequestMapping</span>(value = <span class="string">"/management/domain/{domain}/pagenum/{pageNum}"</span>, method = RequestMethod.GET)</div><div class="line"><span class="function"><span class="keyword">public</span> ModelAndView <span class="title">manager</span><span class="params">(@PathVariable String domain, @PathVariable Integer pageNum)</span> </span>{</div><div class="line"> ModelAndView modelAndView = <span class="keyword">new</span> ModelAndView(<span class="string">"manager"</span>);</div><div class="line"> <span class="comment">// 获得事务 VO 数组</span></div><div class="line"> List<TransactionVo> transactionVos = transactionDao.findTransactions(domain, pageNum, DEFAULT_PAGE_SIZE);</div><div class="line"> <span class="comment">// 获得事务总数量</span></div><div class="line"> Integer totalCount = transactionDao.countOfFindTransactions(domain);</div><div class="line"> <span class="comment">// 计算总页数</span></div><div class="line"> Integer pages = totalCount / DEFAULT_PAGE_SIZE;</div><div class="line"> <span class="keyword">if</span> (totalCount % DEFAULT_PAGE_SIZE > <span class="number">0</span>) {</div><div class="line"> pages++;</div><div class="line"> }</div><div class="line"> <span class="comment">// 返回</span></div><div class="line"> modelAndView.addObject(<span class="string">"transactionVos"</span>, transactionVos);</div><div class="line"> modelAndView.addObject(<span class="string">"pageNum"</span>, pageNum);</div><div class="line"> modelAndView.addObject(<span class="string">"pageSize"</span>, DEFAULT_PAGE_SIZE);</div><div class="line"> modelAndView.addObject(<span class="string">"pages"</span>, pages);</div><div class="line"> modelAndView.addObject(<span class="string">"domain"</span>, domain);</div><div class="line"> modelAndView.addObject(<span class="string">"urlWithoutPaging"</span>, tccDomain + <span class="string">"/management/domain/"</span> + domain);</div><div class="line"> <span class="keyword">return</span> modelAndView;</div><div class="line">}</div></pre></td></tr></table></figure><p>UI 界面如下:</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_28/02.png" alt=""></p><h2 id="3-2-重置事务恢复重试次数"><a href="#3-2-重置事务恢复重试次数" class="headerlink" title="3.2 重置事务恢复重试次数"></a>3.2 重置事务恢复重试次数</h2><p>调用 <code>TransactionController#reset(...)</code> 方法,事务重置重试次数。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@RequestMapping</span>(value = <span class="string">"/domain/{domain}/retry/reset"</span>, method = RequestMethod.PUT)</div><div class="line"><span class="meta">@ResponseBody</span></div><div class="line"><span class="function"><span class="keyword">public</span> CommonResponse<Void> <span class="title">reset</span><span class="params">(@PathVariable String domain, String globalTxId, String branchQualifier)</span> </span>{</div><div class="line"> transactionDao.resetRetryCount(domain,</div><div class="line"> DatatypeConverter.parseHexBinary(globalTxId),</div><div class="line"> DatatypeConverter.parseHexBinary(branchQualifier));</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> CommonResponse<Void>();</div><div class="line">}</div></pre></td></tr></table></figure><p>UI 界面如下:</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_28/03.png" alt=""></p><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>可能有人会吐槽运维平台怎么做的这么简陋。这个不是 TCC-Transaction 一个开源项目存在的问题,其他例如 Dubbo、Disconf 等等都会存在这个情况。</p><p>开源作者因为时间关系,更多的精力关注在核心代码,所以对运维友好性可能花费的精力较少。</p><p>当然,因为是开源的关系,我们可以自己做运维平台反向的贡献到这些项目。</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_28/04.png" alt=""></p><p>胖友,分享一个朋友圈可好?</p>]]></content>
<summary type="html">
<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. 数据访问层</a><ul>
<li><a hre
</summary>
<category term="TCC-Transaction" scheme="http://www.iocoder.cn/categories/TCC-Transaction/"/>
</entry>
<entry>
<title>TCC-Transaction 源码分析 —— Dubbo 支持</title>
<link href="http://www.iocoder.cn/TCC-Transaction/dubbo-support/"/>
<id>http://www.iocoder.cn/TCC-Transaction/dubbo-support/</id>
<published>2018-02-27T16:00:00.000Z</published>
<updated>2017-09-17T13:15:11.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p><ul><li><a href="#1-%E6%A6%82%E8%BF%B0">1. 概述</a></li><li><a href="#2-dubbo-%E4%BB%A3%E7%90%86">2. Dubbo 代理</a><ul><li><a href="#21-javassistproxyfactory">2.1 JavassistProxyFactory</a><ul><li><a href="#211-javassist">2.1.1 Javassist</a></li><li><a href="#212-tccjavassistproxyfactory">2.1.2 TccJavassistProxyFactory</a></li><li><a href="#213-tccproxy--tccclassgenerator">2.1.3 TccProxy & TccClassGenerator</a></li><li><a href="#214-%E9%85%8D%E7%BD%AE-dubbo-proxy">2.1.4 配置 Dubbo Proxy</a></li></ul></li><li><a href="#22-jdkproxyfactory">2.2 JdkProxyFactory</a><ul><li><a href="#221-jdk-proxy">2.2.1 JDK Proxy</a></li><li><a href="#222-tccjdkproxyfactory">2.2.2 TccJdkProxyFactory</a></li><li><a href="#223-tccinvokerinvocationhandler">2.2.3 TccInvokerInvocationHandler</a></li><li><a href="#224-%E9%85%8D%E7%BD%AE-dubbo-proxy">2.2.4 配置 Dubbo Proxy</a></li></ul></li></ul></li><li><a href="#3-dubbo-%E4%BA%8B%E5%8A%A1%E4%B8%8A%E4%B8%8B%E6%96%87%E7%BC%96%E8%BE%91%E5%99%A8">3. Dubbo 事务上下文编辑器</a></li><li><a href="#666-%E5%BD%A9%E8%9B%8B">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。</li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文分享 <strong>Dubbo 支持</strong>。</p><p>TCC-Transaction 通过 Dubbo <strong>隐式传参</strong>的功能,避免自己对业务代码的入侵。可能有同学不太理解为什么说 TCC-Transaction 对业务代码有一定的入侵性,一起来看个代码例子:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CapitalTradeOrderService</span> </span>{</div><div class="line"> <span class="function">String <span class="title">record</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>代码来自 <code>tcc-transaction-http-sample</code> 。声明远程调用时,增加了参数 TransactionContext。当然你也可以通过自己使用的远程调用框架做一定封装,避免入侵。</li></ul><p>如下是对 Dubbo 封装了后,Dubbo Service 方法的例子:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CapitalTradeOrderService</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Compensable</span></div><div class="line"> <span class="function">String <span class="title">record</span><span class="params">(CapitalTradeOrderDto tradeOrderDto)</span></span>;</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>代码来自 <code>http-transaction-dubbo-sample</code> 。是不是不需要传入参数 TransactionContext。当然,注解是肯定需要的,否则 TCC-Transaction 怎么知道哪些方法是 TCC 方法。</li></ul><p>TCC-Transaction 通过 Dubbo Proxy 的机制,实现 <code>@Compensable</code> 属性自动生成,增加开发体验,也避免出错。</p><hr><p>Dubbo 支持( Maven 项目 <code>tcc-transaction-dubbo</code> ) 整体代码结构如下:</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_03_07/01.png" alt=""></p><ul><li><code>proxy</code></li><li><code>context</code></li></ul><p>我们分成两个小节分享这两个包实现的功能。</p><p><strong>笔者暂时对 Dubbo 了解的不够深入,如果有错误的地方,还烦请指出,谢谢。</strong></p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 TCC-Transaction 点赞!<a href="https://github.com/changmingxie/tcc-transaction" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><p>ps:笔者假设你已经阅读过<a href="https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x" rel="external nofollow noopener noreferrer" target="_blank">《tcc-transaction 官方文档 —— 使用指南1.2.x》</a>。</p><h1 id="2-Dubbo-代理"><a href="#2-Dubbo-代理" class="headerlink" title="2. Dubbo 代理"></a>2. Dubbo 代理</h1><p>将 Dubbo Service 方法上的<strong>注解</strong> <code>@Compensable</code> ,自动生成注解的 <code>confirmMethod</code>、<code>cancelMethod</code>、<code>transactionContextEditor</code> 属性,例子代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Compensable</span>(propagation=Propagation.SUPPORTS, confirmMethod=<span class="string">"record"</span>, cancelMethod=<span class="string">"record"</span>, transactionContextEditor=DubboTransactionContextEditor.class)</div><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">record</span><span class="params">(RedPacketTradeOrderDto paramRedPacketTradeOrderDto)</span> </span>{</div><div class="line"> <span class="comment">// ... 省略代码</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>该代码通过 Javassist 生成的 Proxy 代码的示例。</li><li><code>propagation=Propagation.SUPPORTS</code> :支持当前事务,如果当前没有事务,就以非事务方式执行。<strong>为什么不使用 REQUIRED</strong> ?如果使用 REQUIRED 事务传播级别,事务恢复重试时,会发起新的事务。</li><li><code>confirmMethod</code>、<code>cancelMethod</code> 使用和 try 方法<strong>相同方法名</strong>:<strong>本地发起</strong>远程服务 TCC confirm / cancel 阶段,调用相同方法进行事务的提交或回滚。远程服务的 CompensableTransactionInterceptor 会根据事务的状态是 CONFIRMING / CANCELLING 来调用对应方法。<ul><li><img src="../../../images/TCC-Transaction/2018_03_07/02.png" alt=""> </li></ul></li><li><code>transactionContextEditor=DubboTransactionContextEditor.class</code>,使用 Dubbo 事务上下文编辑器,在<a href="#">「3. Dubbo 事务上下文编辑器」</a>详细分享。</li></ul><p>Dubbo Service Proxy 提供了两种生成方式:</p><ul><li>JavassistProxyFactory,基于 Javassist 方式</li><li>JdkProxyFactory,基于 JDK 动态代理机制</li></ul><p>这块内容我们不拓展开,感兴趣的同学点击如下文章:</p><ul><li><a href="http://daveztong.github.io/2016/11/23/Dubbo%E5%AD%A6%E4%B9%A0-%E7%90%86%E8%A7%A3%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86/" rel="external nofollow noopener noreferrer" target="_blank">《Dubbo学习-理解动态代理》</a></li><li><a href="http://javatar.iteye.com/blog/814426" rel="external nofollow noopener noreferrer" target="_blank">《Dubbo 作者博客 —— 动态代理方案性能对比》</a></li><li><a href="http://blog.csdn.net/quhongwei_zhanqiu/article/details/41597261" rel="external nofollow noopener noreferrer" target="_blank">《Dubbo原理解析-代理之Javassist生成的伪代码》</a></li><li><strong><a href="http://blog.kazaff.me/2015/01/27/dubbo%E4%B8%AD%E6%9C%8D%E5%8A%A1%E6%9A%B4%E9%9C%B2%E7%9A%84%E7%BB%86%E8%8A%82/" rel="external nofollow noopener noreferrer" target="_blank">《Dubbo的服务暴露细节》</a></strong></li></ul><p>Dubbo 的 Invoker 模型是非常关键的概念,看下图:</p><p><img src="../../../images/TCC-Transaction/2018_03_07/03.jpeg" alt=""></p><h2 id="2-1-JavassistProxyFactory"><a href="#2-1-JavassistProxyFactory" class="headerlink" title="2.1 JavassistProxyFactory"></a>2.1 JavassistProxyFactory</h2><h3 id="2-1-1-Javassist"><a href="#2-1-1-Javassist" class="headerlink" title="2.1.1 Javassist"></a>2.1.1 Javassist</h3><blockquote><p>Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。通过使用Javassist 对字节码操作可以实现动态 ”AOP” 框架。 </p><p>关于 Java 字节码的处理,目前有很多工具,如 bcel,asm( cglib只是对asm又封装了一层 )。不过这些都需要直接跟虚拟机指令打交道。 </p><p>Javassist 的主要的优点,在于简单,而且快速,直接使用 Java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。 </p></blockquote><ul><li>粗略一看,可能不够形象,下面我们通过看 TCC-Transaction 如何使用来理解理解。</li><li><a href="http://www.cnblogs.com/sunfie/p/5154246.html" rel="external nofollow noopener noreferrer" target="_blank">《Java学习之javassist<br>》</a></li><li><a href="http://blog.csdn.net/qbg19881206/article/details/8993562" rel="external nofollow noopener noreferrer" target="_blank">《Javassist 字节码操作》</a></li></ul><h3 id="2-1-2-TccJavassistProxyFactory"><a href="#2-1-2-TccJavassistProxyFactory" class="headerlink" title="2.1.2 TccJavassistProxyFactory"></a>2.1.2 TccJavassistProxyFactory</h3><p><code>org.mengyun.tcctransaction.dubbo.proxy.javassist.TccJavassistProxyFactory</code>,TCC Javassist 代理工厂。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TccJavassistProxyFactory</span> <span class="keyword">extends</span> <span class="title">JavassistProxyFactory</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</div><div class="line"> <span class="keyword">public</span> <T> <span class="function">T <span class="title">getProxy</span><span class="params">(Invoker<T> invoker, Class<?>[] interfaces)</span> </span>{</div><div class="line"> <span class="keyword">return</span> (T) TccProxy.getProxy(interfaces).newInstance(<span class="keyword">new</span> InvokerInvocationHandler(invoker));</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>项目启动时</strong>,调用 <code>TccJavassistProxyFactory#getProxy(...)</code> 方法,生成 Dubbo Service 调用 Proxy。</li><li><code>com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler</code>,Dubbo 调用处理器,点击<a href="https://github.com/alibaba/dubbo/blob/17619dfa974457b00fe27cf68ae3f9d266709666/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/proxy/InvokerInvocationHandler.java" rel="external nofollow noopener noreferrer" target="_blank">连接</a>查看代码。</li></ul><h3 id="2-1-3-TccProxy-amp-TccClassGenerator"><a href="#2-1-3-TccProxy-amp-TccClassGenerator" class="headerlink" title="2.1.3 TccProxy & TccClassGenerator"></a>2.1.3 TccProxy & TccClassGenerator</h3><p><code>org.mengyun.tcctransaction.dubbo.proxy.javassist.TccProxy</code>,TCC Proxy 工厂,生成 Dubbo Service 调用 Proxy 。笔者认为,TccProxy 改成 TccProxyFactory 更合适,原因在下文。</p><p><code>org.mengyun.tcctransaction.dubbo.proxy.javassist.TccClassGenerator</code>,TCC 类代码生成器,基于 Javassist 实现。 </p><p><strong>🦅案例</strong></p><p>一个 Dubbo Service,TccProxy 会动态生成两个类:</p><ul><li>Dubbo Service 调用 Proxy</li><li>Dubbo Service 调用 ProxyFactory,生成对应的 Dubbo Service Proxy</li></ul><p>例如 Dubbo Service 接口如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">RedPacketTradeOrderService</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Compensable</span></div><div class="line"> <span class="function">String <span class="title">record</span><span class="params">(RedPacketTradeOrderDto tradeOrderDto)</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><p>生成 Dubbo Service 调用 <strong>ProxyFactory</strong> 如下 :</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TccProxy3</span> <span class="keyword">extends</span> <span class="title">TccProxy</span> <span class="keyword">implements</span> <span class="title">TccClassGenerator</span>.<span class="title">DC</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">newInstance</span><span class="params">(InvocationHandler paramInvocationHandler)</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> proxy3(paramInvocationHandler);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>TccProxy 提供 <code>#newInstance(handler)</code> 方法,创建 Proxy,所以笔者认为,TccProxy 改成 TccProxyFactory 更合适。</li><li><code>org.mengyun.tcctransaction.dubbo.proxy.javassist.TccClassGenerator.DC</code> 动态生成类标记,标记该类由 TccClassGenerator 生成的。</li></ul><p>生成 Dubbo Service 调用 <strong>Proxy</strong> 如下 :</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">proxy3</span> <span class="keyword">implements</span> <span class="title">TccClassGenerator</span>.<span class="title">DC</span>, <span class="title">RedPacketTradeOrderService</span>, <span class="title">EchoService</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> Method[] methods;</div><div class="line"> <span class="keyword">private</span> InvocationHandler handler;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">proxy3</span><span class="params">()</span> </span>{}</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">proxy3</span><span class="params">(InvocationHandler paramInvocationHandler)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.handler = paramInvocationHandler;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Compensable</span>(propagation = Propagation.SUPPORTS, confirmMethod = <span class="string">"record"</span>, cancelMethod = <span class="string">"record"</span>, transactionContextEditor = DubboTransactionContextEditor.class)</div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">record</span><span class="params">(RedPacketTradeOrderDto paramRedPacketTradeOrderDto)</span> </span>{</div><div class="line"> Object[] arrayOfObject = <span class="keyword">new</span> Object[<span class="number">1</span>];</div><div class="line"> arrayOfObject[<span class="number">0</span>] = paramRedPacketTradeOrderDto;</div><div class="line"> Object localObject = <span class="keyword">this</span>.handler.invoke(<span class="keyword">this</span>, methods[<span class="number">0</span>], arrayOfObject);</div><div class="line"> <span class="keyword">return</span> (String) localObject;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">public</span> Object $echo(Object paramObject) {</div><div class="line"> Object[] arrayOfObject = <span class="keyword">new</span> Object[<span class="number">1</span>];</div><div class="line"> arrayOfObject[<span class="number">0</span>] = paramObject;</div><div class="line"> Object localObject = <span class="keyword">this</span>.handler.invoke(<span class="keyword">this</span>, methods[<span class="number">1</span>], arrayOfObject);</div><div class="line"> <span class="keyword">return</span> (Object) localObject;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>com.alibaba.dubbo.rpc.service.EchoService</code>,Dubbo Service 回声服务接口,用于服务健康检查,Dubbo Service 默认自动实现该接口,点击<a href="https://github.com/alibaba/dubbo/blob/17619dfa974457b00fe27cf68ae3f9d266709666/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/service/EchoService.java" rel="external nofollow noopener noreferrer" target="_blank">连接</a>查看代码。</li><li><code>org.mengyun.tcctransaction.dubbo.proxy.javassist.TccClassGenerator.DC</code> 动态生成类标记,标记该类由 TccClassGenerator 生成的。</li></ul><p><strong>🦅实现</strong></p><p>调用 <code>TccProxy#getProxy(...)</code> 方法,获得 <strong>TCC Proxy 工厂</strong>,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"> <span class="number">1</span>: <span class="comment">// 【TccProxy.java】</span></div><div class="line"> <span class="number">2</span>: <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> TccProxy <span class="title">getProxy</span><span class="params">(ClassLoader cl, Class<?>... ics)</span> </span>{</div><div class="line"> <span class="number">3</span>: <span class="comment">// 校验接口超过上限</span></div><div class="line"> <span class="number">4</span>: <span class="keyword">if</span> (ics.length > <span class="number">65535</span>) {</div><div class="line"> <span class="number">5</span>: <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"interface limit exceeded"</span>);</div><div class="line"> <span class="number">6</span>: }</div><div class="line"> <span class="number">7</span>: </div><div class="line"> <span class="number">8</span>: <span class="comment">// use interface class name list as key.</span></div><div class="line"> <span class="number">9</span>: StringBuilder sb = <span class="keyword">new</span> StringBuilder();</div><div class="line"> <span class="number">10</span>: <span class="keyword">for</span> (Class<?> ic : ics) {</div><div class="line"> <span class="number">11</span>: String itf = ic.getName();</div><div class="line"> <span class="number">12</span>: <span class="comment">// 校验是否为接口</span></div><div class="line"> <span class="number">13</span>: <span class="keyword">if</span> (!ic.isInterface()) {</div><div class="line"> <span class="number">14</span>: <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(itf + <span class="string">" is not a interface."</span>);</div><div class="line"> <span class="number">15</span>: }</div><div class="line"> <span class="number">16</span>: <span class="comment">// 加载接口类</span></div><div class="line"> <span class="number">17</span>: Class<?> tmp = <span class="keyword">null</span>;</div><div class="line"> <span class="number">18</span>: <span class="keyword">try</span> {</div><div class="line"> <span class="number">19</span>: tmp = Class.forName(itf, <span class="keyword">false</span>, cl);</div><div class="line"> <span class="number">20</span>: } <span class="keyword">catch</span> (ClassNotFoundException ignored) {</div><div class="line"> <span class="number">21</span>: }</div><div class="line"> <span class="number">22</span>: <span class="keyword">if</span> (tmp != ic) { <span class="comment">// 加载接口类失败</span></div><div class="line"> <span class="number">23</span>: <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(ic + <span class="string">" is not visible from class loader"</span>);</div><div class="line"> <span class="number">24</span>: }</div><div class="line"> <span class="number">25</span>: sb.append(itf).append(<span class="string">';'</span>);</div><div class="line"> <span class="number">26</span>: }</div><div class="line"> <span class="number">27</span>: String key = sb.toString();</div><div class="line"> <span class="number">28</span>: </div><div class="line"> <span class="number">29</span>: <span class="comment">// get cache by class loader.</span></div><div class="line"> <span class="number">30</span>: Map<String, Object> cache;</div><div class="line"> <span class="number">31</span>: <span class="keyword">synchronized</span> (ProxyCacheMap) {</div><div class="line"> <span class="number">32</span>: cache = ProxyCacheMap.get(cl);</div><div class="line"> <span class="number">33</span>: <span class="keyword">if</span> (cache == <span class="keyword">null</span>) {</div><div class="line"> <span class="number">34</span>: cache = <span class="keyword">new</span> HashMap<String, Object>();</div><div class="line"> <span class="number">35</span>: ProxyCacheMap.put(cl, cache);</div><div class="line"> <span class="number">36</span>: }</div><div class="line"> <span class="number">37</span>: }</div><div class="line"> <span class="number">38</span>: </div><div class="line"> <span class="number">39</span>: <span class="comment">// 获得 TccProxy 工厂</span></div><div class="line"> <span class="number">40</span>: TccProxy proxy = <span class="keyword">null</span>;</div><div class="line"> <span class="number">41</span>: <span class="keyword">synchronized</span> (cache) {</div><div class="line"> <span class="number">42</span>: <span class="keyword">do</span> {</div><div class="line"> <span class="number">43</span>: <span class="comment">// 从缓存中获取 TccProxy 工厂</span></div><div class="line"> <span class="number">44</span>: Object value = cache.get(key);</div><div class="line"> <span class="number">45</span>: <span class="keyword">if</span> (value <span class="keyword">instanceof</span> Reference<?>) {</div><div class="line"> <span class="number">46</span>: proxy = (TccProxy) ((Reference<?>) value).get();</div><div class="line"> <span class="number">47</span>: <span class="keyword">if</span> (proxy != <span class="keyword">null</span>) {</div><div class="line"> <span class="number">48</span>: <span class="keyword">return</span> proxy;</div><div class="line"> <span class="number">49</span>: }</div><div class="line"> <span class="number">50</span>: }</div><div class="line"> <span class="number">51</span>: <span class="comment">// 缓存中不存在,设置生成 TccProxy 代码标记。创建中时,其他创建请求等待,避免并发。</span></div><div class="line"> <span class="number">52</span>: <span class="keyword">if</span> (value == PendingGenerationMarker) {</div><div class="line"> <span class="number">53</span>: <span class="keyword">try</span> {</div><div class="line"> <span class="number">54</span>: cache.wait();</div><div class="line"> <span class="number">55</span>: } <span class="keyword">catch</span> (InterruptedException ignored) {</div><div class="line"> <span class="number">56</span>: }</div><div class="line"> <span class="number">57</span>: } <span class="keyword">else</span> {</div><div class="line"> <span class="number">58</span>: cache.put(key, PendingGenerationMarker);</div><div class="line"> <span class="number">59</span>: <span class="keyword">break</span>;</div><div class="line"> <span class="number">60</span>: }</div><div class="line"> <span class="number">61</span>: }</div><div class="line"> <span class="number">62</span>: <span class="keyword">while</span> (<span class="keyword">true</span>);</div><div class="line"> <span class="number">63</span>: }</div><div class="line"> <span class="number">64</span>: </div><div class="line"> <span class="number">65</span>: <span class="keyword">long</span> id = PROXY_CLASS_COUNTER.getAndIncrement();</div><div class="line"> <span class="number">66</span>: String pkg = <span class="keyword">null</span>;</div><div class="line"> <span class="number">67</span>: TccClassGenerator ccp = <span class="keyword">null</span>; <span class="comment">// proxy class generator</span></div><div class="line"> <span class="number">68</span>: TccClassGenerator ccm = <span class="keyword">null</span>; <span class="comment">// proxy factory class generator</span></div><div class="line"> <span class="number">69</span>: <span class="keyword">try</span> {</div><div class="line"> <span class="number">70</span>: <span class="comment">// 创建 Tcc class 代码生成器</span></div><div class="line"> <span class="number">71</span>: ccp = TccClassGenerator.newInstance(cl);</div><div class="line"> <span class="number">72</span>: </div><div class="line"> <span class="number">73</span>: Set<String> worked = <span class="keyword">new</span> HashSet<String>(); <span class="comment">// 已处理方法签名集合。key:方法签名</span></div><div class="line"> <span class="number">74</span>: List<Method> methods = <span class="keyword">new</span> ArrayList<Method>(); <span class="comment">// 已处理方法集合。</span></div><div class="line"> <span class="number">75</span>: </div><div class="line"> <span class="number">76</span>: <span class="comment">// 处理接口</span></div><div class="line"> <span class="number">77</span>: <span class="keyword">for</span> (Class<?> ic : ics) {</div><div class="line"> <span class="number">78</span>: <span class="comment">// 非 public 接口,使用接口包名</span></div><div class="line"> <span class="number">79</span>: <span class="keyword">if</span> (!Modifier.isPublic(ic.getModifiers())) {</div><div class="line"> <span class="number">80</span>: String npkg = ic.getPackage().getName();</div><div class="line"> <span class="number">81</span>: <span class="keyword">if</span> (pkg == <span class="keyword">null</span>) {</div><div class="line"> <span class="number">82</span>: pkg = npkg;</div><div class="line"> <span class="number">83</span>: } <span class="keyword">else</span> {</div><div class="line"> <span class="number">84</span>: <span class="keyword">if</span> (!pkg.equals(npkg)) { <span class="comment">// 实现了两个非 public 的接口,</span></div><div class="line"> <span class="number">85</span>: <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"non-public interfaces from different packages"</span>);</div><div class="line"> <span class="number">86</span>: }</div><div class="line"> <span class="number">87</span>: }</div><div class="line"> <span class="number">88</span>: }</div><div class="line"> <span class="number">89</span>: <span class="comment">// 添加接口</span></div><div class="line"> <span class="number">90</span>: ccp.addInterface(ic);</div><div class="line"> <span class="number">91</span>: <span class="comment">// 处理接口方法</span></div><div class="line"> <span class="number">92</span>: <span class="keyword">for</span> (Method method : ic.getMethods()) {</div><div class="line"> <span class="number">93</span>: <span class="comment">// 添加方法签名到已处理方法签名集合</span></div><div class="line"> <span class="number">94</span>: String desc = ReflectUtils.getDesc(method);</div><div class="line"> <span class="number">95</span>: <span class="keyword">if</span> (worked.contains(desc)) {</div><div class="line"> <span class="number">96</span>: <span class="keyword">continue</span>;</div><div class="line"> <span class="number">97</span>: }</div><div class="line"> <span class="number">98</span>: worked.add(desc);</div><div class="line"> <span class="number">99</span>: <span class="comment">// 生成接口方法实现代码</span></div><div class="line"><span class="number">100</span>: <span class="keyword">int</span> ix = methods.size();</div><div class="line"><span class="number">101</span>: Class<?> rt = method.getReturnType();</div><div class="line"><span class="number">102</span>: Class<?>[] pts = method.getParameterTypes();</div><div class="line"><span class="number">103</span>: StringBuilder code = <span class="keyword">new</span> StringBuilder(<span class="string">"Object[] args = new Object["</span>).append(pts.length).append(<span class="string">"];"</span>);</div><div class="line"><span class="number">104</span>: <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < pts.length; j++) {</div><div class="line"><span class="number">105</span>: code.append(<span class="string">" args["</span>).append(j).append(<span class="string">"] = ($w)$"</span>).append(j + <span class="number">1</span>).append(<span class="string">";"</span>);</div><div class="line"><span class="number">106</span>: }</div><div class="line"><span class="number">107</span>: code.append(<span class="string">" Object ret = handler.invoke(this, methods["</span>).append(ix).append(<span class="string">"], args);"</span>);</div><div class="line"><span class="number">108</span>: <span class="keyword">if</span> (!Void.TYPE.equals(rt)) {</div><div class="line"><span class="number">109</span>: code.append(<span class="string">" return "</span>).append(asArgument(rt, <span class="string">"ret"</span>)).append(<span class="string">";"</span>);</div><div class="line"><span class="number">110</span>: }</div><div class="line"><span class="number">111</span>: methods.add(method);</div><div class="line"><span class="number">112</span>: <span class="comment">// 添加方法</span></div><div class="line"><span class="number">113</span>: Compensable compensable = method.getAnnotation(Compensable.class);</div><div class="line"><span class="number">114</span>: <span class="keyword">if</span> (compensable != <span class="keyword">null</span>) {</div><div class="line"><span class="number">115</span>: ccp.addMethod(<span class="keyword">true</span>, method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());</div><div class="line"><span class="number">116</span>: } <span class="keyword">else</span> {</div><div class="line"><span class="number">117</span>: ccp.addMethod(<span class="keyword">false</span>, method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());</div><div class="line"><span class="number">118</span>: }</div><div class="line"><span class="number">119</span>: }</div><div class="line"><span class="number">120</span>: }</div><div class="line"><span class="number">121</span>: </div><div class="line"><span class="number">122</span>: <span class="comment">// 设置包路径</span></div><div class="line"><span class="number">123</span>: <span class="keyword">if</span> (pkg == <span class="keyword">null</span>) {</div><div class="line"><span class="number">124</span>: pkg = PACKAGE_NAME;</div><div class="line"><span class="number">125</span>: }</div><div class="line"><span class="number">126</span>: </div><div class="line"><span class="number">127</span>: <span class="comment">// create ProxyInstance class.</span></div><div class="line"><span class="number">128</span>: <span class="comment">// 设置类名</span></div><div class="line"><span class="number">129</span>: String pcn = pkg + <span class="string">".proxy"</span> + id;</div><div class="line"><span class="number">130</span>: ccp.setClassName(pcn);</div><div class="line"><span class="number">131</span>: <span class="comment">// 添加静态属性 methods</span></div><div class="line"><span class="number">132</span>: ccp.addField(<span class="string">"public static java.lang.reflect.Method[] methods;"</span>);</div><div class="line"><span class="number">133</span>: <span class="comment">// 添加属性 handler</span></div><div class="line"><span class="number">134</span>: ccp.addField(<span class="string">"private "</span> + InvocationHandler.class.getName() + <span class="string">" handler;"</span>);</div><div class="line"><span class="number">135</span>: <span class="comment">// 添加构造方法,参数 handler</span></div><div class="line"><span class="number">136</span>: ccp.addConstructor(Modifier.PUBLIC, <span class="keyword">new</span> Class<?>[]{InvocationHandler.class}, <span class="keyword">new</span> Class<?>[<span class="number">0</span>], <span class="string">"handler=$1;"</span>);</div><div class="line"><span class="number">137</span>: <span class="comment">// 添加构造方法,参数 空</span></div><div class="line"><span class="number">138</span>: ccp.addDefaultConstructor();</div><div class="line"><span class="number">139</span>: <span class="comment">// 生成类</span></div><div class="line"><span class="number">140</span>: Class<?> clazz = ccp.toClass();</div><div class="line"><span class="number">141</span>: <span class="comment">// 设置静态属性 methods</span></div><div class="line"><span class="number">142</span>: clazz.getField(<span class="string">"methods"</span>).set(<span class="keyword">null</span>, methods.toArray(<span class="keyword">new</span> Method[<span class="number">0</span>]));</div><div class="line"><span class="number">143</span>: </div><div class="line"><span class="number">144</span>: <span class="comment">// create TccProxy class.</span></div><div class="line"><span class="number">145</span>: <span class="comment">// 创建 Tcc class 代码生成器</span></div><div class="line"><span class="number">146</span>: ccm = TccClassGenerator.newInstance(cl);</div><div class="line"><span class="number">147</span>: <span class="comment">// 设置类名</span></div><div class="line"><span class="number">148</span>: String fcn = TccProxy.class.getName() + id;</div><div class="line"><span class="number">149</span>: ccm.setClassName(fcn);</div><div class="line"><span class="number">150</span>: <span class="comment">// 添加构造方法,参数 空</span></div><div class="line"><span class="number">151</span>: ccm.addDefaultConstructor();</div><div class="line"><span class="number">152</span>: <span class="comment">// 设置父类为 TccProxy.class</span></div><div class="line"><span class="number">153</span>: ccm.setSuperClass(TccProxy.class);</div><div class="line"><span class="number">154</span>: <span class="comment">// 添加方法 #newInstance(handler)</span></div><div class="line"><span class="number">155</span>: ccm.addMethod(<span class="string">"public Object newInstance("</span> + InvocationHandler.class.getName() + <span class="string">" h){ return new "</span> + pcn + <span class="string">"($1); }"</span>);</div><div class="line"><span class="number">156</span>: <span class="comment">// 生成类</span></div><div class="line"><span class="number">157</span>: Class<?> pc = ccm.toClass();</div><div class="line"><span class="number">158</span>: <span class="comment">// 创建 TccProxy 对象</span></div><div class="line"><span class="number">159</span>: proxy = (TccProxy) pc.newInstance();</div><div class="line"><span class="number">160</span>: } <span class="keyword">catch</span> (RuntimeException e) {</div><div class="line"><span class="number">161</span>: <span class="keyword">throw</span> e;</div><div class="line"><span class="number">162</span>: } <span class="keyword">catch</span> (Exception e) {</div><div class="line"><span class="number">163</span>: <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e.getMessage(), e);</div><div class="line"><span class="number">164</span>: } <span class="keyword">finally</span> {</div><div class="line"><span class="number">165</span>: <span class="comment">// release TccClassGenerator</span></div><div class="line"><span class="number">166</span>: <span class="keyword">if</span> (ccp != <span class="keyword">null</span>) {</div><div class="line"><span class="number">167</span>: ccp.release();</div><div class="line"><span class="number">168</span>: }</div><div class="line"><span class="number">169</span>: <span class="keyword">if</span> (ccm != <span class="keyword">null</span>) {</div><div class="line"><span class="number">170</span>: ccm.release();</div><div class="line"><span class="number">171</span>: }</div><div class="line"><span class="number">172</span>: <span class="comment">// 唤醒缓存 wait</span></div><div class="line"><span class="number">173</span>: <span class="keyword">synchronized</span> (cache) {</div><div class="line"><span class="number">174</span>: <span class="keyword">if</span> (proxy == <span class="keyword">null</span>) {</div><div class="line"><span class="number">175</span>: cache.remove(key);</div><div class="line"><span class="number">176</span>: } <span class="keyword">else</span> {</div><div class="line"><span class="number">177</span>: cache.put(key, <span class="keyword">new</span> WeakReference<TccProxy>(proxy));</div><div class="line"><span class="number">178</span>: }</div><div class="line"><span class="number">179</span>: cache.notifyAll();</div><div class="line"><span class="number">180</span>: }</div><div class="line"><span class="number">181</span>: }</div><div class="line"><span class="number">182</span>: <span class="keyword">return</span> proxy;</div><div class="line"><span class="number">183</span>: }</div></pre></td></tr></table></figure><ul><li>第 3 至 7 行 :校验接口超过上限。</li><li>第 8 至 27 行 :使用接口集合类名以 <code>;</code> 分隔拼接,作为 Proxy 的唯一标识。例如 :<code>key=org.mengyun.tcctransaction.sample.dubbo.redpacket.api.RedPacketAccountService;com.alibaba.dubbo.rpc.service.EchoService;</code> 。</li><li><p>第 29 至 37 行 :获得 Proxy 对应的 ClassLoader。这里我们看下静态属性 <code>ProxyCacheMap</code> 的定义:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* Proxy 对象缓存</span></div><div class="line"><span class="comment">* key :ClassLoader</span></div><div class="line"><span class="comment">* value.key :Tcc Proxy 标识。使用 Tcc Proxy 实现接口名拼接</span></div><div class="line"><span class="comment">* value.value :Tcc Proxy 工厂对象</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map<ClassLoader, Map<String, Object>> ProxyCacheMap = <span class="keyword">new</span> WeakHashMap<ClassLoader, Map<String, Object>>();</div></pre></td></tr></table></figure><ul><li>使用 WeakHashMap,当 ClassLoader 被回收时,其对应的值一起被移除。</li><li><a href="http://blog.csdn.net/yangzl2008/article/details/6980709" rel="external nofollow noopener noreferrer" target="_blank">《WeakHashMap和HashMap的区别》</a></li><li><a href="http://www.cnblogs.com/skywang12345/p/3311092.html" rel="external nofollow noopener noreferrer" target="_blank">《Java 集合系列13之 WeakHashMap详细介绍(源码解析)和使用示例》</a></li></ul></li><li>第 39 至 63 行 :一直获得 <strong>TCC Proxy 工厂</strong>直到成功。<ul><li>第 43 至 50 行 :从<strong>缓存</strong>中获取 TCC Proxy 工厂。</li><li>第 51 至 60 行 :若缓存中不存在,设置<strong>正在生成 TccProxy 代码标记</strong>。创建中时,其他创建请求等待,避免并发。</li></ul></li><li><p>第 65 行 :<code>PROXY_CLASS_COUNTER</code>,Proxy Class 计数,用于生成 Proxy 类名自增。代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> AtomicLong PROXY_CLASS_COUNTER = <span class="keyword">new</span> AtomicLong(<span class="number">0</span>);</div></pre></td></tr></table></figure></li><li><p>第 66 至 67 行</p><ul><li><code>ccm</code>,生成 Dubbo Service 调用 <strong>ProxyFactory</strong> 的代码生成器</li><li><code>ccp</code>,生成 Dubbo Service 调用 <strong>Proxy</strong> 的代码生成器</li></ul></li><li><p><strong>第 70 至 142 行 :生成 Dubbo Service 调用 Proxy 的代码</strong>。</p><ul><li><p>第 70 至 71 行 :调用 <code>TccClassGenerator#newInstance(loader)</code> 方法, 创建生成 Dubbo Service 调用 <strong>Proxy</strong> 的代码生成器。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TccClassGenerator.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TccClassGenerator</span> </span>{</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * CtClass hash 集合</span></div><div class="line"><span class="comment"> * key:类名</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> ClassPool mPool;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> TccClassGenerator <span class="title">newInstance</span><span class="params">(ClassLoader loader)</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> TccClassGenerator(getClassPool(loader));</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">private</span> <span class="title">TccClassGenerator</span><span class="params">(ClassPool pool)</span> </span>{</div><div class="line"> mPool = pool;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>ClassPool</strong> 是一个 CtClass 对象的 hash 表,类名做为 key 。ClassPool 的 <code>#get(key)</code> 搜索 hash 表找到与指定 key 关联的 CtClass 对象。如果没有找到 CtClass 对象,<code>#get(key)</code> 读一个类文件构建新的 CtClass 对象,它是被记录在 hash 表中然后返回这个对象。</li></ul></li><li><p>第 76 至 120 行,处理接口。</p><ul><li>第 79 至 88 行,生成类的包名。</li><li><p>第 89 至 90 行,调用 <code>TccClassGenerator#addInterface(cl)</code> 方法,添加生成类的接口( <strong>Dubbo Service 接口</strong> )。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 生成类的接口集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> Set<String> mInterfaces;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">addInterface</span><span class="params">(Class<?> cl)</span> </span>{</div><div class="line"> <span class="keyword">return</span> addInterface(cl.getName());</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">addInterface</span><span class="params">(String cn)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (mInterfaces == <span class="keyword">null</span>) {</div><div class="line"> mInterfaces = <span class="keyword">new</span> HashSet<String>();</div><div class="line"> }</div><div class="line"> mInterfaces.add(cn);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>x</li></ul></li><li><p>第 93 至 98 行,添加方法签名到已处理方法签名集合。多个接口可能存在相同的接口方法,跳过相同的方法,避免冲突。</p></li><li><p>第 99 至 110 行,生成 Dubbo Service 调用实现代码。案例代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">record</span><span class="params">(RedPacketTradeOrderDto paramRedPacketTradeOrderDto)</span> </span>{</div><div class="line"> Object[] arrayOfObject = <span class="keyword">new</span> Object[<span class="number">1</span>];</div><div class="line"> arrayOfObject[<span class="number">0</span>] = paramRedPacketTradeOrderDto;</div><div class="line"> Object localObject = <span class="keyword">this</span>.handler.invoke(<span class="keyword">this</span>, methods[<span class="number">0</span>], arrayOfObject);</div><div class="line"> <span class="keyword">return</span> (String)localObject;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><img src="../../../images/TCC-Transaction/2018_03_07/04.png" alt=""></li></ul></li><li><p>第 112 至 118 行 :调用 <code>TccClassGenerator#addMethod(...)</code> 方法,添加生成的方法。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 生成类的方法代码集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> List<String> mMethods;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 带 <span class="doctag">@Compensable</span> 方法代码集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> Set<String> compensableMethods = <span class="keyword">new</span> HashSet<String>();</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">addMethod</span><span class="params">(<span class="keyword">boolean</span> isCompensableMethod, String name, <span class="keyword">int</span> mod, Class<?> rt, Class<?>[] pts, Class<?>[] ets, String body)</span> </span>{</div><div class="line"> <span class="comment">// 拼接方法</span></div><div class="line"> StringBuilder sb = <span class="keyword">new</span> StringBuilder();</div><div class="line"> sb.append(modifier(mod)).append(<span class="string">' '</span>).append(ReflectUtils.getName(rt)).append(<span class="string">' '</span>).append(name);</div><div class="line"> sb.append(<span class="string">'('</span>);</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < pts.length; i++) {</div><div class="line"> <span class="keyword">if</span> (i > <span class="number">0</span>)</div><div class="line"> sb.append(<span class="string">','</span>);</div><div class="line"> sb.append(ReflectUtils.getName(pts[i]));</div><div class="line"> sb.append(<span class="string">" arg"</span>).append(i);</div><div class="line"> }</div><div class="line"> sb.append(<span class="string">')'</span>);</div><div class="line"> <span class="keyword">if</span> (ets != <span class="keyword">null</span> && ets.length > <span class="number">0</span>) {</div><div class="line"> sb.append(<span class="string">" throws "</span>);</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < ets.length; i++) {</div><div class="line"> <span class="keyword">if</span> (i > <span class="number">0</span>)</div><div class="line"> sb.append(<span class="string">','</span>);</div><div class="line"> sb.append(ReflectUtils.getName(ets[i]));</div><div class="line"> }</div><div class="line"> }</div><div class="line"> sb.append(<span class="string">'{'</span>).append(body).append(<span class="string">'}'</span>);</div><div class="line"> <span class="comment">// 是否有 @Compensable 注解</span></div><div class="line"> <span class="keyword">if</span> (isCompensableMethod) {</div><div class="line"> compensableMethods.add(sb.toString());</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> addMethod(sb.toString());</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">addMethod</span><span class="params">(String code)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (mMethods == <span class="keyword">null</span>) {</div><div class="line"> mMethods = <span class="keyword">new</span> ArrayList<String>();</div><div class="line"> }</div><div class="line"> mMethods.add(code);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul></li><li><p>第 122 至 130 行,生成类名( 例如,<code>org.mengyun.tcctransaction.dubbo.proxy.javassist.proxy3</code> ),并调用 <code>TccClassGenerator#setClassName(...)</code> 方法,设置类名。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 生成类的类名</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> String mClassName;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">setClassName</span><span class="params">(String name)</span> </span>{</div><div class="line"> mClassName = name;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>x</li></ul></li><li><p>第 131 至 134 行,调用 <code>TccClassGenerator#addField(...)</code> 方法,添加<strong>静态</strong>属性 <code>methods</code> ( Dubbo Service 方法集合 )和属性 <code>handler</code> ( Dubbo InvocationHandler )。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 生成类的属性集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> List<String> mFields;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">addField</span><span class="params">(String code)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (mFields == <span class="keyword">null</span>) {</div><div class="line"> mFields = <span class="keyword">new</span> ArrayList<String>();</div><div class="line"> }</div><div class="line"> mFields.add(code);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>x</li></ul></li><li><p>第 135 至 136 行,调用 <code>TccClassGenerator#addConstructor(...)</code> 方法,添加参数为 <code>handler</code> 的构造方法。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 生成类的非空构造方法代码集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> List<String> mConstructors;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">addConstructor</span><span class="params">(<span class="keyword">int</span> mod, Class<?>[] pts, Class<?>[] ets, String body)</span> </span>{</div><div class="line"> <span class="comment">// 构造方法代码</span></div><div class="line"> StringBuilder sb = <span class="keyword">new</span> StringBuilder();</div><div class="line"> sb.append(modifier(mod)).append(<span class="string">' '</span>).append(SIMPLE_NAME_TAG);</div><div class="line"> sb.append(<span class="string">'('</span>);</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < pts.length; i++) {</div><div class="line"> <span class="keyword">if</span> (i > <span class="number">0</span>)</div><div class="line"> sb.append(<span class="string">','</span>);</div><div class="line"> sb.append(ReflectUtils.getName(pts[i]));</div><div class="line"> sb.append(<span class="string">" arg"</span>).append(i);</div><div class="line"> }</div><div class="line"> sb.append(<span class="string">')'</span>);</div><div class="line"> <span class="keyword">if</span> (ets != <span class="keyword">null</span> && ets.length > <span class="number">0</span>) {</div><div class="line"> sb.append(<span class="string">" throws "</span>);</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < ets.length; i++) {</div><div class="line"> <span class="keyword">if</span> (i > <span class="number">0</span>)</div><div class="line"> sb.append(<span class="string">','</span>);</div><div class="line"> sb.append(ReflectUtils.getName(ets[i]));</div><div class="line"> }</div><div class="line"> }</div><div class="line"> sb.append(<span class="string">'{'</span>).append(body).append(<span class="string">'}'</span>);</div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="keyword">return</span> addConstructor(sb.toString());</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">addConstructor</span><span class="params">(String code)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (mConstructors == <span class="keyword">null</span>) {</div><div class="line"> mConstructors = <span class="keyword">new</span> LinkedList<String>();</div><div class="line"> }</div><div class="line"> mConstructors.add(code);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">addConstructor</span><span class="params">(String code)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (mConstructors == <span class="keyword">null</span>) {</div><div class="line"> mConstructors = <span class="keyword">new</span> LinkedList<String>();</div><div class="line"> }</div><div class="line"> mConstructors.add(code);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>x</li></ul></li><li><p>第 137 至 138 行,调用 <code>TccClassGenerator#addDefaultConstructor()</code> 方法,添加默认空构造方法。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 默认空构造方法</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">boolean</span> mDefaultConstructor = <span class="keyword">false</span>;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">addDefaultConstructor</span><span class="params">()</span> </span>{</div><div class="line"> mDefaultConstructor = <span class="keyword">true</span>;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>x</li></ul></li><li><p>第 139 行,调用 <code>TccClassGenerator#toClass()</code> 方法,<strong>生成类</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"> <span class="number">1</span>: <span class="keyword">public</span> Class<?> toClass() {</div><div class="line"> <span class="number">2</span>: <span class="comment">// mCtc 非空时,进行释放;下面会进行创建 mCtc</span></div><div class="line"> <span class="number">3</span>: <span class="keyword">if</span> (mCtc != <span class="keyword">null</span>) {</div><div class="line"> <span class="number">4</span>: mCtc.detach();</div><div class="line"> <span class="number">5</span>: }</div><div class="line"> <span class="number">6</span>: <span class="keyword">long</span> id = CLASS_NAME_COUNTER.getAndIncrement();</div><div class="line"> <span class="number">7</span>: <span class="keyword">try</span> {</div><div class="line"> <span class="number">8</span>: CtClass ctcs = mSuperClass == <span class="keyword">null</span> ? <span class="keyword">null</span> : mPool.get(mSuperClass);</div><div class="line"> <span class="number">9</span>: <span class="keyword">if</span> (mClassName == <span class="keyword">null</span>) { <span class="comment">// 类名</span></div><div class="line"><span class="number">10</span>: mClassName = (mSuperClass == <span class="keyword">null</span> || javassist.Modifier.isPublic(ctcs.getModifiers())</div><div class="line"><span class="number">11</span>: ? TccClassGenerator.class.getName() : mSuperClass + <span class="string">"$sc"</span>) + id;</div><div class="line"><span class="number">12</span>: }</div><div class="line"><span class="number">13</span>: <span class="comment">// 创建 mCtc</span></div><div class="line"><span class="number">14</span>: mCtc = mPool.makeClass(mClassName);</div><div class="line"><span class="number">15</span>: <span class="keyword">if</span> (mSuperClass != <span class="keyword">null</span>) { <span class="comment">// 继承类</span></div><div class="line"><span class="number">16</span>: mCtc.setSuperclass(ctcs);</div><div class="line"><span class="number">17</span>: }</div><div class="line"><span class="number">18</span>: mCtc.addInterface(mPool.get(DC.class.getName())); <span class="comment">// add dynamic class tag.</span></div><div class="line"><span class="number">19</span>: <span class="keyword">if</span> (mInterfaces != <span class="keyword">null</span>) { <span class="comment">// 实现接口集合</span></div><div class="line"><span class="number">20</span>: <span class="keyword">for</span> (String cl : mInterfaces) {</div><div class="line"><span class="number">21</span>: mCtc.addInterface(mPool.get(cl));</div><div class="line"><span class="number">22</span>: }</div><div class="line"><span class="number">23</span>: }</div><div class="line"><span class="number">24</span>: <span class="keyword">if</span> (mFields != <span class="keyword">null</span>) { <span class="comment">// 属性集合</span></div><div class="line"><span class="number">25</span>: <span class="keyword">for</span> (String code : mFields) {</div><div class="line"><span class="number">26</span>: mCtc.addField(CtField.make(code, mCtc));</div><div class="line"><span class="number">27</span>: }</div><div class="line"><span class="number">28</span>: }</div><div class="line"><span class="number">29</span>: <span class="keyword">if</span> (mMethods != <span class="keyword">null</span>) { <span class="comment">// 方法集合</span></div><div class="line"><span class="number">30</span>: <span class="keyword">for</span> (String code : mMethods) {</div><div class="line"><span class="number">31</span>: <span class="keyword">if</span> (code.charAt(<span class="number">0</span>) == <span class="string">':'</span>) {</div><div class="line"><span class="number">32</span>: mCtc.addMethod(CtNewMethod.copy(getCtMethod(mCopyMethods.get(code.substring(<span class="number">1</span>))), code.substring(<span class="number">1</span>, code.indexOf(<span class="string">'('</span>)), mCtc, <span class="keyword">null</span>));</div><div class="line"><span class="number">33</span>: } <span class="keyword">else</span> {</div><div class="line"><span class="number">34</span>: CtMethod ctMethod = CtNewMethod.make(code, mCtc);</div><div class="line"><span class="number">35</span>: <span class="keyword">if</span> (compensableMethods.contains(code)) {</div><div class="line"><span class="number">36</span>: <span class="comment">// 设置 @Compensable 属性</span></div><div class="line"><span class="number">37</span>: ConstPool constpool = mCtc.getClassFile().getConstPool();</div><div class="line"><span class="number">38</span>: AnnotationsAttribute attr = <span class="keyword">new</span> AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);</div><div class="line"><span class="number">39</span>: Annotation annot = <span class="keyword">new</span> Annotation(<span class="string">"org.mengyun.tcctransaction.api.Compensable"</span>, constpool);</div><div class="line"><span class="number">40</span>: EnumMemberValue enumMemberValue = <span class="keyword">new</span> EnumMemberValue(constpool);</div><div class="line"><span class="number">41</span>: enumMemberValue.setType(<span class="string">"org.mengyun.tcctransaction.api.Propagation"</span>);</div><div class="line"><span class="number">42</span>: enumMemberValue.setValue(<span class="string">"SUPPORTS"</span>);</div><div class="line"><span class="number">43</span>: annot.addMemberValue(<span class="string">"propagation"</span>, enumMemberValue);</div><div class="line"><span class="number">44</span>: annot.addMemberValue(<span class="string">"confirmMethod"</span>, <span class="keyword">new</span> StringMemberValue(ctMethod.getName(), constpool));</div><div class="line"><span class="number">45</span>: annot.addMemberValue(<span class="string">"cancelMethod"</span>, <span class="keyword">new</span> StringMemberValue(ctMethod.getName(), constpool));</div><div class="line"><span class="number">46</span>: ClassMemberValue classMemberValue = <span class="keyword">new</span> ClassMemberValue(<span class="string">"org.mengyun.tcctransaction.dubbo.context.DubboTransactionContextEditor"</span>, constpool);</div><div class="line"><span class="number">47</span>: annot.addMemberValue(<span class="string">"transactionContextEditor"</span>, classMemberValue);</div><div class="line"><span class="number">48</span>: attr.addAnnotation(annot);</div><div class="line"><span class="number">49</span>: ctMethod.getMethodInfo().addAttribute(attr);</div><div class="line"><span class="number">50</span>: }</div><div class="line"><span class="number">51</span>: mCtc.addMethod(ctMethod);</div><div class="line"><span class="number">52</span>: }</div><div class="line"><span class="number">53</span>: }</div><div class="line"><span class="number">54</span>: }</div><div class="line"><span class="number">55</span>: <span class="keyword">if</span> (mDefaultConstructor) { <span class="comment">// 空参数构造方法</span></div><div class="line"><span class="number">56</span>: mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));</div><div class="line"><span class="number">57</span>: }</div><div class="line"><span class="number">58</span>: <span class="keyword">if</span> (mConstructors != <span class="keyword">null</span>) { <span class="comment">// 带参数构造方法</span></div><div class="line"><span class="number">59</span>: <span class="keyword">for</span> (String code : mConstructors) {</div><div class="line"><span class="number">60</span>: <span class="keyword">if</span> (code.charAt(<span class="number">0</span>) == <span class="string">':'</span>) {</div><div class="line"><span class="number">61</span>: mCtc.addConstructor(CtNewConstructor.copy(getCtConstructor(mCopyConstructors.get(code.substring(<span class="number">1</span>))), mCtc, <span class="keyword">null</span>));</div><div class="line"><span class="number">62</span>: } <span class="keyword">else</span> {</div><div class="line"><span class="number">63</span>: String[] sn = mCtc.getSimpleName().split(<span class="string">"\\$+"</span>); <span class="comment">// inner class name include $.</span></div><div class="line"><span class="number">64</span>: mCtc.addConstructor(CtNewConstructor.make(code.replaceFirst(SIMPLE_NAME_TAG, sn[sn.length - <span class="number">1</span>]), mCtc));</div><div class="line"><span class="number">65</span>: }</div><div class="line"><span class="number">66</span>: }</div><div class="line"><span class="number">67</span>: }</div><div class="line"><span class="number">68</span>: <span class="comment">// mCtc.debugWriteFile("/Users/yunai/test/" + mCtc.getSimpleName().replaceAll(".", "/") + ".class");</span></div><div class="line"><span class="number">69</span>: <span class="comment">// 生成</span></div><div class="line"><span class="number">70</span>: <span class="keyword">return</span> mCtc.toClass();</div><div class="line"><span class="number">71</span>: } <span class="keyword">catch</span> (RuntimeException e) {</div><div class="line"><span class="number">72</span>: <span class="keyword">throw</span> e;</div><div class="line"><span class="number">73</span>: } <span class="keyword">catch</span> (NotFoundException e) {</div><div class="line"><span class="number">74</span>: <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e.getMessage(), e);</div><div class="line"><span class="number">75</span>: } <span class="keyword">catch</span> (CannotCompileException e) {</div><div class="line"><span class="number">76</span>: <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e.getMessage(), e);</div><div class="line"><span class="number">77</span>: }</div><div class="line"><span class="number">78</span>: }</div></pre></td></tr></table></figure><ul><li>基于 Javassist 生成类。这里不做拓展解释,配合<a href="http://www.cnblogs.com/sunfie/p/5154246.html" rel="external nofollow noopener noreferrer" target="_blank">《Java学习之javassist》</a>一起理解。</li><li>第 18 行,添加 <code>org.mengyun.tcctransaction.dubbo.proxy.javassist.TccClassGenerator.DC</code> 动态生成类标记,标记该类由 TccClassGenerator 生成的。</li><li>第 34 至 50 行,设置 @Compensable 默认属性。</li></ul></li><li><p>第 141 至 142 行,设置 Dubbo Service 方法集合设置到静态属性 <code>methods</code> 上。</p></li></ul></li><li><p><strong>第 144 至 157 行,生成 Dubbo Service 调用 Proxy 工厂的代码</strong>。</p><ul><li>第 146 行,调用 <code>TccClassGenerator#newInstance(loader)</code> 方法, 创建生成 Dubbo Service 调用 <strong>Proxy 工厂</strong> 的代码生成器。</li><li>第 147 至 149 行,生成类名( 例如,<code>org.mengyun.tcctransaction.dubbo.proxy.javassist.TccProxy3</code> ),并调用 <code>TccClassGenerator#setClassName(...)</code> 方法,设置类名。</li><li>第 150 至 151 行,调用 <code>TccClassGenerator#addDefaultConstructor()</code> 方法,添加默认空构造方法。</li><li><p>第 152 至 153 行,调用 <code>TccClassGenerator#mSuperClass()</code> 方法,设置继承父类 <strong><code>TccProxy</code></strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 生成类的父类名字</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> String mSuperClass;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> TccClassGenerator <span class="title">setSuperClass</span><span class="params">(Class<?> cl)</span> </span>{</div><div class="line"> mSuperClass = cl.getName();</div><div class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>x</li></ul></li><li><p>第 154 至 155 行,调用 <code>TccClassGenerator#addInterface(cl)</code> 方法,添加生成 Proxy 实现代码的方法。代码案例如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">newInstance</span><span class="params">(InvocationHandler paramInvocationHandler)</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> proxy3(paramInvocationHandler);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>x</li></ul></li><li><p>第 156 至 157 行,调用 <code>TccClassGenerator#toClass()</code> 方法,<strong>生成类</strong>。</p></li></ul></li><li><p>第 159 行,调用 <code>TccProxy#newInstance()</code> 方法,创建 Proxy 。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* get instance with default handler.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> instance.</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">newInstance</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> newInstance(THROW_UNSUPPORTED_INVOKER);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* get instance with special handler.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> instance.</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">abstract</span> <span class="keyword">public</span> Object <span class="title">newInstance</span><span class="params">(InvocationHandler handler)</span></span>;</div></pre></td></tr></table></figure><ul><li><code>#newInstance(handler)</code>,抽象方法,上面第 154 至 155 行生成。TccJavassistProxyFactory 调用该方法,获得 Proxy 。</li></ul></li><li><p>第 165 至 171 行,释放 TccClassGenerator 。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">release</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (mCtc != <span class="keyword">null</span>) {</div><div class="line"> mCtc.detach();</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (mInterfaces != <span class="keyword">null</span>) {</div><div class="line"> mInterfaces.clear();</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (mFields != <span class="keyword">null</span>) {</div><div class="line"> mFields.clear();</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (mMethods != <span class="keyword">null</span>) {</div><div class="line"> mMethods.clear();</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (mConstructors != <span class="keyword">null</span>) {</div><div class="line"> mConstructors.clear();</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (mCopyMethods != <span class="keyword">null</span>) {</div><div class="line"> mCopyMethods.clear();</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (mCopyConstructors != <span class="keyword">null</span>) {</div><div class="line"> mCopyConstructors.clear();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>第 172 至 180 行,设置 Proxy 工厂缓存,并唤醒等待线程。</p></li></ul><p><strong>ps:</strong>代码比较多,收获会比较多,算是 Javassist 实战案例了。TCC-Transaction 作者在实现上述类,可能参考了 Dubbo 自带的实现:</p><ul><li><a href="https://github.com/alibaba/dubbo/blob/8f20e3a68efc350e3fbaa965e0a8e8a59fef1b3c/dubbo-common/src/main/java/com/alibaba/dubbo/common/bytecode/Proxy.java" rel="external nofollow noopener noreferrer" target="_blank"><code>com.alibaba.dubbo.common.bytecode.Proxy</code></a></li><li><a href="https://github.com/alibaba/dubbo/blob/8f20e3a68efc350e3fbaa965e0a8e8a59fef1b3c/dubbo-common/src/main/java/com/alibaba/dubbo/common/bytecode/ClassGenerator.java" rel="external nofollow noopener noreferrer" target="_blank"><code>com.alibaba.dubbo.common.bytecode.ClassGenerator</code></a></li><li><a href="https://github.com/alibaba/dubbo/blob/8f20e3a68efc350e3fbaa965e0a8e8a59fef1b3c/dubbo-common/src/main/java/com/alibaba/dubbo/common/bytecode/Wrapper.java" rel="external nofollow noopener noreferrer" target="_blank"><code>com.alibaba.dubbo.common.bytecode.Wrapper</code></a></li></ul><h3 id="2-1-4-配置-Dubbo-Proxy"><a href="#2-1-4-配置-Dubbo-Proxy" class="headerlink" title="2.1.4 配置 Dubbo Proxy"></a>2.1.4 配置 Dubbo Proxy</h3><figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// META-INF.dubbo/com.alibaba.dubbo.rpc.ProxyFactory</div><div class="line">tccJavassist=org.mengyun.tcctransaction.dubbo.proxy.javassist.TccJavassistProxyFactory</div><div class="line"></div><div class="line">// tcc-transaction-dubbo.xml</div><div class="line"><span class="tag"><<span class="name">dubbo:provider</span> <span class="attr">proxy</span>=<span class="string">"tccJavassist"</span>/></span></div></pre></td></tr></table></figure><p>目前 Maven 项目 <code>tcc-transaction-dubbo</code> 已经<strong>默认</strong>配置,引入即可。</p><p><img src="../../../images/TCC-Transaction/2018_03_07/05.png" alt=""></p><h2 id="2-2-JdkProxyFactory"><a href="#2-2-JdkProxyFactory" class="headerlink" title="2.2 JdkProxyFactory"></a>2.2 JdkProxyFactory</h2><h3 id="2-2-1-JDK-Proxy"><a href="#2-2-1-JDK-Proxy" class="headerlink" title="2.2.1 JDK Proxy"></a>2.2.1 JDK Proxy</h3><p><a href="http://blog.csdn.net/jiankunking/article/details/52143504#" rel="external nofollow noopener noreferrer" target="_blank">《 Java JDK 动态代理(AOP)使用及实现原理分析》</a></p><h3 id="2-2-2-TccJdkProxyFactory"><a href="#2-2-2-TccJdkProxyFactory" class="headerlink" title="2.2.2 TccJdkProxyFactory"></a>2.2.2 TccJdkProxyFactory</h3><p><code>org.mengyun.tcctransaction.dubbo.proxy.jd.TccJdkProxyFactory</code>,TCC JDK 代理工厂。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TccJdkProxyFactory</span> <span class="keyword">extends</span> <span class="title">JdkProxyFactory</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</div><div class="line"> <span class="keyword">public</span> <T> <span class="function">T <span class="title">getProxy</span><span class="params">(Invoker<T> invoker, Class<?>[] interfaces)</span> </span>{</div><div class="line"> T proxy = (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, <span class="keyword">new</span> InvokerInvocationHandler(invoker));</div><div class="line"> <span class="keyword">return</span> (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, <span class="keyword">new</span> TccInvokerInvocationHandler(proxy, invoker));</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>项目启动时</strong>,调用 <code>TccJavassistProxyFactory#getProxy(...)</code> 方法,生成 Dubbo Service 调用 Proxy。</li><li><strong>第一次</strong>调用 <code>Proxy#newProxyInstance(...)</code> 方法,创建调用 Dubbo Service 服务的 Proxy。<code>com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler</code>,Dubbo 调用处理器,点击<a href="https://github.com/alibaba/dubbo/blob/17619dfa974457b00fe27cf68ae3f9d266709666/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/proxy/InvokerInvocationHandler.java" rel="external nofollow noopener noreferrer" target="_blank">连接</a>查看代码。</li><li><strong>第二次</strong>调用 <code>Proxy#newProxyInstance(...)</code> 方法,创建对调用 Dubbo Service 的 Proxy 的 Proxy。为什么会有两层 Proxy?答案在下节 TccInvokerInvocationHandler 。</li></ul><h3 id="2-2-3-TccInvokerInvocationHandler"><a href="#2-2-3-TccInvokerInvocationHandler" class="headerlink" title="2.2.3 TccInvokerInvocationHandler"></a>2.2.3 TccInvokerInvocationHandler</h3><p><code>org.mengyun.tcctransaction.dubbo.proxy.jdk.TccInvokerInvocationHandler</code>,TCC 调用处理器,在调用 Dubbo Service 服务时,使用 ResourceCoordinatorInterceptor 拦截处理。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"> <span class="number">1</span>: <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TccInvokerInvocationHandler</span> <span class="keyword">extends</span> <span class="title">InvokerInvocationHandler</span> </span>{</div><div class="line"> <span class="number">2</span>: </div><div class="line"> <span class="number">3</span>: <span class="comment">/**</span></div><div class="line"><span class="comment"> 4: * proxy</span></div><div class="line"><span class="comment"> 5: */</span></div><div class="line"> <span class="number">6</span>: <span class="keyword">private</span> Object target;</div><div class="line"> <span class="number">7</span>: </div><div class="line"> <span class="number">8</span>: <span class="function"><span class="keyword">public</span> <span class="title">TccInvokerInvocationHandler</span><span class="params">(Invoker<?> handler)</span> </span>{</div><div class="line"> <span class="number">9</span>: <span class="keyword">super</span>(handler);</div><div class="line"><span class="number">10</span>: }</div><div class="line"><span class="number">11</span>: </div><div class="line"><span class="number">12</span>: <span class="keyword">public</span> <T> TccInvokerInvocationHandler(T target, Invoker<T> invoker) {</div><div class="line"><span class="number">13</span>: <span class="keyword">super</span>(invoker);</div><div class="line"><span class="number">14</span>: <span class="keyword">this</span>.target = target;</div><div class="line"><span class="number">15</span>: }</div><div class="line"><span class="number">16</span>: </div><div class="line"><span class="number">17</span>: <span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"><span class="number">18</span>: Compensable compensable = method.getAnnotation(Compensable.class);</div><div class="line"><span class="number">19</span>: <span class="keyword">if</span> (compensable != <span class="keyword">null</span>) {</div><div class="line"><span class="number">20</span>: <span class="comment">// 设置 @Compensable 属性</span></div><div class="line"><span class="number">21</span>: <span class="keyword">if</span> (StringUtils.isEmpty(compensable.confirmMethod())) {</div><div class="line"><span class="number">22</span>: ReflectionUtils.changeAnnotationValue(compensable, <span class="string">"confirmMethod"</span>, method.getName());</div><div class="line"><span class="number">23</span>: ReflectionUtils.changeAnnotationValue(compensable, <span class="string">"cancelMethod"</span>, method.getName());</div><div class="line"><span class="number">24</span>: ReflectionUtils.changeAnnotationValue(compensable, <span class="string">"transactionContextEditor"</span>, DubboTransactionContextEditor.class);</div><div class="line"><span class="number">25</span>: ReflectionUtils.changeAnnotationValue(compensable, <span class="string">"propagation"</span>, Propagation.SUPPORTS);</div><div class="line"><span class="number">26</span>: }</div><div class="line"><span class="number">27</span>: <span class="comment">// 生成切面</span></div><div class="line"><span class="number">28</span>: ProceedingJoinPoint pjp = <span class="keyword">new</span> MethodProceedingJoinPoint(proxy, target, method, args);</div><div class="line"><span class="number">29</span>: <span class="comment">// 执行</span></div><div class="line"><span class="number">30</span>: <span class="keyword">return</span> FactoryBuilder.factoryOf(ResourceCoordinatorAspect.class).getInstance().interceptTransactionContextMethod(pjp);</div><div class="line"><span class="number">31</span>: } <span class="keyword">else</span> {</div><div class="line"><span class="number">32</span>: <span class="keyword">return</span> <span class="keyword">super</span>.invoke(target, method, args);</div><div class="line"><span class="number">33</span>: }</div><div class="line"><span class="number">34</span>: }</div><div class="line"><span class="number">35</span>: </div><div class="line"><span class="number">36</span>: }</div></pre></td></tr></table></figure><ul><li>第 18 至 26 行,设置带有 @Compensable 属性的默认属性。</li><li><p>第 28 行,生成方法切面 <code>org.mengyun.tcctransaction.dubbo.proxy.jdk.MethodProceedingJoinPoint</code>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MethodProceedingJoinPoint</span> <span class="keyword">implements</span> <span class="title">ProceedingJoinPoint</span>, <span class="title">JoinPoint</span>.<span class="title">StaticPart</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 代理对象</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Object proxy;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 目标对象</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Object target;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 方法</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Method method;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 参数</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Object[] args;</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">proceed</span><span class="params">()</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"> <span class="comment">// Use reflection to invoke the method.</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> ReflectionUtils.makeAccessible(method);</div><div class="line"> <span class="keyword">return</span> method.invoke(target, args);</div><div class="line"> } <span class="keyword">catch</span> (InvocationTargetException ex) {</div><div class="line"> <span class="comment">// Invoked method threw a checked exception.</span></div><div class="line"> <span class="comment">// We must rethrow it. The client won't see the interceptor.</span></div><div class="line"> <span class="keyword">throw</span> ex.getTargetException();</div><div class="line"> } <span class="keyword">catch</span> (IllegalArgumentException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemException(<span class="string">"Tried calling method ["</span> +</div><div class="line"> method + <span class="string">"] on target ["</span> + target + <span class="string">"] failed"</span>, ex);</div><div class="line"> } <span class="keyword">catch</span> (IllegalAccessException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemException(<span class="string">"Could not access method ["</span> + method + <span class="string">"]"</span>, ex);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">proceed</span><span class="params">(Object[] objects)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"> <span class="comment">// throw new UnsupportedOperationException(); // TODO 芋艿:疑问</span></div><div class="line"> <span class="keyword">return</span> proceed();</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// ... 省略不重要的方法和对象</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>该类参考 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java" rel="external nofollow noopener noreferrer" target="_blank"><code>org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint</code></a> 实现。</li><li>TODO【1】 proxy 和 target 是否保留一个即可?</li><li>在切面处理完成后,调用 <code>#proceed(...)</code> 方法,进行远程 Dubbo Service 服务调用。</li><li>TODO【2】<code>#proceed(objects)</code> 抛出 throw new UnsupportedOperationException();。需要跟作者确认下。</li></ul></li><li>调用 <code>ResourceCoordinatorAspect#interceptTransactionContextMethod(...)</code> 方法,对方法切面拦截处理。<strong>为什么无需调用 CompensableTransactionAspect 切面</strong>?因为传播级别为 Propagation.SUPPORTS,不会发起事务。</li></ul><h3 id="2-2-4-配置-Dubbo-Proxy"><a href="#2-2-4-配置-Dubbo-Proxy" class="headerlink" title="2.2.4 配置 Dubbo Proxy"></a>2.2.4 配置 Dubbo Proxy</h3><figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// META-INF.dubbo/com.alibaba.dubbo.rpc.ProxyFactory</div><div class="line">tccJdk=org.mengyun.tcctransaction.dubbo.proxy.jdk.TccJdkProxyFactory</div><div class="line"></div><div class="line">// appcontext-service-dubbo.xml</div><div class="line"><span class="tag"><<span class="name">dubbo:provider</span> <span class="attr">proxy</span>=<span class="string">"tccJdk"</span>/></span></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">dubbo:reference</span> <span class="attr">proxy</span>=<span class="string">"tccJdk"</span> <span class="attr">id</span>=<span class="string">"captialTradeOrderService"</span></span></div><div class="line"><span class="tag"> <span class="attr">interface</span>=<span class="string">"org.mengyun.tcctransaction.sample.dubbo.capital.api.CapitalTradeOrderService"</span> <span class="attr">timeout</span>=<span class="string">"5000"</span>/></span></div></pre></td></tr></table></figure><ul><li>ProxyFactory 的 <code>tccJdk</code> 在 Maven 项 <code>tcc-transaction-dubbo</code> 已经声明。</li><li>声明 <code>dubbo:provider</code> 的 <code>proxy="tccJdk"</code>。</li><li>声明 <code>dubbo:reference</code> 的 <code>proxy="tccJdk"</code>,否则不生效。</li></ul><h1 id="3-Dubbo-事务上下文编辑器"><a href="#3-Dubbo-事务上下文编辑器" class="headerlink" title="3. Dubbo 事务上下文编辑器"></a>3. Dubbo 事务上下文编辑器</h1><p><code>org.mengyun.tcctransaction.dubbo.context.DubboTransactionContextEditor</code>,Dubbo 事务上下文编辑器实现,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DubboTransactionContextEditor</span> <span class="keyword">implements</span> <span class="title">TransactionContextEditor</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> TransactionContext <span class="title">get</span><span class="params">(Object target, Method method, Object[] args)</span> </span>{</div><div class="line"> String context = RpcContext.getContext().getAttachment(TransactionContextConstants.TRANSACTION_CONTEXT);</div><div class="line"> <span class="keyword">if</span> (StringUtils.isNotEmpty(context)) {</div><div class="line"> <span class="keyword">return</span> JSON.parseObject(context, TransactionContext.class);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(TransactionContext transactionContext, Object target, Method method, Object[] args)</span> </span>{</div><div class="line"> RpcContext.getContext().setAttachment(TransactionContextConstants.TRANSACTION_CONTEXT, JSON.toJSONString(transactionContext));</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>通过 Dubbo 的隐式传参的方式,避免在 Dubbo Service 接口上声明 TransactionContext 参数,对接口产生一定的入侵。</li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>HOHO,对动态代理又学习了一遍,蛮 High 的。</p><p>这里推荐动态代理无关,和 Dubbo 相关的文章:</p><ul><li><a href="http://blog.kazaff.me/2015/01/27/dubbo%E4%B8%AD%E6%9C%8D%E5%8A%A1%E6%9A%B4%E9%9C%B2%E7%9A%84%E7%BB%86%E8%8A%82/" rel="external nofollow noopener noreferrer" target="_blank">《Dubbo的服务暴露细节》</a>。</li><li><a href="http://weifuwu.io/2016/01/03/dubbo-provider-start/" rel="external nofollow noopener noreferrer" target="_blank">《Dubbo Provider启动主流程》</a></li></ul><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_03_07/06.png" alt=""></p><p>胖友,分享一波朋友圈可好。</p>]]></content>
<summary type="html">
<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p>
<ul>
<li><a href="#1-%E6%A6%82%E8%BF%B0">1. 概述</a></li>
<li><a href="#2-dubbo-%
</summary>
<category term="TCC-Transaction" scheme="http://www.iocoder.cn/categories/TCC-Transaction/"/>
</entry>
<entry>
<title>TCC-Transaction 源码分析 —— 事务恢复</title>
<link href="http://www.iocoder.cn/TCC-Transaction/transaction-recovery/"/>
<id>http://www.iocoder.cn/TCC-Transaction/transaction-recovery/</id>
<published>2018-02-21T16:00:00.000Z</published>
<updated>2017-09-17T11:06:38.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. 事务重试配置</a></li><li><a href="#">3. 事务重试定时任务</a></li><li><a href="#">4. 异常事务恢复</a><ul><li><a href="#">4.1 加载异常事务集合</a></li><li><a href="#">4.2 恢复异常事务集合</a></li></ul></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文分享 <strong>TCC 恢复</strong>。主要涉及如下二个 package 路径下的类:</p><ul><li><code>org.mengyun.tcctransaction.recover</code><ul><li>RecoverConfig,事务恢复配置<strong>接口</strong> </li><li>TransactionRecovery,事务恢复逻辑</li></ul></li><li><code>org.mengyun.tcctransaction.spring.recover</code> :<ul><li>DefaultRecoverConfig,默认事务恢复配置<strong>实现</strong></li><li>RecoverScheduledJob,事务恢复定时任务</li></ul></li></ul><p>本文涉及到的类关系如下图( <a href="http://www.iocoder.cn/images/TCC-Transaction/2018_02_22/01.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_22/01.png" alt=""></p><p>在<a href="http://www.iocoder.cn/TCC-Transaction/transaction-repository/?self">《TCC-Transaction 源码分析 —— 事务存储器》</a>中,事务信息被持久化到外部的存储器中。<strong>事务存储是事务恢复的基础</strong>。通过读取外部存储器中的异常事务,定时任务会按照一定频率对事务进行重试,直到事务完成或超过最大重试次数。</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_15/01.png" alt=""></p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 TCC-Transaction 点赞!<a href="https://github.com/changmingxie/tcc-transaction" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><p>ps:笔者假设你已经阅读过<a href="https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x" rel="external nofollow noopener noreferrer" target="_blank">《tcc-transaction 官方文档 —— 使用指南1.2.x》</a>。</p><h1 id="2-事务重试配置"><a href="#2-事务重试配置" class="headerlink" title="2. 事务重试配置"></a>2. 事务重试配置</h1><p><code>org.mengyun.tcctransaction.recover.RecoverConfig</code>,事务恢复配置<strong>接口</strong>,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">RecoverConfig</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 最大重试次数</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">int</span> <span class="title">getMaxRetryCount</span><span class="params">()</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 恢复间隔时间,单位:秒</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">int</span> <span class="title">getRecoverDuration</span><span class="params">()</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> cron 表达式</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">String <span class="title">getCronExpression</span><span class="params">()</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 延迟取消异常集合</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> Set<Class<? extends Exception>> getDelayCancelExceptions();</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 设置延迟取消异常集合</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> delayRecoverExceptions 延迟取消异常集合</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">setDelayCancelExceptions</span><span class="params">(Set<Class<? extends Exception>> delayRecoverExceptions)</span></span>;</div><div class="line"> </div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>#getMaxRetryCount()</code>,单个事务恢复最大重试次数。超过最大重试次数后,目前仅打出错误日志,下文会看到实现。</li><li><code>#getRecoverDuration()</code>,单个事务恢复重试的间隔时间,单位:秒。</li><li><code>#getCronExpression()</code>,定时任务 cron 表达式。</li><li><code>#getDelayCancelExceptions()</code>,延迟取消异常集合。</li></ul><hr><p><code>org.mengyun.tcctransaction.spring.recover.DefaultRecoverConfig</code>,<strong>默认</strong>事务恢复配置实现,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DefaultRecoverConfig</span> <span class="keyword">implements</span> <span class="title">RecoverConfig</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> RecoverConfig INSTANCE = <span class="keyword">new</span> DefaultRecoverConfig();</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 最大重试次数</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">int</span> maxRetryCount = <span class="number">30</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 恢复间隔时间,单位:秒</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">int</span> recoverDuration = <span class="number">120</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * cron 表达式</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String cronExpression = <span class="string">"0 */1 * * * ?"</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 延迟取消异常集合</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Set<Class<? extends Exception>> delayCancelExceptions = <span class="keyword">new</span> HashSet<Class<? extends Exception>>();</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DefaultRecoverConfig</span><span class="params">()</span> </span>{</div><div class="line"> delayCancelExceptions.add(OptimisticLockException.class);</div><div class="line"> delayCancelExceptions.add(SocketTimeoutException.class);</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setDelayCancelExceptions</span><span class="params">(Set<Class<? extends Exception>> delayCancelExceptions)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.delayCancelExceptions.addAll(delayCancelExceptions);</div><div class="line"> }</div><div class="line"> </div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>maxRetryCount</code>,单个事务恢复最大重试次数 为 30。</li><li><code>recoverDuration</code>,单个事务恢复重试的间隔时间为 120 秒。</li><li><code>cronExpression</code>,定时任务 cron 表达式为 <code>"0 */1 * * * ?"</code>,每分钟执行一次。如果你希望定时任务执行的更频繁,可以修改 cron 表达式,例如 <code>0/30 * * * * ?</code>,每 30 秒执行一次。</li><li><code>delayCancelExceptions</code>,延迟取消异常集合。在 DefaultRecoverConfig 构造方法里,预先添加了 OptimisticLockException / SocketTimeoutException 。<ul><li>针对 <strong>SocketTimeoutException</strong> :try 阶段,本地参与者调用远程参与者( 远程服务,例如 Dubbo,Http 服务),远程参与者 try 阶段的方法逻辑执行时间较长,超过 Socket 等待时长,发生 SocketTimeoutException,如果立刻执行事务回滚,远程参与者 try 的方法未执行完成,可能导致 cancel 的方法实际未执行( try 的方法未执行完成,数据库事务【非 TCC 事务】未提交,cancel 的方法读取数据时发现未变更,导致方法实际未执行,最终 try 的方法执行完后,提交数据库事务【非 TCC 事务】,较为极端 ),最终引起数据不一致。在<strong>事务恢复</strong>时,会对这种情况的事务进行取消回滚,如果此时远程参与者的 try 的方法还未结束,还是可能发生数据不一致。<ul><li>官方解释:<a href="https://github.com/changmingxie/tcc-transaction/issues/87" rel="external nofollow noopener noreferrer" target="_blank">为什么 tcc 事务切面中对乐观锁与socket超时异常不做回滚处理,只抛异常?</a></li></ul></li><li>针对 OptimisticLockException :还是 SocketTimeoutException 的情况,事务恢复间隔时间小于 Socket 超时时间,此时事务恢复调用远程参与者取消回滚事务,远程参与者下次更新事务时,会因为乐观锁更新失败,抛出 OptimisticLockException。如果 CompensableTransactionInterceptor 此时立刻取消回滚,可能会和定时任务的取消回滚冲突,因此统一交给定时任务处理。<ul><li>官方解释:<a href="https://github.com/changmingxie/tcc-transaction/issues/53" rel="external nofollow noopener noreferrer" target="_blank">事务恢复的疑问</a></li><li>这块笔者还有一些疑问,如果有别的可能性导致这个情况,麻烦告知下笔者。谢谢。</li></ul></li></ul></li></ul><h1 id="3-事务重试定时任务"><a href="#3-事务重试定时任务" class="headerlink" title="3. 事务重试定时任务"></a>3. 事务重试定时任务</h1><p><code>org.mengyun.tcctransaction.spring.recover.RecoverScheduledJob</code>,事务恢复定时任务,基于 Quartz 实现调度,不断不断不断执行事务恢复。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RecoverScheduledJob</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> TransactionRecovery transactionRecovery;</div><div class="line"></div><div class="line"> <span class="keyword">private</span> TransactionConfigurator transactionConfigurator;</div><div class="line"></div><div class="line"> <span class="keyword">private</span> Scheduler scheduler;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// Quartz JobDetail</span></div><div class="line"> MethodInvokingJobDetailFactoryBean jobDetail = <span class="keyword">new</span> MethodInvokingJobDetailFactoryBean();</div><div class="line"> jobDetail.setTargetObject(transactionRecovery);</div><div class="line"> jobDetail.setTargetMethod(<span class="string">"startRecover"</span>);</div><div class="line"> jobDetail.setName(<span class="string">"transactionRecoveryJob"</span>);</div><div class="line"> jobDetail.setConcurrent(<span class="keyword">false</span>); <span class="comment">// 禁止并发</span></div><div class="line"> jobDetail.afterPropertiesSet();</div><div class="line"> <span class="comment">// Quartz CronTriggerFactoryBean</span></div><div class="line"> CronTriggerFactoryBean cronTrigger = <span class="keyword">new</span> CronTriggerFactoryBean();</div><div class="line"> cronTrigger.setBeanName(<span class="string">"transactionRecoveryCronTrigger"</span>);</div><div class="line"> cronTrigger.setCronExpression(transactionConfigurator.getRecoverConfig().getCronExpression());</div><div class="line"> cronTrigger.setJobDetail(jobDetail.getObject());</div><div class="line"> cronTrigger.afterPropertiesSet();</div><div class="line"> <span class="comment">// 启动任务调度</span></div><div class="line"> scheduler.scheduleJob(jobDetail.getObject(), cronTrigger.getObject());</div><div class="line"> <span class="comment">// 启动 Quartz Scheduler</span></div><div class="line"> scheduler.start();</div><div class="line"> } <span class="keyword">catch</span> (Exception e) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemException(e);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>MethodInvokingJobDetailFactoryBean#setConcurrent(false)</code> 方法,禁用任务并发执行。</li><li>调用 <code>MethodInvokingJobDetailFactoryBean#setTargetObject(...)</code> + <code>MethodInvokingJobDetailFactoryBean#setTargetMethod(...)</code> 方法,设置任务调用 <code>TransactionRecovery#startRecover(...)</code> 方法执行。</li></ul><p><strong>如果应用集群部署,会不会相同事务被多个定时任务同时重试</strong>?</p><p>答案是不会,事务在重试时会乐观锁更新,同时只有一个应用节点能更新成功。</p><p>官方解释:<a href="https://github.com/changmingxie/tcc-transaction/issues/98" rel="external nofollow noopener noreferrer" target="_blank">多机部署下,所有机器都宕机,从异常中恢复时,所有的机器岂不是都可以查询到所有的需要恢复的服务?</a></p><p>当然极端情况下,Socket 调用超时时间大于事务重试间隔,第一个节点在重试某个事务,一直未执行完成,第二个节点已经可以重试。</p><p>ps:建议,Socket 调用超时时间小于事务重试间隔。</p><p><strong>是否定时任务和应用服务器解耦</strong>?</p><p>蚂蚁金服的分布式事务服务 DTS 采用 client-server 模式:</p><ul><li>xts-client :负责事务的创建、提交、回滚、记录。</li><li>xts-server :负责异常事务的恢复。</li></ul><blockquote><p>FROM <a href="https://www.cloud.alipay.com/docs/2/46887" rel="external nofollow noopener noreferrer" target="_blank">《蚂蚁金融云 DTS 文档》</a><br>分布式事务服务 (Distributed Transaction Service, DTS) 是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。DTS 从架构上分为 xts-client 和 xts-server 两部分,前者是一个嵌入客户端应用的 JAR 包,主要负责事务数据的写入和处理;后者是一个独立的系统,主要负责异常事务的恢复。</p></blockquote><h1 id="4-异常事务恢复"><a href="#4-异常事务恢复" class="headerlink" title="4. 异常事务恢复"></a>4. 异常事务恢复</h1><p> <code>org.mengyun.tcctransaction.recover.TransactionRecovery</code>,异常事务恢复,实现主体代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TransactionRecovery</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 启动恢复事务逻辑</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">startRecover</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 加载异常事务集合</span></div><div class="line"> List<Transaction> transactions = loadErrorTransactions();</div><div class="line"> <span class="comment">// 恢复异常事务集合</span></div><div class="line"> recoverErrorTransactions(transactions);</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><h2 id="4-1-加载异常事务集合"><a href="#4-1-加载异常事务集合" class="headerlink" title="4.1 加载异常事务集合"></a>4.1 加载异常事务集合</h2><p>调用 <code>#loadErrorTransactions()</code> 方法,加载异常事务集合。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> List<Transaction> <span class="title">loadErrorTransactions</span><span class="params">()</span> </span>{</div><div class="line"> TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();</div><div class="line"> <span class="keyword">long</span> currentTimeInMillis = Calendar.getInstance().getTimeInMillis();</div><div class="line"> RecoverConfig recoverConfig = transactionConfigurator.getRecoverConfig();</div><div class="line"> <span class="keyword">return</span> transactionRepository.findAllUnmodifiedSince(<span class="keyword">new</span> Date(currentTimeInMillis - recoverConfig.getRecoverDuration() * <span class="number">1000</span>));</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>异常事务的定义</strong>:当前时间超过 - 事务变更时间( 最后执行时间 ) >= 事务恢复间隔( <code>RecoverConfig#getRecoverDuration()</code> )。这里有一点要注意,已完成的事务会从事务存储器删除。</li></ul><h2 id="4-2-恢复异常事务集合"><a href="#4-2-恢复异常事务集合" class="headerlink" title="4.2 恢复异常事务集合"></a>4.2 恢复异常事务集合</h2><p>调用 <code>#recoverErrorTransactions(...)</code> 方法,恢复异常事务集合。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">recoverErrorTransactions</span><span class="params">(List<Transaction> transactions)</span> </span>{</div><div class="line"> <span class="keyword">for</span> (Transaction transaction : transactions) {</div><div class="line"> <span class="comment">// 超过最大重试次数</span></div><div class="line"> <span class="keyword">if</span> (transaction.getRetriedCount() > transactionConfigurator.getRecoverConfig().getMaxRetryCount()) {</div><div class="line"> logger.error(String.format(<span class="string">"recover failed with max retry count,will not try again. txid:%s, status:%s,retried count:%d,transaction content:%s"</span>, transaction.getXid(), transaction.getStatus().getId(), transaction.getRetriedCount(), JSON.toJSONString(transaction)));</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 分支事务超过最大可重试时间</span></div><div class="line"> <span class="keyword">if</span> (transaction.getTransactionType().equals(TransactionType.BRANCH)</div><div class="line"> && (transaction.getCreateTime().getTime() +</div><div class="line"> transactionConfigurator.getRecoverConfig().getMaxRetryCount() *</div><div class="line"> transactionConfigurator.getRecoverConfig().getRecoverDuration() * <span class="number">1000</span></div><div class="line"> > System.currentTimeMillis())) {</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// Confirm / Cancel</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 增加重试次数</span></div><div class="line"> transaction.addRetriedCount();</div><div class="line"> <span class="comment">// Confirm</span></div><div class="line"> <span class="keyword">if</span> (transaction.getStatus().equals(TransactionStatus.CONFIRMING)) {</div><div class="line"> transaction.changeStatus(TransactionStatus.CONFIRMING);</div><div class="line"> transactionConfigurator.getTransactionRepository().update(transaction);</div><div class="line"> transaction.commit();</div><div class="line"> transactionConfigurator.getTransactionRepository().delete(transaction);</div><div class="line"> <span class="comment">// Cancel</span></div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (transaction.getStatus().equals(TransactionStatus.CANCELLING)</div><div class="line"> || transaction.getTransactionType().equals(TransactionType.ROOT)) { <span class="comment">// 处理延迟取消的情况</span></div><div class="line"> transaction.changeStatus(TransactionStatus.CANCELLING);</div><div class="line"> transactionConfigurator.getTransactionRepository().update(transaction);</div><div class="line"> transaction.rollback();</div><div class="line"> transactionConfigurator.getTransactionRepository().delete(transaction);</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (Throwable throwable) {</div><div class="line"> <span class="keyword">if</span> (throwable <span class="keyword">instanceof</span> OptimisticLockException</div><div class="line"> || ExceptionUtils.getRootCause(throwable) <span class="keyword">instanceof</span> OptimisticLockException) {</div><div class="line"> logger.warn(String.format(<span class="string">"optimisticLockException happened while recover. txid:%s, status:%s,retried count:%d,transaction content:%s"</span>, transaction.getXid(), transaction.getStatus().getId(), transaction.getRetriedCount(), JSON.toJSONString(transaction)), throwable);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> logger.error(String.format(<span class="string">"recover failed, txid:%s, status:%s,retried count:%d,transaction content:%s"</span>, transaction.getXid(), transaction.getStatus().getId(), transaction.getRetriedCount(), JSON.toJSONString(transaction)), throwable);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>当单个事务超过最大重试次数时,不再重试,只打印异常,此时需要<strong>人工介入</strong>解决。可以接入 ELK 收集日志监控报警。</li><li>当<strong>分支事务</strong>超过最大可重试时间时,不再重试。可能有同学和我一开始理解的是相同的,实际<strong>分支事务</strong>对应的应用服务器也可以重试<strong>分支事务</strong>,不是必须<strong>根事务</strong>发起重试,从而一起重试<strong>分支事务</strong>。这点要注意下。</li><li>当事务处于 TransactionStatus.CONFIRMING 状态时,提交事务,逻辑和 <code>TransactionManager#commit()</code> 类似。</li><li>当事务处于 TransactionStatus.CONFIRMING 状态,或者<strong>事务类型为根事务</strong>,回滚事务,逻辑和 <code>TransactionManager#rollback()</code> 类似。这里加判断的<strong>事务类型为根事务</strong>,用于处理延迟回滚异常的事务的回滚。</li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>在写本文的过程中,无意中翻到蚂蚁云的文档,分享给看到此处的真爱们。</p><p>真爱们,请猛击<a href="https://git.cloud.alipay.com/dx/AntCloudPayPublic" rel="external nofollow noopener noreferrer" target="_blank">《AntCloudPayPublic》</a>跳转。</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_22/02.png" alt=""></p><p>胖友,分享一个朋友圈可好?</p>]]></content>
<summary type="html">
<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. 事务重试配置</a></li>
<li><a h
</summary>
<category term="TCC-Transaction" scheme="http://www.iocoder.cn/categories/TCC-Transaction/"/>
</entry>
<entry>
<title>TCC-Transaction 源码分析 —— 事务存储器</title>
<link href="http://www.iocoder.cn/TCC-Transaction/transaction-repository/"/>
<id>http://www.iocoder.cn/TCC-Transaction/transaction-repository/</id>
<published>2018-02-14T16:00:00.000Z</published>
<updated>2017-09-17T11:06:30.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. 序列化</a><ul><li><a href="#">2.1 JDK 序列化实现</a></li><li><a href="#">2.2 Kyro 序列化实现</a></li><li><a href="#">2.3 JSON 序列化实现</a></li></ul></li><li><a href="#">3. 存储器</a><ul><li><a href="#">3.1 可缓存的事务存储器抽象类</a></li><li><a href="#">3.2 JDBC 事务存储器</a></li><li><a href="#">3.3 Redis 事务存储器</a></li><li><a href="#">3.4 Zookeeper 事务存储器</a></li><li><a href="#">3.5 File 事务存储器</a></li></ul></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。</li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文分享 <strong>事务存储器</strong>。主要涉及如下 Maven 项目:</p><ul><li><code>tcc-transaction-core</code> :tcc-transaction 底层实现。</li></ul><p>在 TCC 的过程中,根据应用内存中的事务信息完成整个事务流程。But 实际业务场景中,将事务信息只放在应用内存中是远远不够可靠的。例如:</p><ol><li>应用进程异常崩溃,未完成的事务信息将丢失。</li><li>应用进程集群,当提供远程服务调用时,事务信息需要集群内共享。</li><li>发起事务的应用需要重启部署新版本,因为各种原因,有未完成的事务。</li></ol><p>因此,TCC-Transaction 将事务信息添加到内存中的同时,会使用外部存储进行持久化。目前提供四种外部存储:</p><ul><li>JdbcTransactionRepository,JDBC 事务存储器</li><li>RedisTransactionRepository,Redis 事务存储器</li><li>ZooKeeperTransactionRepository,Zookeeper 事务存储器</li><li>FileSystemTransactionRepository,File 事务存储器</li></ul><p>本文涉及到的类关系如下图( <a href="http://www.iocoder.cn/images/TCC-Transaction/2018_02_15/01.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_15/01.png" alt=""></p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 TCC-Transaction 点赞!<a href="https://github.com/changmingxie/tcc-transaction" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><p>ps:笔者假设你已经阅读过<a href="https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x" rel="external nofollow noopener noreferrer" target="_blank">《tcc-transaction 官方文档 —— 使用指南1.2.x》</a>。</p><h1 id="2-序列化"><a href="#2-序列化" class="headerlink" title="2. 序列化"></a>2. 序列化</h1><p>在<a href="http://www.iocoder.cn/TCC-Transaction/tcc-core/?self">《TCC-Transaction 源码分析 —— TCC 实现》「4. 事务与参与者」</a>,可以看到 Transaction 是一个比较复杂的对象,内嵌 Participant 数组,而 Participant 本身也是复杂的对象,内嵌了更多的其他对象,因此,存储器在持久化 Transaction 时,需要序列化后才能存储。</p><p><code>org.mengyun.tcctransaction.serializer.ObjectSerializer</code>,对象序列化<strong>接口</strong>。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ObjectSerializer</span><<span class="title">T</span>> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">byte</span>[] serialize(T t);</div><div class="line"> </div><div class="line"> <span class="function">T <span class="title">deserialize</span><span class="params">(<span class="keyword">byte</span>[] bytes)</span></span>;</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><p>目前提供 <strong>JDK自带序列化</strong> 和 <strong>Kyro序列化</strong> 两种实现。</p><h2 id="2-1-JDK-序列化实现"><a href="#2-1-JDK-序列化实现" class="headerlink" title="2.1 JDK 序列化实现"></a>2.1 JDK 序列化实现</h2><p><code>org.mengyun.tcctransaction.serializer.JdkSerializationSerializer</code>,JDK 序列化实现。比较易懂,点击<a href="https://github.com/changmingxie/tcc-transaction/blob/70130d12004456fd4b97510c210c24502a1b3acb/tcc-transaction-core/src/main/java/org/mengyun/tcctransaction/serializer/JdkSerializationSerializer.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>直接查看。</p><p><strong>TCC-Transaction 使用的默认的序列化</strong>。</p><h2 id="2-2-Kyro-序列化实现"><a href="#2-2-Kyro-序列化实现" class="headerlink" title="2.2 Kyro 序列化实现"></a>2.2 Kyro 序列化实现</h2><p><code>org.mengyun.tcctransaction.serializer.KryoTransactionSerializer</code>,Kyro 序列化实现。比较易懂,点击<a href="https://github.com/changmingxie/tcc-transaction/blob/70130d12004456fd4b97510c210c24502a1b3acb/tcc-transaction-core/src/main/java/org/mengyun/tcctransaction/serializer/KryoTransactionSerializer.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>直接查看。</p><h2 id="2-3-JSON-序列化实现"><a href="#2-3-JSON-序列化实现" class="headerlink" title="2.3 JSON 序列化实现"></a>2.3 JSON 序列化实现</h2><p>JDK 和 Kyro 的序列化实现,肉眼无法直观具体存储事务的信息,你可以通过实现 ObjectSerializer 接口,实现自定义的 JSON 序列化。</p><h1 id="3-存储器"><a href="#3-存储器" class="headerlink" title="3. 存储器"></a>3. 存储器</h1><p><code>org.mengyun.tcctransaction.TransactionRepository</code>,事务存储器<strong>接口</strong>。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">TransactionRepository</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 新增事务</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> transaction 事务</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 新增数量</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">int</span> <span class="title">create</span><span class="params">(Transaction transaction)</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 更新事务</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> transaction 事务</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 更新数量</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">int</span> <span class="title">update</span><span class="params">(Transaction transaction)</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 删除事务</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> transaction 事务</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 删除数量</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">int</span> <span class="title">delete</span><span class="params">(Transaction transaction)</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 获取事务</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> xid 事务编号</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 事务</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">Transaction <span class="title">findByXid</span><span class="params">(TransactionXid xid)</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 获取超过指定时间的事务集合</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> date 指定时间</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 事务集合</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">List<Transaction> <span class="title">findAllUnmodifiedSince</span><span class="params">(Date date)</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><p>不同的存储器通过实现该接口,提供事务的增删改查功能。</p><h2 id="3-1-可缓存的事务存储器抽象类"><a href="#3-1-可缓存的事务存储器抽象类" class="headerlink" title="3.1 可缓存的事务存储器抽象类"></a>3.1 可缓存的事务存储器抽象类</h2><p><code>org.mengyun.tcctransaction.repository.CachableTransactionRepository</code>,<strong>可缓存</strong>的事务存储器<strong>抽象类</strong>,实现增删改查事务时,同时缓存事务信息。在上面类图,我们也可以看到 TCC-Transaction 自带的多种存储器都继承该抽象类。</p><p><strong>CachableTransactionRepository 构造方法</strong>实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">CachableTransactionRepository</span> <span class="keyword">implements</span> <span class="title">TransactionRepository</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 缓存过期时间</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">int</span> expireDuration = <span class="number">120</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 缓存</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Cache<Xid, Transaction> transactionXidCompensableTransactionCache;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">CachableTransactionRepository</span><span class="params">()</span> </span>{</div><div class="line"> transactionXidCompensableTransactionCache = CacheBuilder.newBuilder().expireAfterAccess(expireDuration, TimeUnit.SECONDS).maximumSize(<span class="number">1000</span>).build();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>使用 <a href="https://github.com/google/guava/wiki/CachesExplained" rel="external nofollow noopener noreferrer" target="_blank">Guava Cache</a> 内存缓存事务信息,设置最大缓存个数为 1000 个,缓存过期时间为最后访问时间 120 秒。</li></ul><hr><p><strong><code>#create(...)</code></strong> 实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">create</span><span class="params">(Transaction transaction)</span> </span>{</div><div class="line"> <span class="keyword">int</span> result = doCreate(transaction);</div><div class="line"> <span class="keyword">if</span> (result > <span class="number">0</span>) {</div><div class="line"> putToCache(transaction);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 添加到缓存</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transaction 事务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">putToCache</span><span class="params">(Transaction transaction)</span> </span>{</div><div class="line"> transactionXidCompensableTransactionCache.put(transaction.getXid(), transaction);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 新增事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transaction 事务</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 新增数量</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">int</span> <span class="title">doCreate</span><span class="params">(Transaction transaction)</span></span>;</div></pre></td></tr></table></figure><ul><li>调用 <code>#doCreate(...)</code> 方法,新增事务。新增成功后,调用 <code>#putToCache(...)</code> 方法,添加事务到缓存。</li><li><code>#doCreate(...)</code> 为抽象方法,子类实现该方法,提供新增事务功能。</li></ul><hr><p><strong><code>#update(...)</code></strong> 实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">update</span><span class="params">(Transaction transaction)</span> </span>{</div><div class="line"> <span class="keyword">int</span> result = <span class="number">0</span>;</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> result = doUpdate(transaction);</div><div class="line"> <span class="keyword">if</span> (result > <span class="number">0</span>) {</div><div class="line"> putToCache(transaction);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> OptimisticLockException();</div><div class="line"> }</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> <span class="keyword">if</span> (result <= <span class="number">0</span>) { <span class="comment">// 更新失败,移除缓存。下次访问,从存储器读取</span></div><div class="line"> removeFromCache(transaction);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 移除事务从缓存</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transaction 事务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">removeFromCache</span><span class="params">(Transaction transaction)</span> </span>{</div><div class="line"> transactionXidCompensableTransactionCache.invalidate(transaction.getXid());</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 更新事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transaction 事务</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 更新数量</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">int</span> <span class="title">doUpdate</span><span class="params">(Transaction transaction)</span></span>;</div></pre></td></tr></table></figure><ul><li>调用 <code>#doUpdate(...)</code> 方法,更新事务。<ul><li>若更新成功后,调用 <code>#putToCache(...)</code> 方法,添加事务到缓存。</li><li>若更新失败后,抛出 OptimisticLockException 异常。有两种情况会导致更新失败:(1) 该事务已经被提交,被删除;(2) 乐观锁更新时,缓存的事务的版本号( <code>Transaction.version</code> )和存储器里的事务的版本号不同,更新失败。<strong>为什么</strong>?在<a href="http://www.iocoder.cn/TCC-Transaction/transaction-recovery/">《TCC-Transaction 源码分析 —— 事务恢复》</a>详细解析。更新失败,意味着缓存已经不不一致,调用 <code>#removeFromCache(...)</code> 方法,移除事务从缓存中。</li></ul></li><li><code>#doUpdate(...)</code> 为抽象方法,子类实现该方法,提供更新事务功能。</li></ul><hr><p><strong><code>#delete(...)</code></strong> 实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">delete</span><span class="params">(Transaction transaction)</span> </span>{</div><div class="line"> <span class="keyword">int</span> result;</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> result = doDelete(transaction);</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> removeFromCache(transaction);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 删除事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transaction 事务</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 删除数量</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">int</span> <span class="title">doDelete</span><span class="params">(Transaction transaction)</span></span>;</div></pre></td></tr></table></figure><ul><li>调用 <code>#doDelete(...)</code> 方法,删除事务。</li><li>调用 <code>#removeFromCache(...)</code> 方法,移除事务从缓存中。</li><li><code>#doDelete(...)</code> 为抽象方法,子类实现该方法,提供删除事务功能。</li></ul><hr><p><strong><code>#findByXid(...)</code></strong> 实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> Transaction <span class="title">findByXid</span><span class="params">(TransactionXid transactionXid)</span> </span>{</div><div class="line"> Transaction transaction = findFromCache(transactionXid);</div><div class="line"> <span class="keyword">if</span> (transaction == <span class="keyword">null</span>) {</div><div class="line"> transaction = doFindOne(transactionXid);</div><div class="line"> <span class="keyword">if</span> (transaction != <span class="keyword">null</span>) {</div><div class="line"> putToCache(transaction);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> transaction;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 获得事务从缓存中</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transactionXid 事务编号</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 事务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">protected</span> Transaction <span class="title">findFromCache</span><span class="params">(TransactionXid transactionXid)</span> </span>{</div><div class="line"> <span class="keyword">return</span> transactionXidCompensableTransactionCache.getIfPresent(transactionXid);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 查询事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> xid 事务编号</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 事务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> Transaction <span class="title">doFindOne</span><span class="params">(Xid xid)</span></span>;</div></pre></td></tr></table></figure><ul><li>调用 <code>#findFromCache()</code> 方法,优先从缓存中获取事务。</li><li>调用 <code>#doFindOne()</code> 方法,缓存中事务不存在,从存储器中获取。获取到后,调用 <code>#putToCache()</code> 方法,添加事务到缓存中。</li><li><code>#doFindOne(...)</code> 为抽象方法,子类实现该方法,提供查询事务功能。</li></ul><hr><p><strong><code>#findAllUnmodifiedSince(...)</code></strong> 实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> List<Transaction> <span class="title">findAllUnmodifiedSince</span><span class="params">(Date date)</span> </span>{</div><div class="line"> List<Transaction> transactions = doFindAllUnmodifiedSince(date);</div><div class="line"> <span class="comment">// 添加到缓存</span></div><div class="line"> <span class="keyword">for</span> (Transaction transaction : transactions) {</div><div class="line"> putToCache(transaction);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> transactions;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 获取超过指定时间的事务集合</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> date 指定时间</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 事务集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> List<Transaction> <span class="title">doFindAllUnmodifiedSince</span><span class="params">(Date date)</span></span>;</div></pre></td></tr></table></figure><ul><li>调用 <code>#findAllUnmodifiedSince(...)</code> 方法,从存储器获取超过指定时间的事务集合。调用 <code>#putToCache(...)</code> 方法,循环事务集合添加到缓存。</li><li><code>#doFindAllUnmodifiedSince(...)</code> 为抽象方法,子类实现该方法,提供获取超过指定时间的事务集合功能。</li></ul><h2 id="3-2-JDBC-事务存储器"><a href="#3-2-JDBC-事务存储器" class="headerlink" title="3.2 JDBC 事务存储器"></a>3.2 JDBC 事务存储器</h2><p><code>org.mengyun.tcctransaction.repository.JdbcTransactionRepository</code>,JDBC 事务存储器,通过 JDBC 驱动,将 Transaction 存储到 MySQL / Oracle / PostgreSQL / SQLServer 等关系数据库。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcTransactionRepository</span> <span class="keyword">extends</span> <span class="title">CachableTransactionRepository</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 领域</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String domain;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 表后缀</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String tbSuffix;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 数据源</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> DataSource dataSource;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 序列化</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> ObjectSerializer serializer = <span class="keyword">new</span> JdkSerializationSerializer();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>domain</code>,领域,或者也可以称为模块名,应用名,<strong>用于唯一标识一个资源</strong>。例如,Maven 模块 <code>xxx-order</code>,我们可以配置该属性为 <code>ORDER</code>。</li><li><code>tbSuffix</code>,表后缀。默认存储表名为 <code>TCC_TRANSACTION</code>,配置表名后,为 <code>TCC_TRANSACTION${tbSuffix}</code>。</li><li><code>dataSource</code>,存储数据的数据源。</li><li><code>serializer</code>,序列化。<strong>当数据库里已经有数据的情况下,不要更换别的序列化,否则会导致反序列化报错。</strong>建议:TCC-Transaction 存储时,新增字段,记录序列化的方式。</li></ul><p>表结构如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line">CREATE TABLE `TCC_TRANSACTION` (</div><div class="line"> `TRANSACTION_ID` <span class="keyword">int</span>(<span class="number">11</span>) NOT NULL AUTO_INCREMENT,</div><div class="line"> `DOMAIN` varchar(<span class="number">100</span>) DEFAULT NULL,</div><div class="line"> `GLOBAL_TX_ID` varbinary(<span class="number">32</span>) NOT NULL,</div><div class="line"> `BRANCH_QUALIFIER` varbinary(<span class="number">32</span>) NOT NULL,</div><div class="line"> `CONTENT` varbinary(<span class="number">8000</span>) DEFAULT NULL,</div><div class="line"> `STATUS` <span class="keyword">int</span>(<span class="number">11</span>) DEFAULT NULL,</div><div class="line"> `TRANSACTION_TYPE` <span class="keyword">int</span>(<span class="number">11</span>) DEFAULT NULL,</div><div class="line"> `RETRIED_COUNT` <span class="keyword">int</span>(<span class="number">11</span>) DEFAULT NULL,</div><div class="line"> `CREATE_TIME` datetime DEFAULT NULL,</div><div class="line"> `LAST_UPDATE_TIME` datetime DEFAULT NULL,</div><div class="line"> `VERSION` <span class="keyword">int</span>(<span class="number">11</span>) DEFAULT NULL,</div><div class="line"> <span class="function">PRIMARY <span class="title">KEY</span> <span class="params">(`TRANSACTION_ID`)</span>,</span></div><div class="line"><span class="function"> UNIQUE KEY `UX_TX_BQ` <span class="params">(`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`)</span></span></div><div class="line"><span class="function">) ENGINE</span>=InnoDB DEFAULT CHARSET=utf8</div></pre></td></tr></table></figure><ul><li><code>TRANSACTION_ID</code>,仅仅数据库自增,无实际用途。</li><li><code>CONTENT</code>,Transaction 序列化。</li></ul><p>ps:点击<a href="https://github.com/YunaiV/tcc-transaction/blob/c164ff5ab29d31e08bc7061de5bc7403f3e40f1d/tcc-transaction-core/src/main/java/org/mengyun/tcctransaction/repository/JdbcTransactionRepository.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>查看 JdbcTransactionRepository 代码实现,已经添加完整中文注释。</p><h2 id="3-3-Redis-事务存储器"><a href="#3-3-Redis-事务存储器" class="headerlink" title="3.3 Redis 事务存储器"></a>3.3 Redis 事务存储器</h2><p><code>org.mengyun.tcctransaction.repository.RedisTransactionRepository</code>,Redis 事务存储器,将 Transaction 存储到 Redis。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisTransactionRepository</span> <span class="keyword">extends</span> <span class="title">CachableTransactionRepository</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Jedis Pool</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> JedisPool jedisPool;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * key 前缀</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String keyPrefix = <span class="string">"TCC:"</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 序列化</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> ObjectSerializer serializer = <span class="keyword">new</span> JdkSerializationSerializer();</div><div class="line"> </div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>keyPrefix</code>,key 前缀。类似 JdbcTransactionRepository 的 <code>domain</code> 属性。</li></ul><p>一个事务存储到 Reids,使用 Redis 的数据结构为 <a href="https://redis.io/commands#hash" rel="external nofollow noopener noreferrer" target="_blank">HASHES</a>。</p><ul><li><p>key : 使用 <code>keyPrefix</code> + <code>xid</code>,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 创建事务的 Redis Key</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> keyPrefix key 前缀</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> xid 事务</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> Redis Key</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">byte</span>[] getRedisKey(String keyPrefix, Xid xid) {</div><div class="line"> <span class="keyword">byte</span>[] prefix = keyPrefix.getBytes();</div><div class="line"> <span class="keyword">byte</span>[] globalTransactionId = xid.getGlobalTransactionId();</div><div class="line"> <span class="keyword">byte</span>[] branchQualifier = xid.getBranchQualifier();</div><div class="line"> <span class="comment">// 拼接 key</span></div><div class="line"> <span class="keyword">byte</span>[] key = <span class="keyword">new</span> <span class="keyword">byte</span>[prefix.length + globalTransactionId.length + branchQualifier.length];</div><div class="line"> System.arraycopy(prefix, <span class="number">0</span>, key, <span class="number">0</span>, prefix.length);</div><div class="line"> System.arraycopy(globalTransactionId, <span class="number">0</span>, key, prefix.length, globalTransactionId.length);</div><div class="line"> System.arraycopy(branchQualifier, <span class="number">0</span>, key, prefix.length + globalTransactionId.length, branchQualifier.length);</div><div class="line"> <span class="keyword">return</span> key;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>HASHES 的 key :使用 <code>version</code>。</p><ul><li>添加和更新 Transaction 时,使用 Redis <a href="https://redis.io/commands/hsetnx" rel="external nofollow noopener noreferrer" target="_blank">HSETNX</a>,不存在当前版本的值时,进行设置,重而实现类似乐观锁的更新。</li><li>读取 Transaction 时,使用 Redis <a href="https://redis.io/commands/hgetall" rel="external nofollow noopener noreferrer" target="_blank">HGETALL</a>,将 Transaction 所有 <code>version</code> 对应的值读取到内存后,取 <code>version</code> 值最大的对应的值。</li></ul></li><li><p>HASHES 的 value :调用 <code>TransactionSerializer#serialize(...)</code> 方法,序列化 Transaction。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">byte</span>[] serialize(ObjectSerializer serializer, Transaction transaction) {</div><div class="line"> Map<String, Object> map = <span class="keyword">new</span> HashMap<String, Object>();</div><div class="line"> map.put(<span class="string">"GLOBAL_TX_ID"</span>, transaction.getXid().getGlobalTransactionId());</div><div class="line"> map.put(<span class="string">"BRANCH_QUALIFIER"</span>, transaction.getXid().getBranchQualifier());</div><div class="line"> map.put(<span class="string">"STATUS"</span>, transaction.getStatus().getId());</div><div class="line"> map.put(<span class="string">"TRANSACTION_TYPE"</span>, transaction.getTransactionType().getId());</div><div class="line"> map.put(<span class="string">"RETRIED_COUNT"</span>, transaction.getRetriedCount());</div><div class="line"> map.put(<span class="string">"CREATE_TIME"</span>, transaction.getCreateTime());</div><div class="line"> map.put(<span class="string">"LAST_UPDATE_TIME"</span>, transaction.getLastUpdateTime());</div><div class="line"> map.put(<span class="string">"VERSION"</span>, transaction.getVersion());</div><div class="line"> <span class="comment">// 序列化</span></div><div class="line"> map.put(<span class="string">"CONTENT"</span>, serializer.serialize(transaction));</div><div class="line"> <span class="keyword">return</span> serializer.serialize(map);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>TODO 为什么序列化两次</li></ul></li></ul><p>在实现 <code>#doFindAllUnmodifiedSince(date)</code> 方法,无法像数据库使用时间条件进行过滤,因此,加载所有事务后在内存中过滤。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> List<Transaction> <span class="title">doFindAllUnmodifiedSince</span><span class="params">(Date date)</span> </span>{</div><div class="line"> <span class="comment">// 获得所有事务</span></div><div class="line"> List<Transaction> allTransactions = doFindAll();</div><div class="line"> <span class="comment">// 过滤时间</span></div><div class="line"> List<Transaction> allUnmodifiedSince = <span class="keyword">new</span> ArrayList<Transaction>();</div><div class="line"> <span class="keyword">for</span> (Transaction transaction : allTransactions) {</div><div class="line"> <span class="keyword">if</span> (transaction.getLastUpdateTime().compareTo(date) < <span class="number">0</span>) {</div><div class="line"> allUnmodifiedSince.add(transaction);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> allUnmodifiedSince;</div><div class="line">}</div></pre></td></tr></table></figure><p>ps:点击<a href="https://github.com/YunaiV/tcc-transaction/blob/c164ff5ab29d31e08bc7061de5bc7403f3e40f1d/tcc-transaction-core/src/main/java/org/mengyun/tcctransaction/repository/RedisTransactionRepository.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>查看 RedisTransactionRepository 代码实现,已经添加完整中文注释。</p><blockquote><p>FROM <a href="https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x#%E9%85%8D%E7%BD%AEtcc-transaction" rel="external nofollow noopener noreferrer" target="_blank">《TCC-Transaction 官方文档 —— 使用指南1.2.x》</a><br>使用 RedisTransactionRepository 需要配置 Redis 服务器如下:<br>appendonly yes<br>appendfsync always</p></blockquote><h2 id="3-4-Zookeeper-事务存储器"><a href="#3-4-Zookeeper-事务存储器" class="headerlink" title="3.4 Zookeeper 事务存储器"></a>3.4 Zookeeper 事务存储器</h2><p><code>org.mengyun.tcctransaction.repository.ZooKeeperTransactionRepository</code>,Zookeeper 事务存储器,将 Transaction 存储到 Zookeeper。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ZooKeeperTransactionRepository</span> <span class="keyword">extends</span> <span class="title">CachableTransactionRepository</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Zookeeper 服务器地址数组</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String zkServers;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Zookeeper 超时时间</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">int</span> zkTimeout;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * TCC 存储 Zookeeper 根目录</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String zkRootPath = <span class="string">"/tcc"</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Zookeeper 连接</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> ZooKeeper zk;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 序列化</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> ObjectSerializer serializer = <span class="keyword">new</span> JdkSerializationSerializer();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>zkRootPath</code>,存储 Zookeeper 根目录,类似 JdbcTransactionRepository 的 <code>domain</code> 属性。</li></ul><p>一个事务存储到 Zookeeper,使用 Zookeeper 的<strong>持久数据节点</strong>。</p><ul><li><p>path:<code>${zkRootPath}</code> + <code>/</code> + <code>${xid}</code>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// ZooKeeperTransactionRepository.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> String <span class="title">getTxidPath</span><span class="params">(Xid xid)</span> </span>{</div><div class="line"> <span class="keyword">return</span> String.format(<span class="string">"%s/%s"</span>, zkRootPath, xid);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// TransactionXid.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>{</div><div class="line"> StringBuilder stringBuilder = <span class="keyword">new</span> StringBuilder();</div><div class="line"> stringBuilder.append(<span class="string">"globalTransactionId:"</span>).append(UUID.nameUUIDFromBytes(globalTransactionId).toString());</div><div class="line"> stringBuilder.append(<span class="string">","</span>).append(<span class="string">"branchQualifier:"</span>).append(UUID.nameUUIDFromBytes(branchQualifier).toString());</div><div class="line"> <span class="keyword">return</span> stringBuilder.toString();</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>data:调用 <code>TransactionSerializer#serialize(...)</code> 方法,序列化 Transaction。</p></li><li>version:使用 Zookeeper 数据节点自带版本功能。这里要注意下,Transaction 的版本从 1 开始,而 Zookeeper 数据节点版本从 0 开始。</li></ul><p>ps:点击<a href="https://github.com/YunaiV/tcc-transaction/blob/c164ff5ab29d31e08bc7061de5bc7403f3e40f1d/tcc-transaction-core/src/main/java/org/mengyun/tcctransaction/repository/ZooKeeperTransactionRepository.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>查看 ZooKeeperTransactionRepository 代码实现,已经添加完整中文注释。</p><p>另外,在生产上暂时不建议使用 ZooKeeperTransactionRepository,原因有两点:</p><ul><li>不支持 Zookeeper 安全认证。</li><li>使用 Zookeeper 时,未考虑断网重连等情况。</li></ul><p>如果你要使用 Zookeeper 进行事务的存储,可以考虑使用 <a href="https://curator.apache.org/" rel="external nofollow noopener noreferrer" target="_blank">Apache Curator</a> 操作 Zookeeper,重写 ZooKeeperTransactionRepository 部分代码。</p><h2 id="3-5-File-事务存储器"><a href="#3-5-File-事务存储器" class="headerlink" title="3.5 File 事务存储器"></a>3.5 File 事务存储器</h2><p><code>org.mengyun.tcctransaction.repository.FileSystemTransactionRepository</code>,File 事务存储器,将 Transaction 存储到文件系统。</p><p>实现上和 ZooKeeperTransactionRepository,区别主要在于不支持乐观锁更新。有兴趣的同学点击<a href="https://github.com/YunaiV/tcc-transaction/blob/c164ff5ab29d31e08bc7061de5bc7403f3e40f1d/tcc-transaction-core/src/main/java/org/mengyun/tcctransaction/repository/FileSystemTransactionRepository.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>查看,这里就不拓展开来。</p><p>另外,在生产上不建议使用 FileSystemTransactionRepository,因为不支持多节点共享。用分布式存储挂载文件另说,当然还是不建议,因为不支持乐观锁并发更新。</p><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>这篇略( 超 )微( 级 )水更,哈哈哈,为<a href="http://www.iocoder.cn/TCC-Transaction/transaction-recover/?self">《TCC-Transaction 源码分析 —— 事务恢复》</a>做铺垫啦。</p><p>使用 RedisTransactionRepository 和 ZooKeeperTransactionRepository 存储事务还是 Get 蛮多点的。</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_15/02.png" alt=""></p><p>胖友,分享一个朋友圈可好?</p>]]></content>
<summary type="html">
<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. 序列化</a><ul>
<li><a href=
</summary>
<category term="TCC-Transaction" scheme="http://www.iocoder.cn/categories/TCC-Transaction/"/>
</entry>
<entry>
<title>TCC-Transaction 源码分析 —— TCC 实现</title>
<link href="http://www.iocoder.cn/TCC-Transaction/tcc-core/"/>
<id>http://www.iocoder.cn/TCC-Transaction/tcc-core/</id>
<published>2018-02-07T16:00:00.000Z</published>
<updated>2017-09-17T11:06:25.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. TCC 原理</a></li><li><a href="#">3. TCC-Transaction 原理</a></li><li><a href="#">4. 事务与参与者</a><ul><li><a href="#">4.1 事务</a></li><li><a href="#">4.2 参与者</a></li></ul></li><li><a href="#">5. 事务管理器</a><ul><li><a href="#">5.1 发起根事务</a></li><li><a href="#">5.2 传播发起分支事务</a></li><li><a href="#">5.3 传播获取分支事务</a></li><li><a href="#">5.4 提交事务</a></li><li><a href="#">5.5 回滚事务</a></li><li><a href="#">5.6 添加参与者到事务</a></li></ul></li><li><a href="#">6. 事务拦截器</a><ul><li><a href="#">6.1 Compensable</a></li><li><a href="#">6.2 可补偿事务拦截器</a></li><li><a href="#">6.3 资源协调者拦截器</a></li></ul></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。</li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文分享 <strong>TCC 实现</strong>。主要涉及如下三个 Maven 项目:</p><ul><li><code>tcc-transaction-core</code> :tcc-transaction 底层实现。</li><li><code>tcc-transaction-api</code> :tcc-transaction 使用 API。</li><li><code>tcc-transaction-spring</code> :tcc-transaction Spring 支持。</li></ul><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 TCC-Transaction 点赞!<a href="https://github.com/changmingxie/tcc-transaction" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><p>OK,开始我们的第一段 TCC 旅程吧。</p><p>ps:笔者假设你已经阅读过<a href="https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x" rel="external nofollow noopener noreferrer" target="_blank">《tcc-transaction 官方文档 —— 使用指南1.2.x》</a>。</p><p>ps2:<strong>未特殊说明的情况下,本文事务指的是 TCC事务</strong>。</p><h1 id="2-TCC-原理"><a href="#2-TCC-原理" class="headerlink" title="2. TCC 原理"></a>2. TCC 原理</h1><blockquote><p>FROM <a href="https://support.hwclouds.com/devg-servicestage/zh-cn_topic_0056814426.html" rel="external nofollow noopener noreferrer" target="_blank">https://support.hwclouds.com/devg-servicestage/zh-cn_topic_0056814426.html</a><br><strong>TCC事务</strong><br>为了解决在事务运行过程中大颗粒度资源锁定的问题,业界提出一种新的事务模型,它是基于<strong>业务层面</strong>的事务定义。锁粒度完全由业务自己控制。它本质是一种补偿的思路。它把事务运行过程分成 Try、Confirm / Cancel 两个阶段。在每个阶段的逻辑由<strong>业务代码控制</strong>。这样就事务的锁粒度可以完全自由控制。业务可以在牺牲隔离性的情况下,获取更高的性能。</p></blockquote><ul><li>Try 阶段<ul><li>Try :尝试执行业务 <ul><li>完成所有业务检查( 一致性 ) </li><li>预留必须业务资源( 准隔离性 )</li></ul></li></ul></li><li>Confirm / Cancel 阶段:<ul><li>Confirm :确认执行业务<ul><li>真正执行业务</li><li>不做任务业务检查</li><li>Confirm 操作满足幂等性</li></ul></li><li>Cancel :取消执行业务<ul><li>释放 Try 阶段预留的业务资源</li><li>Cancel 操作满足幂等性 </li></ul></li><li>Confirm 与 Cancel 互斥</li></ul></li></ul><p>整体流程如下图:</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_08/01.jpeg" alt=""></p><ul><li><p><strong>红框部分</strong>功能由 <code>tcc-transaction-core</code> 实现:</p><ul><li>启动业务活动</li><li>登记业务操作</li><li>提交 / 回滚业务活动</li></ul></li><li><p><strong>黄框部分</strong>功能由 <code>tcc-transaction-http-sample</code> 实现( 官方提供的示例项目 ):</p><ul><li>Try 操作</li><li>Confirm 操作</li><li>Cancel 操作 </li></ul></li></ul><p><strong>与 2PC协议 比较</strong>:</p><ul><li>位于业务服务层而非自愿层</li><li>没有单独的准备( Prepare )阶段,Try 操作兼备自愿操作与准备能力</li><li>Try 操作可以灵活选择业务资源的锁定粒度</li><li>较高开发成本</li></ul><p>参考资料:</p><ul><li><a href="https://www.zhihu.com/question/31813039" rel="external nofollow noopener noreferrer" target="_blank">《支付宝运营架构中柔性事务指的是什么?》</a></li><li><a href="http://kaimingwan.com/post/fen-bu-shi/fen-bu-shi-shi-wu-de-dian-xing-chu-li-fang-shi-2pc-tcc-yi-bu-que-bao-he-zui-da-nu-li-xing" rel="external nofollow noopener noreferrer" target="_blank">《分布式事务的典型处理方式:2PC、TCC、异步确保和最大努力型》</a></li></ul><h1 id="3-TCC-Transaction-原理"><a href="#3-TCC-Transaction-原理" class="headerlink" title="3. TCC-Transaction 原理"></a>3. TCC-Transaction 原理</h1><p>在 TCC 里,一个业务活动可以有多个事务,每个业务操作归属于不同的事务,即一个事务可以包含多个业务操作。TCC-Transaction 将每个业务操作抽象成<strong>事务参与者</strong>,每个事务可以包含多个<strong>参与者</strong>。</p><p>参与者需要声明 try / confirm / cancel 三个类型的方法,和 TCC 的操作一一对应。在程序里,通过 @Compensable 注解标记在 try 方法上,并填写对应的 confirm / cancel 方法,示例代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// try</span></div><div class="line"><span class="meta">@Compensable</span>(confirmMethod = <span class="string">"confirmRecord"</span>, cancelMethod = <span class="string">"cancelRecord"</span>, transactionContextEditor = MethodTransactionContextEditor.class)</div><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">record</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>{}</div><div class="line"></div><div class="line"><span class="comment">// confirm</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">confirmRecord</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>{}</div><div class="line"></div><div class="line"><span class="comment">// cancel</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancelRecord</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>{}</div></pre></td></tr></table></figure><ul><li>在示例代码中,我们看到 TransactionContext,事务上下文,这个是怎么生成的呢?这里先卖一个关子。</li></ul><p>TCC-Transaction 有两个拦截器,通过对 @Compensable AOP 切面( 参与者 try 方法 )进行拦截,透明化对参与者 confirm / cancel 方法调用,从而实现 TCC 。<strong>简化</strong>流程如下图:</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_08/03.png" alt=""></p><p>第一个拦截器,可补偿事务拦截器,实现如下功能:</p><ul><li>在 Try 阶段,对事务的发起、传播。</li><li>在 Confirm / Cancel 阶段,对事务提交或回滚。</li><li><strong>为什么会有对事务的传播呢</strong>?在远程调用服务的参与者时,会通过<strong>“参数”</strong>( 需要序列化 )的形式传递事务给远程参与者。</li></ul><p>第二个拦截器,资源协调者拦截器,实现如下功能:</p><ul><li>在 Try 阶段,添加参与者到事务中。当事务上下文不存在时,进行创建。</li></ul><p>实际拦截器对事务的处理会比上图复杂一些,在本文<a href="#">「6. 事务拦截器」</a>详细解析。</p><p>在 TCC-Transaction 代码实现上,组件分层如下图:</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_08/04.png" alt=""></p><p>本文按照如下顺序分享:</p><ul><li><a href="#">「4. 事务拦截器」</a></li><li><a href="#">「5. 事务管理器」</a></li><li><a href="#">「6. 事务管理器」</a></li></ul><p>内容是<strong>自下而上</strong>的方式分享,每个组件可以更加整体的被认识。当然这可能对你理解时产生一脸闷逼,所以推荐两种阅读方式:</p><ul><li>简读 x 1 + 深读 x 1</li><li>倒着读,发现未分享的方法,全文检索该方法。</li></ul><p>事务存储器在<a href="http://www.iocoder.cn/TCC-Transaction/transaction-repository/?self">《TCC-Transaction 源码解析 —— 事务存储于恢复》</a>详细解析。</p><p>事务恢复在<a href="http://www.iocoder.cn/TCC-Transaction/transaction-recovery/?self">《TCC-Transaction 源码解析 —— 事务恢复》</a>详细解析。</p><h1 id="4-事务与参与者"><a href="#4-事务与参与者" class="headerlink" title="4. 事务与参与者"></a>4. 事务与参与者</h1><p>在 TCC 里,<strong>一个</strong>事务( <code>org.mengyun.tcctransaction.Transaction</code> ) 可以有<strong>多个</strong>参与者( <code>org.mengyun.tcctransaction.Participant</code> )参与业务活动。类图关系如下( <a href="http://www.iocoder.cn/images/TCC-Transaction/2018_02_08/02.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_08/02.png" alt=""></p><h2 id="4-1-事务"><a href="#4-1-事务" class="headerlink" title="4.1 事务"></a>4.1 事务</h2><p><strong>Transaction 实现代码如下</strong>:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Transaction</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">7291423944314337931L</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 事务编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> TransactionXid xid;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 事务状态</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> TransactionStatus status;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 事务类型</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> TransactionType transactionType;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 重试次数</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> retriedCount = <span class="number">0</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 创建时间</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Date createTime = <span class="keyword">new</span> Date();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 最后更新时间</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Date lastUpdateTime = <span class="keyword">new</span> Date();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 版本号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">long</span> version = <span class="number">1</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 参与者集合</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> List<Participant> participants = <span class="keyword">new</span> ArrayList<Participant>();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 附带属性映射</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Map<String, Object> attachments = <span class="keyword">new</span> ConcurrentHashMap<String, Object>();</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 添加参与者</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> participant 参与者</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">enlistParticipant</span><span class="params">(Participant participant)</span> </span>{</div><div class="line"> participants.add(participant);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 提交 TCC 事务</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">commit</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">for</span> (Participant participant : participants) {</div><div class="line"> participant.commit();</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 回滚 TCC 事务</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">rollback</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">for</span> (Participant participant : participants) {</div><div class="line"> participant.rollback();</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>xid,事务编号( TransactionXid ),用于唯一标识一个事务。使用 UUID 算法生成,<strong>保证唯一性</strong>。<code>org.mengyun.tcctransaction.api.TransactionXid</code> 实现 <a href="https://docs.oracle.com/javase/8/docs/api/javax/transaction/xa/Xid.html" rel="external nofollow noopener noreferrer" target="_blank"><code>javax.transaction.xa.Xid</code></a> 接口,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TransactionXid</span> <span class="keyword">implements</span> <span class="title">Xid</span>, <span class="title">Serializable</span> </span>{</div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">6817267250789142043L</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * xid 格式标识符</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">int</span> formatId = <span class="number">1</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 全局事务编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">byte</span>[] globalTransactionId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 分支事务编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">byte</span>[] branchQualifier;</div><div class="line"> </div><div class="line">}</div></pre></td></tr></table></figure><ul><li>TODO 为什么要继承 Xid 接口?</li></ul></li><li><p>status,事务状态( TransactionStatus )。<code>org.mengyun.tcctransaction.api.TransactionStatus</code> 实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">enum</span> TransactionStatus {</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 尝试中状态</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> TRYING(<span class="number">1</span>),</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 确认中状态</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> CONFIRMING(<span class="number">2</span>),</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 取消中状态</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> CANCELLING(<span class="number">3</span>);</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">int</span> id;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>transactionType,事务类型( TransactionType )。<code>org.mengyun.tcctransaction.common.TransactionType</code> 实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">enum</span> TransactionType {</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 根事务</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> ROOT(<span class="number">1</span>),</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 分支事务</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> BRANCH(<span class="number">2</span>);</div><div class="line"></div><div class="line"> <span class="keyword">int</span> id;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>在<a href="#">「6.2 可补偿事务拦截器」</a>有详细解析,可以看到看到这两种事务是如何发起。</li></ul></li><li><p>retriedCount,重试次数。在 TCC 过程中,可能参与者异常崩溃,这个时候会进行重试直到成功或超过最大次数。在<a href="http://www.iocoder.cn/TCC-Transaction/transaction-recovery/?self">《TCC-Transaction 源码解析 —— 事务恢复》</a>详细解析。</p></li><li>version,版本号,用于乐观锁更新事务。在<a href="http://www.iocoder.cn/TCC-Transaction/transaction-repository/?self">《TCC-Transaction 源码解析 —— 事务存储器》</a>详细解析。</li><li>attachments,附带属性映射。在<a href="http://www.iocoder.cn/TCC-Transaction/dubbo-support/?self">《TCC-Transaction 源码解析 —— Dubbo 支持》</a>详细解析。</li><li>提供 <code>#enlistParticipant()</code> 方法,添加事务参与者。</li><li>提供 <code>#commit()</code> 方法,调用参与者们提交事务。</li><li>提供 <code>#rollback()</code> 方法,调用参与者回滚事务。</li></ul><h2 id="4-2-参与者"><a href="#4-2-参与者" class="headerlink" title="4.2 参与者"></a>4.2 参与者</h2><p><strong>Participant 实现代码如下</strong>:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Participant</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">4127729421281425247L</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 事务编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> TransactionXid xid;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 确认执行业务方法调用上下文</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> InvocationContext confirmInvocationContext;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 取消执行业务方法</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> InvocationContext cancelInvocationContext;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 执行器</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Terminator terminator = <span class="keyword">new</span> Terminator();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 事务上下文编辑</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> Class<? extends TransactionContextEditor> transactionContextEditorClass;</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 提交事务</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">commit</span><span class="params">()</span> </span>{</div><div class="line"> terminator.invoke(<span class="keyword">new</span> TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 回滚事务</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">rollback</span><span class="params">()</span> </span>{</div><div class="line"> terminator.invoke(<span class="keyword">new</span> TransactionContext(xid, TransactionStatus.CANCELLING.getId()), cancelInvocationContext, transactionContextEditorClass);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>xid,参与者事务编号。通过 <code>TransactionXid.globalTransactionId</code> 属性,关联上其所属的事务。当参与者进行远程调用时,远程的<strong>分支</strong>事务的事务编号等于该参与者的事务编号。通过事务编号的关联,TCC Confirm / Cancel 阶段,使用参与者的事务编号和远程的<strong>分支</strong>事务进行关联,从而实现事务的提交和回滚,在<a href="#">「5.2 传播发起分支事务」 + 「6.2 可补偿事务拦截器」</a>可以看到具体实现。</li><li><p>confirmInvocationContext,确认执行业务方法调用上下文( InvocationContext )。<code>org.mengyun.tcctransaction.InvocationContext</code> 实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">InvocationContext</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">7969140711432461165L</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 类</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Class targetClass;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 方法名</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String methodName;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 参数类型数组</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Class[] parameterTypes;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 参数数组</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Object[] args;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>InvocationContext,执行方法调用上下文,记录类、方法名、参数类型数组、参数数组。通过这些属性,可以执行提交 / 回滚事务。在 <code>org.mengyun.tcctransaction.Terminator</code> 会看到具体的代码实现。<strong>本质上,TCC 通过多个参与者的 try / confirm / cancel 方法,实现事务的最终一致性</strong>。</li></ul></li><li><p>cancelInvocationContext,取消执行业务方法调用上下文( InvocationContext )。</p></li><li><p>terminator,执行器( Terminator )。<code>org.mengyun.tcctransaction.Terminator</code> 实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Terminator</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">164958655471605778L</span>;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(TransactionContext transactionContext, InvocationContext invocationContext, Class<? extends TransactionContextEditor> transactionContextEditorClass)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (StringUtils.isNotEmpty(invocationContext.getMethodName())) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 获得 参与者对象</span></div><div class="line"> Object target = FactoryBuilder.factoryOf(invocationContext.getTargetClass()).getInstance();</div><div class="line"> <span class="comment">// 获得 方法</span></div><div class="line"> Method method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes());</div><div class="line"> <span class="comment">// 设置 事务上下文 到方法参数</span></div><div class="line"> FactoryBuilder.factoryOf(transactionContextEditorClass).getInstance().set(transactionContext, target, method, invocationContext.getArgs());</div><div class="line"> <span class="comment">// 执行方法</span></div><div class="line"> <span class="keyword">return</span> method.invoke(target, invocationContext.getArgs());</div><div class="line"> } <span class="keyword">catch</span> (Exception e) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemException(e);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>FactoryBuilder,工厂 Builder,感兴趣的同学点击<a href="https://github.com/YunaiV/tcc-transaction/blob/8553baad29597603d9007d61aec3ea5201632d1b/tcc-transaction-core/src/main/java/org/mengyun/tcctransaction/support/FactoryBuilder.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>查看,已经添加完整中文代码注释。</li><li>TransactionContextEditor,在本文<a href="#">「6.1 Compensable」</a>详细解析。</li></ul></li><li>transactionContextEditorClass,事务上下文编辑,在<a href="#">「6.1 Compensable」</a>详细解析。</li><li>提交 <code>#commit()</code> 方法,提交参与者自己的事务。</li><li>提交 <code>#rollback()</code> 方法,回滚参与者自己的事务。 </li></ul><h1 id="5-事务管理器"><a href="#5-事务管理器" class="headerlink" title="5. 事务管理器"></a>5. 事务管理器</h1><p><code>org.mengyun.tcctransaction.TransactionManager</code>,事务管理器,提供事务的获取、发起、提交、回滚,参与者的新增等等方法。</p><h2 id="5-1-发起根事务"><a href="#5-1-发起根事务" class="headerlink" title="5.1 发起根事务"></a>5.1 发起根事务</h2><p>提供 <code>begin()</code> 方法,发起根事务。该方法在<strong>调用方法类型为 MethodType.ROOT 并且 事务处于 Try 阶段</strong>被调用。MethodType 在<a href="#">「6.2 可补偿事务拦截器」</a>详细解析。</p><p>实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TransactionManager.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 发起根事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 事务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> Transaction <span class="title">begin</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 创建 根事务</span></div><div class="line"> Transaction transaction = <span class="keyword">new</span> Transaction(TransactionType.ROOT);</div><div class="line"> <span class="comment">// 存储 事务</span></div><div class="line"> transactionRepository.create(transaction);</div><div class="line"> <span class="comment">// 注册 事务</span></div><div class="line"> registerTransaction(transaction);</div><div class="line"> <span class="keyword">return</span> transaction;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 Transaction 构造方法,创建<strong>根事务</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// Transaction.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 创建指定类型的事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transactionType 事务类型</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">Transaction</span><span class="params">(TransactionType transactionType)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.xid = <span class="keyword">new</span> TransactionXid();</div><div class="line"> <span class="keyword">this</span>.status = TransactionStatus.TRYING; <span class="comment">// 尝试中状态</span></div><div class="line"> <span class="keyword">this</span>.transactionType = transactionType;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>目前该构造方法只有 <code>TransactionManager#begin()</code> 在调用,即只创建<strong>根事务</strong>。</li></ul></li><li>调用 <code>TransactionRepository#crete()</code> 方法,存储事务。</li><li><p>调用 <code>#registerTransaction(...)</code> 方法,注册事务到当前线程事务队列。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TransactionManager.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 当前线程事务队列</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal<Deque<Transaction>> CURRENT = <span class="keyword">new</span> ThreadLocal<Deque<Transaction>>();</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 注册事务到当前线程事务队列</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transaction 事务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerTransaction</span><span class="params">(Transaction transaction)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (CURRENT.get() == <span class="keyword">null</span>) {</div><div class="line"> CURRENT.set(<span class="keyword">new</span> LinkedList<Transaction>());</div><div class="line"> }</div><div class="line"> CURRENT.get().push(transaction); <span class="comment">// 添加到头部</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>可能有同学会比较好奇,为什么使用队列存储当前线程事务</strong>?TCC-Transaction 支持<strong>多个</strong>的事务<strong>独立存在</strong>,后创建的事务先提交,类似 Spring 的<code>org.springframework.transaction.annotation.Propagation.REQUIRES_NEW</code> 。在下文,很快我们就会看到 TCC-Transaction 自己的 <code>org.mengyun.tcctransaction.api.Propagation</code> 。</li></ul></li></ul><h2 id="5-2-传播发起分支事务"><a href="#5-2-传播发起分支事务" class="headerlink" title="5.2 传播发起分支事务"></a>5.2 传播发起分支事务</h2><p>调用 <code>#propagationNewBegin(...)</code> 方法,传播发起<strong>分支</strong>事务。该方法在<strong>调用方法类型为 MethodType.PROVIDER 并且 事务处于 Try 阶段</strong>被调用。MethodType 在<a href="#">「6.2 可补偿事务拦截器」</a>详细解析。</p><p>实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 传播发起分支事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transactionContext 事务上下文</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 分支事务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> Transaction <span class="title">propagationNewBegin</span><span class="params">(TransactionContext transactionContext)</span> </span>{</div><div class="line"> <span class="comment">// 创建 分支事务</span></div><div class="line"> Transaction transaction = <span class="keyword">new</span> Transaction(transactionContext);</div><div class="line"> <span class="comment">// 存储 事务</span></div><div class="line"> transactionRepository.create(transaction);</div><div class="line"> <span class="comment">// 注册 事务</span></div><div class="line"> registerTransaction(transaction);</div><div class="line"> <span class="keyword">return</span> transaction;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 Transaction 构造方法,创建<strong>分支事务</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 创建分支事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transactionContext 事务上下文</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">Transaction</span><span class="params">(TransactionContext transactionContext)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.xid = transactionContext.getXid(); <span class="comment">// 事务上下文的 xid</span></div><div class="line"> <span class="keyword">this</span>.status = TransactionStatus.TRYING; <span class="comment">// 尝试中状态</span></div><div class="line"> <span class="keyword">this</span>.transactionType = TransactionType.BRANCH; <span class="comment">// 分支事务</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>分支</strong>事务使用传播的事务上下文的事务编号。</li></ul></li><li>调用 <code>TransactionRepository#crete()</code> 方法,存储事务。为什么要存储<strong>分支</strong>事务,在<a href="#">「6.3 资源协调者拦截器」</a>详细解析。</li><li>调用 <code>#registerTransaction(...)</code> 方法,注册事务到当前线程事务队列。</li></ul><h2 id="5-3-传播获取分支事务"><a href="#5-3-传播获取分支事务" class="headerlink" title="5.3 传播获取分支事务"></a>5.3 传播获取分支事务</h2><p>调用 <code>#propagationExistBegin(...)</code> 方法,传播发起<strong>分支</strong>事务。该方法在<strong>调用方法类型为 MethodType.PROVIDER 并且 事务处于 Confirm / Cancel 阶段</strong>被调用。MethodType 在<a href="#">「6.2 可补偿事务拦截器」</a>详细解析。</p><p>实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 传播获取分支事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transactionContext 事务上下文</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 分支事务</span></div><div class="line"><span class="comment">* <span class="doctag">@throws</span> NoExistedTransactionException 当事务不存在时</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> Transaction <span class="title">propagationExistBegin</span><span class="params">(TransactionContext transactionContext)</span> <span class="keyword">throws</span> NoExistedTransactionException </span>{</div><div class="line"> <span class="comment">// 查询 事务</span></div><div class="line"> Transaction transaction = transactionRepository.findByXid(transactionContext.getXid());</div><div class="line"> <span class="keyword">if</span> (transaction != <span class="keyword">null</span>) {</div><div class="line"> <span class="comment">// 设置 事务 状态</span></div><div class="line"> transaction.changeStatus(TransactionStatus.valueOf(transactionContext.getStatus()));</div><div class="line"> <span class="comment">// 注册 事务</span></div><div class="line"> registerTransaction(transaction);</div><div class="line"> <span class="keyword">return</span> transaction;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NoExistedTransactionException();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>TransactionRepository#findByXid()</code> 方法,查询事务。</li><li>调用 <code>Transaction#changeStatus(...)</code> 方法,<strong>设置</strong>事务状态为 CONFIRMING 或 CANCELLING。</li><li>调用 <code>#registerTransaction(...)</code> 方法,注册事务到当前线程事务队列。</li><li>为什么此处是<strong>分支</strong>事务呢?结合 <code>#propagationNewBegin(...)</code> 思考下。 </li></ul><h2 id="5-4-提交事务"><a href="#5-4-提交事务" class="headerlink" title="5.4 提交事务"></a>5.4 提交事务</h2><p>调用 <code>#commit(...)</code> 方法,提交事务。该方法在<strong>事务处于 Confirm / Cancel 阶段</strong>被调用。</p><p>实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 提交事务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">commit</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 获取 事务</span></div><div class="line"> Transaction transaction = getCurrentTransaction();</div><div class="line"> <span class="comment">// 设置 事务状态 为 CONFIRMING</span></div><div class="line"> transaction.changeStatus(TransactionStatus.CONFIRMING);</div><div class="line"> <span class="comment">// 更新 事务</span></div><div class="line"> transactionRepository.update(transaction);</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 提交 事务</span></div><div class="line"> transaction.commit();</div><div class="line"> <span class="comment">// 删除 事务</span></div><div class="line"> transactionRepository.delete(transaction);</div><div class="line"> } <span class="keyword">catch</span> (Throwable commitException) {</div><div class="line"> logger.error(<span class="string">"compensable transaction confirm failed."</span>, commitException);</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ConfirmingException(commitException);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>#getCurrentTransaction()</code> 方法, 获取事务。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> Transaction <span class="title">getCurrentTransaction</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (isTransactionActive()) {</div><div class="line"> <span class="keyword">return</span> CURRENT.get().peek(); <span class="comment">// 获得头部元素</span></div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isTransactionActive</span><span class="params">()</span> </span>{</div><div class="line"> Deque<Transaction> transactions = CURRENT.get();</div><div class="line"> <span class="keyword">return</span> transactions != <span class="keyword">null</span> && !transactions.isEmpty();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>为什么获得队列<strong>头部</strong>元素呢?该元素即是上文调用 <code>#registerTransaction(...)</code> 注册到队列头部。</li></ul></li><li>调用 <code>Transaction#changeStatus(...)</code> 方法, <strong>设置</strong>事务状态为 CONFIRMING。</li><li>调用 <code>TransactionRepository#update(...)</code> 方法, <strong>更新</strong>事务。</li><li>调用 <code>Transaction#commit(...)</code> 方法, <strong>提交</strong>事务。</li><li>调用 <code>TransactionRepository#delete(...)</code> 方法,<strong>删除</strong>事务。</li></ul><h2 id="5-5-回滚事务"><a href="#5-5-回滚事务" class="headerlink" title="5.5 回滚事务"></a>5.5 回滚事务</h2><p>调用 <code>#rollback(...)</code> 方法,取消事务,和 <code>#commit()</code> 方法基本类似。该方法在<strong>事务处于 Confirm / Cancel 阶段</strong>被调用。</p><p>实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 回滚事务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">rollback</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 获取 事务</span></div><div class="line"> Transaction transaction = getCurrentTransaction();</div><div class="line"> <span class="comment">// 设置 事务状态 为 CANCELLING</span></div><div class="line"> transaction.changeStatus(TransactionStatus.CANCELLING);</div><div class="line"> <span class="comment">// 更新 事务</span></div><div class="line"> transactionRepository.update(transaction);</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 提交 事务</span></div><div class="line"> transaction.rollback();</div><div class="line"> <span class="comment">// 删除 事务</span></div><div class="line"> transactionRepository.delete(transaction);</div><div class="line"> } <span class="keyword">catch</span> (Throwable rollbackException) {</div><div class="line"> logger.error(<span class="string">"compensable transaction rollback failed."</span>, rollbackException);</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> CancellingException(rollbackException);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#getCurrentTransaction()</code> 方法,获取事务。</li><li>调用 <code>Transaction#changeStatus(...)</code> 方法, <strong>设置</strong>事务状态为 CANCELLING。</li><li>调用 <code>TransactionRepository#update(...)</code> 方法, <strong>更新</strong>事务。</li><li>调用 <code>Transaction#rollback(...)</code> 方法, <strong>回滚</strong>事务。</li><li>调用 <code>TransactionRepository#delete(...)</code> 方法,<strong>删除</strong>事务。</li></ul><h2 id="5-6-添加参与者到事务"><a href="#5-6-添加参与者到事务" class="headerlink" title="5.6 添加参与者到事务"></a>5.6 添加参与者到事务</h2><p>调用 <code>#enlistParticipant(...)</code> 方法,添加参与者到事务。该方法在<strong>事务处于 Try 阶段</strong>被调用,在<a href="#">「6.3 资源协调者拦截器」</a>有详细解析。</p><p>实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 添加参与者到事务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> participant 参与者</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">enlistParticipant</span><span class="params">(Participant participant)</span> </span>{</div><div class="line"> <span class="comment">// 获取 事务</span></div><div class="line"> Transaction transaction = <span class="keyword">this</span>.getCurrentTransaction();</div><div class="line"> <span class="comment">// 添加参与者</span></div><div class="line"> transaction.enlistParticipant(participant);</div><div class="line"> <span class="comment">// 更新 事务</span></div><div class="line"> transactionRepository.update(transaction);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#getCurrentTransaction()</code> 方法,获取事务。</li><li>调用 <code>Transaction#enlistParticipant(...)</code> 方法, 添加参与者到事务。</li><li>调用 <code>TransactionRepository#update(...)</code> 方法, <strong>更新</strong>事务。</li></ul><h1 id="6-事务拦截器"><a href="#6-事务拦截器" class="headerlink" title="6. 事务拦截器"></a>6. 事务拦截器</h1><p>TCC-Transaction 基于 <code>org.mengyun.tcctransaction.api.@Compensable</code> + <code>org.aspectj.lang.annotation.@Aspect</code> <strong>注解</strong> <strong>AOP 切面</strong>实现业务方法的 TCC 事务声明<strong>拦截</strong>,同 Spring 的 <code>org.springframework.transaction.annotation.@Transactional</code> 的实现。</p><p>TCC-Transaction 有两个拦截器:</p><ul><li><code>org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor</code>,可补偿事务拦截器。</li><li><code>org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor</code>,资源协调者拦截器。</li></ul><p>在分享拦截器的实现之前,我们先一起看看 @Compensable 注解。</p><h2 id="6-1-Compensable"><a href="#6-1-Compensable" class="headerlink" title="6.1 Compensable"></a>6.1 Compensable</h2><p>@Compensable,标记可补偿的方法注解。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Compensable {</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 传播级别</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">Propagation <span class="title">propagation</span><span class="params">()</span> <span class="keyword">default</span> Propagation.REQUIRED</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 确认执行业务方法</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">String <span class="title">confirmMethod</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 取消执行业务方法</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">String <span class="title">cancelMethod</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 事务上下文编辑</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> Class<? extends TransactionContextEditor> transactionContextEditor() <span class="keyword">default</span> DefaultTransactionContextEditor.class;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>propagation,传播级别( Propagation ),默认 Propagation.REQUIRED。和 Spring 的 Propagation 除了缺少几个属性,基本一致。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">enum</span> Propagation {</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 支持当前事务,如果当前没有事务,就新建一个事务。</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> REQUIRED(<span class="number">0</span>),</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 支持当前事务,如果当前没有事务,就以非事务方式执行。</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> SUPPORTS(<span class="number">1</span>),</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 支持当前事务,如果当前没有事务,就抛出异常。</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> MANDATORY(<span class="number">2</span>),</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 新建事务,如果当前存在事务,把当前事务挂起。</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> REQUIRES_NEW(<span class="number">3</span>);</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> value;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>confirmMethod,确认执行业务方法名。</p></li><li>cancelMethod,取消执行业务方法名。</li><li><p>TransactionContextEditor,事务上下文编辑器( TransactionContextEditor ),用于设置和获得事务上下文( TransactionContext ),在<a href="#">「6.3 资源协调者拦截器」</a>可以看到被调用,此处只看它的代码实现。<code>org.mengyun.tcctransaction.api.TransactionContextEditor</code> 接口代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">TransactionContextEditor</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 从参数中获得事务上下文</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> target 对象</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> method 方法</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> args 参数</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 事务上下文</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">TransactionContext <span class="title">get</span><span class="params">(Object target, Method method, Object[] args)</span></span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 设置事务上下文到参数中</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> transactionContext 事务上下文</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> target 对象</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> method 方法</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> args 参数</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">set</span><span class="params">(TransactionContext transactionContext, Object target, Method method, Object[] args)</span></span>;</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>DefaultTransactionContextEditor,<strong>默认</strong>事务上下文编辑器实现。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">DefaultTransactionContextEditor</span> <span class="keyword">implements</span> <span class="title">TransactionContextEditor</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> TransactionContext <span class="title">get</span><span class="params">(Object target, Method method, Object[] args)</span> </span>{</div><div class="line"> <span class="keyword">int</span> position = getTransactionContextParamPosition(method.getParameterTypes());</div><div class="line"> <span class="keyword">if</span> (position >= <span class="number">0</span>) {</div><div class="line"> <span class="keyword">return</span> (TransactionContext) args[position];</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(TransactionContext transactionContext, Object target, Method method, Object[] args)</span> </span>{</div><div class="line"> <span class="keyword">int</span> position = getTransactionContextParamPosition(method.getParameterTypes());</div><div class="line"> <span class="keyword">if</span> (position >= <span class="number">0</span>) {</div><div class="line"> args[position] = transactionContext; <span class="comment">// 设置方法参数</span></div><div class="line"> }</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 获得事务上下文在方法参数里的位置</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> parameterTypes 参数类型集合</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 位置</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">getTransactionContextParamPosition</span><span class="params">(Class<?>[] parameterTypes)</span> </span>{</div><div class="line"> <span class="keyword">int</span> position = -<span class="number">1</span>;</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < parameterTypes.length; i++) {</div><div class="line"> <span class="keyword">if</span> (parameterTypes[i].equals(org.mengyun.tcctransaction.api.TransactionContext.class)) {</div><div class="line"> position = i;</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> position;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>x</li></ul></li></ul><ul><li><p>NullableTransactionContextEditor,无事务上下文编辑器实现。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">NullableTransactionContextEditor</span> <span class="keyword">implements</span> <span class="title">TransactionContextEditor</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> TransactionContext <span class="title">get</span><span class="params">(Object target, Method method, Object[] args)</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(TransactionContext transactionContext, Object target, Method method, Object[] args)</span> </span>{</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure></li><li><p>DubboTransactionContextEditor,Dubbo 事务上下文编辑器实现,通过 Dubbo 隐式传参方式获得事务上下文,在<a href="http://www.iocoder.cn/TCC-Transaction/dubbo-support/?self">《TCC-Transaction 源码解析 —— Dubbo 支持》</a>详细解析。</p></li></ul></li></ul><h2 id="6-2-可补偿事务拦截器"><a href="#6-2-可补偿事务拦截器" class="headerlink" title="6.2 可补偿事务拦截器"></a>6.2 可补偿事务拦截器</h2><p>先一起来看下可补偿事务拦截器对应的切面 <code>org.mengyun.tcctransaction.interceptor.CompensableTransactionAspect</code>,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Aspect</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">CompensableTransactionAspect</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> CompensableTransactionInterceptor compensableTransactionInterceptor;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setCompensableTransactionInterceptor</span><span class="params">(CompensableTransactionInterceptor compensableTransactionInterceptor)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.compensableTransactionInterceptor = compensableTransactionInterceptor;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Pointcut</span>(<span class="string">"@annotation(org.mengyun.tcctransaction.api.Compensable)"</span>)</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">compensableService</span><span class="params">()</span> </span>{</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Around</span>(<span class="string">"compensableService()"</span>)</div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">interceptCompensableMethod</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"> <span class="keyword">return</span> compensableTransactionInterceptor.interceptCompensableMethod(pjp);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">int</span> <span class="title">getOrder</span><span class="params">()</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>通过 <code>org.aspectj.lang.annotation.@Pointcut</code> + <code>org.aspectj.lang.annotation.@Around</code> 注解,配置对 <strong>@Compensable 注解的方法</strong>进行拦截,调用 <code>CompensableTransactionInterceptor#interceptCompensableMethod(...)</code> 方法进行处理。</li></ul><p><strong>CompensableTransactionInterceptor 实现代码如下</strong>:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CompensableTransactionInterceptor</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> TransactionManager transactionManager;</div><div class="line"></div><div class="line"> <span class="keyword">private</span> Set<Class<? extends Exception>> delayCancelExceptions;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">interceptCompensableMethod</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"> <span class="comment">// 获得带 @Compensable 注解的方法</span></div><div class="line"> Method method = CompensableMethodUtils.getCompensableMethod(pjp);</div><div class="line"> <span class="comment">//</span></div><div class="line"> Compensable compensable = method.getAnnotation(Compensable.class);</div><div class="line"> Propagation propagation = compensable.propagation();</div><div class="line"> <span class="comment">// 获得 事务上下文</span></div><div class="line"> TransactionContext transactionContext = FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs());</div><div class="line"> <span class="comment">// 当前线程是否在事务中</span></div><div class="line"> <span class="keyword">boolean</span> isTransactionActive = transactionManager.isTransactionActive();</div><div class="line"> <span class="comment">// 判断事务上下文是否合法</span></div><div class="line"> <span class="keyword">if</span> (!TransactionUtils.isLegalTransactionContext(isTransactionActive, propagation, transactionContext)) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemException(<span class="string">"no active compensable transaction while propagation is mandatory for method "</span> + method.getName());</div><div class="line"> }</div><div class="line"> <span class="comment">// 计算方法类型</span></div><div class="line"> MethodType methodType = CompensableMethodUtils.calculateMethodType(propagation, isTransactionActive, transactionContext);</div><div class="line"> <span class="comment">// 处理</span></div><div class="line"> <span class="keyword">switch</span> (methodType) {</div><div class="line"> <span class="keyword">case</span> ROOT:</div><div class="line"> <span class="keyword">return</span> rootMethodProceed(pjp);</div><div class="line"> <span class="keyword">case</span> PROVIDER:</div><div class="line"> <span class="keyword">return</span> providerMethodProceed(pjp, transactionContext);</div><div class="line"> <span class="keyword">default</span>:</div><div class="line"> <span class="keyword">return</span> pjp.proceed();</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>CompensableMethodUtils#getCompensableMethod(...)</code> 方法,获得带 @Compensable 注解的方法。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CompensableMethodUtils.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 获得带 <span class="doctag">@Compensable</span> 注解的方法</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> pjp 切面点</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 方法</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Method <span class="title">getCompensableMethod</span><span class="params">(ProceedingJoinPoint pjp)</span> </span>{</div><div class="line"> Method method = ((MethodSignature) (pjp.getSignature())).getMethod(); <span class="comment">// 代理方法对象</span></div><div class="line"> <span class="keyword">if</span> (method.getAnnotation(Compensable.class) == <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes()); <span class="comment">// 实际方法对象</span></div><div class="line"> } <span class="keyword">catch</span> (NoSuchMethodException e) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> method;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>TransactionContextEditor#get(...)</code> 方法,从参数中获得事务上下文。<strong>为什么从参数中可以获得事务上下文呢</strong>?在<a href="#">「6.3 资源协调者拦截器」</a>揭晓答案。</p></li><li><p>调用 <code>TransactionManager#isTransactionActive()</code> 方法,当前线程是否在事务中。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TransactionManager.java</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal<Deque<Transaction>> CURRENT = <span class="keyword">new</span> ThreadLocal<Deque<Transaction>>();</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isTransactionActive</span><span class="params">()</span> </span>{</div><div class="line"> Deque<Transaction> transactions = CURRENT.get();</div><div class="line"> <span class="keyword">return</span> transactions != <span class="keyword">null</span> && !transactions.isEmpty();</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>TransactionUtils#isLegalTransactionContext(...)</code> 方法,判断事务上下文是否合法。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TransactionUtils.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 判断事务上下文是否合法</span></div><div class="line"><span class="comment">* 在 Propagation.MANDATORY 必须有在事务内</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> isTransactionActive 是否</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> propagation 传播级别</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transactionContext 事务上下文</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 是否合法</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isLegalTransactionContext</span><span class="params">(<span class="keyword">boolean</span> isTransactionActive, Propagation propagation, TransactionContext transactionContext)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (propagation.equals(Propagation.MANDATORY) && !isTransactionActive && transactionContext == <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>当传播级别为 Propagation.MANDATORY 时,要求必须在事务中。</li></ul></li><li><p>调用 <code>CompensableMethodUtils#calculateMethodType(...)</code> 方法,计算方法类型。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 计算方法类型</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> propagation 传播级别</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> isTransactionActive 是否事务开启</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> transactionContext 事务上下文</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 方法类型</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> MethodType <span class="title">calculateMethodType</span><span class="params">(Propagation propagation, <span class="keyword">boolean</span> isTransactionActive, TransactionContext transactionContext)</span> </span>{</div><div class="line"> <span class="keyword">if</span> ((propagation.equals(Propagation.REQUIRED) && !isTransactionActive && transactionContext == <span class="keyword">null</span>) <span class="comment">// Propagation.REQUIRED:支持当前事务,当前没有事务,就新建一个事务。</span></div><div class="line"> || propagation.equals(Propagation.REQUIRES_NEW)) { <span class="comment">// Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。</span></div><div class="line"> <span class="keyword">return</span> MethodType.ROOT;</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> ((propagation.equals(Propagation.REQUIRED) <span class="comment">// Propagation.REQUIRED:支持当前事务</span></div><div class="line"> || propagation.equals(Propagation.MANDATORY)) <span class="comment">// Propagation.MANDATORY:支持当前事务</span></div><div class="line"> && !isTransactionActive && transactionContext != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">return</span> MethodType.PROVIDER;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> MethodType.NORMAL;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>计算方法类型( MethodType )的目的,可以根据不同方法类型,做不同的事务处理。</li><li>方法类型为 MethodType.ROOT 时,发起<strong>根事务</strong>,判断条件如下二选一:<ul><li>事务传播级别为 Propagation.REQUIRED,并且当前没有事务。</li><li>事务传播级别为 Propagation.REQUIRES_NEW,新建事务,如果当前存在事务,把当前事务挂起。<strong>此时,事务管理器的当前线程事务队列可能会存在多个事务</strong>。<ul><li>方法类型为 MethodType.ROOT 时,发起<strong>分支事务</strong>,判断条件如下二选一:</li></ul></li><li>事务传播级别为 Propagation.REQUIRED,并且当前不存在事务,<strong>并且方法参数传递了事务上下文</strong>。</li><li>事务传播级别为 Propagation.PROVIDER,并且当前不存在事务,<strong>并且方法参数传递了事务上下文</strong>。</li><li><strong>当前不存在事务,方法参数传递了事务上下文是什么意思</strong>?当跨服务<strong>远程</strong>调用时,被调用服务本身( <strong>服务提供者</strong> )不在事务中,通过传递事务上下文参数,融入当前事务。<ul><li>方法类型为 MethodType.Normal 时,不进行事务处理。</li><li>MethodType.CONSUMER 项目已经不再使用,猜测已废弃。</li></ul></li></ul></li></ul></li><li><p>当方法类型为 MethodType.ROOT 时,调用 <code>#rootMethodProceed(...)</code> 方法,发起 <strong>TCC 整体流程</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> Object <span class="title">rootMethodProceed</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"> Object returnValue;</div><div class="line"> Transaction transaction = <span class="keyword">null</span>;</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 发起根事务</span></div><div class="line"> transaction = transactionManager.begin();</div><div class="line"> <span class="comment">// 执行方法原逻辑</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> returnValue = pjp.proceed();</div><div class="line"> } <span class="keyword">catch</span> (Throwable tryingException) {</div><div class="line"> <span class="keyword">if</span> (isDelayCancelException(tryingException)) { <span class="comment">// 是否延迟回滚</span></div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> logger.warn(String.format(<span class="string">"compensable transaction trying failed. transaction content:%s"</span>, JSON.toJSONString(transaction)), tryingException);</div><div class="line"> <span class="comment">// 回滚事务</span></div><div class="line"> transactionManager.rollback();</div><div class="line"> }</div><div class="line"> <span class="keyword">throw</span> tryingException;</div><div class="line"> }</div><div class="line"> <span class="comment">// 提交事务</span></div><div class="line"> transactionManager.commit();</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> <span class="comment">// 将事务从当前线程事务队列移除</span></div><div class="line"> transactionManager.cleanAfterCompletion(transaction);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> returnValue;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#transactionManager()</code> 方法,发起<strong>根事务</strong>,<strong>TCC Try 阶段开始</strong>。</li><li>调用 <code>ProceedingJoinPoint#proceed()</code> 方法,执行方法<strong>原逻辑( 即 Try 逻辑 )</strong>。</li><li>当原逻辑执行异常时,<strong>TCC Try 阶段失败</strong>,调用 <code>TransactionManager#rollback(...)</code> 方法,<strong>TCC Cancel 阶段</strong>,回滚事务。此处 <code>#isDelayCancelException(...)</code> 方法,判断异常是否为延迟取消回滚异常,部分异常不适合立即回滚事务,在<a href="http://www.iocoder.cn/TCC-Transaction/transaction-recovery/?self">《TCC-Transaction 源码分析 —— 事务恢复》</a>详细解析。</li><li>当原逻辑执行成功时,<strong>TCC Try 阶段成功</strong>,调用 <code>TransactionManager#commit(...)</code> 方法,<strong>TCC Confirm 阶段</strong>,提交事务。</li><li><p>调用 <code>TransactionManager#cleanAfterCompletion(...)</code> 方法,将事务从当前线程事务队列移除,避免线程冲突。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TransactionManager.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cleanAfterCompletion</span><span class="params">(Transaction transaction)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (isTransactionActive() && transaction != <span class="keyword">null</span>) {</div><div class="line"> Transaction currentTransaction = getCurrentTransaction();</div><div class="line"> <span class="keyword">if</span> (currentTransaction == transaction) {</div><div class="line"> CURRENT.get().pop();</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SystemException(<span class="string">"Illegal transaction when clean after completion"</span>);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>x</li></ul></li></ul></li><li><p>当方法类型为 Propagation.PROVIDER 时,服务提供者参与 <strong>TCC 整体流程</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> Object <span class="title">providerMethodProceed</span><span class="params">(ProceedingJoinPoint pjp, TransactionContext transactionContext)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"> Transaction transaction = <span class="keyword">null</span>;</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">switch</span> (TransactionStatus.valueOf(transactionContext.getStatus())) {</div><div class="line"> <span class="keyword">case</span> TRYING:</div><div class="line"> <span class="comment">// 传播发起分支事务</span></div><div class="line"> transaction = transactionManager.propagationNewBegin(transactionContext);</div><div class="line"> <span class="keyword">return</span> pjp.proceed();</div><div class="line"> <span class="keyword">case</span> CONFIRMING:</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 传播获取分支事务</span></div><div class="line"> transaction = transactionManager.propagationExistBegin(transactionContext);</div><div class="line"> <span class="comment">// 提交事务</span></div><div class="line"> transactionManager.commit();</div><div class="line"> } <span class="keyword">catch</span> (NoExistedTransactionException excepton) {</div><div class="line"> <span class="comment">//the transaction has been commit,ignore it.</span></div><div class="line"> }</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> CANCELLING:</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 传播获取分支事务</span></div><div class="line"> transaction = transactionManager.propagationExistBegin(transactionContext);</div><div class="line"> <span class="comment">// 回滚事务</span></div><div class="line"> transactionManager.rollback();</div><div class="line"> } <span class="keyword">catch</span> (NoExistedTransactionException exception) {</div><div class="line"> <span class="comment">//the transaction has been rollback,ignore it.</span></div><div class="line"> }</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> <span class="comment">// 将事务从当前线程事务队列移除</span></div><div class="line"> transactionManager.cleanAfterCompletion(transaction);</div><div class="line"> }</div><div class="line"> <span class="comment">// 返回空值</span></div><div class="line"> Method method = ((MethodSignature) (pjp.getSignature())).getMethod();</div><div class="line"> <span class="keyword">return</span> ReflectionUtils.getNullValue(method.getReturnType());</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>当事务处于 TransactionStatus.TRYING 时,调用 <code>TransactionManager#propagationExistBegin(...)</code> 方法,传播发起<strong>分支</strong>事务。发起<strong>分支</strong>事务完成后,调用 <code>ProceedingJoinPoint#proceed()</code> 方法,执行方法<strong>原逻辑( 即 Try 逻辑 )</strong>。<ul><li><strong>为什么要传播发起分支事务</strong>?在<strong>根事务</strong>进行 Confirm / Cancel 时,调用<strong>根事务</strong>上的参与者们提交或回滚事务时,进行远程服务方法调用的参与者,可以通过自己的事务编号关联上传播的<strong>分支</strong>事务( 两者的事务编号相等 ),进行事务的提交或回滚。</li></ul></li><li>当事务处于 TransactionStatus.CONFIRMING 时,调用 <code>TransactionManager#commit()</code> 方法,提交事务。</li><li>当事务处于 TransactionStatus.CANCELLING 时,调用 <code>TransactionManager#rollback()</code> 方法,提交事务。</li><li>调用 <code>TransactionManager#cleanAfterCompletion(...)</code> 方法,将事务从当前线程事务队列移除,避免线程冲突。</li><li><p>当事务处于 TransactionStatus.CONFIRMING / TransactionStatus.CANCELLING 时,调用 <code>ReflectionUtils#getNullValue(...)</code> 方法,返回空值。<strong>为什么返回空值</strong>?Confirm / Cancel 相关方法,是通过 AOP 切面调用,只调用,不处理返回值,但是又不能没有返回值,因此直接返回空。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title">getNullValue</span><span class="params">(Class type)</span> </span>{</div><div class="line"> <span class="comment">// 处理基本类型</span></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">boolean</span>.class.equals(type)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">byte</span>.class.equals(type)) {</div><div class="line"> <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">short</span>.class.equals(type)) {</div><div class="line"> <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">int</span>.class.equals(type)) {</div><div class="line"> <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">long</span>.class.equals(type)) {</div><div class="line"> <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">float</span>.class.equals(type)) {</div><div class="line"> <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">double</span>.class.equals(type)) {</div><div class="line"> <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 处理对象</span></div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul></li><li><p>当方法类型为 Propagation.NORMAL 时,执行方法原逻辑,<strong>不进行事务处理</strong>。</p></li></ul><h2 id="6-3-资源协调者拦截器"><a href="#6-3-资源协调者拦截器" class="headerlink" title="6.3 资源协调者拦截器"></a>6.3 资源协调者拦截器</h2><p>先一起来看下资源协调者拦截器 对应的切面 <code>org.mengyun.tcctransaction.interceptor.CompensableTransactionAspect</code>,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Aspect</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">ResourceCoordinatorAspect</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> ResourceCoordinatorInterceptor resourceCoordinatorInterceptor;</div><div class="line"></div><div class="line"> <span class="meta">@Pointcut</span>(<span class="string">"@annotation(org.mengyun.tcctransaction.api.Compensable)"</span>)</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">transactionContextCall</span><span class="params">()</span> </span>{</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Around</span>(<span class="string">"transactionContextCall()"</span>)</div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">interceptTransactionContextMethod</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"> <span class="keyword">return</span> resourceCoordinatorInterceptor.interceptTransactionContextMethod(pjp);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setResourceCoordinatorInterceptor</span><span class="params">(ResourceCoordinatorInterceptor resourceCoordinatorInterceptor)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.resourceCoordinatorInterceptor = resourceCoordinatorInterceptor;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">int</span> <span class="title">getOrder</span><span class="params">()</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>通过 <code>org.aspectj.lang.annotation.@Pointcut</code> + <code>org.aspectj.lang.annotation.@Around</code> 注解,配置对 <strong>@Compensable 注解的方法</strong>进行拦截,调用 <code>ResourceCoordinatorInterceptor#interceptTransactionContextMethod(...)</code> 方法进行处理。</li></ul><p><strong>ResourceCoordinatorInterceptor 实现代码如下</strong>:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ResourceCoordinatorInterceptor</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> TransactionManager transactionManager;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">interceptTransactionContextMethod</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"> Transaction transaction = transactionManager.getCurrentTransaction();</div><div class="line"> <span class="keyword">if</span> (transaction != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">switch</span> (transaction.getStatus()) {</div><div class="line"> <span class="keyword">case</span> TRYING:</div><div class="line"> <span class="comment">// 添加事务参与者</span></div><div class="line"> enlistParticipant(pjp);</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> CONFIRMING:</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> CANCELLING:</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="comment">// 执行方法原逻辑</span></div><div class="line"> <span class="keyword">return</span> pjp.proceed(pjp.getArgs());</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>当事务处于 TransactionStatus.TRYING 时,调用 <code>#enlistParticipant(...)</code> 方法,添加事务参与者。</li><li>调用 <code>ProceedingJoinPoint#proceed(...)</code> 方法,执行方法原逻辑。</li></ul><p><strong><code>ResourceCoordinatorInterceptor#enlistParticipant()</code> 实现代码如下</strong>:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">enlistParticipant</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> IllegalAccessException, InstantiationException </span>{</div><div class="line"> <span class="comment">// 获得 @Compensable 注解</span></div><div class="line"> Method method = CompensableMethodUtils.getCompensableMethod(pjp);</div><div class="line"> <span class="keyword">if</span> (method == <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(String.format(<span class="string">"join point not found method, point is : %s"</span>, pjp.getSignature().getName()));</div><div class="line"> }</div><div class="line"> Compensable compensable = method.getAnnotation(Compensable.class);</div><div class="line"> <span class="comment">// 获得 确认执行业务方法 和 取消执行业务方法</span></div><div class="line"> String confirmMethodName = compensable.confirmMethod();</div><div class="line"> String cancelMethodName = compensable.cancelMethod();</div><div class="line"> <span class="comment">// 获取 当前线程事务第一个(头部)元素</span></div><div class="line"> Transaction transaction = transactionManager.getCurrentTransaction();</div><div class="line"> <span class="comment">// 创建 事务编号</span></div><div class="line"> TransactionXid xid = <span class="keyword">new</span> TransactionXid(transaction.getXid().getGlobalTransactionId());</div><div class="line"> <span class="comment">// TODO</span></div><div class="line"> <span class="keyword">if</span> (FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs()) == <span class="keyword">null</span>) {</div><div class="line"> FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().set(<span class="keyword">new</span> TransactionContext(xid, TransactionStatus.TRYING.getId()), pjp.getTarget(), ((MethodSignature) pjp.getSignature()).getMethod(), pjp.getArgs());</div><div class="line"> }</div><div class="line"> <span class="comment">// 获得类</span></div><div class="line"> Class targetClass = ReflectionUtils.getDeclaringType(pjp.getTarget().getClass(), method.getName(), method.getParameterTypes());</div><div class="line"> <span class="comment">// 创建 确认执行方法调用上下文 和 取消执行方法调用上下文</span></div><div class="line"> InvocationContext confirmInvocation = <span class="keyword">new</span> InvocationContext(targetClass,</div><div class="line"> confirmMethodName,</div><div class="line"> method.getParameterTypes(), pjp.getArgs());</div><div class="line"> InvocationContext cancelInvocation = <span class="keyword">new</span> InvocationContext(targetClass,</div><div class="line"> cancelMethodName,</div><div class="line"> method.getParameterTypes(), pjp.getArgs());</div><div class="line"> <span class="comment">// 创建 事务参与者</span></div><div class="line"> Participant participant =</div><div class="line"> <span class="keyword">new</span> Participant(</div><div class="line"> xid,</div><div class="line"> confirmInvocation,</div><div class="line"> cancelInvocation,</div><div class="line"> compensable.transactionContextEditor());</div><div class="line"> <span class="comment">// 添加 事务参与者 到 事务</span></div><div class="line"> transactionManager.enlistParticipant(participant);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>CompensableMethodUtils#getCompensableMethod(...)</code> 方法,获得带 @Compensable 注解的方法。</li><li>调用 <code>#getCurrentTransaction()</code> 方法, 获取事务。</li><li><p>调用 TransactionXid 构造方法,创建<strong>分支</strong>事务编号。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * 全局事务编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">byte</span>[] globalTransactionId;</div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * 分支事务编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">byte</span>[] branchQualifier;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">TransactionXid</span><span class="params">(<span class="keyword">byte</span>[] globalTransactionId)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.globalTransactionId = globalTransactionId;</div><div class="line"> branchQualifier = uuidToByteArray(UUID.randomUUID()); <span class="comment">// 生成 分支事务编号</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>分支事务编号( <code>branchQualifier</code> ) 需要生成。</li></ul></li><li><p>TODO TransactionContext 和 Participant 的关系。</p></li><li><p>调用 <code>ReflectionUtils#getDeclaringType(...)</code> 方法,获得声明 @Compensable 方法的实际类。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Class <span class="title">getDeclaringType</span><span class="params">(Class aClass, String methodName, Class<?>[] parameterTypes)</span> </span>{</div><div class="line"> Method method;</div><div class="line"> Class findClass = aClass;</div><div class="line"> <span class="keyword">do</span> {</div><div class="line"> Class[] clazzes = findClass.getInterfaces();</div><div class="line"> <span class="keyword">for</span> (Class clazz : clazzes) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> method = clazz.getDeclaredMethod(methodName, parameterTypes);</div><div class="line"> } <span class="keyword">catch</span> (NoSuchMethodException e) {</div><div class="line"> method = <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (method != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">return</span> clazz;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> findClass = findClass.getSuperclass();</div><div class="line"> } <span class="keyword">while</span> (!findClass.equals(Object.class));</div><div class="line"> <span class="keyword">return</span> aClass;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 InvocationContext 构造方法,分别创建确认执行方法调用上下文和取消执行方法调用上下文。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 类</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> Class targetClass;</div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 方法名</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> String methodName;</div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 参数类型数组</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> Class[] parameterTypes;</div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 参数数组</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> Object[] args;</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">InvocationContext</span><span class="params">(Class targetClass, String methodName, Class[] parameterTypes, Object... args)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.methodName = methodName;</div><div class="line"> <span class="keyword">this</span>.parameterTypes = parameterTypes;</div><div class="line"> <span class="keyword">this</span>.targetClass = targetClass;</div><div class="line"> <span class="keyword">this</span>.args = args;</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 Participant 构造方法,创建事务参与者。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Participant</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">4127729421281425247L</span>;</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 事务编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> TransactionXid xid;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 确认执行业务方法调用上下文</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> InvocationContext confirmInvocationContext;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 取消执行业务方法</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> InvocationContext cancelInvocationContext;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 执行器</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Terminator terminator = <span class="keyword">new</span> Terminator();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 事务上下文编辑</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> Class<? extends TransactionContextEditor> transactionContextEditorClass;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Participant</span><span class="params">()</span> </span>{</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Participant</span><span class="params">(TransactionXid xid, InvocationContext confirmInvocationContext, InvocationContext cancelInvocationContext, Class<? extends TransactionContextEditor> transactionContextEditorClass)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.xid = xid;</div><div class="line"> <span class="keyword">this</span>.confirmInvocationContext = confirmInvocationContext;</div><div class="line"> <span class="keyword">this</span>.cancelInvocationContext = cancelInvocationContext;</div><div class="line"> <span class="keyword">this</span>.transactionContextEditorClass = transactionContextEditorClass;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>TransactionManager#enlistParticipant(...)</code> 方法,添加事务参与者到事务。</p></li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>受限于本人的能力,蛮多处表达不够清晰或者易懂,非常抱歉。如果你对任何地方有任何疑问,欢迎添加本人微信号( wangwenbin-server ),期待与你的交流。不限于 TCC,也可以是分布式事务,也可以是微服务,以及等等。</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_08/05.png" alt=""></p><p>外送一本武林秘籍:带中文注释的 TCC-Transaction 仓库地址,目前正在慢慢完善。传送门:<a href="https://github.com/YunaiV/tcc-transaction" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/YunaiV/tcc-transaction</a>。</p><p>再送一本葵花宝典:<a href="https://my.oschina.net/fileoptions/blog/899991" rel="external nofollow noopener noreferrer" target="_blank">《TCC型分布式事务原理和实现》系列</a>。</p><p>胖友,分享一个朋友圈可好?</p>]]></content>
<summary type="html">
<p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. TCC 原理</a></li>
<li><a h
</summary>
<category term="TCC-Transaction" scheme="http://www.iocoder.cn/categories/TCC-Transaction/"/>
</entry>
<entry>
<title>TCC-Transaction 源码分析 —— 调试环境搭建</title>
<link href="http://www.iocoder.cn/TCC-Transaction/build-debugging-environment/"/>
<id>http://www.iocoder.cn/TCC-Transaction/build-debugging-environment/</id>
<published>2018-01-31T16:00:00.000Z</published>
<updated>2017-09-17T11:06:25.000Z</updated>
<content type="html"><![CDATA[<p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。</li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><p><strong>本文主要基于 TCC-Transaction 1.2.3.3 正式版</strong> </p><ul><li><a href="#">1. 依赖工具</a></li><li><a href="#">2. 源码拉取</a></li><li><a href="#">3. 初始化数据库</a></li><li><a href="#">4. 启动 capital 项目</a></li><li><a href="#">5. 启动 redpacket 项目</a></li><li><a href="#">6. 启动 order 项目</a></li><li><a href="#">7. 交流</a></li></ul><h1 id="1-依赖工具"><a href="#1-依赖工具" class="headerlink" title="1. 依赖工具"></a>1. 依赖工具</h1><ul><li>Maven</li><li>Git</li><li>JDK</li><li>MySQL</li><li>IntelliJ IDEA</li></ul><h1 id="2-源码拉取"><a href="#2-源码拉取" class="headerlink" title="2. 源码拉取"></a>2. 源码拉取</h1><p>从官方仓库 <a href="https://github.com/changmingxie/tcc-transaction.git" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/changmingxie/tcc-transaction.git</a> <code>Fork</code> 出属于自己的仓库。为什么要 <code>Fork</code> ?既然开始阅读、调试源码,我们可能会写一些注释,有了自己的仓库,可以进行自由的提交。😈</p><p>使用 <code>IntelliJ IDEA</code> 从 <code>Fork</code> 出来的仓库拉取代码。拉取完成后,<code>Maven</code> 会下载依赖包,可能会花费一些时间,耐心等待下。</p><p>本文基于 <code>master-1.2.x</code> 分支。</p><h1 id="3-初始化数据库"><a href="#3-初始化数据库" class="headerlink" title="3. 初始化数据库"></a>3. 初始化数据库</h1><p>官方提供了两个 Demo 项目例子:</p><ul><li>tcc-transaction-dubbo-sample</li><li>tcc-transaction-http-sample</li></ul><p>考虑到不是所有所有同学都使用过 Dubbo 服务化框架,我们以 tcc-transaction-http-sample 项为例子。</p><p>打开 tcc-transaction-http-sample/src/main/dbscripts 目录,有四个 SQL 脚本文件:</p><ul><li><code>create_db_cap.sql</code> :tcc-transaction-http-capital 项目数据库初始化脚本。</li><li><code>create_db_ord.sql</code> :tcc-transaction-http-order 项目数据库初始化脚本。</li><li><code>create_db_red.sql</code> :tcc-transaction-http-redpacket 项目数据库初始化脚本。</li><li><code>create_db_tcc.sql</code> :tcc-transaction <strong>底层</strong>数据库初始化脚本。</li></ul><p>笔者使用 Navicat 进行数据库脚本执行。使用方式为:Navicat 菜单 Connection -> Execute SQL File,选择脚本文件,逐个执行。</p><p>目前数据库脚本未使用 <code>USE</code> 语句选择对应数据库,每个脚本都需要进行添加。以 <code>create_db_cap.sql</code> 举例子:</p><figure class="highlight sql"><table><tr><td class="code"><pre><div class="line"><span class="keyword">CREATE</span> <span class="keyword">DATABASE</span> <span class="string">`tcc_cap`</span> <span class="comment">/*!40100 DEFAULT CHARACTER SET utf8 */</span>;</div><div class="line"><span class="comment">-- 新增 USE</span></div><div class="line"><span class="keyword">USE</span> <span class="string">`tcc_cap`</span>;</div></pre></td></tr></table></figure><h1 id="4-启动-capital-项目"><a href="#4-启动-capital-项目" class="headerlink" title="4. 启动 capital 项目"></a>4. 启动 capital 项目</h1><ol><li>修改项目下 <code>jdbc.properties</code> 文件,<strong>填写成你的数据库地址</strong>。</li><li><p>使用 IDEA 配置 Tomcat 进行启动。这里要注意下:</p> <figure class="highlight xml"><table><tr><td class="code"><pre><div class="line">// appcontext-service-provider.xml</div><div class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"httpServer"</span></span></div><div class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.springframework.remoting.support.SimpleHttpServerFactoryBean"</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"contexts"</span>></span></div><div class="line"> <span class="tag"><<span class="name">util:map</span>></span></div><div class="line"> <span class="tag"><<span class="name">entry</span> <span class="attr">key</span>=<span class="string">"/remoting/CapitalTradeOrderService"</span> <span class="attr">value-ref</span>=<span class="string">"capitalTradeOrderServiceExporter"</span>/></span></div><div class="line"> <span class="tag"><<span class="name">entry</span> <span class="attr">key</span>=<span class="string">"/remoting/CapitalAccountService"</span> <span class="attr">value-ref</span>=<span class="string">"capitalAccountServiceExporter"</span>/></span></div><div class="line"> <span class="tag"></<span class="name">util:map</span>></span></div><div class="line"> <span class="tag"></<span class="name">property</span>></span></div><div class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"port"</span> <span class="attr">value</span>=<span class="string">"8081"</span>/></span></div><div class="line"><span class="tag"></<span class="name">bean</span>></span></div></pre></td></tr></table></figure><ul><li>默认开启 8081 端口提供接口服务。所以配置 Tomcat 的端口不能再使用 8081,避免冲突。例如,笔者使用 18081。</li></ul></li><li><p>访问 <code>http://127.0.0.1:18081/</code>,看到 “hello tcc transacton http sample capital”,代表项目启动完成。<strong><code>18081</code> 为你填写的 Tomcat 端口</strong>。</p></li></ol><h1 id="5-启动-redpacket-项目"><a href="#5-启动-redpacket-项目" class="headerlink" title="5. 启动 redpacket 项目"></a>5. 启动 redpacket 项目</h1><p>同 tcc-transaction-http-capital 项目。</p><h1 id="6-启动-order-项目"><a href="#6-启动-order-项目" class="headerlink" title="6. 启动 order 项目"></a>6. 启动 order 项目</h1><ol><li>修改项目下 <code>jdbc.properties</code> 文件,<strong>填写成你的数据库地址</strong>。</li><li>使用 IDEA 配置 Tomcat 进行启动。</li><li>访问 <code>http://127.0.0.1:8080/</code>,看到 “sample 说明…”,代表项目启动完成。<strong><code>8080</code> 为你填写的 Tomcat 端口</strong>。</li><li>点击 [商品列表链接] -> [购买] -> [支付],如果看到 “支付成功” 或者 “支付失败”,恭喜你🎉,你已经成功搭建完你的调试环境。愉快的开始玩耍把。</li></ol><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>调试环境搭建是阅读源码的第一步,如果你碰到无法搭建成功的情况,请给笔者公众号( <strong>芋道源码</strong> )留言。笔者会给你 1:1 的高级( <strong>搞基</strong> )支持。</p><p>另外这是一个系列文,本系列更新 TCC-Transaction ,下一个系列更新 ByteTCC 。嗨皮不?!</p><p><img src="http://www.iocoder.cn/images/TCC-Transaction/2018_02_01/01.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p>
<blockquote>
<p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>
</summary>
<category term="TCC-Transaction" scheme="http://www.iocoder.cn/categories/TCC-Transaction/"/>
</entry>
<entry>
<title>Elastic-Job-Cloud 源码分析 —— 高可用</title>
<link href="http://www.iocoder.cn/Elastic-Job/cloud-high-availability/"/>
<id>http://www.iocoder.cn/Elastic-Job/cloud-high-availability/</id>
<published>2018-01-14T16:00:00.000Z</published>
<updated>2017-09-16T04:31:16.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. Scheduler 集群</a></li><li><a href="#">3. Scheduler 部署</a></li><li><a href="#">4. Scheduler 故障转移</a></li><li><a href="#">5. Scheduler 数据存储</a><ul><li><a href="#">5.1 RunningService</a></li><li><a href="#">5.2 ProducerManager</a></li><li><a href="#">5.3 TaskScheduler</a></li></ul></li><li><a href="#">6. Mesos Master 崩溃</a></li><li><a href="#">7. Mesos Slave 崩溃</a></li><li><a href="#">8. Scheduler 核对</a></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Cloud 高可用</strong>。</p><p>一个高可用的 Elastic-Job-Cloud 组成如下图:</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2018_01_15/01.png" alt=""></p><ul><li>Mesos Master 集群</li><li>Mesos Slave 集群</li><li>Zookeeper 集群</li><li>Elastic-Job-Cloud-Scheduler 集群</li><li>Elastic-Job-Cloud-Executor 集群</li></ul><p><strong>本文重点分享 Elastic-Job-Cloud-Scheduler 如何实现高可用。</strong></p><p>Mesos Master / Mesos Slave / Zookeeper 高可用,同学们可以自行 Google 解决。Elastic-Job-Cloud-Executor 运行在 Mesos Slave 上,通过 Mesos Slave 集群多节点实现高可用。</p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-Scheduler-集群"><a href="#2-Scheduler-集群" class="headerlink" title="2. Scheduler 集群"></a>2. Scheduler 集群</h1><p>Elastic-Job-Cloud-Scheduler 通过至少两个节点实现集群。<strong>集群中通过主节点选举一个主节点,只有主节点提供服务,从实例处于”待命”状态。当主节点故障时,从节点会选举出新的主节点继续提供服务。</strong>实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Bootstrap</span> </span>{</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(<span class="keyword">final</span> String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>{</div><div class="line"> <span class="comment">// 初始化 注册中心</span></div><div class="line"> CoordinatorRegistryCenter regCenter = <span class="keyword">new</span> ZookeeperRegistryCenter(BootstrapEnvironment.getInstance().getZookeeperConfiguration());</div><div class="line"> regCenter.init();</div><div class="line"> <span class="comment">// 初始化 Zookeeper 选举服务</span></div><div class="line"> <span class="keyword">final</span> ZookeeperElectionService electionService = <span class="keyword">new</span> ZookeeperElectionService(</div><div class="line"> BootstrapEnvironment.getInstance().getFrameworkHostPort(), (CuratorFramework) regCenter.getRawClient(), HANode.ELECTION_NODE, <span class="keyword">new</span> SchedulerElectionCandidate(regCenter));</div><div class="line"> electionService.start();</div><div class="line"> <span class="comment">// 挂起 主进程</span></div><div class="line"> <span class="keyword">final</span> CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</div><div class="line"> latch.await();</div><div class="line"> <span class="comment">// Hook 貌似位置不对?</span></div><div class="line"> Runtime.getRuntime().addShutdownHook(<span class="keyword">new</span> Thread(<span class="string">"shutdown-hook"</span>) {</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> electionService.stop();</div><div class="line"> latch.countDown();</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>Bootstrap,Elastic-Job-Cloud-Scheduler 启动器(仿佛在说废话)。</li><li>CoordinatorRegistryCenter,用于协调分布式服务的注册中心,在<a href="http://www.iocoder.cn/Elastic-Job/reg-center-zookeeper/?">《Elastic-Job-Lite 源码分析 —— 注册中心》</a>有详细解析。</li><li>ZookeeperElectionService,Zookeeper 选举服务,本小节的主角。</li><li>ShutdownHook 关闭进程钩子,代码放置的位置不对,需要放在 <code>CountDownLatch#await()</code> 方法上面。目前实际不影响使用。</li></ul><p>调用 <code>ZookeeperElectionService#start()</code> 方法,初始化 Zookeeper 选举服务以实现 Elastic-Job-Cloud-Scheduler 主节点选举。</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"></div><div class="line"><span class="keyword">private</span> <span class="keyword">final</span> CountDownLatch leaderLatch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</div><div class="line"> </div><div class="line"><span class="keyword">private</span> <span class="keyword">final</span> LeaderSelector leaderSelector;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">ZookeeperElectionService</span><span class="params">(<span class="keyword">final</span> String identity, <span class="keyword">final</span> CuratorFramework client, <span class="keyword">final</span> String electionPath, <span class="keyword">final</span> ElectionCandidate electionCandidate)</span> </span>{</div><div class="line"> <span class="comment">// 创建 LeaderSelector</span></div><div class="line"> leaderSelector = <span class="keyword">new</span> LeaderSelector(client, electionPath, <span class="keyword">new</span> LeaderSelectorListenerAdapter() {</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">takeLeadership</span><span class="params">(<span class="keyword">final</span> CuratorFramework client)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="comment">// ... 省略【暂时】无关代码</span></div><div class="line"> }</div><div class="line"> });</div><div class="line"> <span class="comment">// 设置重复参与选举主节点</span></div><div class="line"> leaderSelector.autoRequeue();</div><div class="line"> <span class="comment">// 设置参与节点的编号</span></div><div class="line"> leaderSelector.setId(identity);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 开始选举.</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</div><div class="line"> log.debug(<span class="string">"Elastic job: {} start to elect leadership"</span>, leaderSelector.getId());</div><div class="line"> leaderSelector.start();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>通过 <a href="https://curator.apache.org/apidocs/org/apache/curator/framework/recipes/leader/LeaderSelector.html" rel="external nofollow noopener noreferrer" target="_blank">Apache Curator LeaderSelector</a> 实现分布式多节点选举。</p><blockquote><p>FROM <a href="https://curator.apache.org/apidocs/org/apache/curator/framework/recipes/leader/LeaderSelector.html" rel="external nofollow noopener noreferrer" target="_blank">https://curator.apache.org/apidocs/org/apache/curator/framework/recipes/leader/LeaderSelector.html</a><br>Abstraction to select a “leader” amongst multiple contenders in a group of JMVs connected to a Zookeeper cluster. If a group of N thread/processes contends for leadership, one will be assigned leader until it releases leadership at which time another one from the group will be chosen.<br>Note that this class uses an underlying InterProcessMutex and as a result leader election is “fair” - each user will become leader in the order originally requested (from ZK’s point of view).</p></blockquote></li><li><p>调用 <code>LeaderSelector#autoRequeue()</code> 方法,设置重复参与选举主节点。默认情况下,自己选举成为主节点后,不再参与下次选举。设置重复参与选举主节点后,每次选举都会参与。在 Elastic-Job-Cloud-Scheduler 里,我们显然要重复参与选举。</p></li><li>调用 <code>LeaderSelector#setId()</code> 方法,设置参与节点的编号。在 Elastic-Job-Cloud-Scheduler 里暂时没有实际用途。编号算法为 <code>BootstrapEnvironment.getInstance().getFrameworkHostPort()</code>,即:<code>HOST:PORT</code>。</li><li>调用 <code>#start()</code> 方法,开始选举。<strong>当自己选举主节点成功</strong>,回调 <code>LeaderSelector#takeLeadership()</code> 方法。</li></ul><p>回调 <code>LeaderSelector#takeLeadership()</code> 方法,Elastic-Job-Cloud-Scheduler <strong>主节点开始领导状态</strong>。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// ZookeeperElectionService.LeaderSelector 内部实现类</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">takeLeadership</span><span class="params">(<span class="keyword">final</span> CuratorFramework client)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> log.info(<span class="string">"Elastic job: {} has leadership"</span>, identity);</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 开始领导状态</span></div><div class="line"> electionCandidate.startLeadership();</div><div class="line"> <span class="comment">// 挂起 进程</span></div><div class="line"> leaderLatch.await();</div><div class="line"> log.warn(<span class="string">"Elastic job: {} lost leadership."</span>, identity);</div><div class="line"> <span class="comment">// 终止领导状态</span></div><div class="line"> electionCandidate.stopLeadership();</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> JobSystemException exception) {</div><div class="line"> <span class="comment">// 异常退出</span></div><div class="line"> log.error(<span class="string">"Elastic job: Starting error"</span>, exception);</div><div class="line"> System.exit(<span class="number">1</span>); </div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>SchedulerElectionCandidate#startLeadership()</code> 方法,开始领导状态。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// SchedulerElectionCandidate.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">SchedulerElectionCandidate</span> <span class="keyword">implements</span> <span class="title">ElectionCandidate</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> CoordinatorRegistryCenter regCenter;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> SchedulerService schedulerService;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">SchedulerElectionCandidate</span><span class="params">(<span class="keyword">final</span> CoordinatorRegistryCenter regCenter)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.regCenter = regCenter;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">startLeadership</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> schedulerService = <span class="keyword">new</span> SchedulerService(regCenter);</div><div class="line"> schedulerService.start();</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Throwable throwable) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(throwable);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// SchedulerService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 以守护进程方式启动.</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</div><div class="line"> facadeService.start();</div><div class="line"> producerManager.startup();</div><div class="line"> statisticManager.startup();</div><div class="line"> cloudJobConfigurationListener.start();</div><div class="line"> taskLaunchScheduledService.startAsync();</div><div class="line"> restfulService.start();</div><div class="line"> schedulerDriver.start();</div><div class="line"> <span class="keyword">if</span> (env.getFrameworkConfiguration().isEnabledReconcile()) {</div><div class="line"> reconcileService.startAsync();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>当 Elastic-Job-Cloud-Scheduler <strong>主节点</strong>调用 <code>SchedulerService#start()</code> 方法后,各种服务初始化完成,特别是和 Mesos Master 的连接,可以愉快的进行作业调度等等服务。</li><li>Elastic-Job-Cloud-Scheduler <strong>从节点</strong>,因为无法回调 <code>LeaderSelector#takeLeadership()</code> 方法,处于”待命”状态。当主节点故障时,从节点会选举出新的主节点,触发 <code>LeaderSelector#takeLeadership()</code> 方法回调,继续提供服务。</li></ul></li><li><p>调用 <code>CountLatch#await()</code> 方法,挂起<strong>主节点</strong> <code>LeaderSelector#takeLeadership()</code> 方法继续向下执行。为什么要进行挂起?如果调用完该方法,<strong>主节点</strong>就会让出<strong>主节点</strong>身份,这样会导致 Elastic-Job-Cloud-Scheduler 集群不断不断不断更新主节点,无法正常提供服务。</p></li><li><p>当 Elastic-Job-Cloud-Scheduler <strong>主节点</strong>关闭时,触发上文代码看到的 ShutdownHook ,关闭服务。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// Bootstrap.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Bootstrap</span> </span>{</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(<span class="keyword">final</span> String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>{</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> Runtime.getRuntime().addShutdownHook(<span class="keyword">new</span> Thread(<span class="string">"shutdown-hook"</span>) {</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 停止选举</span></div><div class="line"> electionService.stop();</div><div class="line"> latch.countDown();</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>ElectionService#stop()</code> 方法,停止选举,从而终止领导状态,关闭各种服务。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// ZookeeperElectionService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">stop</span><span class="params">()</span> </span>{</div><div class="line"> log.info(<span class="string">"Elastic job: stop leadership election"</span>);</div><div class="line"> <span class="comment">// 结束 #takeLeadership() 方法的进程挂起</span></div><div class="line"> leaderLatch.countDown();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 关闭 LeaderSelector</span></div><div class="line"> leaderSelector.close();</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Exception ignored) {</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// SchedulerElectionCandidate.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">stopLeadership</span><span class="params">()</span> </span>{</div><div class="line"> schedulerService.stop();</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// SchedulerService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * 停止运行.</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">stop</span><span class="params">()</span> </span>{</div><div class="line"> restfulService.stop();</div><div class="line"> taskLaunchScheduledService.stopAsync();</div><div class="line"> cloudJobConfigurationListener.stop();</div><div class="line"> statisticManager.shutdown();</div><div class="line"> producerManager.shutdown();</div><div class="line"> schedulerDriver.stop(<span class="keyword">true</span>);</div><div class="line"> facadeService.stop();</div><div class="line"> <span class="keyword">if</span> (env.getFrameworkConfiguration().isEnabledReconcile()) {</div><div class="line"> reconcileService.stopAsync();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li></ul></li><li><p>当发生 JobSystemException 异常时,即调用 <code>SchedulerElectionCandidate#startLeadership()</code> 方法发生异常( <code>SchedulerElectionCandidate#stopLeadership()</code> 实际不会抛出异常 ),调用 <code>System.exit(1)</code> 方法,Elastic-Job-Cloud-Scheduler 主节点<strong>异常崩溃</strong>。</p><ul><li>目前猜测<strong>可能</strong>有种情况会导致异常崩溃。(1)一个 Elastic-Job-Cloud-Scheduler 集群有两个节点 A / B,通过选举 A 成为主节点;(2)突然 Zookeeper 集群崩溃,恢复后,A 节点选举<strong>恰好</strong>又成为主节点,因为未调用 <code>SchedulerElectionCandidate#stopLeadership()</code> 关闭原来的各种服务,导致<strong>再次</strong>调用 <code>SchedulerElectionCandidate#startLeadership()</code> 会发生异常,例如说 RestfulService 服务,需要占用一个端口提供服务,重新初始化,会发生端口冲突抛出异常。笔者尝试模拟,通过一个 Elastic-Job-Cloud-Scheduler + Zookeeper 的情况,能够触发该情况,步骤如下:(1)Zookeeper 启动;(2)Elastic-Job-Cloud-Scheduler 启动,选举成为主节点,正常初始化;(3)重启 Zookeeper;(4)Elastic-Job-Cloud-Scheduler 再次选举成为主节点,因为 RestfulService 端口冲突异常初始化崩溃。<strong>如果真出现这种情况怎么办呢?</strong>在「3. Scheduler 部署」揭晓答案。</li></ul></li></ul><p>Elastic-Job-Lite 在主节点选举实现方式上略有不同,有兴趣的同学可以看下<a href="http://www.iocoder.cn/Elastic-Job/election/?self">《Elastic-Job-Lite 源码分析 —— 主节点选举》</a>的实现。</p><h1 id="3-Scheduler-部署"><a href="#3-Scheduler-部署" class="headerlink" title="3. Scheduler 部署"></a>3. Scheduler 部署</h1><p>比较容易想到的一种方式,选择多台主机部署 Elastic-Job-Cloud-Executor 多个节点。</p><p>But…… 我们要想下,Elastic-Job-Cloud-Executor 运行在 Mesos 之上,可以使用上 Mesos 的资源调度和部署服务。引入 Mesos 上著名的框架 <a href="https://mesosphere.github.io/marathon/" rel="external nofollow noopener noreferrer" target="_blank">Marathon</a>。它可以带来<strong>所有后台进程( 例如,Elastic-Job-Cloud-Executor )能够运行在任意机器上,Marathon 会在后台已有实例失败时,自动启动新实例</strong>的好处。是不是很赞 +1024 ?!</p><blockquote><p>FROM <a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》</a> P47<br>Mesos 集群里的常见方案是在 Marathon 上运行集群的 Mesos 框架。但是 Marathon 本身就是一种 Mesos 的框架!那么在 Marathon 上运行 Mesos 框架意味着什么呢?不用考虑如何将每种框架的调度器部署到特定的主机上并且处理这些主机的故障,Marathon 能够确保框架的调度器总是在集群里的某处运行着。这样大幅简化了在高可用配置里部署新框架的复杂度。</p></blockquote><p>嗯…… 当然,Marathon 我们也要做高可用。</p><p>😈 Marathon 原来中文是马拉松。哈哈哈,很适合的名字。</p><h1 id="4-Scheduler-故障转移"><a href="#4-Scheduler-故障转移" class="headerlink" title="4. Scheduler 故障转移"></a>4. Scheduler 故障转移</h1><p>当原有 Elastic-Job-Cloud-Scheduler <strong>主节点</strong>崩溃时,<strong>从节点</strong>重新进行主节点选举,完成故障转移。那么此时会有一个问题,新<strong>主节点</strong>如何接管已经在执行中的 Elastic-Job-Cloud-Executer 们呢?</p><p>第一种方案,关闭原有的所有 Elastic-Job-Cloud-Executor 们,然后重新调度启动。显然,这个方式太过暴力。如果有些作业任务运行时间较长,直接中断不是很友好。再比如,Elastic-Job-Cloud-Scheduler 节点需要进行升级,也关闭 Elastic-Job-Cloud-Executor,也不合理,和使用高可用性集群操作系统的初衷是背离的。<strong>该方案,不推荐</strong>。</p><p>第二种方案,重用<strong>原主节点</strong>的 Mesos <strong>FrameworkID</strong>。原理如下:</p><blockquote><p>FROM <a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》</a> P72<br>在 Mesos 里,调度器由其 FrameworkID、FrameworkInfo 里的可选值唯一确定。FrameworkID 必须由 Mesos 分配,从而确保对于每个框架来说该值是唯一确定的。现在,需要在分配 FrameworkID 时存储该值,这样未来的主实例才可以重用该值。 </p></blockquote><p>在 Elastic-Job-Cloud-Scheduler 使用注册中心( Zookeeper ) 的<strong>持久</strong>数据节点 <code>/${NAMESPACE}/ha/framework_id</code> 存储 FrameworkID,存储值为 <code>${FRAMEWORK_ID}</code>。使用 zkClient 查看如下:</p><blockquote><p>[zk: localhost:2181(CONNECTED) 1] get /elastic-job-cloud/ha/framework_id<br>d31e7faa-aa72-4d0a-8941-512984d5af49-0001</p></blockquote><p>调用 <code>SchedulerService#getSchedulerDriver()</code> 方法,初始化 Mesos Scheduler Driver 时,从 Zookeeper 获取是否已经存在 FrameworkID。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// SchedulerService.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> SchedulerDriver <span class="title">getSchedulerDriver</span><span class="params">(<span class="keyword">final</span> TaskScheduler taskScheduler, <span class="keyword">final</span> JobEventBus jobEventBus, <span class="keyword">final</span> FrameworkIDService frameworkIDService)</span> </span>{</div><div class="line"> <span class="comment">// 获取 FrameworkID</span></div><div class="line"> Optional<String> frameworkIDOptional = frameworkIDService.fetch();</div><div class="line"> Protos.FrameworkInfo.Builder builder = Protos.FrameworkInfo.newBuilder();</div><div class="line"> <span class="comment">// 如果存在,设置 FrameworkID</span></div><div class="line"> <span class="keyword">if</span> (frameworkIDOptional.isPresent()) {</div><div class="line"> builder.setId(Protos.FrameworkID.newBuilder().setValue(frameworkIDOptional.get()).build());</div><div class="line"> }</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> Protos.FrameworkInfo frameworkInfo = builder.setUser(mesosConfig.getUser()).setName(frameworkName)</div><div class="line"> .setHostname(mesosConfig.getHostname())</div><div class="line"> .setFailoverTimeout(FRAMEWORK_FAILOVER_TIMEOUT_SECONDS)</div><div class="line"> .setWebuiUrl(WEB_UI_PROTOCOL + env.getFrameworkHostPort()).setCheckpoint(<span class="keyword">true</span>).build();</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>FrameworkIDService#fetch()</code> 方法,从注册中心获取 FrameworkID 。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> Optional<String> <span class="title">fetch</span><span class="params">()</span> </span>{</div><div class="line"> String frameworkId = regCenter.getDirectly(HANode.FRAMEWORK_ID_NODE);</div><div class="line"> <span class="keyword">return</span> Strings.isNullOrEmpty(frameworkId) ? Optional.<String>absent() : Optional.of(frameworkId);</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>Protos.FrameworkInfo.Builder#setId(...)</code> 方法,当 FrameworkID 存在时,设置 FrameworkID。</p></li><li>调用 <code>Protos.FrameworkInfo.Builder#setFailoverTimeout(...)</code> 方法,设置 Scheduler 最大故障转移时间,即 FrameworkID 过期时间。Elastic-Job-Cloud-Scheduler 默认设置一周。</li></ul><p>当 Elastic-Job-Cloud-Scheduler 集群第一次初始化,上面的逻辑显然获取不到 FrameworkID,在向 Mesos Master 初始化成功后,回调 <code>SchedulerEngine#registered(...)</code> 方法进行保存,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// SchedulerEngine.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">SchedulerEngine</span> <span class="keyword">implements</span> <span class="title">Scheduler</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registered</span><span class="params">(<span class="keyword">final</span> SchedulerDriver schedulerDriver, <span class="keyword">final</span> Protos.FrameworkID frameworkID, <span class="keyword">final</span> Protos.MasterInfo masterInfo)</span> </span>{</div><div class="line"> log.info(<span class="string">"call registered"</span>);</div><div class="line"> <span class="comment">// 保存FrameworkID</span></div><div class="line"> frameworkIDService.save(frameworkID.getValue());</div><div class="line"> <span class="comment">// 过期 TaskScheduler Lease</span></div><div class="line"> taskScheduler.expireAllLeases();</div><div class="line"> <span class="comment">// 注册 Mesos Master 信息</span></div><div class="line"> MesosStateService.register(masterInfo.getHostname(), masterInfo.getPort());</div><div class="line"> }</div><div class="line"> </div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// FrameworkIDService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">save</span><span class="params">(<span class="keyword">final</span> String id)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (!regCenter.isExisted(HANode.FRAMEWORK_ID_NODE)) { <span class="comment">// 不存在才保存</span></div><div class="line"> regCenter.persist(HANode.FRAMEWORK_ID_NODE, id);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><h1 id="5-Scheduler-数据存储"><a href="#5-Scheduler-数据存储" class="headerlink" title="5. Scheduler 数据存储"></a>5. Scheduler 数据存储</h1><p>新的 Elastic-Job-Cloud-Scheduler <strong>主节点</strong>在故障转移,不仅仅接管 Elastic-Job-Cloud-Executor,<strong>还需要接管数据存储</strong>。</p><p>Elastic-Job-Cloud-Executor 使用注册中心( Zookeeper )存储数据。数据存储分成两部分:</p><ul><li>config,云作业应用配置、云作业配置。</li><li>state,作业状态信息。</li></ul><p>整体如下图:</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2018_01_15/02.png" alt=""></p><p>Elastic-Job-Cloud-Scheduler <strong>各个服务</strong>根据数据存储启动初始化。下面来看看依赖数据存储进行初始化的服务代码实现。</p><h2 id="5-1-RunningService"><a href="#5-1-RunningService" class="headerlink" title="5.1 RunningService"></a>5.1 RunningService</h2><p>RunningService,任务运行时服务。调用 <code>RunningService#start()</code> 方法,启动任务运行队列。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">RunningService</span> </span>{</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 运行中作业映射</span></div><div class="line"><span class="comment"> * key:作业名称</span></div><div class="line"><span class="comment"> * value:任务运行时上下文集合</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Getter</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ConcurrentHashMap<String, Set<TaskContext>> RUNNING_TASKS = <span class="keyword">new</span> ConcurrentHashMap<>(TASK_INITIAL_SIZE);</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</div><div class="line"> clear();</div><div class="line"> List<String> jobKeys = regCenter.getChildrenKeys(RunningNode.ROOT);</div><div class="line"> <span class="keyword">for</span> (String each : jobKeys) {</div><div class="line"> <span class="comment">// 从运行中队列移除不存在配置的作业任务</span></div><div class="line"> <span class="keyword">if</span> (!configurationService.load(each).isPresent()) {</div><div class="line"> remove(each);</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 添加 运行中作业映射</span></div><div class="line"> RUNNING_TASKS.put(each, Sets.newCopyOnWriteArraySet(Lists.transform(regCenter.getChildrenKeys(RunningNode.getRunningJobNodePath(each)), <span class="keyword">new</span> Function<String, TaskContext>() {</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> TaskContext <span class="title">apply</span><span class="params">(<span class="keyword">final</span> String input)</span> </span>{</div><div class="line"> <span class="keyword">return</span> TaskContext.from(regCenter.get(RunningNode.getRunningTaskNodePath(TaskContext.MetaInfo.from(input).toString())));</div><div class="line"> }</div><div class="line"> })));</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>因为运行中作业映射( <code>RUNNING_TASKS</code> )使用的频次很多,Elastic-Job-Cloud-Scheduler 缓存在内存中。每次初始化时,使用从数据存储<strong>运行中作业队列</strong>加载到内存。</li><li><p>这里我们在看下<strong>运行中作业队列</strong>的添加( <code>#add()</code> )方法,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (!configurationService.load(taskContext.getMetaInfo().getJobName()).isPresent()) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 添加到运行中的任务集合</span></div><div class="line"> getRunningTasks(taskContext.getMetaInfo().getJobName()).add(taskContext);</div><div class="line"> <span class="comment">// 判断是否为常驻任务</span></div><div class="line"> <span class="keyword">if</span> (!isDaemon(taskContext.getMetaInfo().getJobName())) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 添加到运行中队列</span></div><div class="line"> String runningTaskNodePath = RunningNode.getRunningTaskNodePath(taskContext.getMetaInfo().toString());</div><div class="line"> <span class="keyword">if</span> (!regCenter.isExisted(runningTaskNodePath)) {</div><div class="line"> regCenter.persist(runningTaskNodePath, taskContext.getId());</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>运行中作业队列只存储常驻作业的任务</strong>。所以<strong>瞬时</strong>作业,在故障转移时,可能存在相同作业相同分片任务<strong>同时</strong>调度执行。举个栗子🌰,Elastic-Job-Cloud-Scheduler 集群有两个节点 A( 主节点 ) / B( 从节点 ),(1)A 节点每 5 分钟调度一次瞬时作业任务 T ,T 每次执行消耗时间实际超过 5 分钟( 先不要考虑是否合理 )。(2)A 节点崩溃,B 节点成为主节点,5 分钟后调度 T 作业,因为<strong>运行中作业队列只存储常驻作业的任务</strong>,恢复后的 <code>RUNNING_TASKS</code> 不存在该作业任务,因此可以调度 T 作业,实际 T 作业正在 Elastic-Job-Cloud-Executor 执行中。</li></ul></li></ul><h2 id="5-2-ProducerManager"><a href="#5-2-ProducerManager" class="headerlink" title="5.2 ProducerManager"></a>5.2 ProducerManager</h2><p>ProducerManager,发布任务作业调度管理器。调用 <code>ProducerManager#startup()</code> 方法,启动作业调度器。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ProducerManager</span> </span>{</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">startup</span><span class="params">()</span> </span>{</div><div class="line"> log.info(<span class="string">"Start producer manager"</span>);</div><div class="line"> <span class="comment">// 发布瞬时作业任务的调度器</span></div><div class="line"> transientProducerScheduler.start();</div><div class="line"> <span class="comment">// 初始化调度作业</span></div><div class="line"> <span class="keyword">for</span> (CloudJobConfiguration each : configService.loadAll()) {</div><div class="line"> schedule(each);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>ConfigService#loadAll()</code> 方法,从<strong>数据存储</strong>读取所有作业配置。</li><li>调用 <code>#schedule()</code> 方法,初始化调度作业。<ul><li><strong>瞬时</strong>作业,在 Elastic-Job-Cloud-Scheduler 计时调度,类似每 XX 秒 / 分 / 时 / 天之类的作业需要重新计时,这个请注意。</li><li><strong>常驻</strong>作业,在 Elastic-Job-Cloud-Executor 计时调度,暂无影响。</li><li>在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(一)》「3. Producer 发布任务」</a>有详细解析。</li></ul></li></ul><h2 id="5-3-TaskScheduler"><a href="#5-3-TaskScheduler" class="headerlink" title="5.3 TaskScheduler"></a>5.3 TaskScheduler</h2><p>TaskScheduler,Fenzo 作业调度器,根据 Mesos Offer 和作业任务的优化分配。因为其分配是依赖当前实际 Mesos Offer 和 作业任务运行的情况,猜测<strong>可能</strong>对优化分配有影响,但不影响正确性。笔者对 TaskScheduler 了解不是很深入,仅仅作为猜测。</p><p>在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(一)》「4.1」「4.2」「4.3」</a>有和 TaskScheduler 相关的内容解析。</p><h1 id="6-Mesos-Master-崩溃"><a href="#6-Mesos-Master-崩溃" class="headerlink" title="6. Mesos Master 崩溃"></a>6. Mesos Master 崩溃</h1><p>Mesos Master 集群,Mesos Master 主节点崩溃后,Mesos Master 集群重新选举后,Scheduler、Mesos Slave <strong>从 Zookeeper 获取到最新的 Mesos Master 主节点重新进行注册</strong>,不影响 Scheduler 、Mesos Slave 、任务执行。</p><p>调用 <code>SchedulerService#getSchedulerDriver(...)</code> 方法,设置 SchedulerDriver 从 Mesos Zookeeper Address 读取当前 Mesos Master 地址,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// SchedulerService.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> SchedulerDriver <span class="title">getSchedulerDriver</span><span class="params">(<span class="keyword">final</span> TaskScheduler taskScheduler, <span class="keyword">final</span> JobEventBus jobEventBus, <span class="keyword">final</span> FrameworkIDService frameworkIDService)</span> </span>{</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> MesosConfiguration mesosConfig = env.getMesosConfiguration();</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> MesosSchedulerDriver(<span class="keyword">new</span> SchedulerEngine(taskScheduler, facadeService, jobEventBus, frameworkIDService, statisticManager), frameworkInfo, mesosConfig.getUrl() <span class="comment">// Mesos Master URL</span></div><div class="line"> );</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// MesosSchedulerDriver.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="title">MesosSchedulerDriver</span><span class="params">(Scheduler scheduler,</span></span></div><div class="line"><span class="function"><span class="params"> FrameworkInfo framework,</span></span></div><div class="line"><span class="function"><span class="params"> String master)</span> </span>{</div><div class="line"> <span class="comment">// ... 省略无关代码 </span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>MesosSchedulerDriver 构造方法第三个参数 <code>master</code>,代表 Mesos 使用的 Zookeeper 地址,例如:<code>zk://127.0.0.1:2181/mesos</code>。生产环境请配置多 Zookeeper 节点,例如:<code>zk://host1:port1,host2:port2,.../path</code>。</li><li><p>使用 zkClient 查看如下:</p> <figure class="highlight shell"><table><tr><td class="code"><pre><div class="line">[zk: localhost:2181(CONNECTED) 10] ls /mesos</div><div class="line">[log_replicas, json.info_0000000017]</div><div class="line">[zk: localhost:2181(CONNECTED) 11] get /mesos/json.info_0000000017</div><div class="line">{"address":{"hostname":"localhost","ip":"127.0.0.1","port":5050},"hostname":"localhost","id":"685fe32d-e30c-4df7-b891-3d96b06fee88","ip":16777343,"pid":"[email protected]:5050","port":5050,"version":"1.4.0"}</div></pre></td></tr></table></figure></li></ul><p>Elastic-Job-Cloud-Scheduler 注册上、重新注册上、断开 Mesos Master 实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">SchedulerEngine</span> <span class="keyword">implements</span> <span class="title">Scheduler</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registered</span><span class="params">(<span class="keyword">final</span> SchedulerDriver schedulerDriver, <span class="keyword">final</span> Protos.FrameworkID frameworkID, <span class="keyword">final</span> Protos.MasterInfo masterInfo)</span> </span>{</div><div class="line"> log.info(<span class="string">"call registered"</span>);</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="comment">// 注册 Mesos Master 信息</span></div><div class="line"> MesosStateService.register(masterInfo.getHostname(), masterInfo.getPort());</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">reregistered</span><span class="params">(<span class="keyword">final</span> SchedulerDriver schedulerDriver, <span class="keyword">final</span> Protos.MasterInfo masterInfo)</span> </span>{</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="comment">// 注册 Mesos Master 信息</span></div><div class="line"> MesosStateService.register(masterInfo.getHostname(), masterInfo.getPort());</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">disconnected</span><span class="params">(<span class="keyword">final</span> SchedulerDriver schedulerDriver)</span> </span>{</div><div class="line"> log.warn(<span class="string">"call disconnected"</span>);</div><div class="line"> MesosStateService.deregister();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>MesosStateService,Mesos状态服务,提供调用 Mesos Master API 服务,例如获取所有执行器。</li><li><p>调用 <code>MesosStateService#register(...)</code> 方法,注册 Mesos Master 信息,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MesosStateService</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> String stateUrl;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(<span class="keyword">final</span> String hostName, <span class="keyword">final</span> <span class="keyword">int</span> port)</span> </span>{</div><div class="line"> stateUrl = String.format(<span class="string">"http://%s:%d/state"</span>, hostName, port);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>MesosStateService#deregister(...)</code> 方法,注销 Mesos Master 信息,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">deregister</span><span class="params">()</span> </span>{</div><div class="line"> stateUrl = <span class="keyword">null</span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><p><a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》P110 如何处理 master 的故障</a>,有兴趣的同学也可以<strong>仔细</strong>看看。</p><h1 id="7-Mesos-Slave-崩溃"><a href="#7-Mesos-Slave-崩溃" class="headerlink" title="7. Mesos Slave 崩溃"></a>7. Mesos Slave 崩溃</h1><p>在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-failover/?self">《Elastic-Job-Cloud 源码分析 —— 作业失效转移》</a>中,搜索关键字 <strong>“TASK_LOST”</strong>,有 Mesos Slave 崩溃后,对 Elastic-Job-Cloud-Scheduler 和 Elastic-Job-Cloud-Executor 的影响。</p><p><a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》P109 如何处理 slave 的故障</a>,有兴趣的同学也可以<strong>仔细</strong>看看。</p><h1 id="8-Scheduler-核对"><a href="#8-Scheduler-核对" class="headerlink" title="8. Scheduler 核对"></a>8. Scheduler 核对</h1><blockquote><p>FROM <a href="http://mesos.apache.org/documentation/latest/reconciliation/" rel="external nofollow noopener noreferrer" target="_blank">http://mesos.apache.org/documentation/latest/reconciliation/</a><br>Messages between framework schedulers and the Mesos master may be dropped due to failures and network partitions. This may cause a framework scheduler and the master to have <strong>different views of the current state of the cluster</strong>. For example, consider a launch task request sent by a framework. There are many ways that failures can prevent the task launch operation from succeeding, such as:</p><ul><li>Framework fails after persisting its intent to launch the task, but before the launch task message was sent. </li><li>Master fails before receiving the message. </li><li>Master fails after receiving the message but before sending it to the agent. </li></ul></blockquote><p>通过<strong>核对</strong>特性解决这个问题。核对是协调器如何和 Mesos Master 一起检查调度器所认为的集群状态是否和 Mesos Master 所认为的集群状态完成匹配。</p><p>调用 <code>SchedulerDriver#reconcileTasks(...)</code> 方法,查询任务状态。代码接口如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">SchedulerDriver</span> </span>{</div><div class="line"> <span class="function">Status <span class="title">reconcileTasks</span><span class="params">(Collection<TaskStatus> statuses)</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>只能</strong>查询<strong>非终止状态( non-terminal )</strong>的任务。核对的主要原因,确认任务是否还在运行,或者已经进入了中断状态。<ul><li>terminal:TASK_ERROR、TASK_FAILED、TASK_FINISHED、TASK_KILLED</li><li>non-terminal:TASK_DROPPED、TASK_GONE、TASK_GONE_BY_OPERATOR、TASK_KILLING、TASK_LOST、TASK_RUNNING、TASK_STAGING、TASK_STARTING、TASK_UNREACHABLE、TASK_UNKNOWN</li></ul></li><li>当 <code>statuses</code> 非空时,<strong>显示</strong>查询,通过回调 <code>Scheduler#statusUpdate(...)</code> 方法异步返回<strong>指定</strong>的任务的状态。</li><li>当 <code>statuses</code> 为空时,<strong>隐式</strong>查询,通过回调 <code>Scheduler#statusUpdate(...)</code> 方法异步返回<strong>全部</strong>的任务的状态。</li></ul><p>ReconcileService,核对 Mesos 与 Scheduler 之间的任务状态。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReconcileService</span> <span class="keyword">extends</span> <span class="title">AbstractScheduledService</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ReentrantLock lock = <span class="keyword">new</span> ReentrantLock();</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">runOneIteration</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> lock.lock();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> explicitReconcile();</div><div class="line"> implicitReconcile();</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> lock.unlock();</div><div class="line"> }</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">protected</span> Scheduler <span class="title">scheduler</span><span class="params">()</span> </span>{</div><div class="line"> FrameworkConfiguration configuration = BootstrapEnvironment.getInstance().getFrameworkConfiguration();</div><div class="line"> <span class="keyword">return</span> Scheduler.newFixedDelaySchedule(configuration.getReconcileIntervalMinutes(), configuration.getReconcileIntervalMinutes(), TimeUnit.MINUTES);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>通过配置 <code>FrameworkConfiguration#reconcileIntervalMinutes</code> 设置,每隔多少分钟执行一次核对。若配置时间大于 0 才开启任务状态核对功能。</li><li><p>调用 <code>#explicitReconcile()</code> 方法,查询运行中的任务。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">explicitReconcile</span><span class="params">()</span> </span>{</div><div class="line"> lock.lock();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 获取运行中的作业任务上下文集合</span></div><div class="line"> Set<TaskContext> runningTask = <span class="keyword">new</span> HashSet<>();</div><div class="line"> <span class="keyword">for</span> (Set<TaskContext> each : facadeService.getAllRunningTasks().values()) {</div><div class="line"> runningTask.addAll(each);</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (runningTask.isEmpty()) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> log.info(<span class="string">"Requesting {} tasks reconciliation with the Mesos master"</span>, runningTask.size());</div><div class="line"> <span class="comment">// 查询指定任务</span></div><div class="line"> schedulerDriver.reconcileTasks(Collections2.transform(runningTask, <span class="keyword">new</span> Function<TaskContext, Protos.TaskStatus>() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="keyword">public</span> Protos.<span class="function">TaskStatus <span class="title">apply</span><span class="params">(<span class="keyword">final</span> TaskContext input)</span> </span>{</div><div class="line"> <span class="keyword">return</span> Protos.TaskStatus.newBuilder()</div><div class="line"> .setTaskId(Protos.TaskID.newBuilder().setValue(input.getId()).build())</div><div class="line"> .setSlaveId(Protos.SlaveID.newBuilder().setValue(input.getSlaveId()).build())</div><div class="line"> .setState(Protos.TaskState.TASK_RUNNING)</div><div class="line"> .build();</div><div class="line"> }</div><div class="line"> }));</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> lock.unlock();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>#implicitReconcile()</code> 方法,查询所有任务。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">implicitReconcile</span><span class="params">()</span> </span>{</div><div class="line"> lock.lock();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 查询全部任务</span></div><div class="line"> schedulerDriver.reconcileTasks(Collections.<Protos.TaskStatus>emptyList());</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> lock.unlock();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>为什么这里要使用 ReentrantLock 锁呢?Elastic-Job-Cloud-Scheduler 提供 CloudOperationRestfulApi,支持使用 HTTP Restful API 主动触发 <code>#explicitReconcile()</code> 和 <code>#implicitReconcile()</code> 方法,<strong>通过锁避免并发核对</strong>。对 CloudOperationRestfulApi 有兴趣的同学,直接点击<a href="https://github.com/dangdangdotcom/elastic-job/blob/a52d4062bf1f1d729fa4dbf2d1225e0d97778cb9/elastic-job-cloud/elastic-job-cloud-scheduler/src/main/java/com/dangdang/ddframe/job/cloud/scheduler/restful/CloudOperationRestfulApi.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>查看实现。</p></li><li>虽然 <code>#implicitReconcile()</code> 方法,能查询到所有 Mesos 任务状的态,但是性能较差,而 <code>#explicitReconcile()</code> 方法显式查询运行中的 Mesos 任务的状态,性能更好,所以先进行调用。</li><li><p>优化点(目前暂未实现):Elastic-Job-Cloud-Scheduler 注册到 Mesos 和 重注册到 Mesos,都执行一次核对。</p><blockquote><p>FROM <a href="http://www.iocoder.cn/Elastic-Job/reconcile/?self">《Elastic-Job-Lite 源码分析 —— 自诊断修复》</a><br>This reconciliation algorithm must be run after each (re-)registration.</p></blockquote></li></ul><p>其他 Scheduler 核对资料,有兴趣的同学可以看看:</p><ul><li><a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》P76 添加核对 、P111 故障转移期间的核对</a></li><li><a href="http://mesos.apache.org/documentation/latest/reconciliation/" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 官方文档 —— reconciliation》</a></li></ul><p>Elastic-Job-Lite 也会存在作业节点 和 Zookeeper 数据不一致的情况,有兴趣的同学可以看下<a href="http://www.iocoder.cn/Elastic-Job/reconcile/?self">《Elastic-Job-Lite 源码分析 —— 自诊断修复》</a>的实现。</p><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>给英文和我一样半斤八两的同学一本葵花宝典+辟邪剑谱:</p><ul><li><a href="https://mesos-cn.gitbooks.io/mesos-cn/content/" rel="external nofollow noopener noreferrer" target="_blank">《Mesos中文手册》</a>。</li><li><a href="http://www.jianshu.com/p/726e28ea488a" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 容错、故障》</a></li></ul><p><img src="http://www.iocoder.cn/images/Elastic-Job/2018_01_15/03.png" alt=""></p><p>整个 Elastic-Job-Cloud 完结,撒花!</p><p>收获蛮多的,学习的第一套基于云原生( CloudNative )实现的中间件,期待有基于云原生的服务化中间件。</p><p>一开始因为 Elastic-Job-Cloud 基于 Mesos 实现,内心还是有点恐惧感,后面硬啃 + 搭配<a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》</a>,比预想的时间快了一半完成这个系列。在这里强烈推荐这本书。另外,等时间相对空,会研究下另外一个沪江开源的基于 Mesos 实现的分布式调度系统 <a href="https://github.com/HujiangTechnology/Juice" rel="external nofollow noopener noreferrer" target="_blank">Juice</a>。不是很确定会不会出源码解析的文章,尽量输出噶。</p><p>后面会继续更新源码解析系列,下一个系列应该是<a href="https://github.com/changmingxie/tcc-transaction" rel="external nofollow noopener noreferrer" target="_blank">《tcc-transaction 源码解析》</a>。在选择要研究的 tcc 中间件还是蛮纠结的,哈哈,这里听从 <a href="http://www.54tianzhisheng.cn/" rel="external nofollow noopener noreferrer" target="_blank">zhisheng</a> 的建议。如果不好,我保证会打死你的。</p><p>希望坚持不懈的分享源码解析会有更多的同行者阅读。确实,源码解析的受众略小。</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2018_01_15/04.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. Scheduler 集群</a></li>
<li><a hre
</summary>
<category term="Elastic-Job-Cloud" scheme="http://www.iocoder.cn/categories/Elastic-Job-Cloud/"/>
</entry>
<entry>
<title>Elastic-Job-Cloud 源码分析 —— 作业失效转移</title>
<link href="http://www.iocoder.cn/Elastic-Job/cloud-job-failover/"/>
<id>http://www.iocoder.cn/Elastic-Job/cloud-job-failover/</id>
<published>2018-01-09T16:00:00.000Z</published>
<updated>2017-09-16T04:56:06.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. 记录作业失效转移</a></li><li><a href="#">3. 提交失效转移作业</a></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Cloud 作业失效转移</strong>。对应到 Elastic-Job-Lite 源码解析文章为<a href="http://www.iocoder.cn/Elastic-Job/job-failover/?self">《Elastic-Job-Lite 作业作业失效转移》</a>。</p><p>你需要对<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(一)》</a>有一定的了解。</p><p>当作业任务在 Elastic-Job-Cloud-Executor 异常崩溃时,该任务在下次调度之前不会被重新执行。开启失效转移功能后,该作业任务会立即被 Elastic-Job-Cloud-Scheduler 重新调度,提交 Elastic-Job-Cloud-Executor <strong>立即</strong>执行。</p><p>在 Elastic-Job-Cloud 里,我们了解到作业分成<strong>瞬时</strong>作业和<strong>常驻</strong>作业。实际上面失效转移的定义暂时只适用于<strong>瞬时</strong>作业。对于<strong>常驻</strong>作业,作业任务异常崩溃后,无论你是否开启失效转移功能,Elastic-Job-Cloud-Scheduler 会立刻提交 Elastic-Job-Cloud-Executor <strong>重新调度</strong>执行。</p><p><strong>为什么此处使用的是“重新调度”,而不是“立即执行”呢</strong>?目前版本 Elasitc-Job-Cloud 暂时不支持<strong>常驻</strong>作业的失效转移,当作业任务异常崩溃,本次执行<strong>不会重新执行</strong>,但是为了作业任务后续能够调度执行,所以再次提交 Elastic-Job-Cloud-Scheduler。</p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><p>OK,下面我们来看看作业失效转移的实现方式和作业任务异常崩溃的多重场景。</p><h1 id="2-记录作业失效转移"><a href="#2-记录作业失效转移" class="headerlink" title="2. 记录作业失效转移"></a>2. 记录作业失效转移</h1><p>当作业任务异常崩溃时,Elastic-Job-Cloud-Scheduler 通过 Mesos 任务状态变更接口( <code>#statusUpdate()</code> )实现对任务状态的监听处理,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">SchedulerEngine</span> <span class="keyword">implements</span> <span class="title">Scheduler</span> </span>{</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">statusUpdate</span><span class="params">(<span class="keyword">final</span> SchedulerDriver schedulerDriver, <span class="keyword">final</span> Protos.TaskStatus taskStatus)</span> </span>{</div><div class="line"> String taskId = taskStatus.getTaskId().getValue();</div><div class="line"> TaskContext taskContext = TaskContext.from(taskId);</div><div class="line"> String jobName = taskContext.getMetaInfo().getJobName();</div><div class="line"> log.trace(<span class="string">"call statusUpdate task state is: {}, task id is: {}"</span>, taskStatus.getState(), taskId);</div><div class="line"> jobEventBus.post(<span class="keyword">new</span> JobStatusTraceEvent(jobName, taskContext.getId(), taskContext.getSlaveId(), Source.CLOUD_SCHEDULER, </div><div class="line"> taskContext.getType(), String.valueOf(taskContext.getMetaInfo().getShardingItems()), State.valueOf(taskStatus.getState().name()), taskStatus.getMessage()));</div><div class="line"> <span class="keyword">switch</span> (taskStatus.getState()) {</div><div class="line"> <span class="keyword">case</span> TASK_RUNNING:</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> TASK_FINISHED:</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> TASK_KILLED:</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> TASK_LOST:</div><div class="line"> <span class="keyword">case</span> TASK_DROPPED:</div><div class="line"> <span class="keyword">case</span> TASK_GONE:</div><div class="line"> <span class="keyword">case</span> TASK_GONE_BY_OPERATOR:</div><div class="line"> <span class="keyword">case</span> TASK_FAILED: <span class="comment">// 执行作业任务被错误终止</span></div><div class="line"> <span class="keyword">case</span> TASK_ERROR: <span class="comment">// 任务错误</span></div><div class="line"> log.warn(<span class="string">"task id is: {}, status is: {}, message is: {}, source is: {}"</span>, taskId, taskStatus.getState(), taskStatus.getMessage(), taskStatus.getSource());</div><div class="line"> <span class="comment">// 将任务从运行时队列删除</span></div><div class="line"> facadeService.removeRunning(taskContext);</div><div class="line"> <span class="comment">// 记录失效转移队列</span></div><div class="line"> facadeService.recordFailoverTask(taskContext);</div><div class="line"> <span class="comment">// 通知 TaskScheduler 任务不分配在对应主机上</span></div><div class="line"> unAssignTask(taskId);</div><div class="line"> <span class="comment">// 统计</span></div><div class="line"> statisticManager.taskRunFailed();</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> TASK_UNKNOWN:</div><div class="line"> <span class="keyword">case</span> TASK_UNREACHABLE:</div><div class="line"> log.error(<span class="string">"task id is: {}, status is: {}, message is: {}, source is: {}"</span>, taskId, taskStatus.getState(), taskStatus.getMessage(), taskStatus.getSource());</div><div class="line"> statisticManager.taskRunFailed();</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">default</span>:</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>一共有 6 种状态判定为作业任务崩溃,我们来一个一个看看:</p><ul><li><p>TASK_DROPPED / TASK_GONE / TASK_GONE_BY_OPERATOR</p><p> 这三个状态,笔者暂时不太了解,这里先引用一些资料,欢迎有了解的同学指教一下。 </p><blockquote><p>FROM <a href="http://mesos.apache.org/api/latest/java/org/apache/mesos/Protos.TaskState.html" rel="external nofollow noopener noreferrer" target="_blank">http://mesos.apache.org/api/latest/java/org/apache/mesos/Protos.TaskState.html</a><br><strong>TASK_DROPPED</strong>:The task failed to launch because of a transient error.<br><strong>TASK_GONE</strong>:The task is no longer running.<br><strong>TASK_GONE_BY_OPERATOR</strong>:The task was running on an agent that the master cannot contact; the operator has asserted that the agent has been shutdown, but this has not been directly confirmed by the master. </p><p>FROM <a href="http://mesos.apache.org/blog/mesos-1-1-0-released/" rel="external nofollow noopener noreferrer" target="_blank">http://mesos.apache.org/blog/mesos-1-1-0-released/</a><br>[MESOS-5344] - Experimental support for partition-aware Mesos frameworks. In previous Mesos releases, when an agent is partitioned from the master and then reregisters with the cluster, all tasks running on the agent are terminated and the agent is shutdown. In Mesos 1.1, partitioned agents will no longer be shutdown when they reregister with the master. By default, tasks running on such agents will still be killed (for backward compatibility); however, frameworks can opt-in to the new PARTITION_AWARE capability. If they do this, their tasks will not be killed when a partition is healed. This allows frameworks to define their own policies for how to handle partitioned tasks. Enabling the PARTITION_AWARE capability also introduces a new set of task states: TASK_UNREACHABLE, TASK_DROPPED, TASK_GONE, TASK_GONE_BY_OPERATOR, and TASK_UNKNOWN. <strong>These new states are intended to eventually replace the TASK_LOST state</strong>.</p></blockquote></li><li><p>TASK_FAILED</p><p> 执行作业任务被<strong>错误</strong>终止。例如,执行器( Elastic-Job-Cloud-Executor )异常崩溃,或者被杀死。</p></li><li><p>TASK_ERROR</p><p> 任务启动尝试失败错误。例如,执行器( Elastic-Job-Cloud-Executor ) 接收到的任务的作业配置不正确。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"> <span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 更新 Mesos 任务状态,运行中。</span></div><div class="line"> executorDriver.sendStatusUpdate(Protos.TaskStatus.newBuilder().setTaskId(taskInfo.getTaskId()).setState(Protos.TaskState.TASK_RUNNING).build());</div><div class="line"> <span class="comment">//</span></div><div class="line"> Map<String, Object> data = SerializationUtils.deserialize(taskInfo.getData().toByteArray());</div><div class="line"> ShardingContexts shardingContexts = (ShardingContexts) data.get(<span class="string">"shardingContext"</span>);</div><div class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</div><div class="line"> JobConfigurationContext jobConfig = <span class="keyword">new</span> JobConfigurationContext((Map<String, String>) data.get(<span class="string">"jobConfigContext"</span>));</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 获得 分布式作业</span></div><div class="line"> ElasticJob elasticJob = getElasticJobInstance(jobConfig);</div><div class="line"> <span class="comment">// 调度器提供内部服务的门面对象</span></div><div class="line"> <span class="keyword">final</span> CloudJobFacade jobFacade = <span class="keyword">new</span> CloudJobFacade(shardingContexts, jobConfig, jobEventBus);</div><div class="line"> <span class="comment">// 执行作业</span></div><div class="line"> <span class="keyword">if</span> (jobConfig.isTransient()) {</div><div class="line"> <span class="comment">// 执行作业</span></div><div class="line"> JobExecutorFactory.getJobExecutor(elasticJob, jobFacade).execute();</div><div class="line"> <span class="comment">// 更新 Mesos 任务状态,已完成。</span></div><div class="line"> executorDriver.sendStatusUpdate(Protos.TaskStatus.newBuilder().setTaskId(taskInfo.getTaskId()).setState(Protos.TaskState.TASK_FINISHED).build());</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="comment">// 初始化 常驻作业调度器</span></div><div class="line"> <span class="keyword">new</span> DaemonTaskScheduler(elasticJob, jobConfig, jobFacade, executorDriver, taskInfo.getTaskId()).init();</div><div class="line"> }</div><div class="line"> <span class="comment">// CHECKSTYLE:OFF</span></div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Throwable ex) {</div><div class="line"> <span class="comment">// CHECKSTYLE:ON</span></div><div class="line"> log.error(<span class="string">"Elastic-Job-Cloud-Executor error"</span>, ex);</div><div class="line"> <span class="comment">// 更新 Mesos 任务状态,错误。</span></div><div class="line"> executorDriver.sendStatusUpdate(Protos.TaskStatus.newBuilder().setTaskId(taskInfo.getTaskId()).setState(Protos.TaskState.TASK_ERROR).setMessage(ExceptionUtil.transform(ex)).build());</div><div class="line"> <span class="comment">// 停止自己</span></div><div class="line"> executorDriver.stop();</div><div class="line"> <span class="keyword">throw</span> ex;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#getElasticJobInstance()</code> 方法,因为任务的作业配置不正确抛出<strong>异常</strong>。例如,任务类不存在;Spring 的 配置文件不存在;Spring 容器初始化出错;Spring Bean 对象初始化或获取出错;以及等等。</li><li><p><strong>瞬时</strong>作业,调用 <code>AbstractElasticJobExecutor#execute(...)</code> 方法,发生<strong>异常</strong>,并且<strong>异常被抛出</strong>。默认情况下,AbstractElasticJobExecutor 内部使用 DefaultJobExceptionHandler 处理发生的异常,<strong>不会抛出异常</strong>,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">DefaultJobExceptionHandler</span> <span class="keyword">implements</span> <span class="title">JobExceptionHandler</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleException</span><span class="params">(<span class="keyword">final</span> String jobName, <span class="keyword">final</span> Throwable cause)</span> </span>{</div><div class="line"> log.error(String.format(<span class="string">"Job '%s' exception occur in job processing"</span>, jobName), cause);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li></li></ul></li><li><p><strong>常驻</strong>作业,调用 <code>DaemonTaskScheduler#(...)</code> 方法,初始化发生<strong>异常</strong>。</p></li><li>因为上述的种种异常,调用 <code>ExecutorDriver#sendStatusUpdate(...)</code>,更新 Mesos 任务状态为 TASK_ERROR。另外,调用 <code>ExecutorDriver#stop()</code> 方法,关闭自己。<strong>这意味着,一个执行器上如果存在一个作业任务发生 TASK_ERROR,其他作业任务即使是正常的,也会更新作业任务状态为 TASK_FAILED</strong>。这块千万要注意。</li></ul></li><li><p>TASK_LOST</p><p> 执行作业任务的 Elastic-Job-Cloud-Executor 所在的 Mesos Slave 与 Mesos Master 因为<strong>网络问题或 Mesos Slave 崩溃</strong>引起丢失连接,<strong>可能</strong>导致其上的所有作业任务状态变为 TASK_LOST。</p><p> <strong>当 Slave 宕机后重启,导致 TASK_LOST 时,Mesos又是怎么来处理的呢?</strong></p><blockquote><p>FROM <a href="http://dockone.io/article/2513" rel="external nofollow noopener noreferrer" target="_blank">http://dockone.io/article/2513</a><br>在 Master 和 Slave 之间,一般都是由 Master 主动向每一个 Slave 发送Ping消息,如果在设定时间内(flag.slave_ping_timeout,默认15s)没有收到Slave 的回复,并且达到一定次数(flag.max_slave_ping_timeouts,默认次数为5),那么 Master 会操作以下几个步骤: </p><ol><li>将该 Slave 从 Master 中删除,此时该 Slave 的资源将不会再分配给Scheduler。 </li><li>遍历该 Slave 上运行的所有任务,向对应的 Framework 发送任务的 Task_Lost 状态更新,同时把这些任务从Master中删除。 </li><li>遍历该 Slave 上的所有 Executor,并删除。 </li><li>触发 Rescind Offer,把这个 Slave 上已经分配给 Scheduler 的 Offer 撤销。 </li><li>把这个 Slave 从 Master 的 Replicated log 中删除(Mesos Master 依赖 Replicated log 中的部分持久化集群配置信息进行 failer over / recovery)。 </li></ol></blockquote><ul><li><p>必须 Slave 进行重启,因为对执行器的相关操作只能通过 Mesos Slave,即 <strong>Scheduler <=> Mesos Master <=> Mesos Slave <=> Executor</strong>。如果 Slave 一直不进行重启,执行器会一直运行,除非有另外的机制,<strong>通知</strong>到执行器。</p><p>But………………<br>笔者尝试如上流程,使用 <code>kill -9</code> 模拟 Mesos Slave 异常崩溃,等待 Mesos Master 发现 Mesos Slave 已经关闭,<strong>重启 Mesos Slave</strong>,结果执行器( Elastic-Job-Cloud-Executor )未关闭,调度器( Elastic-Job-Cloud-Scheduler )并未收到任务的 TASK_LOST。???什么情况???翻查如下文档:</p></li><li><p><a href="http://mesos.apache.org/documentation/latest/high-availability-framework-guide/" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 官方文档 —— high-availability-framework-guide》</a>搜索标题 “Dealing with Partitioned or Failed Agents”。</p></li><li><p><a href="http://mesos.apache.org/documentation/latest/agent-recovery/" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 官方文档 —— agent-recovery》</a>搜索关标题 “Agent Recovery”。</p><p>因为 Elastic-Job-Cloud-Scheduler 注册到 Mesos Master 时,开启了 <code>checkpoint</code> 和 <code>PARTITION_AWARE</code>。开启 <code>checkpoint</code> 后,Mesos Slave 会将记录<strong>检查点</strong>信息, Mesos Slave 重启后,会读取检查点检查信息,<strong>重新连接上( 不会关闭 )</strong>运行在它上面的执行器( Elastic-Job-Cloud-Scheduler )。开启 <code>PARTITION_AWARE</code> 后,TASK_LOST 会被区分成 TASK_UNREACHABLE, TASK_DROPPED, TASK_GONE, TASK_GONE_BY_OPERATOR, and TASK_UNKNOWN。表现如下:</p><ul><li><code>kill -9</code> 模拟 Mesos Slave 异常崩溃,等待 Mesos Master 发现 Mesos Slave 已经关闭</li><li>调度器( Elastic-Job-Cloud-Scheduler ) 接收直接由 Mesos Master 发送的该 Mesos Slave 上的每个任务 TASK_UNREACHABLE。</li><li>Mesos Slave 重启完成。</li><li>执行器( Elastic-Job-Cloud-Executor ) 重新注册到重启好的 Mesos Slave ,并继续运行任务。</li></ul></li></ul><p>如果 Elastic-Job-Cloud-Scheduler 注册到 Mesos Master 时,关闭了 <code>PARTITION_AWARE</code> 和 <code>checkpoint</code>,表现同 <strong>TASK_LOST</strong> 描述的过程。</p><p>开启 <code>checkpoint</code> 和 <code>PARTITION_AWARE</code> 实现代码如下:</p><pre><code><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// SchedulerService.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> SchedulerDriver <span class="title">getSchedulerDriver</span><span class="params">(<span class="keyword">final</span> TaskScheduler taskScheduler, <span class="keyword">final</span> JobEventBus jobEventBus, <span class="keyword">final</span> FrameworkIDService frameworkIDService)</span> </span>{</div><div class="line"> Protos.FrameworkInfo.Builder builder = Protos.FrameworkInfo.newBuilder();</div><div class="line"> <span class="comment">// PARTITION_AWARE</span></div><div class="line"> builder.addCapabilitiesBuilder().setType(Protos.FrameworkInfo.Capability.Type.PARTITION_AWARE);</div><div class="line"> Protos.FrameworkInfo frameworkInfo = builder.setUser(mesosConfig.getUser()).setName(frameworkName)</div><div class="line"> .setHostname(mesosConfig.getHostname()).setFailoverTimeout(FRAMEWORK_FAILOVER_TIMEOUT_SECONDS)</div><div class="line"> .setWebuiUrl(WEB_UI_PROTOCOL + env.getFrameworkHostPort())</div><div class="line"> .setCheckpoint(<span class="keyword">true</span>) <span class="comment">// checkpoint</span></div><div class="line"> .build();</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> }</div></pre></td></tr></table></figure></code></pre><p><strong>是不是开启了 <code>checkpoint</code>,Mesos Slave 重启不会关闭执行器?</strong></p><p> 答案当然是不是的。当 Mesos Slave 配置 <code>recover = cleanup</code> 或者 重启时间超过 <code>recovery_timeout</code> ( 默认,15 分钟 )时,重启完成后,Mesos Slave 关闭运行在它上面的执行器( Elastic-Job-Cloud-Executor ),调度器( Elastic-Job-Cloud-Scheduler ) 接收到的该 Mesos Slave 上的每个任务 TASK_FAILED。</p><ul><li>参考文档:<a href="http://mesos.apache.org/documentation/latest/agent-recovery/" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 官方文档 —— agent-recovery》</a>搜索标题 “Agent Configuration”。</li></ul></li></ul><p><img src="http://www.iocoder.cn/images/Elastic-Job/2018_01_10/01.png" alt=""></p><hr><p>调用 <code>FacadeService#recordFailoverTask(...)</code> 方法,记录失效转移队列,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">recordFailoverTask</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext)</span> </span>{</div><div class="line"> Optional<CloudJobConfiguration> jobConfigOptional = jobConfigService.load(taskContext.getMetaInfo().getJobName());</div><div class="line"> <span class="keyword">if</span> (!jobConfigOptional.isPresent()) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (isDisable(jobConfigOptional.get())) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> CloudJobConfiguration jobConfig = jobConfigOptional.get();</div><div class="line"> <span class="keyword">if</span> (jobConfig.getTypeConfig().getCoreConfig().isFailover() <span class="comment">// 开启失效转移</span></div><div class="line"> || CloudJobExecutionType.DAEMON == jobConfig.getJobExecutionType()) { <span class="comment">// 常驻作业</span></div><div class="line"> failoverService.add(taskContext);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>对于<strong>瞬时</strong>作业,必须开启 <code>JobCoreConfiguration.failover = true</code>,才能失效转移,这个比较好理解。</li><li>对于<strong>常驻</strong>作业,暂时不支持失效转移。因为常驻作业是在执行器( Elastic-Job-Executor ) 进行调度执行,如果不添加到失效转移作业队列,重新提交到执行器( Elastic-Job-Executor ),后续就不能调度执行该作业了。</li><li>调用 <code>FailoverService#add(...)</code> 方法,将任务放入失效转移队列,实现代码如下:</li></ul><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// FailoverService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (regCenter.getNumChildren(FailoverNode.ROOT) > env.getFrameworkConfiguration().getJobStateQueueSize()) {</div><div class="line"> log.warn(<span class="string">"Cannot add job, caused by read state queue size is larger than {}."</span>, env.getFrameworkConfiguration().getJobStateQueueSize());</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> String failoverTaskNodePath = FailoverNode.getFailoverTaskNodePath(taskContext.getMetaInfo().toString());</div><div class="line"> <span class="keyword">if</span> (!regCenter.isExisted(failoverTaskNodePath) <span class="comment">// 判断不在失效转移队列</span></div><div class="line"> && !runningService.isTaskRunning(taskContext.getMetaInfo())) { <span class="comment">// 判断不在运行中</span></div><div class="line"> regCenter.persist(failoverTaskNodePath, taskContext.getId());</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// FailoverNode.java</span></div><div class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">FailoverNode</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">static</span> <span class="keyword">final</span> String ROOT = StateNode.ROOT + <span class="string">"/failover"</span>;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String FAILOVER_JOB = ROOT + <span class="string">"/%s"</span>; <span class="comment">// %s=${JOB_NAME}</span></div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String FAILOVER_TASK = FAILOVER_JOB + <span class="string">"/%s"</span>; <span class="comment">// %s=${TASK_META_INFO}</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>FailoverService,失效转移队列服务。</li><li><p><strong>失效转移队列</strong>存储在注册中心( Zookeeper )的<strong>持久</strong>数据节点 <code>/${NAMESPACE}/state/failover/${JOB_NAME}/${TASK_META_INFO}</code>,存储值为任务编号。使用 zkClient 查看如下:</p> <figure class="highlight shell"><table><tr><td class="code"><pre><div class="line"> [zk: localhost:2181(CONNECTED) 2] ls /elastic-job-cloud/state/failover/test_job_simple</div><div class="line">[test_job_simple@-@0]</div><div class="line">[zk: localhost:2181(CONNECTED) 3] get /elastic-job-cloud/state/failover/test_job_simple/test_job_simple@-@0</div><div class="line">test_job_simple@-@0@-@READY@-@4da72be3-43d5-4f02-9d7e-45feb30b8fcb-S2@-@8f2a5bb5-2941-4ece-b192-0f936e60faa7</div></pre></td></tr></table></figure></li><li><p>在运维平台,我们可以看到失效转移队列:</p><p> <img src="http://www.iocoder.cn/images/Elastic-Job/2018_01_10/02.png" alt=""> </p></li></ul><h1 id="3-提交失效转移作业"><a href="#3-提交失效转移作业" class="headerlink" title="3. 提交失效转移作业"></a>3. 提交失效转移作业</h1><p>在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(一)》「4.1 创建 Fenzo 任务请求」</a>里,调用 <code>FacadeService#getEligibleJobContext()</code> 方法,获取有资格运行的作业时。<code>FacadeService#getEligibleJobContext()</code> 不仅调用 <code>ReadyService#getAllEligibleJobContexts(...)</code> 方法,从<strong>待执行队列</strong>中获取所有有资格执行的作业上下文,也调用 <code>FailoverService#getAllEligibleJobContexts()</code> 方法,从<strong>失效转移队列</strong>中获取所有有资格执行的作业上下文。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// FailoverService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> Collection<JobContext> <span class="title">getAllEligibleJobContexts</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 不存在 失效转移队列</span></div><div class="line"> <span class="keyword">if</span> (!regCenter.isExisted(FailoverNode.ROOT)) {</div><div class="line"> <span class="keyword">return</span> Collections.emptyList();</div><div class="line"> }</div><div class="line"> <span class="comment">// 获取 失效转移队列 的作业们</span></div><div class="line"> List<String> jobNames = regCenter.getChildrenKeys(FailoverNode.ROOT);</div><div class="line"> Collection<JobContext> result = <span class="keyword">new</span> ArrayList<>(jobNames.size());</div><div class="line"> Set<HashCode> assignedTasks = <span class="keyword">new</span> HashSet<>(jobNames.size() * <span class="number">10</span>, <span class="number">1</span>);</div><div class="line"> <span class="keyword">for</span> (String each : jobNames) {</div><div class="line"> <span class="comment">// 为空时,移除 失效转移队列 的作业</span></div><div class="line"> List<String> taskIdList = regCenter.getChildrenKeys(FailoverNode.getFailoverJobNodePath(each));</div><div class="line"> <span class="keyword">if</span> (taskIdList.isEmpty()) {</div><div class="line"> regCenter.remove(FailoverNode.getFailoverJobNodePath(each));</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 排除 作业配置 不存在的作业</span></div><div class="line"> Optional<CloudJobConfiguration> jobConfig = configService.load(each);</div><div class="line"> <span class="keyword">if</span> (!jobConfig.isPresent()) {</div><div class="line"> regCenter.remove(FailoverNode.getFailoverJobNodePath(each));</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 获得待执行的分片集合</span></div><div class="line"> List<Integer> assignedShardingItems = getAssignedShardingItems(each, taskIdList, assignedTasks);</div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="keyword">if</span> (!assignedShardingItems.isEmpty() && jobConfig.isPresent()) {</div><div class="line"> result.add(<span class="keyword">new</span> JobContext(jobConfig.get(), assignedShardingItems, ExecutionType.FAILOVER)); </div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> List<Integer> <span class="title">getAssignedShardingItems</span><span class="params">(<span class="keyword">final</span> String jobName, <span class="keyword">final</span> List<String> taskIdList, <span class="keyword">final</span> Set<HashCode> assignedTasks)</span> </span>{</div><div class="line"> List<Integer> result = <span class="keyword">new</span> ArrayList<>(taskIdList.size());</div><div class="line"> <span class="keyword">for</span> (String each : taskIdList) {</div><div class="line"> TaskContext.MetaInfo metaInfo = TaskContext.MetaInfo.from(each);</div><div class="line"> <span class="keyword">if</span> (assignedTasks.add(Hashing.md5().newHasher().putString(jobName, Charsets.UTF_8).putInt(metaInfo.getShardingItems().get(<span class="number">0</span>)).hash()) <span class="comment">// 排重</span></div><div class="line"> && !runningService.isTaskRunning(metaInfo)) { <span class="comment">// 排除正在运行中</span></div><div class="line"> result.add(metaInfo.getShardingItems().get(<span class="number">0</span>));</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure><hr><p>在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(一)》「4.4 创建 Mesos 任务信息」</a>里,调用 <code>LaunchingTasks#getIntegrityViolationJobs()</code> 方法,获得作业分片不完整的作业集合。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// LaunchingTasks.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 获得作业分片不完整的作业集合</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> vmAssignmentResults 主机分配任务结果集合</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 作业分片不完整的作业集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function">Collection<String> <span class="title">getIntegrityViolationJobs</span><span class="params">(<span class="keyword">final</span> Collection<VMAssignmentResult> vmAssignmentResults)</span> </span>{</div><div class="line"> Map<String, Integer> assignedJobShardingTotalCountMap = getAssignedJobShardingTotalCountMap(vmAssignmentResults);</div><div class="line"> Collection<String> result = <span class="keyword">new</span> HashSet<>(assignedJobShardingTotalCountMap.size(), <span class="number">1</span>);</div><div class="line"> <span class="keyword">for</span> (Map.Entry<String, Integer> entry : assignedJobShardingTotalCountMap.entrySet()) {</div><div class="line"> JobContext jobContext = eligibleJobContextsMap.get(entry.getKey());</div><div class="line"> <span class="keyword">if</span> (ExecutionType.FAILOVER != jobContext.getType() <span class="comment">// 不包括 FAILOVER 执行类型的作业</span></div><div class="line"> && !entry.getValue().equals(jobContext.getJobConfig().getTypeConfig().getCoreConfig().getShardingTotalCount())) {</div><div class="line"> log.warn(<span class="string">"Job {} is not assigned at this time, because resources not enough to run all sharding instances."</span>, entry.getKey());</div><div class="line"> result.add(entry.getKey());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>一个作业可能存在部分分片需要失效转移,不需要考虑完整性。</li></ul><hr><p>在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(一)》「4.7 从队列中删除已运行的作业」</a>里,调用 <code>FailoverService#remove(...)</code> 方法,从失效转移队列中删除相关任务。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">(<span class="keyword">final</span> Collection<TaskContext.MetaInfo> metaInfoList)</span> </span>{</div><div class="line"> <span class="keyword">for</span> (TaskContext.MetaInfo each : metaInfoList) {</div><div class="line"> regCenter.remove(FailoverNode.getFailoverTaskNodePath(each.toString()));</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>原本以为会是一篇水更,后面研究 TASK_LOST,发现收获大大的,干货妥妥的。</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2018_01_10/03.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. 记录作业失效转移</a></li>
<li><a href="#
</summary>
<category term="Elastic-Job-Cloud" scheme="http://www.iocoder.cn/categories/Elastic-Job-Cloud/"/>
</entry>
<entry>
<title>Elastic-Job-Cloud 源码分析 —— 本地运行模式</title>
<link href="http://www.iocoder.cn/Elastic-Job/cloud-local-executor/"/>
<id>http://www.iocoder.cn/Elastic-Job/cloud-local-executor/</id>
<published>2018-01-02T16:00:00.000Z</published>
<updated>2017-09-06T10:53:50.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. 配置</a></li><li><a href="#">3. 运行</a></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Cloud 本地运行模式</strong>,对应<a href="http://elasticjob.io/docs/elastic-job-cloud/02-guide/local-executor/" rel="external nofollow noopener noreferrer" target="_blank">《官方文档 —— 本地运行模式》</a>。</p><p><strong>有什么用呢</strong>?引用官方解答:</p><blockquote><p>在开发 Elastic-Job-Cloud 作业时,开发人员可以脱离 Mesos 环境,在本地运行和调试作业。可以利用本地运行模式充分的调试业务功能以及单元测试,完成之后再部署至 Mesos 集群。<br>本地运行作业无需安装 Mesos 环境。</p></blockquote><p>😈 是不是很赞 + 1024?!</p><p>本文涉及到主体类的类图如下( <a href="http://www.iocoder.cn/images/Elastic-Job/2018_01_03/01.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2018_01_03/01.png" alt=""></p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-配置"><a href="#2-配置" class="headerlink" title="2. 配置"></a>2. 配置</h1><p>LocalCloudJobConfiguration,本地云作业配置,在<a href="http://www.iocoder.cn/Elastic-Job/cloud-local-executor/?self">《Elastic-Job-Cloud 源码分析 —— 作业配置》「3.2 本地云作业配置」</a>有详细解析。</p><p>创建本地云作业配置示例代码如下(来自官方):</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line">LocalCloudJobConfiguration config = <span class="keyword">new</span> LocalCloudJobConfiguration(</div><div class="line"> <span class="keyword">new</span> SimpleJobConfiguration(</div><div class="line"> <span class="comment">// 配置作业类型和作业基本信息</span></div><div class="line"> JobCoreConfiguration.newBuilder(<span class="string">"FooJob"</span>, <span class="string">"*/2 * * * * ?"</span>, <span class="number">3</span>) </div><div class="line"> .shardingItemParameters(<span class="string">"0=Beijing,1=Shanghai,2=Guangzhou"</span>)</div><div class="line"> .jobParameter(<span class="string">"dbName=dangdang"</span>).build(), <span class="string">"com.dangdang.foo.FooJob"</span>),</div><div class="line"> <span class="comment">// 配置当前运行的作业是第几个分片 </span></div><div class="line"> <span class="number">1</span>, </div><div class="line"> <span class="comment">// 配置Spring相关参数。如果不配置,代表不使用 Spring 配置。</span></div><div class="line"> <span class="string">"testSimpleJob"</span> , <span class="string">"applicationContext.xml"</span>);</div></pre></td></tr></table></figure><h1 id="3-运行"><a href="#3-运行" class="headerlink" title="3. 运行"></a>3. 运行</h1><p>LocalTaskExecutor,本地作业执行器。</p><p>创建本地作业执行器示例代码如下(来自官方):</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">new</span> LocalTaskExecutor(localJobConfig).execute();</div></pre></td></tr></table></figure><p>可以看到,调用 <code>LocalTaskExecutor#execute()</code> 方法,执行作业逻辑,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// LocalTaskExecutor.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">()</span> </span>{</div><div class="line"> AbstractElasticJobExecutor jobExecutor;</div><div class="line"> CloudJobFacade jobFacade = <span class="keyword">new</span> CloudJobFacade(getShardingContexts(), getJobConfigurationContext(), <span class="keyword">new</span> JobEventBus());</div><div class="line"> <span class="comment">// 创建执行器</span></div><div class="line"> <span class="keyword">switch</span> (localCloudJobConfiguration.getTypeConfig().getJobType()) {</div><div class="line"> <span class="keyword">case</span> SIMPLE:</div><div class="line"> jobExecutor = <span class="keyword">new</span> SimpleJobExecutor(getJobInstance(SimpleJob.class), jobFacade);</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> DATAFLOW:</div><div class="line"> jobExecutor = <span class="keyword">new</span> DataflowJobExecutor(getJobInstance(DataflowJob.class), jobFacade);</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> SCRIPT:</div><div class="line"> jobExecutor = <span class="keyword">new</span> ScriptJobExecutor(jobFacade);</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">default</span>:</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UnsupportedOperationException(localCloudJobConfiguration.getTypeConfig().getJobType().name());</div><div class="line"> }</div><div class="line"> <span class="comment">// 执行作业</span></div><div class="line"> jobExecutor.execute();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>#getShardingContexts()</code> 方法,创建分片上下文集合( ShardingContexts ),实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> ShardingContexts <span class="title">getShardingContexts</span><span class="params">()</span> </span>{</div><div class="line"> JobCoreConfiguration coreConfig = localCloudJobConfiguration.getTypeConfig().getCoreConfig();</div><div class="line"> Map<Integer, String> shardingItemMap = <span class="keyword">new</span> HashMap<>(<span class="number">1</span>, <span class="number">1</span>);</div><div class="line"> shardingItemMap.put(localCloudJobConfiguration.getShardingItem(),</div><div class="line"> <span class="keyword">new</span> ShardingItemParameters(coreConfig.getShardingItemParameters()).getMap().get(localCloudJobConfiguration.getShardingItem()));</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ShardingContexts(</div><div class="line"> <span class="comment">// taskId 👇</span></div><div class="line"> Joiner.on(<span class="string">"@-@"</span>).join(localCloudJobConfiguration.getJobName(), localCloudJobConfiguration.getShardingItem(), <span class="string">"READY"</span>, <span class="string">"foo_slave_id"</span>, <span class="string">"foo_uuid"</span>),</div><div class="line"> localCloudJobConfiguration.getJobName(), coreConfig.getShardingTotalCount(), coreConfig.getJobParameter(), shardingItemMap);</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>#getJobConfigurationContext()</code> 方法,创建内部的作业配置上下文( JobConfigurationContext ),实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> JobConfigurationContext <span class="title">getJobConfigurationContext</span><span class="params">()</span> </span>{</div><div class="line"> Map<String, String> jobConfigurationMap = <span class="keyword">new</span> HashMap<>();</div><div class="line"> jobConfigurationMap.put(<span class="string">"jobClass"</span>, localCloudJobConfiguration.getTypeConfig().getJobClass());</div><div class="line"> jobConfigurationMap.put(<span class="string">"jobType"</span>, localCloudJobConfiguration.getTypeConfig().getJobType().name());</div><div class="line"> jobConfigurationMap.put(<span class="string">"jobName"</span>, localCloudJobConfiguration.getJobName());</div><div class="line"> jobConfigurationMap.put(<span class="string">"beanName"</span>, localCloudJobConfiguration.getBeanName());</div><div class="line"> jobConfigurationMap.put(<span class="string">"applicationContext"</span>, localCloudJobConfiguration.getApplicationContext());</div><div class="line"> <span class="keyword">if</span> (JobType.DATAFLOW == localCloudJobConfiguration.getTypeConfig().getJobType()) { <span class="comment">// 数据流作业</span></div><div class="line"> jobConfigurationMap.put(<span class="string">"streamingProcess"</span>, Boolean.toString(((DataflowJobConfiguration) localCloudJobConfiguration.getTypeConfig()).isStreamingProcess()));</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (JobType.SCRIPT == localCloudJobConfiguration.getTypeConfig().getJobType()) { <span class="comment">// 脚本作业</span></div><div class="line"> jobConfigurationMap.put(<span class="string">"scriptCommandLine"</span>, ((ScriptJobConfiguration) localCloudJobConfiguration.getTypeConfig()).getScriptCommandLine());</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> JobConfigurationContext(jobConfigurationMap);</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>#getJobInstance(...)</code> 方法, 获得分布式作业( ElasticJob )实现实例,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">private</span> <T extends ElasticJob> <span class="function">T <span class="title">getJobInstance</span><span class="params">(<span class="keyword">final</span> Class<T> clazz)</span> </span>{</div><div class="line"> Object result;</div><div class="line"> <span class="keyword">if</span> (Strings.isNullOrEmpty(localCloudJobConfiguration.getApplicationContext())) { <span class="comment">// 直接创建 ElasticJob</span></div><div class="line"> String jobClass = localCloudJobConfiguration.getTypeConfig().getJobClass();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> result = Class.forName(jobClass).newInstance();</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> ReflectiveOperationException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(<span class="string">"Elastic-Job: Class '%s' initialize failure, the error message is '%s'."</span>, jobClass, ex.getMessage());</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> { <span class="comment">// Spring 环境获得 ElasticJob</span></div><div class="line"> result = <span class="keyword">new</span> ClassPathXmlApplicationContext(localCloudJobConfiguration.getApplicationContext()).getBean(localCloudJobConfiguration.getBeanName());</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> clazz.cast(result);</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>AbstractElasticJobExecutor#execute()</code> 方法,执行作业逻辑。 Elastic-Job-Lite 和 Elastic-Job-Cloud 作业执行基本一致,在<a href="http://www.iocoder.cn/Elastic-Job/job-execute/?self">《Elastic-Job-Lite 源码分析 —— 作业执行》</a>有详细解析。</p></li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>芋道君:可能有点水更,和大家实际开发太相关,想想还是更新下。<br>旁白君:哎哟哟,哎哟喂。</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2018_01_03/02.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. 配置</a></li>
<li><a href="#">3. 运
</summary>
<category term="Elastic-Job-Cloud" scheme="http://www.iocoder.cn/categories/Elastic-Job-Cloud/"/>
</entry>
<entry>
<title>Elastic-Job-Cloud 源码分析 —— 作业调度(二)</title>
<link href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-second/"/>
<id>http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-second/</id>
<published>2017-12-27T16:00:00.000Z</published>
<updated>2017-09-16T04:53:59.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. 云作业操作</a><ul><li><a href="#">2.1 注册云作业配置</a></li><li><a href="#">2.2 禁用云作业</a></li><li><a href="#">2.3 启动云作业</a></li><li><a href="#">2.4 更新云作业配置</a></li><li><a href="#">2.5 注销云作业</a></li><li><a href="#">2.6 触发一次云作业</a></li></ul></li><li><a href="#">3. 云作业应用操作</a><ul><li><a href="#">3.1 注册云作业应用</a></li><li><a href="#">3.2 更新云作业应用配置</a></li><li><a href="#">3.3 禁用云作业应用</a></li><li><a href="#">3.4 启用云作业应用</a></li><li><a href="#">3.5 注销云作业应用</a></li></ul></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Cloud 云作业应用配置和云作业配置变更对作业调度的影响</strong>,作为<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(一)》</a>的补充内容。所以需要你对<strong>作业调度</strong>已经有一定了解的基础上。</p><p>🙂 如果你做作业调度有任何想交流,欢迎加我的公众号( 芋道源码 ) 或 微信( wangwenbin-server ) 交流。</p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-云作业操作"><a href="#2-云作业操作" class="headerlink" title="2. 云作业操作"></a>2. 云作业操作</h1><p>我们可以使用<strong>运维平台</strong>或 Restful API 对云作业进行操作。前者是对后者的界面包装,如下图所示:</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_28/01.png" alt=""></p><h2 id="2-1-注册云作业配置"><a href="#2-1-注册云作业配置" class="headerlink" title="2.1 注册云作业配置"></a>2.1 注册云作业配置</h2><p><a href="http://www.iocoder.cn/Elastic-Job/cloud-job-config/?self">《Elastic-Job-Cloud 源码分析 —— 作业配置》「3.1.1 操作云作业配置」</a>有详细解析。</p><h2 id="2-2-禁用云作业"><a href="#2-2-禁用云作业" class="headerlink" title="2.2 禁用云作业"></a>2.2 禁用云作业</h2><p>调用 <code>CloudJobRestfulApi#disable(...)</code> 方法,禁用云作业,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@POST</span></div><div class="line"><span class="meta">@Path</span>(<span class="string">"/{jobName}/disable"</span>)</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">disable</span><span class="params">(@PathParam(<span class="string">"jobName"</span>)</span> <span class="keyword">final</span> String jobName) </span>{</div><div class="line"> <span class="keyword">if</span> (configService.load(jobName).isPresent()) {</div><div class="line"> <span class="comment">// 将作业放入禁用队列</span></div><div class="line"> facadeService.disableJob(jobName);</div><div class="line"> <span class="comment">// 停止调度作业</span></div><div class="line"> producerManager.unschedule(jobName);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>FacadeService#disableJob(...)</code> 方法,将作业放入<strong>禁用作业队列</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// FacadeService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">disableJob</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> disableJobService.add(jobName);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// DisableJobService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (regCenter.getNumChildren(DisableJobNode.ROOT) > env.getFrameworkConfiguration().getJobStateQueueSize()) {</div><div class="line"> log.warn(<span class="string">"Cannot add disable job, caused by read state queue size is larger than {}."</span>, env.getFrameworkConfiguration().getJobStateQueueSize());</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 将作业放入禁用队列</span></div><div class="line"> String disableJobNodePath = DisableJobNode.getDisableJobNodePath(jobName);</div><div class="line"> <span class="keyword">if</span> (!regCenter.isExisted(disableJobNodePath)) {</div><div class="line"> regCenter.persist(disableJobNodePath, jobName);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// DisableJobNode.java</span></div><div class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">DisableJobNode</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">static</span> <span class="keyword">final</span> String ROOT = StateNode.ROOT + <span class="string">"/disable/job"</span>;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DISABLE_JOB = ROOT + <span class="string">"/%s"</span>; <span class="comment">// %s = ${JOB_NAME}</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>DisableJobService,禁用作业队列服务。</li><li><p>禁用作业队列存储在注册中心( Zookeeper )的<strong>持久</strong>数据节点 <code>/${NAMESPACE}/state/disable/job/${JOB_NAME}</code>,存储值为作业名称。使用 zkClient 查看如下: </p> <figure class="highlight shell"><table><tr><td class="code"><pre><div class="line"> [zk: localhost:2181(CONNECTED) 6] ls /elastic-job-cloud/state/disable/job</div><div class="line">[test_job_simple]</div></pre></td></tr></table></figure></li></ul></li><li><p>调用 <code>ProducerManager#unschedule(...)</code> 方法,停止调度作业。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unschedule</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> <span class="comment">// 杀死作业对应的 Mesos 任务们</span></div><div class="line"> <span class="keyword">for</span> (TaskContext each : runningService.getRunningTasks(jobName)) {</div><div class="line"> schedulerDriver.killTask(Protos.TaskID.newBuilder().setValue(each.getId()).build());</div><div class="line"> }</div><div class="line"> <span class="comment">// 将作业从运行时队列删除</span></div><div class="line"> runningService.remove(jobName);</div><div class="line"> <span class="comment">// 将作业从待运行队列删除</span></div><div class="line"> readyService.remove(Lists.newArrayList(jobName));</div><div class="line"> <span class="comment">// 停止作业调度</span></div><div class="line"> Optional<CloudJobConfiguration> jobConfig = configService.load(jobName);</div><div class="line"> <span class="keyword">if</span> (jobConfig.isPresent()) {</div><div class="line"> transientProducerScheduler.deregister(jobConfig.get());</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>SchedulerDriver#killTask(...)</code> 方法,杀死作业对应的 Mesos 任务们,适用<strong>常驻作业</strong>。Elastic-Job-Cloud-Scheduler 会接收到 Mesos 杀死任务的请求,调用 <code>TaskExecutor#killTask(...)</code> 方法,停止任务调度。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TaskExecutor.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TaskExecutor</span> <span class="keyword">implements</span> <span class="title">Executor</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">killTask</span><span class="params">(<span class="keyword">final</span> ExecutorDriver executorDriver, <span class="keyword">final</span> Protos.TaskID taskID)</span> </span>{</div><div class="line"> <span class="comment">// 更新 Mesos 任务状态,已杀死。</span></div><div class="line"> executorDriver.sendStatusUpdate(Protos.TaskStatus.newBuilder().setTaskId(taskID).setState(Protos.TaskState.TASK_KILLED).build());</div><div class="line"> <span class="comment">// 关闭该 Mesos 任务的调度</span></div><div class="line"> DaemonTaskScheduler.shutdown(taskID);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// DaemonTaskScheduler.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 停止任务调度.</span></div><div class="line"><span class="comment">* </span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> taskID 任务主键</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">shutdown</span><span class="params">(<span class="keyword">final</span> Protos.TaskID taskID)</span> </span>{</div><div class="line"> <span class="comment">// 移除任务的 Quartz Scheduler</span></div><div class="line"> Scheduler scheduler = RUNNING_SCHEDULERS.remove(taskID.getValue());</div><div class="line"> <span class="comment">// 关闭任务的 Quartz Scheduler</span></div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != scheduler) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> scheduler.shutdown();</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> SchedulerException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(ex);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li></li></ul></li><li><p>调用 <code>RunningService#remove(...)</code> 方法,将作业从<strong>运行时队列</strong>删除。</p></li><li>调用 <code>ReadyService#remove(...)</code> 方法,将作业从<strong>待运行队列</strong>删除。</li><li><p>调用 <code>TransientProducerScheduler#deregister(...)</code> 方法,停止作业调度,适用<strong>瞬时作业</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">deregister</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> <span class="comment">// 移除作业</span></div><div class="line"> repository.remove(jobConfig.getJobName());</div><div class="line"> <span class="comment">// 若 cron 不再对应有作业调度,移除 Quartz Scheduler 对 cron 对应的 Quartz Job</span></div><div class="line"> String cron = jobConfig.getTypeConfig().getCoreConfig().getCron();</div><div class="line"> <span class="keyword">if</span> (!repository.containsKey(buildJobKey(cron))) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> scheduler.unscheduleJob(TriggerKey.triggerKey(cron));</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> SchedulerException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(ex);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li></ul></li></ul><h2 id="2-3-启动云作业"><a href="#2-3-启动云作业" class="headerlink" title="2.3 启动云作业"></a>2.3 启动云作业</h2><p>调用 <code>CloudJobRestfulApi#enable(...)</code> 方法,启用云作业,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@DELETE</span></div><div class="line"><span class="meta">@Path</span>(<span class="string">"/{jobName}/disable"</span>)</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">enable</span><span class="params">(@PathParam(<span class="string">"jobName"</span>)</span> <span class="keyword">final</span> String jobName) <span class="keyword">throws</span> JSONException </span>{</div><div class="line"> Optional<CloudJobConfiguration> configOptional = configService.load(jobName);</div><div class="line"> <span class="keyword">if</span> (configOptional.isPresent()) {</div><div class="line"> <span class="comment">// 将作业移出禁用队列</span></div><div class="line"> facadeService.enableJob(jobName);</div><div class="line"> <span class="comment">// 重新调度作业</span></div><div class="line"> producerManager.reschedule(jobName);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>FacadeService#enableJob(...)</code> 方法,将作业移出<strong>禁用作业队列</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// FacadeService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">enableJob</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> disableJobService.remove(jobName);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// DisableJobService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> regCenter.remove(DisableJobNode.getDisableJobNodePath(jobName));</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>ProducerManager#reschedule(...)</code> 方法,将作业重新调度。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">reschedule</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> <span class="comment">// 停止调度作业</span></div><div class="line"> unschedule(jobName);</div><div class="line"> <span class="comment">// 调度作业</span></div><div class="line"> Optional<CloudJobConfiguration> jobConfig = configService.load(jobName);</div><div class="line"> <span class="keyword">if</span> (jobConfig.isPresent()) {</div><div class="line"> schedule(jobConfig.get());</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#unschedule(...)</code> 方法,停止调度作业。</li><li>调用 <code>#schedule(...)</code> 方法,调度作业,在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(一)》</a>有详细解析。</li></ul></li></ul><h2 id="2-4-更新云作业配置"><a href="#2-4-更新云作业配置" class="headerlink" title="2.4 更新云作业配置"></a>2.4 更新云作业配置</h2><p>调用 <code>CloudJobRestfulApi#update(...)</code> 方法,更新云作业配置,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CloudJobRestfulApi.java</span></div><div class="line"><span class="meta">@PUT</span></div><div class="line"><span class="meta">@Path</span>(<span class="string">"/update"</span>)</div><div class="line"><span class="meta">@Consumes</span>(MediaType.APPLICATION_JSON)</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> producerManager.update(jobConfig);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// ProducerManager.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> Optional<CloudJobConfiguration> jobConfigFromZk = configService.load(jobConfig.getJobName());</div><div class="line"> <span class="keyword">if</span> (!jobConfigFromZk.isPresent()) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobConfigurationException(<span class="string">"Cannot found job '%s', please register first."</span>, jobConfig.getJobName());</div><div class="line"> }</div><div class="line"> <span class="comment">// 修改云作业配置</span></div><div class="line"> configService.update(jobConfig);</div><div class="line"> <span class="comment">// 重新调度作业</span></div><div class="line"> reschedule(jobConfig.getJobName());</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>ConfigService#update(jobConfig)</code> 方法,修改云作业配置。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> regCenter.update(CloudJobConfigurationNode.getRootNodePath(jobConfig.getJobName()), CloudJobConfigurationGsonFactory.toJson(jobConfig));</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>#reschedule(...)</code> 方法,重新调度作业。此处的调用是重复的,实际只需 CloudJobConfigurationListener 监听到配置变化,调用 <code>#reschedule(...)</code> 方法即可。</p></li></ul><p>存储在注册中心( Zookeeper )的 云作业配置被更新时,云作业配置变更监听( CloudJobConfigurationListener )会监听到,并执行更新相应逻辑,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CloudJobConfigurationListener</span> <span class="keyword">implements</span> <span class="title">TreeCacheListener</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">childEvent</span><span class="params">(<span class="keyword">final</span> CuratorFramework client, <span class="keyword">final</span> TreeCacheEvent event)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> String path = <span class="keyword">null</span> == event.getData() ? <span class="string">""</span> : event.getData().getPath();</div><div class="line"> <span class="keyword">if</span> (isJobConfigNode(event, path, Type.NODE_ADDED)) {</div><div class="line"> <span class="comment">// .... 省略无关代码</span></div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (isJobConfigNode(event, path, Type.NODE_UPDATED)) {</div><div class="line"> CloudJobConfiguration jobConfig = getJobConfig(event);</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == jobConfig) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 从待执行队列中删除相关作业</span></div><div class="line"> <span class="keyword">if</span> (CloudJobExecutionType.DAEMON == jobConfig.getJobExecutionType()) {</div><div class="line"> readyService.remove(Collections.singletonList(jobConfig.getJobName()));</div><div class="line"> }</div><div class="line"> <span class="comment">// 设置禁用错过重执行</span></div><div class="line"> <span class="keyword">if</span> (!jobConfig.getTypeConfig().getCoreConfig().isMisfire()) {</div><div class="line"> readyService.setMisfireDisabled(jobConfig.getJobName());</div><div class="line"> }</div><div class="line"> <span class="comment">// 重新调度作业</span></div><div class="line"> producerManager.reschedule(jobConfig.getJobName());</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (isJobConfigNode(event, path, Type.NODE_REMOVED)) {</div><div class="line"> <span class="comment">// .... 省略无关代码</span></div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>CloudJobConfigurationListener 实现 TreeCacheListener 实现对 Zookeeper 数据变更的监听。对 TreeCacheListener 感兴趣的同学,可以查看 <a href="https://curator.apache.org/" rel="external nofollow noopener noreferrer" target="_blank">Apache Curator</a> 相关知识。</li><li>调用 <code>ReadyService#remove(...)</code> 方法,将作业从<strong>待运行队列</strong>删除。TODO,为啥要删除?</li><li><p>调用 <code>ReadyService#setMisfireDisabled(...)</code> 方法,设置禁用错过重执行。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setMisfireDisabled</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> Optional<CloudJobConfiguration> cloudJobConfig = configService.load(jobName);</div><div class="line"> <span class="keyword">if</span> (cloudJobConfig.isPresent() && <span class="keyword">null</span> != regCenter.getDirectly(ReadyNode.getReadyJobNodePath(jobName))) {</div><div class="line"> regCenter.persist(ReadyNode.getReadyJobNodePath(jobName), <span class="string">"1"</span>);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>瞬时作业</strong>开启 <code>misfire</code> 功能时,当任务执行过久触发 <code>misifre</code> 会不断累积待执行次数。如果关闭 <code>misfire</code> 功能,需要将多次执行次数归 <code>"1"</code>。</li></ul></li><li><p>调用 <code>ProducerManager#reschedule(...)</code> 方法,重新调度作业。</p></li></ul><h2 id="2-5-注销云作业"><a href="#2-5-注销云作业" class="headerlink" title="2.5 注销云作业"></a>2.5 注销云作业</h2><p>调用 <code>CloudJobRestfulApi#deregister(...)</code> 方法,注销云作业,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CloudJobRestfulApi.java</span></div><div class="line"><span class="meta">@DELETE</span></div><div class="line"><span class="meta">@Path</span>(<span class="string">"/deregister"</span>)</div><div class="line"><span class="meta">@Consumes</span>(MediaType.APPLICATION_JSON)</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deregister</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> producerManager.deregister(jobName);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// ProducerManager.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deregister</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> Optional<CloudJobConfiguration> jobConfig = configService.load(jobName);</div><div class="line"> <span class="keyword">if</span> (jobConfig.isPresent()) {</div><div class="line"> <span class="comment">// 从作业禁用队列中删除作业</span></div><div class="line"> disableJobService.remove(jobName);</div><div class="line"> <span class="comment">// 删除云作业</span></div><div class="line"> configService.remove(jobName);</div><div class="line"> }</div><div class="line"> <span class="comment">// 停止调度作业</span></div><div class="line"> unschedule(jobName);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>DisableJobService#remove(...)</code> 方法,从<strong>作业禁用队列</strong>中删除作业,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> regCenter.remove(DisableJobNode.getDisableJobNodePath(jobName));</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><ul><li><p>调用 <code>CloudJobConfigurationService#remove(...)</code> 方法,删除云作业配置。 </p></li><li><p>调用 <code>#reschedule(...)</code> 方法,重新调度作业。</p></li></ul><p>存储在注册中心( Zookeeper )的 云作业配置被删除时,<strong>云作业配置变更监听器</strong>( CloudJobConfigurationListener )会监听到,并执行删除相应逻辑,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CloudJobConfigurationListener</span> <span class="keyword">implements</span> <span class="title">TreeCacheListener</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> CoordinatorRegistryCenter regCenter;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ProducerManager producerManager;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ReadyService readyService;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">CloudJobConfigurationListener</span><span class="params">(<span class="keyword">final</span> CoordinatorRegistryCenter regCenter, <span class="keyword">final</span> ProducerManager producerManager)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.regCenter = regCenter;</div><div class="line"> readyService = <span class="keyword">new</span> ReadyService(regCenter);</div><div class="line"> <span class="keyword">this</span>.producerManager = producerManager;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">childEvent</span><span class="params">(<span class="keyword">final</span> CuratorFramework client, <span class="keyword">final</span> TreeCacheEvent event)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> String path = <span class="keyword">null</span> == event.getData() ? <span class="string">""</span> : event.getData().getPath();</div><div class="line"> <span class="keyword">if</span> (isJobConfigNode(event, path, Type.NODE_ADDED)) {</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (isJobConfigNode(event, path, Type.NODE_UPDATED)) {</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (isJobConfigNode(event, path, Type.NODE_REMOVED)) {</div><div class="line"> String jobName = path.substring(CloudJobConfigurationNode.ROOT.length() + <span class="number">1</span>, path.length());</div><div class="line"> producerManager.unschedule(jobName);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#reschedule(...)</code> 方法,重新调度作业。</li></ul><h2 id="2-6-触发一次云作业"><a href="#2-6-触发一次云作业" class="headerlink" title="2.6 触发一次云作业"></a>2.6 触发一次云作业</h2><p>调用 <code>CloudJobRestfulApi#trigger(...)</code> 方法,触发一次云作业,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@POST</span></div><div class="line"><span class="meta">@Path</span>(<span class="string">"/trigger"</span>)</div><div class="line"><span class="meta">@Consumes</span>(MediaType.APPLICATION_JSON)</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">trigger</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> <span class="comment">// 常驻作业不允许触发一次作业</span></div><div class="line"> Optional<CloudJobConfiguration> config = configService.load(jobName);</div><div class="line"> <span class="keyword">if</span> (config.isPresent() && CloudJobExecutionType.DAEMON == config.get().getJobExecutionType()) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(<span class="string">"Daemon job '%s' cannot support trigger."</span>, jobName);</div><div class="line"> }</div><div class="line"> <span class="comment">// 将瞬时作业放入待执行队列</span></div><div class="line"> facadeService.addTransient(jobName);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>目前<strong>常驻</strong>作业<strong>不支持</strong>触发一次云作业。如果想实现该功能,需要 Elastic-Job-Cloud-Scheduler 通过 Mesos 发送自定义消息,通知 Elastic-Job-Cloud-Executor 触发该作业对应的任务们。</li><li>调用 <code>FacadeService#addTransient(...)</code> 方法,将瞬时作业放入<strong>待执行队列</strong>。当且仅当云作业配置 <code>JobCoreConfiguration.misfire = true</code> 时,该作业在<strong>待执行队列</strong>的执行次数不断累积加一。在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(一)》「3.2.3 ProducerJob」</a>有详细解析。</li></ul><h1 id="3-云作业应用操作"><a href="#3-云作业应用操作" class="headerlink" title="3. 云作业应用操作"></a>3. 云作业应用操作</h1><p>我们可以使用<strong>运维平台</strong>或 Restful API 对云作业应用进行操作。前者是对后者的界面包装,如下图所示:</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_28/02.png" alt=""></p><h2 id="3-1-注册云作业应用"><a href="#3-1-注册云作业应用" class="headerlink" title="3.1 注册云作业应用"></a>3.1 注册云作业应用</h2><p><a href="http://www.iocoder.cn/Elastic-Job/cloud-job-config/?self">《Elastic-Job-Cloud 源码分析 —— 作业配置》「2.2 操作云作业App配置」</a>有详细解析。</p><h2 id="3-2-更新云作业应用配置"><a href="#3-2-更新云作业应用配置" class="headerlink" title="3.2 更新云作业应用配置"></a>3.2 更新云作业应用配置</h2><p>调用 <code>CloudAppRestfulApi#update(...)</code> 方法,更新云作业应用配置,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CloudAppRestfulApi.java</span></div><div class="line"><span class="meta">@PUT</span></div><div class="line"><span class="meta">@Consumes</span>(MediaType.APPLICATION_JSON)</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">final</span> CloudAppConfiguration appConfig)</span> </span>{</div><div class="line"> appConfigService.update(appConfig);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// CloudAppConfigurationService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">final</span> CloudAppConfiguration appConfig)</span> </span>{</div><div class="line"> regCenter.update(CloudAppConfigurationNode.getRootNodePath(appConfig.getAppName()), CloudAppConfigurationGsonFactory.toJson(appConfig));</div><div class="line">}</div></pre></td></tr></table></figure><h2 id="3-3-禁用云作业应用"><a href="#3-3-禁用云作业应用" class="headerlink" title="3.3 禁用云作业应用"></a>3.3 禁用云作业应用</h2><p>调用 <code>CloudAppRestfulApi#disable(...)</code> 方法,禁用云作业应用,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@POST</span></div><div class="line"><span class="meta">@Path</span>(<span class="string">"/{appName}/disable"</span>)</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">disable</span><span class="params">(@PathParam(<span class="string">"appName"</span>)</span> <span class="keyword">final</span> String appName) </span>{</div><div class="line"> <span class="keyword">if</span> (appConfigService.load(appName).isPresent()) {</div><div class="line"> <span class="comment">// 将应用放入禁用队列</span></div><div class="line"> disableAppService.add(appName);</div><div class="line"> <span class="comment">// 停止应用对应所有作业的调度</span></div><div class="line"> <span class="keyword">for</span> (CloudJobConfiguration each : jobConfigService.loadAll()) {</div><div class="line"> <span class="keyword">if</span> (appName.equals(each.getAppName())) {</div><div class="line"> producerManager.unschedule(each.getJobName());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>DisableAppService#add(...)</code> 方法,将应用放入<strong>禁用应用队列</strong>,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// DisableAppService.java</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DisableAppService</span> </span>{</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(<span class="keyword">final</span> String appName)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (regCenter.getNumChildren(DisableAppNode.ROOT) > env.getFrameworkConfiguration().getJobStateQueueSize()) {</div><div class="line"> log.warn(<span class="string">"Cannot add disable app, caused by read state queue size is larger than {}."</span>, env.getFrameworkConfiguration().getJobStateQueueSize());</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> String disableAppNodePath = DisableAppNode.getDisableAppNodePath(appName);</div><div class="line"> <span class="keyword">if</span> (!regCenter.isExisted(disableAppNodePath)) {</div><div class="line"> regCenter.persist(disableAppNodePath, appName);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// DisableAppNode.java</span></div><div class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">DisableAppNode</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">static</span> <span class="keyword">final</span> String ROOT = StateNode.ROOT + <span class="string">"/disable/app"</span>;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DISABLE_APP = ROOT + <span class="string">"/%s"</span>; <span class="comment">// %s = ${APP_NAME}</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>DisableAppService,禁用应用队列服务。</li><li><p>禁用应用队列存储在注册中心( Zookeeper )的<strong>持久</strong>数据节点 <code>/${NAMESPACE}/state/disable/app/${APP_NAME}</code>,存储值为应用名。使用 zkClient 查看如下: </p> <figure class="highlight shell"><table><tr><td class="code"><pre><div class="line"> [zk: localhost:2181(CONNECTED) 6] ls /elastic-job-cloud/state/disable/app</div><div class="line">[example_app]</div></pre></td></tr></table></figure></li></ul></li><li><p>遍历应用对应所有作业,调用 <code>ProducerManager#unschedule(...)</code> 方法,停止作业调度。</p></li></ul><h2 id="3-4-启用云作业应用"><a href="#3-4-启用云作业应用" class="headerlink" title="3.4 启用云作业应用"></a>3.4 启用云作业应用</h2><p>调用 <code>CloudAppRestfulApi#enable(...)</code> 方法,启用云作业应用,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CloudAppRestfulApi.java</span></div><div class="line"><span class="meta">@DELETE</span></div><div class="line"><span class="meta">@Path</span>(<span class="string">"/{appName}/disable"</span>)</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">enable</span><span class="params">(@PathParam(<span class="string">"appName"</span>)</span> <span class="keyword">final</span> String appName) <span class="keyword">throws</span> JSONException </span>{</div><div class="line"> <span class="keyword">if</span> (appConfigService.load(appName).isPresent()) {</div><div class="line"> <span class="comment">// 从禁用应用队列中删除应用</span></div><div class="line"> disableAppService.remove(appName);</div><div class="line"> <span class="comment">// 重新开启应用对应所有作业的调度</span></div><div class="line"> <span class="keyword">for</span> (CloudJobConfiguration each : jobConfigService.loadAll()) {</div><div class="line"> <span class="keyword">if</span> (appName.equals(each.getAppName())) {</div><div class="line"> producerManager.reschedule(each.getJobName());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// DisableAppService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">(<span class="keyword">final</span> String appName)</span> </span>{</div><div class="line"> regCenter.remove(DisableAppNode.getDisableAppNodePath(appName));</div><div class="line">}</div></pre></td></tr></table></figure><h2 id="3-5-注销云作业应用"><a href="#3-5-注销云作业应用" class="headerlink" title="3.5 注销云作业应用"></a>3.5 注销云作业应用</h2><p>调用 <code>CloudAppRestfulApi#deregister(...)</code> 方法,注销云作业应用,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@DELETE</span></div><div class="line"><span class="meta">@Path</span>(<span class="string">"/{appName}"</span>)</div><div class="line"><span class="meta">@Consumes</span>(MediaType.APPLICATION_JSON)</div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deregister</span><span class="params">(@PathParam(<span class="string">"appName"</span>)</span> <span class="keyword">final</span> String appName) </span>{</div><div class="line"> <span class="keyword">if</span> (appConfigService.load(appName).isPresent()) {</div><div class="line"> <span class="comment">// 移除应用和应用对应所有作业的配置</span></div><div class="line"> removeAppAndJobConfigurations(appName);</div><div class="line"> <span class="comment">// 停止应用对应的执行器( Elastic-Job-Cloud-Scheduler )</span></div><div class="line"> stopExecutors(appName);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>#removeAppAndJobConfigurations(...)</code> 方法,移除应用和应用对应所有作业的配置,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">removeAppAndJobConfigurations</span><span class="params">(<span class="keyword">final</span> String appName)</span> </span>{</div><div class="line"> <span class="comment">// 注销(移除)应用对应所有作业的配置</span></div><div class="line"> <span class="keyword">for</span> (CloudJobConfiguration each : jobConfigService.loadAll()) {</div><div class="line"> <span class="keyword">if</span> (appName.equals(each.getAppName())) {</div><div class="line"> producerManager.deregister(each.getJobName());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="comment">// 从禁用应用队列中删除应用</span></div><div class="line"> disableAppService.remove(appName);</div><div class="line"> <span class="comment">// 删除云作业App配置</span></div><div class="line"> appConfigService.remove(appName);</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>#stopExecutors(...)</code> 方法,停止应用对应的执行器( Elastic-Job-Cloud-Scheduler ),实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CloudAppRestfulApi.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">stopExecutors</span><span class="params">(<span class="keyword">final</span> String appName)</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> Collection<ExecutorStateInfo> executorBriefInfo = mesosStateService.executors(appName);</div><div class="line"> <span class="keyword">for</span> (ExecutorStateInfo each : executorBriefInfo) {</div><div class="line"> producerManager.sendFrameworkMessage(ExecutorID.newBuilder().setValue(each.getId()).build(),</div><div class="line"> SlaveID.newBuilder().setValue(each.getSlaveId()).build(), <span class="string">"STOP"</span>.getBytes());</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> JSONException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(ex);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// ProducerManager.java </span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendFrameworkMessage</span><span class="params">(<span class="keyword">final</span> ExecutorID executorId, <span class="keyword">final</span> SlaveID slaveId, <span class="keyword">final</span> <span class="keyword">byte</span>[] data)</span> </span>{</div><div class="line"> schedulerDriver.sendFrameworkMessage(executorId, slaveId, data);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>SchedulerDriver#sendFrameworkMessage(...)</code> 方法,通过 Mesos 向云作业应用对应的执行器们( Elastic-Job-Cloud-Executor ) 发送消息为 <code>"STOP"</code> 从而关闭执行器。Elastic-Job-Cloud-Executor 会接收到 Mesos 消息,调用 <code>TaskExecutor#frameworkMessage(...)</code> 方法,关闭自己。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">frameworkMessage</span><span class="params">(<span class="keyword">final</span> ExecutorDriver executorDriver, <span class="keyword">final</span> <span class="keyword">byte</span>[] bytes)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != bytes && <span class="string">"STOP"</span>.equals(<span class="keyword">new</span> String(bytes))) {</div><div class="line"> log.error(<span class="string">"call frameworkMessage executor stopped."</span>);</div><div class="line"> executorDriver.stop();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li></ul></li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>Elastic-Job-Cloud 作业调度两篇内容到此就结束啦。后续我们会更新大家关心的<a href="http://www.iocoder.cn/Elastic-Job/cloud-high-availability/?self">《Elastic-Job-Cloud 源码分析 —— 高可用》</a>是如何实现的噢。</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_28/03.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. 云作业操作</a><ul>
<li><a href="#">2.
</summary>
<category term="Elastic-Job-Cloud" scheme="http://www.iocoder.cn/categories/Elastic-Job-Cloud/"/>
</entry>
<entry>
<title>Elastic-Job-Cloud 源码分析 —— 作业调度(一)</title>
<link href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/"/>
<id>http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-first/</id>
<published>2017-12-20T16:00:00.000Z</published>
<updated>2017-09-07T15:54:52.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. 作业执行类型</a></li><li><a href="#">3. Producer 发布任务</a><ul><li><a href="#">3.1 常驻作业</a></li><li><a href="#">3.2 瞬时作业</a><ul><li><a href="#">3.2.1 TransientProducerScheduler</a></li><li><a href="#">3.2.2 注册瞬时作业</a></li><li><a href="#">3.2.3 ProducerJob</a></li></ul></li><li><a href="#">3.3 小结</a></li></ul></li><li><a href="#">4. TaskLaunchScheduledService 提交任务</a><ul><li><a href="#">4.1 创建 Fenzo 任务请求</a></li><li><a href="#">4.2 AppConstraintEvaluator</a></li><li><a href="#">4.3 将任务请求分配到 Mesos Offer</a></li><li><a href="#">4.4 创建 Mesos 任务信息</a><ul><li><a href="#">4.4.1 创建单个 Mesos 任务信息</a></li></ul></li><li><a href="#">4.5 将任务运行时上下文放入运行时队列</a></li><li><a href="#">4.6 从队列中删除已运行的作业</a></li><li><a href="#">4.7 提交任务给 Mesos</a></li></ul></li><li><a href="#">5. TaskExecutor 执行任务</a><ul><li><a href="#">5.1 TaskThread</a></li><li><a href="#">5.2 DaemonTaskScheduler</a></li></ul></li><li><a href="#">6. SchedulerEngine 处理任务的状态变更</a></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Cloud 调度主流程</strong>。对应到 Elastic-Job-Lite 源码解析文章如下:</p><ul><li><a href="http://www.iocoder.cn/Elastic-Job/job-init/?self">《Elastic-Job-Lite 源码分析 —— 作业初始化》</a></li><li><a href="http://www.iocoder.cn/Elastic-Job/job-execute/?self">《Elastic-Job-Lite 源码分析 —— 作业执行》</a></li><li><a href="http://www.iocoder.cn/Elastic-Job/job-sharding/">《Elastic-Job-Lite 源码分析 —— 作业分片》</a></li></ul><p>如果你阅读过以下文章,有助于对本文的理解:</p><ul><li><a href="http://www.infoq.com/cn/news/2016/09/Mesos-Elastic-Job-Cloud" rel="external nofollow noopener noreferrer" target="_blank">《基于Mesos的当当作业云Elastic Job Cloud》</a></li><li><a href="https://segmentfault.com/a/1190000007723430" rel="external nofollow noopener noreferrer" target="_blank">《由浅入深 | 如何优雅地写一个Mesos Framework》</a></li></ul><p>😈 另外,笔者假设你已经对 <strong><a href="http://www.iocoder.cn/categories/Elastic-Job/?self">《Elastic-Job-Lite 源码分析系列》</a></strong> 有一定的了解。</p><p>本文涉及到主体类的类图如下( <a href="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/01.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/01.png" alt=""></p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><p>Elastic-Job-Cloud 基于 Mesos 实现分布式作业调度,或者说 Elastic-Job-Cloud 是 Mesos 上的 框架( Framework )。</p><p>一个 Mesos 框架由两部分组成:</p><ul><li>控制器部分,称为调度器( Scheduler )。</li><li>工作单元部分,称为执行器( Executor )。</li></ul><p>Elastic-Job-Cloud 由两个项目组成:</p><ul><li>Elastic-Job-Cloud-Scheduler,实现调度器,实现类为 <code>com.dangdang.ddframe.job.cloud.scheduler.mesos.SchedulerEngine</code>。</li><li>Elastic-Job-Cloud-Executor,实现执行器,实现类为 <code>com.dangdang.ddframe.job.cloud.executor.TaskExecutor</code>。</li></ul><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/11.png" alt=""></p><p>本文略微<strong>“啰嗦”</strong>,请保持<strong>耐心</strong>。搭配<a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《用Mesos框架构建分布式应用》</a>一起阅读,理解难度降低 99%。OK,开始我们的 Cloud 之旅。</p><h1 id="2-作业执行类型"><a href="#2-作业执行类型" class="headerlink" title="2. 作业执行类型"></a>2. 作业执行类型</h1><p>在 Elastic-Job-Cloud,作业执行分成两种类型:</p><ul><li>常驻作业</li></ul><blockquote><p>常驻作业是作业一旦启动,无论运行与否均占用系统资源;<br>常驻作业适合初始化时间长、触发间隔短、实时性要求高的作业,要求资源配备充足。</p></blockquote><ul><li>瞬时作业</li></ul><blockquote><p>瞬时作业是在作业启动时占用资源,运行完成后释放资源。<br>瞬时作业适合初始化时间短、触发间隔长、允许延迟的作业,一般用于资源不太充分,或作业要求的资源多,适合资源错峰使用的场景。</p></blockquote><p>Elastic-Job-Cloud 不同于 Elastic-Job-Lite 去中心化执行调度,转变为 <strong>Mesos Framework 的中心节点调度</strong>。这里不太理解,没关系,下文看到具体代码就能明白了。</p><p>常驻作业、瞬时作业在调度中会略有不同,大体<strong>粗略</strong>流程如下:</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/02.png" alt=""></p><p>下面,我们针对每个过程一节一节解析。</p><h1 id="3-Producer-发布任务"><a href="#3-Producer-发布任务" class="headerlink" title="3. Producer 发布任务"></a>3. Producer 发布任务</h1><p>在上文<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-config/?self">《Elastic-Job-Cloud 源码分析 —— 作业配置》的「3.1.1 操作云作业配置」</a>可以看到添加云作业配置后,Elastic-Job-Cloud-Scheduler 会执行<strong>作业调度</strong>,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// ProducerManager.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 调度作业.</span></div><div class="line"><span class="comment">* </span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> jobConfig 作业配置</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">schedule</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> <span class="comment">// 应用 或 作业 被禁用,不调度</span></div><div class="line"> <span class="keyword">if</span> (disableAppService.isDisabled(jobConfig.getAppName()) || disableJobService.isDisabled(jobConfig.getJobName())) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (CloudJobExecutionType.TRANSIENT == jobConfig.getJobExecutionType()) { <span class="comment">// 瞬时作业</span></div><div class="line"> transientProducerScheduler.register(jobConfig);</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (CloudJobExecutionType.DAEMON == jobConfig.getJobExecutionType()) { <span class="comment">// 常驻作业</span></div><div class="line"> readyService.addDaemon(jobConfig.getJobName());</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>瞬时作业和常驻作业在调度上会有一定的不同。</li></ul><h2 id="3-1-常驻作业"><a href="#3-1-常驻作业" class="headerlink" title="3.1 常驻作业"></a>3.1 常驻作业</h2><p>常驻作业在调度时,直接添加到待执行作业队列。What?岂不是马上就运行了!No No No,答案在「5. TaskExecutor 执行任务」,这里先打住。</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// ReadyService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 将常驻作业放入待执行队列.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> jobName 作业名称</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addDaemon</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (regCenter.getNumChildren(ReadyNode.ROOT) > env.getFrameworkConfiguration().getJobStateQueueSize()) {</div><div class="line"> log.warn(<span class="string">"Cannot add daemon job, caused by read state queue size is larger than {}."</span>, env.getFrameworkConfiguration().getJobStateQueueSize());</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> Optional<CloudJobConfiguration> cloudJobConfig = configService.load(jobName);</div><div class="line"> <span class="keyword">if</span> (!cloudJobConfig.isPresent() || CloudJobExecutionType.DAEMON != cloudJobConfig.get().getJobExecutionType() || runningService.isJobRunning(jobName)) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 添加到待执行队列</span></div><div class="line"> regCenter.persist(ReadyNode.getReadyJobNodePath(jobName), <span class="string">"1"</span>);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// ReadyNode.java</span></div><div class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ReadyNode</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">static</span> <span class="keyword">final</span> String ROOT = StateNode.ROOT + <span class="string">"/ready"</span>;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String READY_JOB = ROOT + <span class="string">"/%s"</span>; <span class="comment">// %s = ${JOB_NAME}</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>ReadyService,待执行作业队列服务,提供对待执行作业队列的各种操作方法。</li><li><p><strong>待执行作业队列</strong>存储在注册中心( Zookeeper )的<strong>持久</strong>数据节点 <code>/${NAMESPACE}/state/ready/${JOB_NAME}</code>,存储值为待执行次数。例如此处,待执行次数为 <code>1</code>。使用 zkClient 查看如下:</p> <figure class="highlight shell"><table><tr><td class="code"><pre><div class="line">[zk: localhost:2181(CONNECTED) 4] ls /elastic-job-cloud/state/ready</div><div class="line">[test_job_simple]</div><div class="line">[zk: localhost:2181(CONNECTED) 5] get /elastic-job-cloud/state/ready/test_job_simple</div><div class="line">1</div></pre></td></tr></table></figure></li><li><p>在运维平台,我们可以看到待执行作业队列:</p><p> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/10.png" alt=""> </p></li><li><p>从官方的 RoadMap 来看,<strong>待执行作业队列</strong>未来会使用 Redis 存储以提高性能。</p><blockquote><p>FROM <a href="http://elasticjob.io/docs/elastic-job-cloud/03-design/roadmap/" rel="external nofollow noopener noreferrer" target="_blank">http://elasticjob.io/docs/elastic-job-cloud/03-design/roadmap/</a><br>Redis Based Queue Improvement</p></blockquote></li></ul><h2 id="3-2-瞬时作业"><a href="#3-2-瞬时作业" class="headerlink" title="3.2 瞬时作业"></a>3.2 瞬时作业</h2><p>瞬时作业在调度时,使用<strong>发布瞬时作业任务的调度器</strong>( TransientProducerScheduler )调度作业。当瞬时作业到达作业执行时间,添加到待执行作业队列。</p><h3 id="3-2-1-TransientProducerScheduler"><a href="#3-2-1-TransientProducerScheduler" class="headerlink" title="3.2.1 TransientProducerScheduler"></a>3.2.1 TransientProducerScheduler</h3><p>TransientProducerScheduler,发布瞬时作业任务的调度器,基于 Quartz 实现对瞬时作业的调度。初始化代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TransientProducerScheduler.java</span></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</div><div class="line"> scheduler = getScheduler();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> scheduler.start();</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> SchedulerException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(ex);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">private</span> Scheduler <span class="title">getScheduler</span><span class="params">()</span> </span>{</div><div class="line"> StdSchedulerFactory factory = <span class="keyword">new</span> StdSchedulerFactory();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> factory.initialize(getQuartzProperties());</div><div class="line"> <span class="keyword">return</span> factory.getScheduler();</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> SchedulerException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(ex);</div><div class="line"> }</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> Properties <span class="title">getQuartzProperties</span><span class="params">()</span> </span>{</div><div class="line"> Properties result = <span class="keyword">new</span> Properties();</div><div class="line"> result.put(<span class="string">"org.quartz.threadPool.class"</span>, SimpleThreadPool.class.getName());</div><div class="line"> result.put(<span class="string">"org.quartz.threadPool.threadCount"</span>, Integer.toString(Runtime.getRuntime().availableProcessors() * <span class="number">2</span>)); <span class="comment">// 线程池数量</span></div><div class="line"> result.put(<span class="string">"org.quartz.scheduler.instanceName"</span>, <span class="string">"ELASTIC_JOB_CLOUD_TRANSIENT_PRODUCER"</span>);</div><div class="line"> result.put(<span class="string">"org.quartz.plugin.shutdownhook.class"</span>, ShutdownHookPlugin.class.getName());</div><div class="line"> result.put(<span class="string">"org.quartz.plugin.shutdownhook.cleanShutdown"</span>, Boolean.TRUE.toString());</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure><h3 id="3-2-2-注册瞬时作业"><a href="#3-2-2-注册瞬时作业" class="headerlink" title="3.2.2 注册瞬时作业"></a>3.2.2 注册瞬时作业</h3><p>调用 <code>TransientProducerScheduler#register(...)</code> 方法,注册瞬时作业。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TransientProducerScheduler.java</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">final</span> TransientProducerRepository repository;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> String cron = jobConfig.getTypeConfig().getCoreConfig().getCron();</div><div class="line"> <span class="comment">// 添加 cron 作业集合</span></div><div class="line"> JobKey jobKey = buildJobKey(cron);</div><div class="line"> repository.put(jobKey, jobConfig.getJobName());</div><div class="line"> <span class="comment">// 调度 作业</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">if</span> (!scheduler.checkExists(jobKey)) {</div><div class="line"> scheduler.scheduleJob(buildJobDetail(jobKey), buildTrigger(jobKey.getName()));</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> SchedulerException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(ex);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#buildJobKey(...)</code> 方法,创建 Quartz JobKey。你会发现很有意思的使用的是 <code>cron</code> 参数作为主键。Why?在看下 <code>!scheduler.checkExists(jobKey)</code> 处,相同 JobKey( <code>cron</code> ) 的作业不重复注册到 Quartz Scheduler。Why?此处是一个优化,相同 <code>cron</code> 使用同一个 Quartz Job,Elastic-Job-Cloud-Scheduler 可能会注册大量的瞬时作业,如果一个瞬时作业创建一个 Quartz Job 太过浪费,特别是 <code>cron</code> 每分钟、每5分钟、每小时、每天已经覆盖了大量的瞬时作业的情况。因此,相同 <code>cron</code> 使用同一个 Quartz Job。</li><li><p>调用 <code>TransientProducerRepository#put(...)</code> 以 Quartz JobKey 为主键聚合作业。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TransientProducerRepository</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * cron 作业集合</span></div><div class="line"><span class="comment"> * key:作业Key</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ConcurrentHashMap<JobKey, List<String>> cronTasks = <span class="keyword">new</span> ConcurrentHashMap<>(<span class="number">256</span>, <span class="number">1</span>);</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">put</span><span class="params">(<span class="keyword">final</span> JobKey jobKey, <span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> remove(jobName);</div><div class="line"> List<String> taskList = cronTasks.get(jobKey);</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == taskList) {</div><div class="line"> taskList = <span class="keyword">new</span> CopyOnWriteArrayList<>();</div><div class="line"> taskList.add(jobName);</div><div class="line"> cronTasks.put(jobKey, taskList);</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (!taskList.contains(jobName)) {</div><div class="line"> taskList.add(jobName);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>#buildJobDetail(...)</code> 创建 Quartz Job 信息。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> JobDetail <span class="title">buildJobDetail</span><span class="params">(<span class="keyword">final</span> JobKey jobKey)</span> </span>{</div><div class="line"> JobDetail result = JobBuilder.newJob(ProducerJob.class) <span class="comment">// ProducerJob.java</span></div><div class="line"> .withIdentity(jobKey).build();</div><div class="line"> result.getJobDataMap().put(<span class="string">"repository"</span>, repository);</div><div class="line"> result.getJobDataMap().put(<span class="string">"readyService"</span>, readyService);</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>JobBuilder#newJob(...)</code> 的参数是 ProducerJob,下文会讲解到。</li></ul></li><li><p>调用 <code>#buildTrigger(...)</code> 创建 Quartz Trigger。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> Trigger <span class="title">buildTrigger</span><span class="params">(<span class="keyword">final</span> String cron)</span> </span>{</div><div class="line"> <span class="keyword">return</span> TriggerBuilder.newTrigger()</div><div class="line"> .withIdentity(cron)</div><div class="line"> .withSchedule(CronScheduleBuilder.cronSchedule(cron) <span class="comment">// cron</span></div><div class="line"> .withMisfireHandlingInstructionDoNothing())</div><div class="line"> .build();</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><h3 id="3-2-3-ProducerJob"><a href="#3-2-3-ProducerJob" class="headerlink" title="3.2.3 ProducerJob"></a>3.2.3 ProducerJob</h3><p>ProducerJob,当 Quartz Job 到达 <code>cron</code> 执行时间( 即作业执行时间),将相应的瞬时作业添加到待执行作业队列。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ProducerJob</span> <span class="keyword">implements</span> <span class="title">Job</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> TransientProducerRepository repository;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> ReadyService readyService;</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(<span class="keyword">final</span> JobExecutionContext context)</span> <span class="keyword">throws</span> JobExecutionException </span>{</div><div class="line"> List<String> jobNames = repository.get(context.getJobDetail().getKey());</div><div class="line"> <span class="keyword">for</span> (String each : jobNames) {</div><div class="line"> readyService.addTransient(each);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>TransientProducerRepository#get(...)</code> 方法,获得该 Job 对应的作业集合。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TransientProducerRepository</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * cron 作业集合</span></div><div class="line"><span class="comment"> * key:作业Key</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ConcurrentHashMap<JobKey, List<String>> cronTasks = <span class="keyword">new</span> ConcurrentHashMap<>(<span class="number">256</span>, <span class="number">1</span>);</div><div class="line"> </div><div class="line"> <span class="function">List<String> <span class="title">get</span><span class="params">(<span class="keyword">final</span> JobKey jobKey)</span> </span>{</div><div class="line"> List<String> result = cronTasks.get(jobKey);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span> == result ? Collections.<String>emptyList() : result;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>ReadyService#addTransient(...)</code> 方法,添加瞬时作业到待执行作业队列。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 将瞬时作业放入待执行队列.</span></div><div class="line"><span class="comment">* </span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> jobName 作业名称</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addTransient</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="keyword">if</span> (regCenter.getNumChildren(ReadyNode.ROOT) > env.getFrameworkConfiguration().getJobStateQueueSize()) {</div><div class="line"> log.warn(<span class="string">"Cannot add transient job, caused by read state queue size is larger than {}."</span>, env.getFrameworkConfiguration().getJobStateQueueSize());</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">//</span></div><div class="line"> Optional<CloudJobConfiguration> cloudJobConfig = configService.load(jobName);</div><div class="line"> <span class="keyword">if</span> (!cloudJobConfig.isPresent() || CloudJobExecutionType.TRANSIENT != cloudJobConfig.get().getJobExecutionType()) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// </span></div><div class="line"> String readyJobNode = ReadyNode.getReadyJobNodePath(jobName);</div><div class="line"> String times = regCenter.getDirectly(readyJobNode);</div><div class="line"> <span class="keyword">if</span> (cloudJobConfig.get().getTypeConfig().getCoreConfig().isMisfire()) {</div><div class="line"> regCenter.persist(readyJobNode, Integer.toString(<span class="keyword">null</span> == times ? <span class="number">1</span> : Integer.parseInt(times) + <span class="number">1</span>));</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> regCenter.persist(ReadyNode.getReadyJobNodePath(jobName), <span class="string">"1"</span>);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><strong>添加瞬时作业到待执行作业队列</strong> 和 <strong>添加常驻作业到待执行作业队列</strong>基本是一致的。</li><li>当作业配置允许 <code>misfire</code>,则不断累积作业可执行次数。</li></ul></li></ul><h2 id="3-3-小结"><a href="#3-3-小结" class="headerlink" title="3.3 小结"></a>3.3 小结</h2><p>无论是常驻作业还是瞬时作业,都会加入到<strong>待执行作业队列</strong>。目前我们看到瞬时作业的每次调度是 TransientProducerScheduler 负责。那么常驻作业的每次调度呢?「5. TaskExecutor 执行任务」会看到它的调度,这是 Elastic-Job-Cloud 设计巧妙有趣的地方。</p><h1 id="4-TaskLaunchScheduledService-提交任务"><a href="#4-TaskLaunchScheduledService-提交任务" class="headerlink" title="4. TaskLaunchScheduledService 提交任务"></a>4. TaskLaunchScheduledService 提交任务</h1><p>TaskLaunchScheduledService,任务提交调度服务。它继承 Guava AbstractScheduledService 实现定时将待执行作业队列的作业提交到 Mesos 进行调度执行。实现<strong>定时</strong>代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TaskLaunchScheduledService</span> <span class="keyword">extends</span> <span class="title">AbstractScheduledService</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">protected</span> String <span class="title">serviceName</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="string">"task-launch-processor"</span>;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">protected</span> Scheduler <span class="title">scheduler</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> Scheduler.newFixedDelaySchedule(<span class="number">2</span>, <span class="number">10</span>, TimeUnit.SECONDS);</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">runOneIteration</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="comment">// .... 省略代码</span></div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// ... 省略部分方法</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>每 10 秒执行提交任务( <code>#runOneIteration()</code> )。对 Guava AbstractScheduledService 不了解的同学,可以阅读完本文后 Google 下。因为是通过每 10 秒轮询的方式提交任务,所以<strong>瞬时作业</strong>的执行时间不是非常严格,存在略有延迟,这个实际在使用需要注意的。那<strong>常驻作业</strong>呢,看完本文,你就会知道答案。</li></ul><p><code>#runOneIteration()</code> 方法相对比较复杂,我们一块一块拆解,<strong>耐心</strong>理解。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">runOneIteration</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> System.out.println(<span class="string">"runOneIteration:"</span> + <span class="keyword">new</span> Date());</div><div class="line"> <span class="comment">// 创建 Fenzo 任务请求</span></div><div class="line"> LaunchingTasks launchingTasks = <span class="keyword">new</span> LaunchingTasks(facadeService.getEligibleJobContext());</div><div class="line"> List<TaskRequest> taskRequests = launchingTasks.getPendingTasks();</div><div class="line"> <span class="comment">// 获取所有正在运行的云作业App https://github.com/Netflix/Fenzo/wiki/Constraints</span></div><div class="line"> <span class="keyword">if</span> (!taskRequests.isEmpty()) {</div><div class="line"> AppConstraintEvaluator.getInstance().loadAppRunningState();</div><div class="line"> }</div><div class="line"> <span class="comment">// 将任务请求分配到 Mesos Offer</span></div><div class="line"> Collection<VMAssignmentResult> vmAssignmentResults = taskScheduler.scheduleOnce(taskRequests, LeasesQueue.getInstance().drainTo()).getResultMap().values();</div><div class="line"> <span class="comment">// 创建 Mesos 任务请求</span></div><div class="line"> List<TaskContext> taskContextsList = <span class="keyword">new</span> LinkedList<>(); <span class="comment">// 任务运行时上下文集合</span></div><div class="line"> Map<List<Protos.OfferID>, List<Protos.TaskInfo>> offerIdTaskInfoMap = <span class="keyword">new</span> HashMap<>(); <span class="comment">// Mesos 任务信息集合</span></div><div class="line"> <span class="keyword">for</span> (VMAssignmentResult each: vmAssignmentResults) {</div><div class="line"> List<VirtualMachineLease> leasesUsed = each.getLeasesUsed();</div><div class="line"> List<Protos.TaskInfo> taskInfoList = <span class="keyword">new</span> ArrayList<>(each.getTasksAssigned().size() * <span class="number">10</span>);</div><div class="line"> taskInfoList.addAll(getTaskInfoList(</div><div class="line"> launchingTasks.getIntegrityViolationJobs(vmAssignmentResults), <span class="comment">// 获得作业分片不完整的作业集合</span></div><div class="line"> each, leasesUsed.get(<span class="number">0</span>).hostname(), leasesUsed.get(<span class="number">0</span>).getOffer()));</div><div class="line"> <span class="keyword">for</span> (Protos.TaskInfo taskInfo : taskInfoList) {</div><div class="line"> taskContextsList.add(TaskContext.from(taskInfo.getTaskId().getValue()));</div><div class="line"> }</div><div class="line"> offerIdTaskInfoMap.put(getOfferIDs(leasesUsed), <span class="comment">// 获得 Offer ID 集合</span></div><div class="line"> taskInfoList);</div><div class="line"> }</div><div class="line"> <span class="comment">// 遍历任务运行时上下文</span></div><div class="line"> <span class="keyword">for</span> (TaskContext each : taskContextsList) {</div><div class="line"> <span class="comment">// 将任务运行时上下文放入运行时队列</span></div><div class="line"> facadeService.addRunning(each);</div><div class="line"> <span class="comment">// 发布作业状态追踪事件(State.TASK_STAGING)</span></div><div class="line"> jobEventBus.post(createJobStatusTraceEvent(each));</div><div class="line"> }</div><div class="line"> <span class="comment">// 从队列中删除已运行的作业</span></div><div class="line"> facadeService.removeLaunchTasksFromQueue(taskContextsList);</div><div class="line"> <span class="comment">// 提交任务给 Mesos</span></div><div class="line"> <span class="keyword">for</span> (Entry<List<OfferID>, List<TaskInfo>> each : offerIdTaskInfoMap.entrySet()) {</div><div class="line"> schedulerDriver.launchTasks(each.getKey(), each.getValue());</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (Throwable throwable) {</div><div class="line"> log.error(<span class="string">"Launch task error"</span>, throwable);</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> <span class="comment">// 清理 AppConstraintEvaluator 所有正在运行的云作业App</span></div><div class="line"> AppConstraintEvaluator.getInstance().clearAppRunningState();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><h2 id="4-1-创建-Fenzo-任务请求"><a href="#4-1-创建-Fenzo-任务请求" class="headerlink" title="4.1 创建 Fenzo 任务请求"></a>4.1 创建 Fenzo 任务请求</h2><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// #runOneIteration()</span></div><div class="line">LaunchingTasks launchingTasks = <span class="keyword">new</span> LaunchingTasks(facadeService.getEligibleJobContext());</div><div class="line">List<TaskRequest> taskRequests = launchingTasks.getPendingTasks();</div></pre></td></tr></table></figure><ul><li><p>调用 <code>FacadeService#getEligibleJobContext()</code> 方法,获取有资格运行的作业。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// FacadeService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 获取有资格运行的作业.</span></div><div class="line"><span class="comment">* </span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 作业上下文集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> Collection<JobContext> <span class="title">getEligibleJobContext</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 从失效转移队列中获取所有有资格执行的作业上下文</span></div><div class="line"> Collection<JobContext> failoverJobContexts = failoverService.getAllEligibleJobContexts();</div><div class="line"> <span class="comment">// 从待执行队列中获取所有有资格执行的作业上下文</span></div><div class="line"> Collection<JobContext> readyJobContexts = readyService.getAllEligibleJobContexts(failoverJobContexts);</div><div class="line"> <span class="comment">// 合并</span></div><div class="line"> Collection<JobContext> result = <span class="keyword">new</span> ArrayList<>(failoverJobContexts.size() + readyJobContexts.size());</div><div class="line"> result.addAll(failoverJobContexts);</div><div class="line"> result.addAll(readyJobContexts);</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>FailoverService#getAllEligibleJobContexts()</code> 方法,从<strong>失效转移队列</strong>中获取所有有资格执行的作业上下文。<strong>TaskLaunchScheduledService 提交的任务还可能来自失效转移队列</strong>。本文暂时不解析失效转移队列相关实现,避免增加复杂度影响大家的理解,在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-failover/?self">《Elastic-Job-Cloud 源码分析 —— 作业失效转移》</a>详细解析。</li><li><p>调用 <code>ReadyService#getAllEligibleJobContexts(...)</code> 方法,从<strong>待执行队列</strong>中获取所有有资格执行的作业上下文。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// ReadyService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 从待执行队列中获取所有有资格执行的作业上下文.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> ineligibleJobContexts 无资格执行的作业上下文</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 有资格执行的作业上下文集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> Collection<JobContext> <span class="title">getAllEligibleJobContexts</span><span class="params">(<span class="keyword">final</span> Collection<JobContext> ineligibleJobContexts)</span> </span>{</div><div class="line"> <span class="comment">// 不存在 待执行队列</span></div><div class="line"> <span class="keyword">if</span> (!regCenter.isExisted(ReadyNode.ROOT)) {</div><div class="line"> <span class="keyword">return</span> Collections.emptyList();</div><div class="line"> }</div><div class="line"> <span class="comment">// 无资格执行的作业上下文 转换成 无资格执行的作业集合</span></div><div class="line"> Collection<String> ineligibleJobNames = Collections2.transform(ineligibleJobContexts, <span class="keyword">new</span> Function<JobContext, String>() {</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">apply</span><span class="params">(<span class="keyword">final</span> JobContext input)</span> </span>{</div><div class="line"> <span class="keyword">return</span> input.getJobConfig().getJobName();</div><div class="line"> }</div><div class="line"> });</div><div class="line"> <span class="comment">// 获取 待执行队列 有资格执行的作业上下文</span></div><div class="line"> List<String> jobNames = regCenter.getChildrenKeys(ReadyNode.ROOT);</div><div class="line"> List<JobContext> result = <span class="keyword">new</span> ArrayList<>(jobNames.size());</div><div class="line"> <span class="keyword">for</span> (String each : jobNames) {</div><div class="line"> <span class="keyword">if</span> (ineligibleJobNames.contains(each)) {</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 排除 作业配置 不存在的作业</span></div><div class="line"> Optional<CloudJobConfiguration> jobConfig = configService.load(each);</div><div class="line"> <span class="keyword">if</span> (!jobConfig.isPresent()) {</div><div class="line"> regCenter.remove(ReadyNode.getReadyJobNodePath(each));</div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (!runningService.isJobRunning(each)) { <span class="comment">// 排除 运行中 的作业</span></div><div class="line"> result.add(JobContext.from(jobConfig.get(), ExecutionType.READY));</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li></li></ul></li><li><p>JobContext,作业运行上下文。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// JobContext.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">JobContext</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> CloudJobConfiguration jobConfig;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> List<Integer> assignedShardingItems;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ExecutionType type;</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 通过作业配置创建作业运行上下文.</span></div><div class="line"><span class="comment"> * </span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> jobConfig 作业配置</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> type 执行类型</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 作业运行上下文</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> JobContext <span class="title">from</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig, <span class="keyword">final</span> ExecutionType type)</span> </span>{</div><div class="line"> <span class="keyword">int</span> shardingTotalCount = jobConfig.getTypeConfig().getCoreConfig().getShardingTotalCount();</div><div class="line"> <span class="comment">// 分片项</span></div><div class="line"> List<Integer> shardingItems = <span class="keyword">new</span> ArrayList<>(shardingTotalCount);</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < shardingTotalCount; i++) {</div><div class="line"> shardingItems.add(i);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> JobContext(jobConfig, shardingItems, type);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li></ul></li></ul><ul><li><p>LaunchingTasks,分配任务行为包。创建 LaunchingTasks 代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">LaunchingTasks</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业上下文集合</span></div><div class="line"><span class="comment"> * key:作业名</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Map<String, JobContext> eligibleJobContextsMap;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">LaunchingTasks</span><span class="params">(<span class="keyword">final</span> Collection<JobContext> eligibleJobContexts)</span> </span>{</div><div class="line"> eligibleJobContextsMap = <span class="keyword">new</span> HashMap<>(eligibleJobContexts.size(), <span class="number">1</span>);</div><div class="line"> <span class="keyword">for</span> (JobContext each : eligibleJobContexts) {</div><div class="line"> eligibleJobContextsMap.put(each.getJobConfig().getJobName(), each);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>LaunchingTasks#getPendingTasks()</code> 方法,获得待执行任务集合。<strong>这里要注意,每个作业如果有多个分片,则会生成多个待执行任务,即此处完成了作业分片</strong>。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// LaunchingTasks.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 获得待执行任务</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 待执行任务</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function">List<TaskRequest> <span class="title">getPendingTasks</span><span class="params">()</span> </span>{</div><div class="line"> List<TaskRequest> result = <span class="keyword">new</span> ArrayList<>(eligibleJobContextsMap.size() * <span class="number">10</span>);</div><div class="line"> <span class="keyword">for</span> (JobContext each : eligibleJobContextsMap.values()) {</div><div class="line"> result.addAll(createTaskRequests(each));</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 创建待执行任务集合</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> jobContext 作业运行上下文</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 待执行任务集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">private</span> Collection<TaskRequest> <span class="title">createTaskRequests</span><span class="params">(<span class="keyword">final</span> JobContext jobContext)</span> </span>{</div><div class="line"> Collection<TaskRequest> result = <span class="keyword">new</span> ArrayList<>(jobContext.getAssignedShardingItems().size());</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> each : jobContext.getAssignedShardingItems()) {</div><div class="line"> result.add(<span class="keyword">new</span> JobTaskRequest(<span class="keyword">new</span> TaskContext(jobContext.getJobConfig().getJobName(), Collections.singletonList(each), jobContext.getType()), jobContext.getJobConfig()));</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// TaskContext.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TaskContext</span> </span>{</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 任务编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String id;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 任务元信息</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> MetaInfo metaInfo;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 执行类型</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ExecutionType type;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Mesos Slave 编号</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String slaveId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 是否闲置</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> idle;</div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">MetaInfo</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业名</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String jobName;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业分片项</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> List<Integer> shardingItems;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// ... 省略部分方法</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// JobTaskRequest.JAVA</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">JobTaskRequest</span> <span class="keyword">implements</span> <span class="title">TaskRequest</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> TaskContext taskContext;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> CloudJobConfiguration jobConfig;</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getId</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> taskContext.getId();</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">double</span> <span class="title">getCPUs</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> jobConfig.getCpuCount();</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">double</span> <span class="title">getMemory</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> jobConfig.getMemoryMB();</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// ... 省略部分方法</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#createTaskRequests(...)</code> 方法,<strong>将单个作业按照其作业分片总数拆分成一个或多个待执行任务集合</strong>。</li><li>TaskContext,任务运行时上下文。</li><li>JobTaskRequest,作业任务请求对象。 </li></ul></li><li>因为对象有点多,我们来贴一个 <code>LaunchingTasks#getPendingTasks()</code> 方法的返回结果。<br> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/03.png" alt=""></li></ul><p><strong>友情提示,代码可能比较多,请耐心观看。</strong></p><h2 id="4-2-AppConstraintEvaluator"><a href="#4-2-AppConstraintEvaluator" class="headerlink" title="4.2 AppConstraintEvaluator"></a>4.2 AppConstraintEvaluator</h2><p>在说 AppConstraintEvaluator 之前,我们先一起了<strong>简单</strong>解下 <a href="https://github.com/Netflix/Fenzo/wiki" rel="external nofollow noopener noreferrer" target="_blank">Netflix Fenzo</a>。</p><blockquote><p>FROM <a href="http://dockone.io/article/636" rel="external nofollow noopener noreferrer" target="_blank">http://dockone.io/article/636</a><br>Fenzo是一个在Mesos框架上应用的通用任务调度器。它可以让你通过实现各种优化策略的插件,来优化任务调度,同时这也有利于集群的自动缩放。</p></blockquote><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/05.png" alt=""></p><p>Elastic-Job-Cloud-Scheduler 基于 Fenzo 实现对 Mesos 的弹性资源分配。</p><p>例如,AppConstraintEvaluator,App 目标 Mesos Slave 适配度限制器,选择 Slave 时需要考虑其上是否运行有 App 的 Executor,如果没有运行 Executor 需要将其资源消耗考虑进适配计算算法中。它是 <a href="https://github.com/Netflix/Fenzo/blob/5de0e0861def4a655be35a9624e67318a6c0afac/fenzo-core/src/main/java/com/netflix/fenzo/ConstraintEvaluator.java" rel="external nofollow noopener noreferrer" target="_blank">Fenzo ConstraintEvaluator 接口</a> 在 Elastic-Job-Cloud-Scheduler 的自定义任务约束实现。通过这个任务约束,在下文调用 <code>TaskScheduler#scheduleOnce(...)</code> 方法调度任务所需资源时,会将 AppConstraintEvaluator 考虑进去。</p><p>那么作业任务请求( JobTaskRequest ) 是怎么关联上 AppConstraintEvaluator 的呢?</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// JobTaskRequest.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">JobTaskRequest</span> <span class="keyword">implements</span> <span class="title">TaskRequest</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="keyword">public</span> List<? extends ConstraintEvaluator> getHardConstraints() {</div><div class="line"> <span class="keyword">return</span> Collections.singletonList(AppConstraintEvaluator.getInstance());</div><div class="line"> }</div><div class="line"> </div><div class="line">}</div></pre></td></tr></table></figure><ul><li><a href="https://github.com/Netflix/Fenzo/blob/20d71b5c3213063fc938cd2841dc7569601d1d99/fenzo-core/src/main/java/com/netflix/fenzo/TaskRequest.java" rel="external nofollow noopener noreferrer" target="_blank">Fenzo TaskRequest 接口</a> 是 Fenzo 的任务请求接口,通过实现 <code>#getHardConstraints()</code> 方法,关联上 TaskRequest 和 ConstraintEvaluator。</li></ul><p>关联上之后,任务匹配 Mesos Slave 资源时,调用 <code>ConstraintEvaluator#evaluate(...)</code> 实现方法判断是否符合约束:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ConstraintEvaluator</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Result</span> </span>{</div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">boolean</span> isSuccessful;</div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String failureReason;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Inspects a target to decide whether or not it meets the constraints appropriate to a particular task.</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> taskRequest a description of the task to be assigned</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> targetVM a description of the host that is a potential match for the task</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> taskTrackerState the current status of tasks and task assignments in the system at large</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> a successful Result if the target meets the constraints enforced by this constraint evaluator, or</span></div><div class="line"><span class="comment"> * an unsuccessful Result otherwise</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Result <span class="title">evaluate</span><span class="params">(TaskRequest taskRequest, VirtualMachineCurrentState targetVM,</span></span></div><div class="line"><span class="function"><span class="params"> TaskTrackerState taskTrackerState)</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><p>OK,简单了解结束,有兴趣了解更多的同学,请点击<a href="https://github.com/Netflix/Fenzo/wiki/Constraints" rel="external nofollow noopener noreferrer" target="_blank">《Fenzo Wiki —— Constraints》</a>。下面来看看 Elastic-Job-Cloud-Scheduler 自定义实现的任务约束 AppConstraintEvaluator。</p><hr><p>调用 <code>AppConstraintEvaluator#loadAppRunningState()</code> 方法,加载当前运行中的<strong>云作业App</strong>,为 <code>AppConstraintEvaluator#evaluate(...)</code> 方法提供该数据。代码实现如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// AppConstraintEvaluator.java</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">final</span> Set<String> runningApps = <span class="keyword">new</span> HashSet<>();</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">loadAppRunningState</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">for</span> (MesosStateService.ExecutorStateInfo each : facadeService.loadExecutorInfo()) {</div><div class="line"> runningApps.add(each.getId());</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> JSONException | UniformInterfaceException | ClientHandlerException e) {</div><div class="line"> clearAppRunningState();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>FacadeService#loadExecutorInfo()</code> 方法,从 Mesos 获取所有正在运行的 Mesos 执行器( Executor )的信息。执行器和云作业App有啥关系?<strong>每个云作业App 即是一个 Elastic-Job-Cloud-Executor 实例。</strong>。<code>FacadeService#loadExecutorInfo()</code> 方法这里就不展开了,有兴趣的同学自己看下,主要是对 Mesos 的 API操作,我们来看下 <code>runningApps</code> 的结果:</p><p> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/04.png" alt=""></p></li></ul><hr><p>调用 <code>TaskScheduler#scheduleOnce(...)</code> 方法调度提交任务所需资源时,会调用 <code>ConstraintEvaluator#loadAppRunningState()</code> 检查分配的资源是否符合任务的约束条件。<code>AppConstraintEvaluator#loadAppRunningState()</code> 实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// AppConstraintEvaluator.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> Result <span class="title">evaluate</span><span class="params">(<span class="keyword">final</span> TaskRequest taskRequest, <span class="keyword">final</span> VirtualMachineCurrentState targetVM, <span class="keyword">final</span> TaskTrackerState taskTrackerState)</span> </span>{</div><div class="line"> <span class="keyword">double</span> assigningCpus = <span class="number">0.0</span>d;</div><div class="line"> <span class="keyword">double</span> assigningMemoryMB = <span class="number">0.0</span>d;</div><div class="line"> <span class="keyword">final</span> String slaveId = targetVM.getAllCurrentOffers().iterator().next().getSlaveId().getValue();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 判断当前分配的 Mesos Slave 是否运行着该作业任务请求对应的云作业App</span></div><div class="line"> <span class="keyword">if</span> (isAppRunningOnSlave(taskRequest.getId(), slaveId)) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Result(<span class="keyword">true</span>, <span class="string">""</span>);</div><div class="line"> }</div><div class="line"> <span class="comment">// 判断当前分配的 Mesos Slave 启动云作业App 是否超过资源限制</span></div><div class="line"> Set<String> calculatedApps = <span class="keyword">new</span> HashSet<>(); <span class="comment">// 已计算作业App集合</span></div><div class="line"> List<TaskRequest> taskRequests = <span class="keyword">new</span> ArrayList<>(targetVM.getTasksCurrentlyAssigned().size() + <span class="number">1</span>);</div><div class="line"> taskRequests.add(taskRequest);</div><div class="line"> <span class="keyword">for</span> (TaskAssignmentResult each : targetVM.getTasksCurrentlyAssigned()) { <span class="comment">// 当前已经分配作业请求</span></div><div class="line"> taskRequests.add(each.getRequest());</div><div class="line"> }</div><div class="line"> <span class="keyword">for</span> (TaskRequest each : taskRequests) {</div><div class="line"> assigningCpus += each.getCPUs();</div><div class="line"> assigningMemoryMB += each.getMemory();</div><div class="line"> <span class="keyword">if</span> (isAppRunningOnSlave(each.getId(), slaveId)) { <span class="comment">// 作业App已经启动</span></div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"> CloudAppConfiguration assigningAppConfig = getAppConfiguration(each.getId());</div><div class="line"> <span class="keyword">if</span> (!calculatedApps.add(assigningAppConfig.getAppName())) { <span class="comment">// 是否已经计算该App</span></div><div class="line"> <span class="keyword">continue</span>;</div><div class="line"> }</div><div class="line"> assigningCpus += assigningAppConfig.getCpuCount();</div><div class="line"> assigningMemoryMB += assigningAppConfig.getMemoryMB();</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> LackConfigException ex) {</div><div class="line"> log.warn(<span class="string">"Lack config, disable {}"</span>, getName(), ex);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Result(<span class="keyword">true</span>, <span class="string">""</span>);</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (assigningCpus > targetVM.getCurrAvailableResources().cpuCores()) { <span class="comment">// cpu</span></div><div class="line"> log.debug(<span class="string">"Failure {} {} cpus:{}/{}"</span>, taskRequest.getId(), slaveId, assigningCpus, targetVM.getCurrAvailableResources().cpuCores());</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Result(<span class="keyword">false</span>, String.format(<span class="string">"cpu:%s/%s"</span>, assigningCpus, targetVM.getCurrAvailableResources().cpuCores()));</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (assigningMemoryMB > targetVM.getCurrAvailableResources().memoryMB()) { <span class="comment">// memory</span></div><div class="line"> log.debug(<span class="string">"Failure {} {} mem:{}/{}"</span>, taskRequest.getId(), slaveId, assigningMemoryMB, targetVM.getCurrAvailableResources().memoryMB());</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Result(<span class="keyword">false</span>, String.format(<span class="string">"mem:%s/%s"</span>, assigningMemoryMB, targetVM.getCurrAvailableResources().memoryMB()));</div><div class="line"> }</div><div class="line"> log.debug(<span class="string">"Success {} {} cpus:{}/{} mem:{}/{}"</span>, taskRequest.getId(), slaveId, assigningCpus, targetVM.getCurrAvailableResources()</div><div class="line"> .cpuCores(), assigningMemoryMB, targetVM.getCurrAvailableResources().memoryMB());</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Result(<span class="keyword">true</span>, String.format(<span class="string">"cpus:%s/%s mem:%s/%s"</span>, assigningCpus, targetVM.getCurrAvailableResources()</div><div class="line"> .cpuCores(), assigningMemoryMB, targetVM.getCurrAvailableResources().memoryMB()));</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#isAppRunningOnSlave()</code> 方法,判断当前分配的 Mesos Slave 是否运行着该作业任务请求对应的云作业App。若云作业App未运行,则该作业任务请求提交给 Mesos 后,该 Mesos Slave 会启动该云作业 App,App 本身会占用一定的 <code>CloudAppConfiguration#cpu</code> 和 <code>CloudAppConfiguration#memory</code>,计算时需要统计,避免超过当前 Mesos Slave 剩余 <code>cpu</code> 和 <code>memory</code>。</li><li>当计算符合约束时,返回 <code>Result(true, ...)</code>;否则,返回 <code>Result(false, ...)</code>。</li><li>TODO 异常为啥返回true。</li></ul><h2 id="4-3-将任务请求分配到-Mesos-Offer"><a href="#4-3-将任务请求分配到-Mesos-Offer" class="headerlink" title="4.3 将任务请求分配到 Mesos Offer"></a>4.3 将任务请求分配到 Mesos Offer</h2><p>我们先<strong>简单</strong>了解下 Elastic-Job-Cloud-Scheduler 实现的 Mesos Scheduler 类 <code>com.dangdang.ddframe.job.cloud.scheduler.mesos.SchedulerEngine</code>。调度器的主要职责之一:<strong>在接受到的 Offer 上启动任务</strong>。SchedulerEngine 接收到资源 Offer,先存储到资源预占队列( LeasesQueue ),等到作业被调度需要启动任务时进行使用。存储到资源预占队列实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">SchedulerEngine</span> <span class="keyword">implements</span> <span class="title">Scheduler</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">resourceOffers</span><span class="params">(<span class="keyword">final</span> SchedulerDriver schedulerDriver, <span class="keyword">final</span> List<Protos.Offer> offers)</span> </span>{</div><div class="line"> <span class="keyword">for</span> (Protos.Offer offer: offers) {</div><div class="line"> log.trace(<span class="string">"Adding offer {} from host {}"</span>, offer.getId(), offer.getHostname());</div><div class="line"> LeasesQueue.getInstance().offer(offer);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>org.apache.mesos.Scheduler</code>,Mesos 调度器<strong>接口</strong>,实现该接口成为自定义 Mesos 调度器。</li><li><p>实现 <code>#resourceOffers(...)</code> 方法,有新的资源 Offer 时,会进行调用。在 SchedulerEngine 会调用 <code>#offer(...)</code> 方法,存储 Offer 到资源预占队列,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">LeasesQueue</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 单例</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> LeasesQueue INSTANCE = <span class="keyword">new</span> LeasesQueue();</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> BlockingQueue<VirtualMachineLease> queue = <span class="keyword">new</span> LinkedBlockingQueue<>();</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 获取实例.</span></div><div class="line"><span class="comment"> * </span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 单例对象</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> LeasesQueue <span class="title">getInstance</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> INSTANCE;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 添加资源至队列预占.</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> offer 资源</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">offer</span><span class="params">(<span class="keyword">final</span> Protos.Offer offer)</span> </span>{</div><div class="line"> queue.offer(<span class="keyword">new</span> VMLeaseObject(offer));</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// ... 省略 #drainTo() 方法,下文解析。</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>VMLeaseObject,<a href="#">Netflix Fenzo</a> 对 Mesos Offer 的抽象包装,点击<a href="https://github.com/Netflix/Fenzo/blob/faa8a4dd411fff1792c9d788d1288a11e3635ba7/fenzo-core/src/main/java/com/netflix/fenzo/plugins/VMLeaseObject.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>查看实现代码,马上会看到它的用途。</li></ul></li></ul><p>另外,可能有同学对 Mesos Offer 理解比较生涩,Offer 定义如下:</p><blockquote><p>FROM <a href="https://segmentfault.com/a/1190000007723430" rel="external nofollow noopener noreferrer" target="_blank">https://segmentfault.com/a/1190000007723430</a><br>Offer是Mesos资源的抽象,比如说有多少CPU、多少memory,disc是多少,都放在Offer里,打包给一个Framework,然后Framework来决定到底怎么用这个Offer。</p></blockquote><hr><p>OK,知识铺垫完成,回到本小节的重心:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// #runOneIteration()</span></div><div class="line">Collection<VMAssignmentResult> vmAssignmentResults = taskScheduler.scheduleOnce(taskRequests, LeasesQueue.getInstance().drainTo()).getResultMap().values();</div><div class="line"></div><div class="line"><span class="comment">// LeasesQueue.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">LeasesQueue</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> BlockingQueue<VirtualMachineLease> queue = <span class="keyword">new</span> LinkedBlockingQueue<>();</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> List<VirtualMachineLease> <span class="title">drainTo</span><span class="params">()</span> </span>{</div><div class="line"> List<VirtualMachineLease> result = <span class="keyword">new</span> ArrayList<>(queue.size());</div><div class="line"> queue.drainTo(result);</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>调用 <code>TaskScheduler#scheduleOnce(...)</code> 方法,将任务请求分配到 Mesos Offer。通过 Fenzo TaskScheduler 实现对多个任务分配到多个 Mesos Offer 的<strong>合理优化分配</strong>。这是一个相对复杂的问题。为什么这么说呢?</p><blockquote><p>FROM <a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》</a> P76<br>将任务匹配到 offer 上,首次适配通常是最好的算法。你可能会想,如果在更多的工作里尝试计算出匹配该 offer 的优化组合,可能比首次适配更能高效地利用 offer。这绝对是正确的,但是要考虑如下这些方面:对于启动所有等待运行的任务来说,集群里要么有充足的资源要么没有。如果资源很多,那么首次适配肯定一直都能保证每个任务的启动。如果资源不够,怎么都无法启动所有任务。因此,编写代码选择接下来会运行哪个任务是很自然的,这样才能保证服务的质量。只有当资源刚够用时,才需要更为精细的打包算法。不幸的是,这里的问题 —— 通常称为背包问题( Knapsack problem ) —— 是一个众所周知的 NP 完全问题。NP 完全问题指的是需要相当长时间才能找到最优解决方案的问题,并且没有任何已知道技巧能够快速解决这类问题。</p></blockquote><p>举个简单的例子,只考虑 <code>memory</code> 资源情况下,有一台 Slave 内存为 8GB ,现在要运行三个 1GB 的作业和 5GB 的作业。其中 5GB 的作业在 1GB 运行多次之后才执行。 </p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/06.png" alt=""></p><p>实际情况会比图更加复杂的多的多。通过使用 Fenzo ,可以很方便的,并且令人满意的分配。为了让你对 Fenzo 有更加透彻的理解,这里再引用一段对其的介绍:</p><blockquote><p>FROM <a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》</a> P80<br><strong>调用库函数 Fenzo</strong><br>Fenzo 是 Nettflix 在 2015 年夏天发布的库函数。Fenzo 为基于 java 的调度器提供了完整的解决方案,完成 offer 缓冲,多任务启动,以及软和硬约束条件的匹配。就算不是所有的,也是很多调度器都能够受益于使用 Fenzo 来完成计算任务分配,而不用自己编写 offer 缓冲、打包和放置路由等。</p></blockquote><p>下面,来看两次 <code>TaskScheduler#scheduleOnce(...)</code> 的返回:</p><ul><li>第一次调度:<img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/07.png" alt=""></li><li>第二次调度:<img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/08.png" alt=""></li><li><p><code>com.netflix.fenzo.VMAssignmentResult</code>,每台主机分配任务结果。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">VMAssignmentResult</span> </span>{</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 主机</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String hostname;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 使用的 Mesos Offer</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> List<VirtualMachineLease> leasesUsed;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 分配的任务</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Set<TaskAssignmentResult> tasksAssigned;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><p>受限于笔者的能力,建议你可以在阅读如下文章,更透彻的理解 TaskScheduler :</p><ul><li><a href="https://github.com/Netflix/Fenzo/wiki/How-to-use-Fenzo" rel="external nofollow noopener noreferrer" target="_blank">《Fenzo Wiki —— Constraints》</a></li><li><a href="https://github.com/Netflix/Fenzo/wiki/Building-Your-Scheduler" rel="external nofollow noopener noreferrer" target="_blank">《Fenzo Wiki —— Building Your Scheduler》</a></li><li><a href="https://github.com/Netflix/Fenzo/wiki/Scheduling-Tasks" rel="external nofollow noopener noreferrer" target="_blank">《Fenzo Wiki —— Scheduling Tasks》</a></li><li><a href="https://github.com/Netflix/Fenzo/wiki/Insights#how-to-learn-which-tasks-are-assigned-to-which-hosts" rel="external nofollow noopener noreferrer" target="_blank">《Fenzo Wiki —— How to Learn Which Tasks Are Assigned to Which Hosts》</a></li></ul><h2 id="4-4-创建-Mesos-任务信息"><a href="#4-4-创建-Mesos-任务信息" class="headerlink" title="4.4 创建 Mesos 任务信息"></a>4.4 创建 Mesos 任务信息</h2><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// #runOneIteration()</span></div><div class="line">List<TaskContext> taskContextsList = <span class="keyword">new</span> LinkedList<>(); <span class="comment">// 任务运行时上下文集合</span></div><div class="line">Map<List<Protos.OfferID>, List<Protos.TaskInfo>> offerIdTaskInfoMap = <span class="keyword">new</span> HashMap<>(); <span class="comment">// Mesos 任务信息集合</span></div><div class="line"><span class="keyword">for</span> (VMAssignmentResult each: vmAssignmentResults) {</div><div class="line"> List<VirtualMachineLease> leasesUsed = each.getLeasesUsed();</div><div class="line"> List<Protos.TaskInfo> taskInfoList = <span class="keyword">new</span> ArrayList<>(each.getTasksAssigned().size() * <span class="number">10</span>);</div><div class="line"> taskInfoList.addAll(getTaskInfoList(</div><div class="line"> launchingTasks.getIntegrityViolationJobs(vmAssignmentResults), <span class="comment">// 获得作业分片不完整的作业集合</span></div><div class="line"> each, leasesUsed.get(<span class="number">0</span>).hostname(), leasesUsed.get(<span class="number">0</span>).getOffer()));</div><div class="line"> <span class="keyword">for</span> (Protos.TaskInfo taskInfo : taskInfoList) {</div><div class="line"> taskContextsList.add(TaskContext.from(taskInfo.getTaskId().getValue()));</div><div class="line"> }</div><div class="line"> offerIdTaskInfoMap.put(getOfferIDs(leasesUsed), <span class="comment">// 获得 Offer ID 集合</span></div><div class="line"> taskInfoList);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p><code>offerIdTaskInfoMap</code>,Mesos 任务信息集合。key 和 value 都为相同 Mesos Slave Offer 和 任务。为什么?调用 <code>SchedulerDriver#launchTasks(...)</code> 方法提交<strong>一次</strong>任务时,必须保证所有任务和 Offer 在相同 Mesos Slave 上。</p><blockquote><p>FROM FROM <a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》</a> P61<br><strong>组合 offer</strong><br>latchTasks 接受 offer 列表为输入,这就允许用户将一些相同 slave 的 offer 组合起来,从而将这些 offer 的资源放到池里。它还能接受任务列表为输入,这样就能够启动适合给定 offer 的足够多的任务。注意所有任务和 offer 都必须是同一台 slave —— 如果不在同一台 slave 上,launchTasks 就会失败。如果想在多台 slave 上启动任务,多次调用 latchTasks 即可。</p></blockquote></li><li><p>调用 <code>LaunchingTasks#getIntegrityViolationJobs(...)</code> 方法,获得作业分片不完整的作业集合。<strong>一个作业有多个分片,因为 Mesos Offer 不足,导致有部分分片不能执行,则整个作业都不进行执行</strong>。代码实现如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// LaunchingTasks.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 获得作业分片不完整的作业集合</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> vmAssignmentResults 主机分配任务结果集合</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 作业分片不完整的作业集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function">Collection<String> <span class="title">getIntegrityViolationJobs</span><span class="params">(<span class="keyword">final</span> Collection<VMAssignmentResult> vmAssignmentResults)</span> </span>{</div><div class="line"> Map<String, Integer> assignedJobShardingTotalCountMap = getAssignedJobShardingTotalCountMap(vmAssignmentResults);</div><div class="line"> Collection<String> result = <span class="keyword">new</span> HashSet<>(assignedJobShardingTotalCountMap.size(), <span class="number">1</span>);</div><div class="line"> <span class="keyword">for</span> (Map.Entry<String, Integer> entry : assignedJobShardingTotalCountMap.entrySet()) {</div><div class="line"> JobContext jobContext = eligibleJobContextsMap.get(entry.getKey());</div><div class="line"> <span class="keyword">if</span> (ExecutionType.FAILOVER != jobContext.getType() <span class="comment">// 不包括 FAILOVER 执行类型的作业</span></div><div class="line"> && !entry.getValue().equals(jobContext.getJobConfig().getTypeConfig().getCoreConfig().getShardingTotalCount())) {</div><div class="line"> log.warn(<span class="string">"Job {} is not assigned at this time, because resources not enough to run all sharding instances."</span>, entry.getKey());</div><div class="line"> result.add(entry.getKey());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 获得每个作业分片数集合</span></div><div class="line"><span class="comment">* key:作业名</span></div><div class="line"><span class="comment">* value:分片总数</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> vmAssignmentResults 主机分配任务结果集合</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 每个作业分片数集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">private</span> Map<String, Integer> <span class="title">getAssignedJobShardingTotalCountMap</span><span class="params">(<span class="keyword">final</span> Collection<VMAssignmentResult> vmAssignmentResults)</span> </span>{</div><div class="line"> Map<String, Integer> result = <span class="keyword">new</span> HashMap<>(eligibleJobContextsMap.size(), <span class="number">1</span>);</div><div class="line"> <span class="keyword">for</span> (VMAssignmentResult vmAssignmentResult: vmAssignmentResults) {</div><div class="line"> <span class="keyword">for</span> (TaskAssignmentResult tasksAssigned: vmAssignmentResult.getTasksAssigned()) {</div><div class="line"> String jobName = TaskContext.from(tasksAssigned.getTaskId()).getMetaInfo().getJobName();</div><div class="line"> <span class="keyword">if</span> (result.containsKey(jobName)) {</div><div class="line"> result.put(jobName, result.get(jobName) + <span class="number">1</span>);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> result.put(jobName, <span class="number">1</span>);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><ul><li><p>调用 <code>#getTaskInfoList(...)</code> 方法,创建<strong>单个主机</strong>的 Mesos 任务信息集合。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">private</span> List<Protos.TaskInfo> getTaskInfoList(<span class="keyword">final</span> Collection<String> integrityViolationJobs, <span class="keyword">final</span> VMAssignmentResult vmAssignmentResult, <span class="keyword">final</span> String hostname, <span class="keyword">final</span> Protos.Offer offer) {</div><div class="line"> List<Protos.TaskInfo> result = <span class="keyword">new</span> ArrayList<>(vmAssignmentResult.getTasksAssigned().size());</div><div class="line"> <span class="keyword">for</span> (TaskAssignmentResult each: vmAssignmentResult.getTasksAssigned()) {</div><div class="line"> TaskContext taskContext = TaskContext.from(each.getTaskId());</div><div class="line"> String jobName = taskContext.getMetaInfo().getJobName();</div><div class="line"> <span class="keyword">if</span> (!integrityViolationJobs.contains(jobName) <span class="comment">// 排除作业分片不完整的任务</span></div><div class="line"> && !facadeService.isRunning(taskContext) <span class="comment">// 排除正在运行中的任务</span></div><div class="line"> && !facadeService.isJobDisabled(jobName)) { <span class="comment">// 排除被禁用的任务</span></div><div class="line"> <span class="comment">// 创建 Mesos 任务</span></div><div class="line"> Protos.TaskInfo taskInfo = getTaskInfo(offer, each);</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != taskInfo) {</div><div class="line"> result.add(taskInfo);</div><div class="line"> <span class="comment">// 添加任务主键和主机名称的映射</span></div><div class="line"> facadeService.addMapping(taskInfo.getTaskId().getValue(), hostname);</div><div class="line"> <span class="comment">// 通知 TaskScheduler 主机分配了这个任务</span></div><div class="line"> taskScheduler.getTaskAssigner().call(each.getRequest(), hostname);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#getTaskInfo(...)</code> 方法,创建单个 Mesos 任务,在<a href="#">「4.4.1 创建单个 Mesos 任务信息」</a>详细解析。</li><li><p>调用 <code>FacadeService#addMapping(...)</code> 方法,添加任务主键和主机名称的映射。通过该映射,可以根据任务主键查询到对应的主机名。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// FacadeService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 添加任务主键和主机名称的映射.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> taskId 任务主键</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> hostname 主机名称</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addMapping</span><span class="params">(<span class="keyword">final</span> String taskId, <span class="keyword">final</span> String hostname)</span> </span>{</div><div class="line"> runningService.addMapping(taskId, hostname);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// RunningService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 任务主键和主机名称的映射</span></div><div class="line"><span class="comment">* key: 任务主键</span></div><div class="line"><span class="comment">* value: 主机名称</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ConcurrentHashMap<String, String> TASK_HOSTNAME_MAPPER = <span class="keyword">new</span> ConcurrentHashMap<>(TASK_INITIAL_SIZE);</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addMapping</span><span class="params">(<span class="keyword">final</span> String taskId, <span class="keyword">final</span> String hostname)</span> </span>{</div><div class="line"> TASK_HOSTNAME_MAPPER.putIfAbsent(taskId, hostname);</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>TaskScheduler#getTaskAssigner()#call(...)</code> 方法,通知 TaskScheduler 任务被<strong>确认</strong>分配到这个主机。TaskScheduler 做任务和 Offer 的匹配,对哪些任务运行在哪些主机是有依赖的,不然怎么做匹配优化呢。在<a href="https://github.com/Netflix/Fenzo/wiki/How-to-use-Fenzo#notify-the-scheduler-of-assigns-and-unassigns-of-tasks" rel="external nofollow noopener noreferrer" target="_blank">《Fenzo Wiki —— Notify the Scheduler of Assigns and UnAssigns of Tasks》</a>可以进一步了解。</p></li></ul></li><li><p>调用 <code>#getOfferIDs(...)</code> 方法,获得 Offer ID 集合。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">private</span> List<Protos.OfferID> getOfferIDs(<span class="keyword">final</span> List<VirtualMachineLease> leasesUsed) {</div><div class="line"> List<Protos.OfferID> result = <span class="keyword">new</span> ArrayList<>();</div><div class="line"> <span class="keyword">for</span> (VirtualMachineLease virtualMachineLease: leasesUsed) {</div><div class="line"> result.add(virtualMachineLease.getOffer().getId());</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><h3 id="4-4-1-创建单个-Mesos-任务信息"><a href="#4-4-1-创建单个-Mesos-任务信息" class="headerlink" title="4.4.1 创建单个 Mesos 任务信息"></a>4.4.1 创建单个 Mesos 任务信息</h3><p>调用 <code>#getTaskInfo()</code> 方法,创建单个 Mesos 任务信息。实现代码如下:</p><p><strong>如下会涉及大量的 Mesos API</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">private</span> Protos.<span class="function">TaskInfo <span class="title">getTaskInfo</span><span class="params">(<span class="keyword">final</span> Protos.Offer offer, <span class="keyword">final</span> TaskAssignmentResult taskAssignmentResult)</span> </span>{</div><div class="line"> <span class="comment">// 校验 作业配置 是否存在</span></div><div class="line"> TaskContext taskContext = TaskContext.from(taskAssignmentResult.getTaskId());</div><div class="line"> Optional<CloudJobConfiguration> jobConfigOptional = facadeService.load(taskContext.getMetaInfo().getJobName());</div><div class="line"> <span class="keyword">if</span> (!jobConfigOptional.isPresent()) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> CloudJobConfiguration jobConfig = jobConfigOptional.get();</div><div class="line"> <span class="comment">// 校验 作业配置 是否存在</span></div><div class="line"> Optional<CloudAppConfiguration> appConfigOptional = facadeService.loadAppConfig(jobConfig.getAppName());</div><div class="line"> <span class="keyword">if</span> (!appConfigOptional.isPresent()) {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> CloudAppConfiguration appConfig = appConfigOptional.get();</div><div class="line"> <span class="comment">// 设置 Mesos Slave ID</span></div><div class="line"> taskContext.setSlaveId(offer.getSlaveId().getValue());</div><div class="line"> <span class="comment">// 获得 分片上下文集合</span></div><div class="line"> ShardingContexts shardingContexts = getShardingContexts(taskContext, appConfig, jobConfig);</div><div class="line"> <span class="comment">// 瞬时的脚本作业,使用 Mesos 命令行执行,无需使用执行器</span></div><div class="line"> <span class="keyword">boolean</span> isCommandExecutor = CloudJobExecutionType.TRANSIENT == jobConfig.getJobExecutionType() && JobType.SCRIPT == jobConfig.getTypeConfig().getJobType();</div><div class="line"> String script = appConfig.getBootstrapScript();</div><div class="line"> <span class="keyword">if</span> (isCommandExecutor) {</div><div class="line"> script = ((ScriptJobConfiguration) jobConfig.getTypeConfig()).getScriptCommandLine();</div><div class="line"> }</div><div class="line"> <span class="comment">// 创建 启动命令</span></div><div class="line"> Protos.CommandInfo.URI uri = buildURI(appConfig, isCommandExecutor);</div><div class="line"> Protos.CommandInfo command = buildCommand(uri, script, shardingContexts, isCommandExecutor);</div><div class="line"> <span class="comment">// 创建 Mesos 任务信息</span></div><div class="line"> <span class="keyword">if</span> (isCommandExecutor) {</div><div class="line"> <span class="keyword">return</span> buildCommandExecutorTaskInfo(taskContext, jobConfig, shardingContexts, offer, command);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> buildCustomizedExecutorTaskInfo(taskContext, appConfig, jobConfig, shardingContexts, offer, command);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>#getShardingContexts(...)</code> 方法, 获得分片上下文集合。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> ShardingContexts <span class="title">getShardingContexts</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext, <span class="keyword">final</span> CloudAppConfiguration appConfig, <span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> Map<Integer, String> shardingItemParameters = <span class="keyword">new</span> ShardingItemParameters(jobConfig.getTypeConfig().getCoreConfig().getShardingItemParameters()).getMap();</div><div class="line"> Map<Integer, String> assignedShardingItemParameters = <span class="keyword">new</span> HashMap<>(<span class="number">1</span>, <span class="number">1</span>);</div><div class="line"> <span class="keyword">int</span> shardingItem = taskContext.getMetaInfo().getShardingItems().get(<span class="number">0</span>); <span class="comment">// 单个作业分片</span></div><div class="line"> assignedShardingItemParameters.put(shardingItem, shardingItemParameters.containsKey(shardingItem) ? shardingItemParameters.get(shardingItem) : <span class="string">""</span>);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ShardingContexts(taskContext.getId(), jobConfig.getJobName(), jobConfig.getTypeConfig().getCoreConfig().getShardingTotalCount(),</div><div class="line"> jobConfig.getTypeConfig().getCoreConfig().getJobParameter(), assignedShardingItemParameters, appConfig.getEventTraceSamplingCount());</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>当任务为<strong>瞬时</strong>的<strong>脚本</strong>作业时,使用 Mesos Slave 命令行调用即可,无需使用 Elastic-Job-Cloud-Executor。</p></li><li><p>调用 <code>#buildURI(...)</code> 方法,创建执行器的二进制文件下载地址。试下代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">private</span> Protos.CommandInfo.<span class="function">URI <span class="title">buildURI</span><span class="params">(<span class="keyword">final</span> CloudAppConfiguration appConfig, <span class="keyword">final</span> <span class="keyword">boolean</span> isCommandExecutor)</span> </span>{</div><div class="line"> Protos.CommandInfo.URI.Builder result = Protos.CommandInfo.URI.newBuilder()</div><div class="line"> .setValue(appConfig.getAppURL())</div><div class="line"> .setCache(appConfig.isAppCacheEnable()); <span class="comment">// cache</span></div><div class="line"> <span class="keyword">if</span> (isCommandExecutor && !SupportedExtractionType.isExtraction(appConfig.getAppURL())) {</div><div class="line"> result.setExecutable(<span class="keyword">true</span>); <span class="comment">// 是否可执行</span></div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> result.setExtract(<span class="keyword">true</span>); <span class="comment">// 是否需要解压</span></div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result.build();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>云作业应用配置 <code>CloudAppConfiguration.appURL</code> ,通过 Mesos 实现文件的下载。</li><li><p>云作业应用配置 <code>CloudAppConfiguration.appCacheEnable</code>,应用文件下载是否缓存。</p><blockquote><p>FROM <a href="http://product.dangdang.com/24187450.html" rel="external nofollow noopener noreferrer" target="_blank">《Mesos 框架构建分布式应用》</a> P99<br><strong>Fetcher 缓存</strong><br>Mesos 0.23 里发布称为 fetcher 缓存的新功能。fetcher 缓存确保每个 artifact 在每个 slave 只会下载一次,即使多个执行器请求同一个 artifact,也只需要等待单词下载完成即可。</p></blockquote></li></ul></li><li><p>调用 <code>#buildCommand(...)</code> 方法,创建执行器启动命令。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">private</span> Protos.<span class="function">CommandInfo <span class="title">buildCommand</span><span class="params">(<span class="keyword">final</span> Protos.CommandInfo.URI uri, <span class="keyword">final</span> String script, <span class="keyword">final</span> ShardingContexts shardingContexts, <span class="keyword">final</span> <span class="keyword">boolean</span> isCommandExecutor)</span> </span>{</div><div class="line"> Protos.CommandInfo.Builder result = Protos.CommandInfo.newBuilder().addUris(uri).setShell(<span class="keyword">true</span>);</div><div class="line"> <span class="keyword">if</span> (isCommandExecutor) {</div><div class="line"> CommandLine commandLine = CommandLine.parse(script);</div><div class="line"> commandLine.addArgument(GsonFactory.getGson().toJson(shardingContexts), <span class="keyword">false</span>);</div><div class="line"> result.setValue(Joiner.on(<span class="string">" "</span>).join(commandLine.getExecutable(), Joiner.on(<span class="string">" "</span>).join(commandLine.getArguments())));</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> result.setValue(script);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result.build();</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><ul><li><p>调用 <code>#buildCommandExecutorTaskInfo(...)</code> 方法,为<strong>瞬时</strong>的<strong>脚本</strong>作业创建 Mesos 任务信息。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">private</span> Protos.<span class="function">TaskInfo <span class="title">buildCommandExecutorTaskInfo</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext, <span class="keyword">final</span> CloudJobConfiguration jobConfig, <span class="keyword">final</span> ShardingContexts shardingContexts,</span></span></div><div class="line"><span class="function"><span class="params"> <span class="keyword">final</span> Protos.Offer offer, <span class="keyword">final</span> Protos.CommandInfo command)</span> </span>{</div><div class="line"> Protos.TaskInfo.Builder result = Protos.TaskInfo.newBuilder().setTaskId(Protos.TaskID.newBuilder().setValue(taskContext.getId()).build())</div><div class="line"> .setName(taskContext.getTaskName()).setSlaveId(offer.getSlaveId())</div><div class="line"> .addResources(buildResource(<span class="string">"cpus"</span>, jobConfig.getCpuCount(), offer.getResourcesList()))</div><div class="line"> .addResources(buildResource(<span class="string">"mem"</span>, jobConfig.getMemoryMB(), offer.getResourcesList()))</div><div class="line"> .setData(ByteString.copyFrom(<span class="keyword">new</span> TaskInfoData(shardingContexts, jobConfig).serialize())); <span class="comment">//</span></div><div class="line"> <span class="keyword">return</span> result.setCommand(command).build();</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><ul><li><p>调用 <code>#buildCustomizedExecutorTaskInfo(...)</code> 方法,创建 Mesos 任务信息。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">private</span> Protos.<span class="function">TaskInfo <span class="title">buildCustomizedExecutorTaskInfo</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext, <span class="keyword">final</span> CloudAppConfiguration appConfig, <span class="keyword">final</span> CloudJobConfiguration jobConfig, </span></span></div><div class="line"><span class="function"><span class="params"> <span class="keyword">final</span> ShardingContexts shardingContexts, <span class="keyword">final</span> Protos.Offer offer, <span class="keyword">final</span> Protos.CommandInfo command)</span> </span>{</div><div class="line"> Protos.TaskInfo.Builder result = Protos.TaskInfo.newBuilder().setTaskId(Protos.TaskID.newBuilder().setValue(taskContext.getId()).build())</div><div class="line"> .setName(taskContext.getTaskName()).setSlaveId(offer.getSlaveId())</div><div class="line"> .addResources(buildResource(<span class="string">"cpus"</span>, jobConfig.getCpuCount(), offer.getResourcesList()))</div><div class="line"> .addResources(buildResource(<span class="string">"mem"</span>, jobConfig.getMemoryMB(), offer.getResourcesList()))</div><div class="line"> .setData(ByteString.copyFrom(<span class="keyword">new</span> TaskInfoData(shardingContexts, jobConfig).serialize()));</div><div class="line"> <span class="comment">// ExecutorInfo</span></div><div class="line"> Protos.ExecutorInfo.Builder executorBuilder = Protos.ExecutorInfo.newBuilder().setExecutorId(Protos.ExecutorID.newBuilder()</div><div class="line"> .setValue(taskContext.getExecutorId(jobConfig.getAppName()))) <span class="comment">// 执行器 ID</span></div><div class="line"> .setCommand(command)</div><div class="line"> .addResources(buildResource(<span class="string">"cpus"</span>, appConfig.getCpuCount(), offer.getResourcesList()))</div><div class="line"> .addResources(buildResource(<span class="string">"mem"</span>, appConfig.getMemoryMB(), offer.getResourcesList()));</div><div class="line"> <span class="keyword">if</span> (env.getJobEventRdbConfiguration().isPresent()) {</div><div class="line"> executorBuilder.setData(ByteString.copyFrom(SerializationUtils.serialize(env.getJobEventRdbConfigurationMap()))).build();</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result.setExecutor(executorBuilder.build()).build();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>Protos.ExecutorInfo.Builder#setValue(...)</code> 方法,设置<strong>执行器编号</strong>。大多数在 Mesos 实现的执行器,一个任务对应一个执行器。而 Elastic-Job-Cloud-Executor 不同于大多数在 Mesos 上的执行器,一个执行器可以对应多个作业。什么意思?在一个 Mesos Slave,<strong>相同</strong>作业应用,只会启动一个 Elastic-Job-Cloud-Scheduler。当该执行器不存在时,启动一个。当该执行器已经存在,复用该执行器。那么是如何实现该功能的呢?<strong>相同</strong>作业应用,在同一个 Mesos Slave,使用相同执行器编号。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * 获取任务执行器主键.</span></div><div class="line"><span class="comment"> * </span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> appName 应用名称</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 任务执行器主键</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getExecutorId</span><span class="params">(<span class="keyword">final</span> String appName)</span> </span>{</div><div class="line"> <span class="keyword">return</span> Joiner.on(DELIMITER).join(appName, slaveId);</div><div class="line">}</div></pre></td></tr></table></figure></li></ul></li></ul><h2 id="4-5-将任务运行时上下文放入运行时队列"><a href="#4-5-将任务运行时上下文放入运行时队列" class="headerlink" title="4.5 将任务运行时上下文放入运行时队列"></a>4.5 将任务运行时上下文放入运行时队列</h2><p>调用 <code>FacadeService#addRunning(...)</code> 方法,将任务运行时上下文放入运行时队列。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// FacadeService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 将任务运行时上下文放入运行时队列.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> taskContext 任务运行时上下文</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addRunning</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext)</span> </span>{</div><div class="line"> runningService.add(taskContext);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// RunningService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 将任务运行时上下文放入运行时队列.</span></div><div class="line"><span class="comment">* </span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> taskContext 任务运行时上下文</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (!configurationService.load(taskContext.getMetaInfo().getJobName()).isPresent()) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 添加到运行中的任务集合</span></div><div class="line"> getRunningTasks(taskContext.getMetaInfo().getJobName()).add(taskContext);</div><div class="line"> <span class="comment">// 判断是否为常驻任务</span></div><div class="line"> <span class="keyword">if</span> (!isDaemon(taskContext.getMetaInfo().getJobName())) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 添加到运行中队列</span></div><div class="line"> String runningTaskNodePath = RunningNode.getRunningTaskNodePath(taskContext.getMetaInfo().toString());</div><div class="line"> <span class="keyword">if</span> (!regCenter.isExisted(runningTaskNodePath)) {</div><div class="line"> regCenter.persist(runningTaskNodePath, taskContext.getId());</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// RunningNode.java</span></div><div class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">RunningNode</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">static</span> <span class="keyword">final</span> String ROOT = StateNode.ROOT + <span class="string">"/running"</span>;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String RUNNING_JOB = ROOT + <span class="string">"/%s"</span>; <span class="comment">// %s = ${JOB_NAME}</span></div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String RUNNING_TASK = RUNNING_JOB + <span class="string">"/%s"</span>; <span class="comment">// %s = ${TASK_META_INFO}。${TASK_META_INFO}=${JOB_NAME}@-@${ITEM_ID}。</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>RunningService,任务运行时服务,提供对运行中的任务集合、运行中作业队列的各种操作方法。</li><li><p>调用 <code>#getRunningTasks()</code> 方法,获得<strong>运行中的任务集合</strong>,并将当前任务添加到其中。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> Collection<TaskContext> <span class="title">getRunningTasks</span><span class="params">(<span class="keyword">final</span> String jobName)</span> </span>{</div><div class="line"> Set<TaskContext> taskContexts = <span class="keyword">new</span> CopyOnWriteArraySet<>();</div><div class="line"> Collection<TaskContext> result = RUNNING_TASKS.putIfAbsent(jobName, taskContexts);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span> == result ? taskContexts : result;</div><div class="line">}</div></pre></td></tr></table></figure><p> 在运维平台,我们可以看到当前任务正在运行中:</p><p> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/09.png" alt=""></p></li><li><p>常驻作业会存储在<strong>运行中作业队列</strong>。运行中作业队列存储在注册中心( Zookeeper )的<strong>持久</strong>数据节点 <code>/${NAMESPACE}/state/running/${JOB_NAME}/${TASK_META_INFO}</code>,存储值为任务编号。使用 zkClient 查看如下: </p> <figure class="highlight shell"><table><tr><td class="code"><pre><div class="line">[zk: localhost:2181(CONNECTED) 14] ls /elastic-job-cloud/state/running/test_job_simple</div><div class="line">[test_job_simple@-@0, test_job_simple@-@1, test_job_simple@-@2]</div><div class="line">[zk: localhost:2181(CONNECTED) 15] get /elastic-job-cloud/state/running/test_job_simple/test_job_simple@-@0</div><div class="line">test_job_simple@-@0@-@READY@-@400197d9-76ca-464b-b2f0-e0fba5c2a598-S0@-@9780ed12-9612-45e3-ac14-feb2911896ff</div></pre></td></tr></table></figure></li></ul><h2 id="4-6-从队列中删除已运行的作业"><a href="#4-6-从队列中删除已运行的作业" class="headerlink" title="4.6 从队列中删除已运行的作业"></a>4.6 从队列中删除已运行的作业</h2><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// #runOneIteration()</span></div><div class="line">facadeService.removeLaunchTasksFromQueue(taskContextsList);</div><div class="line"></div><div class="line"><span class="comment">// FacadeService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 从队列中删除已运行的作业.</span></div><div class="line"><span class="comment">* </span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> taskContexts 任务上下文集合</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">removeLaunchTasksFromQueue</span><span class="params">(<span class="keyword">final</span> List<TaskContext> taskContexts)</span> </span>{</div><div class="line"> List<TaskContext> failoverTaskContexts = <span class="keyword">new</span> ArrayList<>(taskContexts.size());</div><div class="line"> Collection<String> readyJobNames = <span class="keyword">new</span> HashSet<>(taskContexts.size(), <span class="number">1</span>);</div><div class="line"> <span class="keyword">for</span> (TaskContext each : taskContexts) {</div><div class="line"> <span class="keyword">switch</span> (each.getType()) {</div><div class="line"> <span class="keyword">case</span> FAILOVER:</div><div class="line"> failoverTaskContexts.add(each);</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> READY:</div><div class="line"> readyJobNames.add(each.getMetaInfo().getJobName());</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">default</span>:</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="comment">// 从失效转移队列中删除相关任务</span></div><div class="line"> failoverService.remove(Lists.transform(failoverTaskContexts, <span class="keyword">new</span> Function<TaskContext, TaskContext.MetaInfo>() {</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="keyword">public</span> TaskContext.<span class="function">MetaInfo <span class="title">apply</span><span class="params">(<span class="keyword">final</span> TaskContext input)</span> </span>{</div><div class="line"> <span class="keyword">return</span> input.getMetaInfo();</div><div class="line"> }</div><div class="line"> }));</div><div class="line"> <span class="comment">// 从待执行队列中删除相关作业</span></div><div class="line"> readyService.remove(readyJobNames);</div><div class="line">}</div></pre></td></tr></table></figure><h2 id="4-7-提交任务给-Mesos"><a href="#4-7-提交任务给-Mesos" class="headerlink" title="4.7 提交任务给 Mesos"></a>4.7 提交任务给 Mesos</h2><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// #runOneIteration()</span></div><div class="line"><span class="keyword">for</span> (Entry<List<OfferID>, List<TaskInfo>> each : offerIdTaskInfoMap.entrySet()) {</div><div class="line"> schedulerDriver.launchTasks(each.getKey(), each.getValue());</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>SchedulerDriver#launchTasks(...)</code> 方法,提交任务给 Mesos Master。由 Mesos Master 调度任务给 Mesos Slave。Mesos Slave 提交执行器执行任务。</li></ul><h1 id="5-TaskExecutor-执行任务"><a href="#5-TaskExecutor-执行任务" class="headerlink" title="5. TaskExecutor 执行任务"></a>5. TaskExecutor 执行任务</h1><p>TaskExecutor,实现了 Mesos Executor 接口 <code>org.apache.mesos.Executor</code>。执行器的主要职责之一:<strong>执行调度器所请求的任务</strong>。TaskExecutor 接收到 Mesos Slave 提交的任务,调用 <code>#launchTask(...)</code> 方法,处理任务。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// DaemonTaskScheduler.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">launchTask</span><span class="params">(<span class="keyword">final</span> ExecutorDriver executorDriver, <span class="keyword">final</span> Protos.TaskInfo taskInfo)</span> </span>{</div><div class="line"> executorService.submit(<span class="keyword">new</span> TaskThread(executorDriver, taskInfo));</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>ExecutorService#submit(...)</code> 方法,提交 TaskThread 到线程池,执行任务。</li></ul><h2 id="5-1-TaskThread"><a href="#5-1-TaskThread" class="headerlink" title="5.1 TaskThread"></a>5.1 TaskThread</h2><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@RequiredArgsConstructor</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">TaskThread</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ExecutorDriver executorDriver;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> TaskInfo taskInfo;</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 更新 Mesos 任务状态,运行中。</span></div><div class="line"> executorDriver.sendStatusUpdate(Protos.TaskStatus.newBuilder().setTaskId(taskInfo.getTaskId()).setState(Protos.TaskState.TASK_RUNNING).build());</div><div class="line"> <span class="comment">//</span></div><div class="line"> Map<String, Object> data = SerializationUtils.deserialize(taskInfo.getData().toByteArray());</div><div class="line"> ShardingContexts shardingContexts = (ShardingContexts) data.get(<span class="string">"shardingContext"</span>);</div><div class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</div><div class="line"> JobConfigurationContext jobConfig = <span class="keyword">new</span> JobConfigurationContext((Map<String, String>) data.get(<span class="string">"jobConfigContext"</span>));</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 获得 分布式作业</span></div><div class="line"> ElasticJob elasticJob = getElasticJobInstance(jobConfig);</div><div class="line"> <span class="comment">// 调度器提供内部服务的门面对象</span></div><div class="line"> <span class="keyword">final</span> CloudJobFacade jobFacade = <span class="keyword">new</span> CloudJobFacade(shardingContexts, jobConfig, jobEventBus);</div><div class="line"> <span class="comment">// 执行作业</span></div><div class="line"> <span class="keyword">if</span> (jobConfig.isTransient()) {</div><div class="line"> <span class="comment">// 执行作业</span></div><div class="line"> JobExecutorFactory.getJobExecutor(elasticJob, jobFacade).execute();</div><div class="line"> <span class="comment">// 更新 Mesos 任务状态,已完成。</span></div><div class="line"> executorDriver.sendStatusUpdate(Protos.TaskStatus.newBuilder().setTaskId(taskInfo.getTaskId()).setState(Protos.TaskState.TASK_FINISHED).build());</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="comment">// 初始化 常驻作业调度器</span></div><div class="line"> <span class="keyword">new</span> DaemonTaskScheduler(elasticJob, jobConfig, jobFacade, executorDriver, taskInfo.getTaskId()).init();</div><div class="line"> }</div><div class="line"> <span class="comment">// CHECKSTYLE:OFF</span></div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Throwable ex) {</div><div class="line"> <span class="comment">// CHECKSTYLE:ON</span></div><div class="line"> log.error(<span class="string">"Elastic-Job-Cloud-Executor error"</span>, ex);</div><div class="line"> executorDriver.sendStatusUpdate(Protos.TaskStatus.newBuilder().setTaskId(taskInfo.getTaskId()).setState(Protos.TaskState.TASK_ERROR).setMessage(ExceptionUtil.transform(ex)).build());</div><div class="line"> executorDriver.stop();</div><div class="line"> <span class="keyword">throw</span> ex;</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>从 <code>TaskInfo.data</code> 属性中,可以获得提交任务附带的数据,例如分片上下文集合( ShardingContexts ),内部的作业配置上下文( JobConfigurationContext )。</li><li><p>调用 <code>#getElasticJobInstance()</code> 方法,获得任务需要执行的分布式作业( Elastic-Job )。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> ElasticJob <span class="title">getElasticJobInstance</span><span class="params">(<span class="keyword">final</span> JobConfigurationContext jobConfig)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (!Strings.isNullOrEmpty(jobConfig.getBeanName()) && !Strings.isNullOrEmpty(jobConfig.getApplicationContext())) { <span class="comment">// spring 环境</span></div><div class="line"> <span class="keyword">return</span> getElasticJobBean(jobConfig);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> getElasticJobClass(jobConfig);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 从 Spring 容器中获得作业对象</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> jobConfig 作业配置</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 作业对象</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">private</span> ElasticJob <span class="title">getElasticJobBean</span><span class="params">(<span class="keyword">final</span> JobConfigurationContext jobConfig)</span> </span>{</div><div class="line"> String applicationContextFile = jobConfig.getApplicationContext();</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == applicationContexts.get(applicationContextFile)) {</div><div class="line"> <span class="keyword">synchronized</span> (applicationContexts) {</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == applicationContexts.get(applicationContextFile)) {</div><div class="line"> applicationContexts.put(applicationContextFile, <span class="keyword">new</span> ClassPathXmlApplicationContext(applicationContextFile));</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> (ElasticJob) applicationContexts.get(applicationContextFile).getBean(jobConfig.getBeanName());</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 创建作业对象</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> jobConfig 作业配置</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 作业对象</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">private</span> ElasticJob <span class="title">getElasticJobClass</span><span class="params">(<span class="keyword">final</span> JobConfigurationContext jobConfig)</span> </span>{</div><div class="line"> String jobClass = jobConfig.getTypeConfig().getJobClass();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> Class<?> elasticJobClass = Class.forName(jobClass);</div><div class="line"> <span class="keyword">if</span> (!ElasticJob.class.isAssignableFrom(elasticJobClass)) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(<span class="string">"Elastic-Job: Class '%s' must implements ElasticJob interface."</span>, jobClass);</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (elasticJobClass != ScriptJob.class) {</div><div class="line"> <span class="keyword">return</span> (ElasticJob) elasticJobClass.newInstance();</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> ReflectiveOperationException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(<span class="string">"Elastic-Job: Class '%s' initialize failure, the error message is '%s'."</span>, jobClass, ex.getMessage());</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>当作业是<strong>瞬时</strong>作业时,调用 <code>AbstractElasticJobExecutor#execute(...)</code> 执行作业逻辑,并调用 <code>ExecutorDriver#sendStatusUpdate(...)</code> 发送状态,更新 Mesos 任务已完成( Protos.TaskState.TASK_FINISHED )。<code>AbstractElasticJobExecutor#execute(...)</code> 实现代码,在 Elastic-Job-Lite 和 Elastic-Job-Cloud 基本一致,在<a href="http://www.iocoder.cn/Elastic-Job/job-execute/?self">《Elastic-Job-Lite 源码分析 —— 作业执行》</a>有详细解析。</li><li>当作业是<strong>常驻</strong>作业时,调用 <code>DaemonTaskScheduler#init()</code> 方法,初始化作业调度,在「5.2 DaemonTaskScheduler」详细解析。</li></ul></li></ul><h2 id="5-2-DaemonTaskScheduler"><a href="#5-2-DaemonTaskScheduler" class="headerlink" title="5.2 DaemonTaskScheduler"></a>5.2 DaemonTaskScheduler</h2><p><strong>瞬时</strong>作业,通过 Elastic-Job-Cloud-Scheduler 调度任务,提交 Elastic-Job-Cloud-Executor 执行后,等待 Elastic-Job-Scheduler 进行下次调度。</p><p><strong>常驻</strong>作业,通过 Elastic-Job-Scheduler 提交 Elastic-Job-Cloud-Executor 进行调度。Elastic-Job-Cloud-Executor 使用 DaemonTaskScheduler 不断对常驻作业进行调度而无需 Elastic-Job-Cloud-Scheduler 参与其中。</p><p>这就是<strong>瞬时</strong>作业和<strong>常驻</strong>作业不同之处。</p><p>DaemonTaskScheduler,常驻作业调度器。调用 <code>DaemonTaskScheduler#init()</code> 方法,对<strong>一个</strong>作业初始化调度,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 初始化作业.</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// Quartz JobDetail</span></div><div class="line"> JobDetail jobDetail = JobBuilder.newJob(DaemonJob.class)</div><div class="line"> .withIdentity(jobRootConfig.getTypeConfig().getCoreConfig().getJobName()).build();</div><div class="line"> jobDetail.getJobDataMap().put(ELASTIC_JOB_DATA_MAP_KEY, elasticJob);</div><div class="line"> jobDetail.getJobDataMap().put(JOB_FACADE_DATA_MAP_KEY, jobFacade);</div><div class="line"> jobDetail.getJobDataMap().put(EXECUTOR_DRIVER_DATA_MAP_KEY, executorDriver);</div><div class="line"> jobDetail.getJobDataMap().put(TASK_ID_DATA_MAP_KEY, taskId);</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> scheduleJob(initializeScheduler(), jobDetail, taskId.getValue(), jobRootConfig.getTypeConfig().getCoreConfig().getCron());</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> SchedulerException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(ex);</div><div class="line"> }</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> Scheduler <span class="title">initializeScheduler</span><span class="params">()</span> <span class="keyword">throws</span> SchedulerException </span>{</div><div class="line"> StdSchedulerFactory factory = <span class="keyword">new</span> StdSchedulerFactory();</div><div class="line"> factory.initialize(getBaseQuartzProperties());</div><div class="line"> <span class="keyword">return</span> factory.getScheduler();</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> Properties <span class="title">getBaseQuartzProperties</span><span class="params">()</span> </span>{</div><div class="line"> Properties result = <span class="keyword">new</span> Properties();</div><div class="line"> result.put(<span class="string">"org.quartz.threadPool.class"</span>, org.quartz.simpl.SimpleThreadPool.class.getName());</div><div class="line"> result.put(<span class="string">"org.quartz.threadPool.threadCount"</span>, <span class="string">"1"</span>); <span class="comment">// 线程数:1</span></div><div class="line"> result.put(<span class="string">"org.quartz.scheduler.instanceName"</span>, taskId.getValue());</div><div class="line"> <span class="keyword">if</span> (!jobRootConfig.getTypeConfig().getCoreConfig().isMisfire()) {</div><div class="line"> result.put(<span class="string">"org.quartz.jobStore.misfireThreshold"</span>, <span class="string">"1"</span>);</div><div class="line"> }</div><div class="line"> result.put(<span class="string">"org.quartz.plugin.shutdownhook.class"</span>, ShutdownHookPlugin.class.getName());</div><div class="line"> result.put(<span class="string">"org.quartz.plugin.shutdownhook.cleanShutdown"</span>, Boolean.TRUE.toString());</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">scheduleJob</span><span class="params">(<span class="keyword">final</span> Scheduler scheduler, <span class="keyword">final</span> JobDetail jobDetail, <span class="keyword">final</span> String triggerIdentity, <span class="keyword">final</span> String cron)</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">if</span> (!scheduler.checkExists(jobDetail.getKey())) {</div><div class="line"> scheduler.scheduleJob(jobDetail, createTrigger(triggerIdentity, cron));</div><div class="line"> }</div><div class="line"> scheduler.start();</div><div class="line"> RUNNING_SCHEDULERS.putIfAbsent(scheduler.getSchedulerName(), scheduler);</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> SchedulerException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobSystemException(ex);</div><div class="line"> }</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> CronTrigger <span class="title">createTrigger</span><span class="params">(<span class="keyword">final</span> String triggerIdentity, <span class="keyword">final</span> String cron)</span> </span>{</div><div class="line"> <span class="keyword">return</span> TriggerBuilder.newTrigger()</div><div class="line"> .withIdentity(triggerIdentity)</div><div class="line"> .withSchedule(CronScheduleBuilder.cronSchedule(cron)</div><div class="line"> .withMisfireHandlingInstructionDoNothing())</div><div class="line"> .build();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>DaemonTaskScheduler 基于 Quartz 实现作业调度。这里大家看下源码,就不啰嗦解释啦。</li><li>JobBuilder#newJob(…) 的参数是 DaemonJob,下文会讲解到。 </li></ul><p><strong>DaemonJob 实现代码</strong>如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">DaemonJob</span> <span class="keyword">implements</span> <span class="title">Job</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> ElasticJob elasticJob;</div><div class="line"> </div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> JobFacade jobFacade;</div><div class="line"> </div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> ExecutorDriver executorDriver;</div><div class="line"> </div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> Protos.TaskID taskId;</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(<span class="keyword">final</span> JobExecutionContext context)</span> <span class="keyword">throws</span> JobExecutionException </span>{</div><div class="line"> ShardingContexts shardingContexts = jobFacade.getShardingContexts();</div><div class="line"> <span class="keyword">int</span> jobEventSamplingCount = shardingContexts.getJobEventSamplingCount();</div><div class="line"> <span class="keyword">int</span> currentJobEventSamplingCount = shardingContexts.getCurrentJobEventSamplingCount();</div><div class="line"> <span class="keyword">if</span> (jobEventSamplingCount > <span class="number">0</span> && ++currentJobEventSamplingCount < jobEventSamplingCount) {</div><div class="line"> shardingContexts.setCurrentJobEventSamplingCount(currentJobEventSamplingCount);</div><div class="line"> <span class="comment">//</span></div><div class="line"> jobFacade.getShardingContexts().setAllowSendJobEvent(<span class="keyword">false</span>);</div><div class="line"> <span class="comment">// 执行作业</span></div><div class="line"> JobExecutorFactory.getJobExecutor(elasticJob, jobFacade).execute();</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="comment">//</span></div><div class="line"> jobFacade.getShardingContexts().setAllowSendJobEvent(<span class="keyword">true</span>);</div><div class="line"> <span class="comment">//</span></div><div class="line"> executorDriver.sendStatusUpdate(Protos.TaskStatus.newBuilder().setTaskId(taskId).setState(Protos.TaskState.TASK_RUNNING).setMessage(<span class="string">"BEGIN"</span>).build());</div><div class="line"> <span class="comment">// 执行作业</span></div><div class="line"> JobExecutorFactory.getJobExecutor(elasticJob, jobFacade).execute();</div><div class="line"> <span class="comment">//</span></div><div class="line"> executorDriver.sendStatusUpdate(Protos.TaskStatus.newBuilder().setTaskId(taskId).setState(Protos.TaskState.TASK_RUNNING).setMessage(<span class="string">"COMPLETE"</span>).build());</div><div class="line"> <span class="comment">// </span></div><div class="line"> shardingContexts.setCurrentJobEventSamplingCount(<span class="number">0</span>);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>AbstractElasticJobExecutor#execute(...)</code> 执行作业逻辑。<code>AbstractElasticJobExecutor#execute(...)</code> 实现代码,在 Elastic-Job-Lite 和 Elastic-Job-Cloud 基本一致,在<a href="http://www.iocoder.cn/Elastic-Job/job-execute/?self">《Elastic-Job-Lite 源码分析 —— 作业执行》</a>有详细解析。</li><li><p><code>jobEventSamplingCount</code> 来自应用配置 (<code>CloudAppConfiguration.eventTraceSamplingCount</code>) 属性,常驻作业事件采样率统计条数,默认采样全部记录。为避免数据量过大,可对频繁调度的常驻作业配置采样率,即作业每执行N次,才会记录作业执行及追踪相关数据。</p><p>当满足采样条件时,调用 <code>ShardingContexts#setAllowSendJobEvent(true)</code>,标记<strong>要</strong>记录作业事件。否则,调用 <code>ShardingContexts#setAllowSendJobEvent(false)</code>,标记<strong>不</strong>记录作业时间。作业事件追踪在<a href="http://www.iocoder.cn/Elastic-Job/job-event-trace/?self">《Elastic-Job-Lite 源码分析 —— 作业事件追踪》</a>有详细解析。</p><p>另外,当满足采样调试时,也会调用 <code>ExecutorDriver#sendStatusUpdate(...)</code> 方法,更新 Mesos 任务状态为运行中,并附带 <code>"BEGIN"</code> 或 <code>"COMPLETE"</code> 消息。</p></li></ul><h1 id="6-SchedulerEngine-处理任务的状态变更"><a href="#6-SchedulerEngine-处理任务的状态变更" class="headerlink" title="6. SchedulerEngine 处理任务的状态变更"></a>6. SchedulerEngine 处理任务的状态变更</h1><p>Mesos 调度器的职责之一,<strong>处理任务的状态,特别是响应任务和故障</strong>。因此在 Elastic-Job-Cloud-Executor 调用 <code>ExecutorDriver#sendStatusUpdate(...)</code> 方法,更新 Mesos 任务状态时,触发调用 Elastic-Job-Cloud-Scheduler 的 SchedulerEngine 的 <code>#statusUpdate(...)</code> 方法,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">statusUpdate</span><span class="params">(<span class="keyword">final</span> SchedulerDriver schedulerDriver, <span class="keyword">final</span> Protos.TaskStatus taskStatus)</span> </span>{</div><div class="line"> String taskId = taskStatus.getTaskId().getValue();</div><div class="line"> TaskContext taskContext = TaskContext.from(taskId);</div><div class="line"> String jobName = taskContext.getMetaInfo().getJobName();</div><div class="line"> log.trace(<span class="string">"call statusUpdate task state is: {}, task id is: {}"</span>, taskStatus.getState(), taskId);</div><div class="line"> jobEventBus.post(<span class="keyword">new</span> JobStatusTraceEvent(jobName, taskContext.getId(), taskContext.getSlaveId(), Source.CLOUD_SCHEDULER, </div><div class="line"> taskContext.getType(), String.valueOf(taskContext.getMetaInfo().getShardingItems()), State.valueOf(taskStatus.getState().name()), taskStatus.getMessage()));</div><div class="line"> <span class="keyword">switch</span> (taskStatus.getState()) {</div><div class="line"> <span class="keyword">case</span> TASK_RUNNING:</div><div class="line"> <span class="keyword">if</span> (!facadeService.load(jobName).isPresent()) {</div><div class="line"> schedulerDriver.killTask(Protos.TaskID.newBuilder().setValue(taskId).build());</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (<span class="string">"BEGIN"</span>.equals(taskStatus.getMessage())) {</div><div class="line"> facadeService.updateDaemonStatus(taskContext, <span class="keyword">false</span>);</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"COMPLETE"</span>.equals(taskStatus.getMessage())) {</div><div class="line"> facadeService.updateDaemonStatus(taskContext, <span class="keyword">true</span>);</div><div class="line"> statisticManager.taskRunSuccessfully();</div><div class="line"> }</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> TASK_FINISHED:</div><div class="line"> facadeService.removeRunning(taskContext);</div><div class="line"> unAssignTask(taskId);</div><div class="line"> statisticManager.taskRunSuccessfully();</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> TASK_KILLED:</div><div class="line"> log.warn(<span class="string">"task id is: {}, status is: {}, message is: {}, source is: {}"</span>, taskId, taskStatus.getState(), taskStatus.getMessage(), taskStatus.getSource());</div><div class="line"> facadeService.removeRunning(taskContext);</div><div class="line"> facadeService.addDaemonJobToReadyQueue(jobName);</div><div class="line"> unAssignTask(taskId);</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> TASK_LOST:</div><div class="line"> <span class="keyword">case</span> TASK_DROPPED:</div><div class="line"> <span class="keyword">case</span> TASK_GONE:</div><div class="line"> <span class="keyword">case</span> TASK_GONE_BY_OPERATOR:</div><div class="line"> <span class="keyword">case</span> TASK_FAILED:</div><div class="line"> <span class="keyword">case</span> TASK_ERROR:</div><div class="line"> log.warn(<span class="string">"task id is: {}, status is: {}, message is: {}, source is: {}"</span>, taskId, taskStatus.getState(), taskStatus.getMessage(), taskStatus.getSource());</div><div class="line"> facadeService.removeRunning(taskContext);</div><div class="line"> facadeService.recordFailoverTask(taskContext);</div><div class="line"> unAssignTask(taskId);</div><div class="line"> statisticManager.taskRunFailed();</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">case</span> TASK_UNKNOWN:</div><div class="line"> <span class="keyword">case</span> TASK_UNREACHABLE:</div><div class="line"> log.error(<span class="string">"task id is: {}, status is: {}, message is: {}, source is: {}"</span>, taskId, taskStatus.getState(), taskStatus.getMessage(), taskStatus.getSource());</div><div class="line"> statisticManager.taskRunFailed();</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> <span class="keyword">default</span>:</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>当更新 Mesos 任务状态为 <code>TASK_RUNNING</code> 时,根据附带消息为 <code>"BEGIN"</code> 或 <code>"COMPLETE"</code>,分别调用 <code>FacadeService#updateDaemonStatus(false / true)</code> 方法,更新作业闲置状态。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// FacadeService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 更新常驻作业运行状态.</span></div><div class="line"><span class="comment">* </span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> taskContext 任务运行时上下文</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> isIdle 是否空闲</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateDaemonStatus</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext, <span class="keyword">final</span> <span class="keyword">boolean</span> isIdle)</span> </span>{</div><div class="line"> runningService.updateIdle(taskContext, isIdle);</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="comment">// RunningService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 更新作业闲置状态.</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> taskContext 任务运行时上下文</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> isIdle 是否闲置</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateIdle</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext, <span class="keyword">final</span> <span class="keyword">boolean</span> isIdle)</span> </span>{</div><div class="line"> <span class="keyword">synchronized</span> (RUNNING_TASKS) {</div><div class="line"> Optional<TaskContext> taskContextOptional = findTask(taskContext);</div><div class="line"> <span class="keyword">if</span> (taskContextOptional.isPresent()) {</div><div class="line"> taskContextOptional.get().setIdle(isIdle);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> add(taskContext);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p> 若作业配置不存在时,调用 <code>SchedulerDriver#killTask(...)</code> 方法,杀死该 Mesos 任务。在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor-second/?self">《Elastic-Job-Cloud 源码分析 —— 作业调度(二)》</a>进一步解析。</p></li><li><p>当更新 Mesos 任务状态为 <code>TASK_FINISHED</code> 时,调用 <code>FacadeService#removeRunning(...)</code> 方法,将任务从运行时队列删除。实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// FacadeService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 将任务从运行时队列删除.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> taskContext 任务运行时上下文</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">removeRunning</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext)</span> </span>{</div><div class="line"> runningService.remove(taskContext);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// RunningService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 将任务从运行时队列删除.</span></div><div class="line"><span class="comment">* </span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> taskContext 任务运行时上下文</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext)</span> </span>{</div><div class="line"> <span class="comment">// 移除运行中的任务集合</span></div><div class="line"> getRunningTasks(taskContext.getMetaInfo().getJobName()).remove(taskContext);</div><div class="line"> <span class="comment">// 判断是否为常驻任务</span></div><div class="line"> <span class="keyword">if</span> (!isDaemonOrAbsent(taskContext.getMetaInfo().getJobName())) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 将任务从运行时队列删除</span></div><div class="line"> regCenter.remove(RunningNode.getRunningTaskNodePath(taskContext.getMetaInfo().toString()));</div><div class="line"> String jobRootNode = RunningNode.getRunningJobNodePath(taskContext.getMetaInfo().getJobName());</div><div class="line"> <span class="keyword">if</span> (regCenter.isExisted(jobRootNode) && regCenter.getChildrenKeys(jobRootNode).isEmpty()) {</div><div class="line"> regCenter.remove(jobRootNode);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>当该作业对应的所有 Mesos 任务状态都更新为 <code>TASK_FINISHED</code> 后,作业可以再次被 Elastic-Job-Cloud-Scheduler 调度。</p><p>调用 <code>#unAssignTask(...)</code> 方法,通知 TaskScheduler 任务被<strong>确认</strong>未分配到这个主机。TaskScheduler 做任务和 Offer 的匹配,对哪些任务运行在哪些主机是有依赖的,不然怎么做匹配优化呢。在<a href="https://github.com/Netflix/Fenzo/wiki/How-to-use-Fenzo#notify-the-scheduler-of-assigns-and-unassigns-of-tasks" rel="external nofollow noopener noreferrer" target="_blank">《Fenzo Wiki —— Notify the Scheduler of Assigns and UnAssigns of Tasks》</a>可以进一步了解。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">unAssignTask</span><span class="params">(<span class="keyword">final</span> String taskId)</span> </span>{</div><div class="line"> String hostname = facadeService.popMapping(taskId);</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != hostname) {</div><div class="line"> taskScheduler.getTaskUnAssigner().call(TaskContext.getIdForUnassignedSlave(taskId), hostname);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li></ul></li><li><p>当更新 Mesos 任务状态为 <code>TASK_KILLED</code> 时,调用 <code>FacadeService#addDaemonJobToReadyQueue(...)</code> 方法,将常驻作业放入待执行队列。<strong>为什么要将常驻作业放入待执行队列呢?</strong>被 Kill 掉的作业后续要继续调度执行,如果不加入待执行队列,TaskLaunchScheduledService 就无法提交作业给 Elastic-Job-Cloud-Executor 继续调度执行。</p><p> 另外会调用 <code>FacadeService#removeRunning(...)</code>、<code>#unAssignTask(...)</code> 方法。</p></li><li><p>当更新 Mesos 任务状态为 <code>TASK_ERROR</code> 等等时,调用 <code>FacadeService#recordFailoverTask(...)</code> 方法,在 <a href="http://www.iocoder.cn/Elastic-Job/cloud-job-failover/?self">《Elastic-Job-Cloud 源码分析 —— 作业失效转移》</a>详细解析。</p><p> 另外会调用 <code>FacadeService#removeRunning(...)</code> 和 <code>#unAssignTask(...)</code> 方法。</p></li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>旁白君:真的真的真的,好长好长好长啊。但是真的真的真的,干货!<br>芋道君:那必须的!</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_21/12.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. 作业执行类型</a></li>
<li><a href="#">
</summary>
<category term="Elastic-Job-Cloud" scheme="http://www.iocoder.cn/categories/Elastic-Job-Cloud/"/>
</entry>
<entry>
<title>闲聊如何阅读文章</title>
<link href="http://www.iocoder.cn/Architecture/how-to-read-article/"/>
<id>http://www.iocoder.cn/Architecture/how-to-read-article/</id>
<published>2017-12-14T16:00:00.000Z</published>
<updated>2017-09-15T16:49:59.000Z</updated>
<content type="html"><![CDATA[<ul><li><a href="#">1. 阅读与整理</a></li><li><a href="#">2. 公众号推荐</a></li><li><a href="#">3. 应用推荐</a></li><li><a href="#">4. 书籍推荐</a></li><li><a href="#">5. Warning</a></li></ul><hr><h1 id="1-整理与阅读"><a href="#1-整理与阅读" class="headerlink" title="1. 整理与阅读"></a>1. 整理与阅读</h1><p>我想,应该有很多胖友碰到困惑:</p><ul><li>微信公众号几十个 <strong>99+</strong> 未阅读。</li><li>掘金 / 开源中国 / 开发者头条 / 极客头条 等等技术社区来不及阅读。</li><li>偶尔看到朋友圈 / 微信群不错的文章,只点了赞,却忘记阅读。</li></ul><p>这里我分享下我的梳理方式,可能不是最优,但是我相信,只要你愿意尝试,对你的收货一定非常大。</p><p><strong>全部文章基于印象笔记整理</strong><br><strong>全部文章基于印象笔记整理</strong><br><strong>全部文章基于印象笔记整理</strong> </p><ul><li>微信公众号 <strong>好文章</strong>转到【印象笔记-待读列表】,删除该文章消息。</li><li>掘金 / 开发者头条 / 开源中国 / 极客头条 <strong>好文章</strong>转到【印象笔记-待读列表】。</li><li>朋友圈 / 微信群 <strong>好文章</strong>转到【印象笔记-待读列表】。</li></ul><p>每天上下班地铁读【印象笔记-待读列表】。</p><ul><li>如果合适的文章,留下,标签归类。 </li><li>不合适,删除。</li><li>过于困难的文章,放入另外一个列表,收拾好心情来看,一般周末。</li></ul><p>如下是我 2017 年在<strong>地铁</strong>上阅读了几千篇文章梳理下的 800 篇左右文章:</p><p><img src="https://user-gold-cdn.xitu.io/2017/7/13/9187cd431f3917d2afc5975e00fb4ee6?imageView2/2/w/1280/h/960/q/85/interlace/1" alt=""></p><p>天下文章一大<strong>“抄”</strong>,看的多了,自然就快。</p><p><strong>如果你想要我整理的文章,请关注我的公众号【芋道源码】,留言发送你的印象笔记邮箱。</strong><br><strong>如果你想要我整理的文章,请关注我的公众号【芋道源码】,留言发送你的印象笔记邮箱。</strong><br><strong>如果你想要我整理的文章,请关注我的公众号【芋道源码】,留言发送你的印象笔记邮箱。</strong> </p><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><h1 id="2-公众号推荐"><a href="#2-公众号推荐" class="headerlink" title="2. 公众号推荐"></a>2. 公众号推荐</h1><p>按照字母排序。</p><p>可能比较多,可以选择自己喜欢的先关注一波。</p><p>另外,公众号之间文章是会有重复的。</p><h2 id="2-1-技术类"><a href="#2-1-技术类" class="headerlink" title="2.1 技术类"></a>2.1 技术类</h2><ul><li>AI前线</li><li>阿里技术</li><li>ArchSummit</li><li>并发编程网</li><li>BySocket泥瓦匠</li><li>程序视界</li><li>程序员DD</li><li>程序员日志</li><li>春天的旁边</li><li>CRUD</li><li>大码猴</li><li>DBAplus社群</li><li>Docker</li><li>dumpcache极客技术</li><li>EGONetworks</li><li>饿科技</li><li>凤凰牌老熊</li><li>服务端思维</li><li>高可用架构</li><li>高效开发运维</li><li>GDG</li><li>GitChat</li><li>Go中国</li><li>沪江科技学院</li><li>InfoQ</li><li>IPDCHAT</li><li>架构师</li><li>架构师之路</li><li>架构文摘</li><li>架构之家</li><li>K8S技术社区</li><li>开涛的博客</li><li>老叶茶馆</li><li>聊聊架构</li><li>美团点评技术团队</li><li>MongoDB中文社区</li><li>Netty之家</li><li>你假笨</li><li>PingCAP</li><li>QCon</li><li>容器时代</li><li>数人云</li><li>SpringCloud社区</li><li>StuQ</li><li>唯技术</li><li>唯品会安全应急响应中心</li><li>唯品会质量工程</li><li>携程技术中心</li><li>云栖社区</li><li>中生代技术</li></ul><h2 id="2-2-非技术类"><a href="#2-2-非技术类" class="headerlink" title="2.2 非技术类"></a>2.2 非技术类</h2><ul><li>笔记侠</li><li>菜单少爷</li><li>caoz的梦呓</li><li>差评</li><li>鸟哥笔记</li><li>鬼脚七</li><li>坏时代</li><li>互联网思维</li><li>混沌大学</li><li>经纬创投</li><li>开柒</li><li>科技美学</li><li>罗辑思维</li><li>MacTalk</li><li>全栈PM笔记</li><li>人人都是产品经理</li><li>warfalcon</li><li>小道消息</li><li>36氪</li><li>3W互联网深度精选</li></ul><h1 id="3-应用推荐"><a href="#3-应用推荐" class="headerlink" title="3. 应用推荐"></a>3. 应用推荐</h1><ul><li>掘金</li><li>开源中国</li><li>开发者头条</li></ul><h1 id="4-书籍推荐"><a href="#4-书籍推荐" class="headerlink" title="4. 书籍推荐"></a>4. 书籍推荐</h1><p><a href="https://item.jd.com/10877320.html" rel="external nofollow noopener noreferrer" target="_blank">《时间管理:如何充分利用你的24小时(漫画版)》</a></p><p>很薄的一本小书,2 小时内阅读完成,只要你愿意实践,就能比笔者更加厉害。😳</p><h1 id="5-Warning"><a href="#5-Warning" class="headerlink" title="5. Warning"></a>5. Warning</h1><p>只有输入没有输出的工程师不是好架构师。</p><p>所以在不断阅读输入的同事,请尝试输出你的道。</p>]]></content>
<summary type="html">
<ul>
<li><a href="#">1. 阅读与整理</a></li>
<li><a href="#">2. 公众号推荐</a></li>
<li><a href="#">3. 应用推荐</a></li>
<li><a href="#">4. 书籍推荐</a></li>
<
</summary>
<category term="技术杂文" scheme="http://www.iocoder.cn/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E6%96%87/"/>
</entry>
<entry>
<title>Elastic-Job-Cloud 源码分析 —— 作业配置</title>
<link href="http://www.iocoder.cn/Elastic-Job/cloud-job-config/"/>
<id>http://www.iocoder.cn/Elastic-Job/cloud-job-config/</id>
<published>2017-12-13T16:00:00.000Z</published>
<updated>2017-09-06T10:53:51.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. 云作业App</a><ul><li><a href="#">2.1 云作业App配置类</a></li><li><a href="#">2.2 操作云作业App配置</a></li></ul></li><li><a href="#3">3. 云作业</a><ul><li><a href="#">3.1 云作业配置</a><ul><li><a href="#">3.1.1 操作云作业配置</a></li></ul></li><li><a href="#">3.2 本地云作业配置</a></li><li><a href="#">3.3 云作业配置总结</a></li></ul></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Cloud 作业配置</strong>。</p><p>如果你阅读过以下文章,有助于对本文的理解:</p><ul><li><a href="http://elasticjob.io/docs/elastic-job-cloud/02-guide/cloud-restful-api/" rel="external nofollow noopener noreferrer" target="_blank">《官方文档 —— RESTFUL API》</a></li><li><a href="http://www.iocoder.cn/Elastic-Job/job-config/?self">《Elastic-Job-Lite 源码分析 —— 作业配置》</a></li><li><a href="https://segmentfault.com/a/1190000007723430" rel="external nofollow noopener noreferrer" target="_blank">《由浅入深 | 如何优雅地写一个Mesos Framework》</a></li></ul><p>😈 另外,笔者假设你已经对 <strong><a href="http://www.iocoder.cn/categories/Elastic-Job/?self">《Elastic-Job-Lite 源码分析系列》</a></strong> 有一定的了解。</p><p>本文涉及到主体类的类图如下( <a href="http://www.iocoder.cn/images/Elastic-Job/2017_12_14/01.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_14/01.png" alt=""></p><ul><li><strong>黄色</strong>的类在 <code>elastic-job-common-core</code> 项目里,为 Elastic-Job-Lite、Elastic-Job-Cloud <strong>公用</strong>作业配置类。</li><li><strong>紫色</strong>的类在 <code>elastic-job-cloud</code> 项目里,为 Elastic-Job-Cloud 作业配置类。</li><li><strong>红色</strong>的类在 <code>elastic-job-lite</code> 项目里,为 Elastic-Job-Lite 作业配置类。</li></ul><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-云作业App"><a href="#2-云作业App" class="headerlink" title="2. 云作业App"></a>2. 云作业App</h1><p>首先,理解下 <strong>云作业App</strong> 的定义:</p><blockquote><p>FROM <a href="http://dangdangdotcom.github.io/elastic-job/elastic-job-cloud/02-guide/cloud-concepts/" rel="external nofollow noopener noreferrer" target="_blank">http://dangdangdotcom.github.io/elastic-job/elastic-job-cloud/02-guide/cloud-concepts/</a> </p><p>作业APP指作业打包部署后的应用,描述了作业启动需要用到的CPU、内存、启动脚本及应用下载路径等基本信息,每个APP可以包含一个或多个作业。</p></blockquote><p>简单来说,一个云作业App可以理解成由多个作业打在一起的 <code>jar</code>。</p><h2 id="2-1-云作业App配置类"><a href="#2-1-云作业App配置类" class="headerlink" title="2.1 云作业App配置类"></a>2.1 云作业App配置类</h2><p>CloudAppConfiguration,云作业App配置。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CloudAppConfiguration</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 应用名</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String appName;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 应用包地址</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String appURL;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 应用启动脚本</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String bootstrapScript;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * cpu 数量</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">double</span> cpuCount = <span class="number">1</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 内存 大小</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">double</span> memoryMB = <span class="number">128</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 每次执行作业时是否从缓存中读取应用。禁用则每次执行任务均从应用仓库下载应用至本地</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> appCacheEnable = <span class="keyword">true</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 常驻作业事件采样率统计条数,默认不采样全部记录。</span></div><div class="line"><span class="comment"> * 为避免数据量过大,可对频繁调度的常驻作业配置采样率,即作业每执行N次,才会记录作业执行及追踪相关数据</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">int</span> eventTraceSamplingCount;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>在 Elastic-Job-Lite 里,打包作业,部署到服务器里启动。而在 Elastic-Job-Cloud 里,打包作业上传至可下载的地址。作业被调度时,Mesos 会从 <code>appURL</code> 下载应用包,使用 <code>bootstrapScript</code> 启动应用进行执行。实际情况会更加复杂一丢丢,在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor/?self">《Elastic-Job-Cloud 源码解析 —— 作业调度(一)》</a>详细解析。</li><li><code>cpuCount</code>, <code>memoryMB</code> 配置<strong>云作业App自身占用的资源情况</strong>。其包含的每个作业占用的资源情况,使用作业对应的云作业配置( CloudJobConfiguration ) ,下文也会看到。</li><li><code>appCacheEnable</code>:每次执行作业时是否从缓存中读取应用。禁用则每次执行任务均从应用仓库下载应用至本地。</li><li><code>eventTraceSamplingCount</code>:常驻作业事件采样率统计条数,默认采样全部记录。为避免数据量过大,可对频繁调度的常驻作业配置采样率,即作业每执行N次,才会记录作业执行及追踪相关数据。</li></ul><h2 id="2-2-操作云作业App配置"><a href="#2-2-操作云作业App配置" class="headerlink" title="2.2 操作云作业App配置"></a>2.2 操作云作业App配置</h2><p>云作业App配置有多种操作:</p><ol><li>添加 / 更新 / 删除</li><li>开启 / 禁用</li></ol><p>有两种方式进行操作,以<strong>添加</strong>举例子:</p><ul><li><p>调用 HTTP 接口:</p> <figure class="highlight shell"><table><tr><td class="code"><pre><div class="line">curl -l -H "Content-type: application/json" -X POST -d '{"appName":"foo_app","appURL":"http://app_host:8080/yourJobs.gz","cpuCount":0.1,"memoryMB":64.0,"bootstrapScript":"bin/start.sh","appCacheEnable":true,"eventTraceSamplingCount":0}' http://elastic_job_cloud_host:8899/api/app</div></pre></td></tr></table></figure></li><li><p>运维平台</p><p> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_14/02.png" alt=""></p></li></ul><p>运维平台是对调用 HTTP 接口的UI封装,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CloudAppRestfulApi</span></div><div class="line"><span class="meta">@Path</span>(<span class="string">"/app"</span>)</div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CloudAppRestfulApi</span> </span>{</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 注册应用配置.</span></div><div class="line"><span class="comment"> * </span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> appConfig 应用配置</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@POST</span></div><div class="line"> <span class="meta">@Consumes</span>(MediaType.APPLICATION_JSON)</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(<span class="keyword">final</span> CloudAppConfiguration appConfig)</span> </span>{</div><div class="line"> Optional<CloudAppConfiguration> appConfigFromZk = appConfigService.load(appConfig.getAppName());</div><div class="line"> <span class="keyword">if</span> (appConfigFromZk.isPresent()) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> AppConfigurationException(<span class="string">"app '%s' already existed."</span>, appConfig.getAppName());</div><div class="line"> }</div><div class="line"> appConfigService.add(appConfig);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// CloudAppConfigurationService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 添加云作业APP配置.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> appConfig 云作业App配置对象</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(<span class="keyword">final</span> CloudAppConfiguration appConfig)</span> </span>{</div><div class="line"> regCenter.persist(CloudAppConfigurationNode.getRootNodePath(appConfig.getAppName()), CloudAppConfigurationGsonFactory.toJson(appConfig));</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// CloudAppConfigurationNode.JAVA</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CloudAppConfigurationNode</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String ROOT = <span class="string">"/config/app"</span>;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String APP_CONFIG = ROOT + <span class="string">"/%s"</span>; <span class="comment">// %s = ${APP_NAME}</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>CloudAppRestfulApi,云作业应用的REST API,实现了云作业App配置的多种操作的 HTTP 接口。</li><li>CloudAppConfigurationService,云作业App配置服务,实现了云作业应用的存储功能。</li><li>调用 <code>AppConfigService#add(...)</code> 方法,存储 CloudAppConfiguration 到注册中心( Zookeeper )的<strong>持久</strong>数据节点 <code>${NAMESPACE}/config/app/${APP_NAME}</code>,JSON 格式化对象。使用 zkClient 查看如下:</li></ul><figure class="highlight shell"><table><tr><td class="code"><pre><div class="line">[zk: localhost:2181(CONNECTED) 1] get /elastic-job-cloud/config/app/exampleApp</div><div class="line">{"appName":"exampleApp","appURL":"http://785j8w.com1.z0.glb.clouddn.com/elastic-job-example-cloud-2.1.5.tar.gz","bootstrapScript":"bin/start.sh","cpuCount":1.0,"memoryMB":128.0,"appCacheEnable":true,"eventTraceSamplingCount":0}</div></pre></td></tr></table></figure><h1 id="3-云作业"><a href="#3-云作业" class="headerlink" title="3. 云作业"></a>3. 云作业</h1><p>一个云作业应用可以包含一个或多个云作业。云作业有<strong>两种</strong>作业配置:云作业配置、本地云作业配置。下面来分别分享它们。</p><h2 id="3-1-云作业配置"><a href="#3-1-云作业配置" class="headerlink" title="3.1 云作业配置"></a>3.1 云作业配置</h2><p>CloudJobConfiguration,云作业配置。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CloudJobConfiguration</span> <span class="keyword">implements</span> <span class="title">JobRootConfiguration</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业应用名称 {<span class="doctag">@link</span> com.dangdang.ddframe.job.cloud.scheduler.config.app.CloudAppConfiguration}</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String appName;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业类型配置</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> JobTypeConfiguration typeConfig;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 单片作业所需要的CPU数量,最小值为0.001</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">double</span> cpuCount;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 单片作业所需要的内存MB,最小值为1</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">double</span> memoryMB;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业执行类型</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> CloudJobExecutionType jobExecutionType;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Spring容器中配置的bean名称</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String beanName;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Spring方式配置Spring配置文件相对路径以及名称,如:META-INF\applicationContext.xml</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String applicationContext; </div><div class="line">}</div></pre></td></tr></table></figure><ul><li>JobTypeConfiguration,作业类型配置,在 <code>elastic-job-common-core</code> 项目里,为 Elastic-Job-Lite、Elastic-Job-Cloud <strong>公用</strong>作业配置类。在<a href="http://www.iocoder.cn/Elastic-Job/job-config/?self">《Elastic-Job-Lite 源码分析 —— 作业配置》的「2.2.1 作业类型配置」</a>有详细解析。</li><li><code>cpuCount</code>, <code>memoryMB</code> 配置<strong>单片作业占用的资源情况</strong>。这里一定要注意是单片作业,例如一个作业有三个分片( <code>shardingTotalCount = 3</code> ),则占用资源为 3 <em> <code>cpuCount</code> + 3 </em> <code>memoryMB</code>。</li><li>作业执行类型( CloudJobExecutionType )有两种:常驻作业( DAEMON ),瞬时作业( TRANSIENT )。在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor/?self">《Elastic-Job-Cloud 源码解析 —— 作业调度(一)》</a>详细解析。Elastic-Job-Cloud 独有,非常有趣。👍👍👍</li><li><code>beanName</code>, <code>applicationContext</code> 实现 Spring 启动方式作业。在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor/?self">《Elastic-Job-Cloud 源码解析 —— 作业调度(一)》</a>有详细解析。</li></ul><h3 id="3-1-1-操作云作业配置"><a href="#3-1-1-操作云作业配置" class="headerlink" title="3.1.1 操作云作业配置"></a>3.1.1 操作云作业配置</h3><p>云作业配置有多种操作:</p><ol><li>添加 / 更新 / 删除</li><li>开启 / 禁用</li></ol><p>有两种方式进行操作,以<strong>添加</strong>举例子:</p><ul><li><p>调用 HTTP 接口:</p> <figure class="highlight shell"><table><tr><td class="code"><pre><div class="line">// Java启动方式作业注册</div><div class="line">curl -l -H "Content-type: application/json" -X POST -d '{"jobName":"foo_job","appName":"foo_app","jobClass":"yourJobClass","jobType":"SIMPLE","jobExecutionType":"TRANSIENT","cron":"0/5 * * * * ?","shardingTotalCount":5,"cpuCount":0.1,"memoryMB":64.0,"appURL":"http://app_host:8080/foo-job.tar.gz","failover":true,"misfire":true,"bootstrapScript":"bin/start.sh"}' http://elastic_job_cloud_host:8899/api/job/register</div><div class="line"></div><div class="line">// Spring启动方式作业注册</div><div class="line">curl -l -H "Content-type: application/json" -X POST -d '{"jobName":"foo_job","jobClass":"yourJobClass","beanName":"yourBeanName","applicationContext":"applicationContext.xml","jobType":"SIMPLE","jobExecutionType":"TRANSIENT","cron":"0/5 * * * * ?","shardingTotalCount":5,"cpuCount":0.1,"memoryMB":64.0,"appURL":"http://file_host:8080/foo-job.tar.gz","failover":false,"misfire":true,"bootstrapScript":"bin/start.sh"}' http://elastic_job_cloud_masterhost:8899/api/job/register</div></pre></td></tr></table></figure></li><li><p>运维平台</p><p> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_14/03.png" alt=""></p></li></ul><p>运维平台是对调用 HTTP 接口的UI封装,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// CloudJobRestfulApi.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CloudJobRestfulApi</span> </span>{</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 注册作业.</span></div><div class="line"><span class="comment"> * </span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> jobConfig 作业配置</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@POST</span></div><div class="line"> <span class="meta">@Path</span>(<span class="string">"/register"</span>)</div><div class="line"> <span class="meta">@Consumes</span>(MediaType.APPLICATION_JSON)</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> producerManager.register(jobConfig);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// ProducerManager.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ProducerManager</span> </span>{</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 注册作业.</span></div><div class="line"><span class="comment"> * </span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> jobConfig 作业配置</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (disableJobService.isDisabled(jobConfig.getJobName())) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobConfigurationException(<span class="string">"Job '%s' has been disable."</span>, jobConfig.getJobName());</div><div class="line"> }</div><div class="line"> Optional<CloudAppConfiguration> appConfigFromZk = appConfigService.load(jobConfig.getAppName());</div><div class="line"> <span class="keyword">if</span> (!appConfigFromZk.isPresent()) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> AppConfigurationException(<span class="string">"Register app '%s' firstly."</span>, jobConfig.getAppName());</div><div class="line"> }</div><div class="line"> Optional<CloudJobConfiguration> jobConfigFromZk = configService.load(jobConfig.getJobName());</div><div class="line"> <span class="keyword">if</span> (jobConfigFromZk.isPresent()) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobConfigurationException(<span class="string">"Job '%s' already existed."</span>, jobConfig.getJobName());</div><div class="line"> }</div><div class="line"> <span class="comment">// 添加云作业配置</span></div><div class="line"> configService.add(jobConfig);</div><div class="line"> <span class="comment">// 调度作业</span></div><div class="line"> schedule(jobConfig);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// CloudJobConfigurationService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 添加云作业配置.</span></div><div class="line"><span class="comment">* </span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> jobConfig 云作业配置对象</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(<span class="keyword">final</span> CloudJobConfiguration jobConfig)</span> </span>{</div><div class="line"> regCenter.persist(CloudJobConfigurationNode.getRootNodePath(jobConfig.getJobName()), CloudJobConfigurationGsonFactory.toJson(jobConfig));</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// CloudJobConfigurationNode.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CloudJobConfigurationNode</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String ROOT = <span class="string">"/config/job"</span>;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String JOB_CONFIG = ROOT + <span class="string">"/%s"</span>; <span class="comment">// %s = ${JOB_NAME}</span></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>CloudJobRestfulApi,作业云Job的REST API,实现了作业云Job配置的多种操作、查询运行中 / 待运行 / 失效转移作业列表等 HTTP 接口。</li><li>ProducerManager,发布任务作业调度管理器。这是一个很重要的类,在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor/?self">《Elastic-Job-Cloud 源码解析 —— 作业调度(一)》</a>详细解析。</li><li>CloudJobConfigurationService,作业配置服务,实现了作业配置的存储功能。</li><li><p>调用 <code>CloudJobConfigurationService#add(...)</code> 方法,存储 CloudJobConfiguration 到注册中心( Zookeeper )的<strong>持久</strong>数据节点 <code>${NAMESPACE}/config/job/${JOB_NAME}</code>,JSON 格式化对象。使用 zkClient 查看如下:</p> <figure class="highlight shell"><table><tr><td class="code"><pre><div class="line"> [zk: localhost:2181(CONNECTED) 3] get /elastic-job-cloud/config/job/test_job_simple</div><div class="line">{"jobName":"test_job_simple","jobClass":"com.dangdang.ddframe.job.example.job.simple.JavaSimpleJob","jobType":"SIMPLE","cron":"0/10 * * * * ?","shardingTotalCount":1,"shardingItemParameters":"","jobParameter":"","failover":false,"misfire":false,"description":"","jobProperties":{"job_exception_handler":"com.dangdang.ddframe.job.executor.handler.impl.DefaultJobExceptionHandler","executor_service_handler":"com.dangdang.ddframe.job.executor.handler.impl.DefaultExecutorServiceHandler"},"appName":"exampleApp","cpuCount":0.1,"memoryMB":64.0,"jobExecutionType":"TRANSIENT"}</div></pre></td></tr></table></figure></li><li><p>调用 <code>#schedule(...)</code> 方法,调度作业。这是个很有趣的方法,在<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor/?self">《Elastic-Job-Cloud 源码解析 —— 作业调度(一)》</a>详细解析。</p></li></ul><h2 id="3-2-本地云作业配置"><a href="#3-2-本地云作业配置" class="headerlink" title="3.2 本地云作业配置"></a>3.2 本地云作业配置</h2><p>LocalCloudJobConfiguration,本地云作业配置。实现代码如下:</p><figure class="highlight plain"><table><tr><td class="code"><pre><div class="line">public final class LocalCloudJobConfiguration implements JobRootConfiguration {</div><div class="line"> </div><div class="line"> private final JobTypeConfiguration typeConfig;</div><div class="line"></div><div class="line"> /**</div><div class="line"> * 分片作业序号</div><div class="line"> */</div><div class="line"> private final int shardingItem;</div><div class="line"> </div><div class="line"> private String beanName;</div><div class="line"> </div><div class="line"> private String applicationContext;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>shardingItem</code>,分片作业序号,用于本地调试指定分片作业项。</li></ul><p>到底有什么用呢?</p><blockquote><p>在开发Elastic-Job-Cloud作业时,开发人员可以脱离Mesos环境,在本地运行和调试作业。可以利用本地运行模式充分的调试业务功能以及单元测试,完成之后再部署至Mesos集群。<br>本地运行作业无需安装Mesos环境。</p></blockquote><p>在<a href="http://elasticjob.io/docs/elastic-job-cloud/02-guide/local-executor/" rel="external nofollow noopener noreferrer" target="_blank">《官方文档 —— 本地运行模式》</a>有详细解析。</p><h2 id="3-3-云作业配置总结"><a href="#3-3-云作业配置总结" class="headerlink" title="3.3 云作业配置总结"></a>3.3 云作业配置总结</h2><ul><li>CloudJobConfiguration:生产运行使用。</li><li>LocalCloudJobConfiguration:本地开发调试。</li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>芋道君:本文主要为<a href="http://www.iocoder.cn/Elastic-Job/cloud-job-scheduler-and-executor/?self">《Elastic-Job-Cloud 源码解析 —— 作业调度(一)》</a>做铺垫,这会是一篇长文。读懂 Elastic-Job-Cloud 作业调度后,整个人脑洞又开的不行不行的!<br>旁白君:支持+1024。</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_14/04.png" alt=""></p><p>另外,推荐资料如下,对理解 Elastic-Job-Cloud 很有帮助。</p><ul><li><a href="http://www.infoq.com/cn/news/2016/09/Mesos-Elastic-Job-Cloud" rel="external nofollow noopener noreferrer" target="_blank">《基于Mesos的当当作业云Elastic Job Cloud》</a></li><li><a href="http://www.infoq.com/cn/presentations/how-to-build-elastic-job-cloud" rel="external nofollow noopener noreferrer" target="_blank">《如何从0到1搭建弹性作业云Elastic-Job-Cloud》</a></li></ul><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. 云作业App</a><ul>
<li><a href="#">2
</summary>
<category term="Elastic-Job-Cloud" scheme="http://www.iocoder.cn/categories/Elastic-Job-Cloud/"/>
</entry>
<entry>
<title>Elastic-Job-Lite 源码分析 —— 运维平台</title>
<link href="http://www.iocoder.cn/Elastic-Job/job-console/"/>
<id>http://www.iocoder.cn/Elastic-Job/job-console/</id>
<published>2017-12-06T16:00:00.000Z</published>
<updated>2017-09-06T10:53:51.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. Maven模块 elastic-job-common-restful</a></li><li><a href="#">3. Maven模块 elastic-job-console</a></li><li><a href="#">4. Maven模块 elastic-job-lite-lifecycle</a></li><li><a href="#">5. 其它</a></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Lite 运维平台</strong>。内容对应<a href="http://dangdangdotcom.github.io/elastic-job/elastic-job-lite/02-guide/web-console/" rel="external nofollow noopener noreferrer" target="_blank">《官方文档 —— 运维平台》</a>。</p><p>运维平台实现上比较易懂,就不特别<strong>啰嗦</strong>的解析,简略说下每个类的用途和 UI 上的关联。</p><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-Maven模块-elastic-job-common-restful"><a href="#2-Maven模块-elastic-job-common-restful" class="headerlink" title="2. Maven模块 elastic-job-common-restful"></a>2. Maven模块 elastic-job-common-restful</h1><ol><li>RestfulServer 内嵌服务器,基于 Jetty 实现</li><li>GSONProvider 后端接口 JSON 格式化 </li><li>RestfulExceptionMapper 异常映射</li><li>WwwAuthFilter 授权认证 Filter</li></ol><h1 id="3-Maven模块-elastic-job-console"><a href="#3-Maven模块-elastic-job-console" class="headerlink" title="3. Maven模块 elastic-job-console"></a>3. Maven模块 elastic-job-console</h1><h2 id="3-1-domain-包"><a href="#3-1-domain-包" class="headerlink" title="3.1 domain 包"></a>3.1 domain 包</h2><ul><li>RegistryCenterConfigurations / RegistryCenterConfiguration :注册中心配置实体相关。</li><li>EventTraceDataSourceConfigurations / EventTraceDataSourceConfiguration / EventTraceDataSource / EventTraceDataSourceFactory :事件事件追踪数据源配置实体相关。</li></ul><h2 id="3-2-filter-包"><a href="#3-2-filter-包" class="headerlink" title="3.2 filter 包"></a>3.2 filter 包</h2><ul><li>GlobalConfigurationFilter :全局配置过滤器,加载当前会话( HttpSession ) 选择的 RegistryCenterConfiguration / EventTraceDataSource 。</li></ul><h2 id="3-3-repository-包"><a href="#3-3-repository-包" class="headerlink" title="3.3 repository 包"></a>3.3 repository 包</h2><p>使用 <strong>XML文件</strong> 存储 EventTraceDataSource / RegistryCenterConfiguration 配置实体。</p><h2 id="3-4-restful-包"><a href="#3-4-restful-包" class="headerlink" title="3.4 restful 包"></a>3.4 restful 包</h2><ul><li><p><code>config</code> / RegistryCenterRestfulApi :注册中心配置( RegistryCenterConfiguration )的RESTful API<br> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_07/01.png" alt=""></p></li><li><p><code>config</code> / EventTraceDataSourceRestfulApi :事件追踪数据源配置( EventTraceDataSource )的RESTful API<br> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_07/02.png" alt=""></p></li><li><p><code>config</code> / LiteJobConfigRestfulApi :作业配置( LiteJobConfiguration )的RESTful API<br> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_07/03.png" alt=""></p></li><li><p>EventTraceHistoryRestfulApi :事件追踪历史记录( <code>JOB_EXECUTION_LOG</code> / <code>JOB_STATUS_TRACE_LOG</code> )的RESTful API<br> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_07/06.png" alt=""><br> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_07/07.png" alt=""></p></li><li><p>ServerOperationRestfulApi :服务器维度操作的RESTful API。<br> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_07/05.png" alt=""></p></li><li><p>JobOperationRestfulApi :作业维度操作的RESTful API。<br> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_07/04.png" alt=""></p></li></ul><h2 id="3-5-service-包"><a href="#3-5-service-包" class="headerlink" title="3.5 service 包"></a>3.5 service 包</h2><ul><li>RegistryCenterConfigurationService :注册中心( RegistryCenterConfiguration )配置服务。</li><li>EventTraceDataSourceConfigurationService :事件追踪数据源配置( EventTraceDataSource )服务。</li><li>JobAPIService :和作业相关的 API 集合服务。这些 API 在 Maven模块 <code>elastic-job-lite-lifecycle</code> 实现。<ul><li>JobSettingsAPI:作业配置的API。</li><li>JobOperateAPI :操作作业的API。</li><li>ShardingOperateAPI :操作分片的API。</li><li>JobStatisticsAPI :JobStatisticsAPI。</li><li>ServerStatisticsAPI :作业服务器状态展示的API。</li><li>ShardingStatisticsAPI :作业分片状态展示的API。</li></ul></li></ul><h1 id="4-Maven模块-elastic-job-lite-lifecycle"><a href="#4-Maven模块-elastic-job-lite-lifecycle" class="headerlink" title="4. Maven模块 elastic-job-lite-lifecycle"></a>4. Maven模块 elastic-job-lite-lifecycle</h1><p>在 JobAPIService 已经基本提到,这里不重复叙述。</p><h1 id="5-其它"><a href="#5-其它" class="headerlink" title="5. 其它"></a>5. 其它</h1><ol><li>前后端分离,后端使用 JSON 为前端提供数据接口。</li><li>后端 API 使用 Restful 设计规范。 </li><li>国际化使用 <code>jquery.i18n.js</code> 实现。</li><li>界面使用 Bootstrap AdminLTE 模板实现。</li></ol><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>旁白君:这写的… 略飘逸(随意)<br>芋道君:哈哈哈,我要开始 Elastic-Job-Cloud 啦啦啦啦。</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_07/08.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. Maven模块 elastic-job-common-restf
</summary>
<category term="Elastic-Job-Lite" scheme="http://www.iocoder.cn/categories/Elastic-Job-Lite/"/>
</entry>
<entry>
<title>闲聊如何阅读源码(音频)</title>
<link href="http://www.iocoder.cn/Architecture/how-to-read-source-code/"/>
<id>http://www.iocoder.cn/Architecture/how-to-read-source-code/</id>
<published>2017-12-01T16:00:00.000Z</published>
<updated>2017-09-06T10:53:51.000Z</updated>
<content type="html"><![CDATA[<h1 id="0-从零开始"><a href="#0-从零开始" class="headerlink" title="0. 从零开始"></a>0. 从零开始</h1><p>一个喝了假酒的视频:</p><p><a href="https://v.qq.com/x/page/p0543tzm648.html" rel="external nofollow noopener noreferrer" target="_blank">视频传送门</a><br><a href="https://v.qq.com/x/page/p0543tzm648.html" rel="external nofollow noopener noreferrer" target="_blank">视频传送门</a><br><a href="https://v.qq.com/x/page/p0543tzm648.html" rel="external nofollow noopener noreferrer" target="_blank">视频传送门</a> </p><p>请关注笔者的公众号:芋道源码</p><ul><li>专注源码解析三十年!</li><li>专注源码解析三十年!</li><li>专注源码解析三十年!</li><li><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></li></ul><h1 id="1-调试"><a href="#1-调试" class="headerlink" title="1. 调试"></a>1. 调试</h1><p>通过 IDE 工具<strong>调试</strong>的方式阅读源码。</p><h2 id="1-1-Elastic-Job"><a href="#1-1-Elastic-Job" class="headerlink" title="1.1 Elastic-Job"></a>1.1 Elastic-Job</h2><ul><li>GitHub地址:<a href="https://github.com/dangdangdotcom/elastic-job" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/dangdangdotcom/elastic-job</a><ul><li>开源不易,请点 Star 支持。</li><li>个人 Fork,增加部分中文注释 GitHub地址:<a href="https://github.com/YunaiV/elastic-job" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/YunaiV/elastic-job</a></li></ul></li><li>为什么阅读 Elastic-Job 源码?:<a href="http://www.iocoder.cn/Elastic-Job/why-read-Elastic-Job-source-code/">http://www.iocoder.cn/Elastic-Job/why-read-Elastic-Job-source-code/</a></li><li>调试方式:<code>com.dangdang.ddframe.job.example.JavaMain</code> 直接 DEBUG 即可。</li></ul><h2 id="1-2-Sharding-JDBC"><a href="#1-2-Sharding-JDBC" class="headerlink" title="1.2 Sharding-JDBC"></a>1.2 Sharding-JDBC</h2><ul><li>GitHub地址:<a href="https://github.com/dangdangdotcom/sharding-jdbc" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/dangdangdotcom/sharding-jdbc</a><ul><li>开源不易,请点 Star 支持。</li><li>个人 Fork,增加部分中文注释 GitHub地址:<a href="https://github.com/YunaiV/sharding-jdbc" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/YunaiV/sharding-jdbc</a><ul><li>基于 Sharding-JDBC V1.5.1 版本,建议阅读最新版本代码,我的可以作为注释补充。 </li></ul></li></ul></li><li>为什么阅读 Sharding-JDBC 源码?:<a href="http://www.iocoder.cn/Sharding-JDBC/why-read-Sharding-JDBC-source-code/">http://www.iocoder.cn/Sharding-JDBC/why-read-Sharding-JDBC-source-code/</a></li><li>调试方式:<code>com.dangdang.ddframe.rdb.sharding.example.jdbc.Main</code> 配置数据源后可以 DEBUG。</li><li>拆解图:<img src="http://www.iocoder.cn/images/Architecture/2017_12_02/01.png" alt=""></li></ul><h2 id="1-3-MyCAT"><a href="#1-3-MyCAT" class="headerlink" title="1.3 MyCAT"></a>1.3 MyCAT</h2><ul><li>GitHub地址:<a href="https://github.com/MyCATApache/Mycat-Server" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/MyCATApache/Mycat-Server</a><ul><li>开源不易,请点 Star 支持。</li><li>个人 Fork,增加部分中文注释 GitHub地址:<a href="https://github.com/YunaiV/Mycat-Server" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/YunaiV/Mycat-Server</a></li></ul></li><li>为什么阅读 MyCAT 源码?:<a href="http://www.iocoder.cn/MyCAT/why-read-MyCAT-source-code/">http://www.iocoder.cn/MyCAT/why-read-MyCAT-source-code/</a></li><li>调试方式:<a href="http://www.iocoder.cn/MyCAT/build-debugging-environment/">http://www.iocoder.cn/MyCAT/build-debugging-environment/</a></li></ul><h2 id="1-4-RocketMQ"><a href="#1-4-RocketMQ" class="headerlink" title="1.4 RocketMQ"></a>1.4 RocketMQ</h2><ul><li>GitHub地址:<a href="https://github.com/apache/incubator-rocketmq" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/apache/incubator-rocketmq</a><ul><li>开源不易,请点 Star 支持。</li><li>个人 Fork,增加部分中文注释 GitHub地址:<a href="https://github.com/YunaiV/incubator-rocketmq" rel="external nofollow noopener noreferrer" target="_blank">https://github.com/YunaiV/incubator-rocketmq</a></li></ul></li><li>为什么阅读 RocketMQ 源码?:<a href="http://www.iocoder.cn/RocketMQ/why-read-RocketMQ-source-code/">http://www.iocoder.cn/RocketMQ/why-read-RocketMQ-source-code/</a></li><li>调试方式:有点想不起来了。以后补充哈。</li></ul><h2 id="1-5-补充"><a href="#1-5-补充" class="headerlink" title="1.5 补充"></a>1.5 补充</h2><p>如果源码比较多,你不太清楚按照什么样的功能顺序调试下去,可以参考我写的博客。这个不是广告。确实有朋友是对照着博客顺序调试的。</p><h1 id="2-UML图"><a href="#2-UML图" class="headerlink" title="2. UML图"></a>2. UML图</h1><p>推荐使用 Astah 社区版。对的,不要考虑破解不破解的。社区版,够!</p><p>另外,请不要纠结用什么 UML 工具牛逼!重点先迈出调试源码那一步,其他的,慢慢优化。</p><p>推荐画两种图:</p><ul><li><p>类图:对整体、或者某个模块,有系统性的认识。</p><p> <img src="http://www.iocoder.cn/images/Architecture/2017_12_02/02.png" alt=""></p></li><li><p>顺序图:对流程的调用顺序有整体的认识,避免调试,越调试越晕。</p><p> <img src="http://www.iocoder.cn/images/Architecture/2017_12_02/03.png" alt=""></p></li></ul><p>如果你不会画 UML 图,直接直接 Google,不要去找什么 UML 的书,先干起来!</p><p>恩,笔者不排除看 UML 书,因为,我也看过几本,收获不小,哈哈哈哈。</p><h1 id="3-输出"><a href="#3-输出" class="headerlink" title="3. 输出"></a>3. 输出</h1><p>阅读源码,不尝试输出,就是在耍流氓。</p><p>推荐三种输出方式:</p><ol><li>写博客。</li><li>团队分享。</li><li>造小轮。</li></ol><p>刚开始推荐写博客。对的,不太建议只写自己看得懂的笔记。</p>]]></content>
<summary type="html">
<h1 id="0-从零开始"><a href="#0-从零开始" class="headerlink" title="0. 从零开始"></a>0. 从零开始</h1><p>一个喝了假酒的视频:</p>
<p><a href="https://v.qq.com/x/page/p
</summary>
<category term="技术杂文" scheme="http://www.iocoder.cn/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E6%96%87/"/>
</entry>
<entry>
<title>Elastic-Job-Lite 源码分析 —— 作业监控服务</title>
<link href="http://www.iocoder.cn/Elastic-Job/job-monitor/"/>
<id>http://www.iocoder.cn/Elastic-Job/job-monitor/</id>
<published>2017-11-30T16:00:00.000Z</published>
<updated>2017-09-06T10:53:51.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. MonitorService</a></li><li><a href="#">3. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Lite 作业监控服务</strong>。内容对应<a href="http://dangdangdotcom.github.io/elastic-job/elastic-job-lite/02-guide/dump/" rel="external nofollow noopener noreferrer" target="_blank">《官方文档 —— DUMP作业运行信息》</a>。</p><blockquote><p>使用Elastic-Job-Lite过程中可能会碰到一些分布式问题,导致作业运行不稳定。<br>由于无法在生产环境调试,通过dump命令可以把作业内部相关信息dump出来,方便开发者debug分析; 另外为了不泄露隐私,已将相关信息中的ip地址以ip1, ip2…的形式过滤,可以在互联网上公开传输环境信息,便于进一步完善Elastic-Job。</p></blockquote><p>涉及到主要类的类图如下( <a href="http://www.iocoder.cn/images/Elastic-Job/2017_12_01/01.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_01/01.png" alt=""></p><ul><li>在 Elastic-Job-lite 里,作业监控服务( MonitorService ) 实现了<strong>DUMP作业运行信息</strong>功能。</li></ul><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-MonitorService"><a href="#2-MonitorService" class="headerlink" title="2. MonitorService"></a>2. MonitorService</h1><p>MonitorService,作业监控服务。</p><p><strong>初始化 MonitorService 方法实现如下</strong>:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// MonitorService.java</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">final</span> String jobName;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listen</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">int</span> port = configService.load(<span class="keyword">true</span>).getMonitorPort();</div><div class="line"> <span class="keyword">if</span> (port < <span class="number">0</span>) {</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> log.info(<span class="string">"Elastic job: Monitor service is running, the port is '{}'"</span>, port);</div><div class="line"> openSocketForMonitor(port);</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> IOException ex) {</div><div class="line"> log.error(<span class="string">"Elastic job: Monitor service listen failure, error is: "</span>, ex);</div><div class="line"> }</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">openSocketForMonitor</span><span class="params">(<span class="keyword">final</span> <span class="keyword">int</span> port)</span> <span class="keyword">throws</span> IOException </span>{</div><div class="line"> serverSocket = <span class="keyword">new</span> ServerSocket(port);</div><div class="line"> <span class="keyword">new</span> Thread() {</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">while</span> (!closed) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> process(serverSocket.accept());</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> IOException ex) {</div><div class="line"> log.error(<span class="string">"Elastic job: Monitor service open socket for monitor failure, error is: "</span>, ex);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }.start();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>在作业配置的监控服务端口属性( <code>LiteJobConfiguration.monitorPort</code> )启动 <strong>ServerSocket</strong>。一个作业对应一个作业监控端口,所以配置时,请不要重复端口噢。</li></ul><p><strong>处理 dump命令 方法如下</strong>:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// MonitorService.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">process</span><span class="params">(<span class="keyword">final</span> Socket socket)</span> <span class="keyword">throws</span> IOException </span>{</div><div class="line"> <span class="keyword">try</span> (</div><div class="line"> BufferedReader reader = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(socket.getInputStream()));</div><div class="line"> BufferedWriter writer = <span class="keyword">new</span> BufferedWriter(<span class="keyword">new</span> OutputStreamWriter(socket.getOutputStream()));</div><div class="line"> Socket autoCloseSocket = socket) {</div><div class="line"> <span class="comment">// 读取命令</span></div><div class="line"> String cmdLine = reader.readLine();</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != cmdLine && DUMP_COMMAND.equalsIgnoreCase(cmdLine)) { <span class="comment">// DUMP</span></div><div class="line"> List<String> result = <span class="keyword">new</span> ArrayList<>();</div><div class="line"> dumpDirectly(<span class="string">"/"</span> + jobName, result);</div><div class="line"> outputMessage(writer, Joiner.on(<span class="string">"\n"</span>).join(SensitiveInfoUtils.filterSensitiveIps(result)) + <span class="string">"\n"</span>);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>#process()</code> 方法,目前只支持 <code>DUMP</code> 命令。如果你有自定义命令的需要,可以拓展该方法。</li><li><p>调用 <code>#dumpDirectly()</code> 方法,输出当前作业名对应的相关调试信息。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">dumpDirectly</span><span class="params">(<span class="keyword">final</span> String path, <span class="keyword">final</span> List<String> result)</span> </span>{</div><div class="line"> <span class="keyword">for</span> (String each : regCenter.getChildrenKeys(path)) {</div><div class="line"> String zkPath = path + <span class="string">"/"</span> + each;</div><div class="line"> String zkValue = regCenter.get(zkPath);</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == zkValue) {</div><div class="line"> zkValue = <span class="string">""</span>;</div><div class="line"> }</div><div class="line"> TreeCache treeCache = (TreeCache) regCenter.getRawCache(<span class="string">"/"</span> + jobName);</div><div class="line"> ChildData treeCacheData = treeCache.getCurrentData(zkPath);</div><div class="line"> String treeCachePath = <span class="keyword">null</span> == treeCacheData ? <span class="string">""</span> : treeCacheData.getPath();</div><div class="line"> String treeCacheValue = <span class="keyword">null</span> == treeCacheData ? <span class="string">""</span> : <span class="keyword">new</span> String(treeCacheData.getData());</div><div class="line"> <span class="comment">// 判断 TreeCache缓存 和 注册中心 数据一致</span></div><div class="line"> <span class="keyword">if</span> (zkValue.equals(treeCacheValue) && zkPath.equals(treeCachePath)) {</div><div class="line"> result.add(Joiner.on(<span class="string">" | "</span>).join(zkPath, zkValue));</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> result.add(Joiner.on(<span class="string">" | "</span>).join(zkPath, zkValue, treeCachePath, treeCacheValue));</div><div class="line"> }</div><div class="line"> <span class="comment">// 递归</span></div><div class="line"> dumpDirectly(zkPath, result);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>当作业本地 <strong>TreeCache缓存</strong> 和注册中心数据不一致时,DUMP 出 [zkPath, zkValue, treeCachePath, treeCacheValue]。当相同时,只需 DUMP 出 [zkPath, zkValue],<strong>方便看出本地和注册中心是否存在数据差异。</strong></li></ul></li><li><p>DUMP 信息例子如下:</p> <figure class="highlight bash"><table><tr><td class="code"><pre><div class="line">Yunai-MacdeMacBook-Pro-2:elastic-job yunai$ <span class="built_in">echo</span> <span class="string">"dump"</span> | nc 127.0.0.1 10024</div><div class="line">/javaSimpleJob/sharding | </div><div class="line">/javaSimpleJob/sharding/2 | </div><div class="line">/javaSimpleJob/sharding/2/instance | ip198@-@5100</div><div class="line">/javaSimpleJob/sharding/1 | </div><div class="line">/javaSimpleJob/sharding/1/instance | ip198@-@5100</div><div class="line">/javaSimpleJob/sharding/0 | </div><div class="line">/javaSimpleJob/sharding/0/instance | ip198@-@5100</div><div class="line">/javaSimpleJob/servers | </div><div class="line">/javaSimpleJob/servers/ip2 | </div><div class="line">/javaSimpleJob/servers/ip198 | </div><div class="line">/javaSimpleJob/leader | </div><div class="line">/javaSimpleJob/leader/sharding | </div><div class="line">/javaSimpleJob/leader/failover | </div><div class="line">/javaSimpleJob/leader/failover/latch | </div><div class="line">/javaSimpleJob/leader/failover/items | </div><div class="line">/javaSimpleJob/leader/election | </div><div class="line">/javaSimpleJob/leader/election/latch | </div><div class="line">/javaSimpleJob/leader/election/instance | ip198@-@5100</div><div class="line">/javaSimpleJob/instances | </div><div class="line">/javaSimpleJob/instances/ip198@-@5100 | </div><div class="line">/javaSimpleJob/config | {<span class="string">"jobName"</span>:<span class="string">"javaSimpleJob"</span>,<span class="string">"jobClass"</span>:<span class="string">"com.dangdang.ddframe.job.example.job.simple.JavaSimpleJob"</span>,<span class="string">"jobType"</span>:<span class="string">"SIMPLE"</span>,<span class="string">"cron"</span>:<span class="string">"0 0/2 * * * ?"</span>,<span class="string">"shardingTotalCount"</span>:3,<span class="string">"shardingItemParameters"</span>:<span class="string">"0\u003dBeijing,1\u003dShanghai,2\u003dGuangzhou"</span>,<span class="string">"jobParameter"</span>:<span class="string">""</span>,<span class="string">"failover"</span>:<span class="literal">true</span>,<span class="string">"misfire"</span>:<span class="literal">true</span>,<span class="string">"description"</span>:<span class="string">""</span>,<span class="string">"jobProperties"</span>:{<span class="string">"job_exception_handler"</span>:<span class="string">"com.dangdang.ddframe.job.executor.handler.impl.DefaultJobExceptionHandler"</span>,<span class="string">"executor_service_handler"</span>:<span class="string">"com.dangdang.ddframe.job.executor.handler.impl.DefaultExecutorServiceHandler"</span>},<span class="string">"monitorExecution"</span>:<span class="literal">false</span>,<span class="string">"maxTimeDiffSeconds"</span>:-1,<span class="string">"monitorPort"</span>:10024,<span class="string">"jobShardingStrategyClass"</span>:<span class="string">"com.dangdang.ddframe.job.lite.api.strategy.impl.OdevitySortByNameJobShardingStrategy"</span>,<span class="string">"reconcileIntervalMinutes"</span>:10,<span class="string">"disabled"</span>:<span class="literal">false</span>,<span class="string">"overwrite"</span>:<span class="literal">true</span>}</div></pre></td></tr></table></figure></li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>芋道君:是是是,对对的,我水更啦!😆</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_12_01/02.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. MonitorService</a></li>
<li><a h
</summary>
<category term="Elastic-Job-Lite" scheme="http://www.iocoder.cn/categories/Elastic-Job-Lite/"/>
</entry>
<entry>
<title>Elastic-Job-Lite 源码分析 —— 自诊断修复</title>
<link href="http://www.iocoder.cn/Elastic-Job/reconcile/"/>
<id>http://www.iocoder.cn/Elastic-Job/reconcile/</id>
<published>2017-11-27T16:00:00.000Z</published>
<updated>2017-09-06T10:53:51.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. ReconcileService</a></li><li><a href="#">3. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Lite 自诊断修复</strong>。</p><blockquote><p>在分布式的场景下由于网络、时钟等原因,可能导致 Zookeeper 的数据与真实运行的作业产生不一致,这种不一致通过正向的校验无法完全避免。需要另外启动一个线程定时校验注册中心数据与真实作业状态的一致性,即维持 Elastic-Job 的<strong>最终一致性</strong>。</p></blockquote><p>涉及到主要类的类图如下( <a href="http://www.iocoder.cn/images/Elastic-Job/2017_11_28/01.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_11_28/01.png" alt=""></p><ul><li>在 Elastic-Job-lite 里,调解分布式作业不一致状态服务( ReconcileService ) 实现了<strong>自诊断修复</strong>功能。</li></ul><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-ReconcileService"><a href="#2-ReconcileService" class="headerlink" title="2. ReconcileService"></a>2. ReconcileService</h1><p>ReconcileService,调解分布式作业不一致状态服务。</p><p>ReconcileService 继承 Google Guava AbstractScheduledService 抽象类,实现 <code>#scheduler()</code>、<code>#runOneIteration()</code> 方法,达到<strong>周期性</strong>校验注册中心数据与真实作业状态的一致性。</p><p><strong><code>#scheduler()</code> 方法实现如下</strong>:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// ReconcileService.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> Scheduler <span class="title">scheduler</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> Scheduler.newFixedDelaySchedule(<span class="number">0</span>, <span class="number">1</span>, TimeUnit.MINUTES);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>每 1 分钟会调用一次 <code>#runOneIteration()</code> 方法进行校验。</li><li>Google Guava AbstractScheduledService 相关的知识,有兴趣的同学可以自己 Google 学习哟。</li></ul><p><strong><code>#runOneIteration()</code> 方法实现如下</strong>:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// ReconcileService.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">runOneIteration</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> LiteJobConfiguration config = configService.load(<span class="keyword">true</span>);</div><div class="line"> <span class="keyword">int</span> reconcileIntervalMinutes = <span class="keyword">null</span> == config ? -<span class="number">1</span> : config.getReconcileIntervalMinutes();</div><div class="line"> <span class="keyword">if</span> (reconcileIntervalMinutes > <span class="number">0</span> && (System.currentTimeMillis() - lastReconcileTime >= reconcileIntervalMinutes * <span class="number">60</span> * <span class="number">1000</span>)) { <span class="comment">// 校验是否达到校验周期</span></div><div class="line"> <span class="comment">// 设置最后校验时间</span></div><div class="line"> lastReconcileTime = System.currentTimeMillis();</div><div class="line"> <span class="keyword">if</span> (leaderService.isLeaderUntilBlock() <span class="comment">// 主作业节点才可以执行</span></div><div class="line"> && !shardingService.isNeedSharding() <span class="comment">// 当前作业不需要重新分片</span></div><div class="line"> && shardingService.hasShardingInfoInOfflineServers()) { <span class="comment">// 查询是包含有分片节点的不在线服务器</span></div><div class="line"> log.warn(<span class="string">"Elastic Job: job status node has inconsistent value,start reconciling..."</span>);</div><div class="line"> <span class="comment">// 设置需要重新分片的标记</span></div><div class="line"> shardingService.setReshardingFlag();</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>通过作业配置,设置<strong>修复作业服务器不一致状态服务调度间隔时间</strong>属性( <code>LiteJobConfiguration.reconcileIntervalMinutes</code> )。</li><li>调用 <code>ShardingService#setReshardingFlag()</code> 方法,设置需要重新分片的标记。这个也是 ReconcileService 最本质的行为,有了这个标记后,作业会重新进行分片,<strong>达到作业节点本地分片数据与 Zookeeper 数据一致</strong>。作业分片逻辑,在<a href="http://www.iocoder.cn/Elastic-Job/job-sharding/?self">《Elastic-Job-Lite 源码分析 —— 作业分片》</a>有详细解析。</li><li><p>调解分布式作业不一致状态服务一共有三个条件:</p><ul><li>调用 <code>LeaderService#isLeaderUntilBlock()</code> 方法,判断当前作业节点是否为主节点。在<a href="http://www.iocoder.cn/Elastic-Job/election/?self">《Elastic-Job-Lite 源码分析 —— 主节点选举》</a>有详细解析。</li><li>调用 <code>ShardingService#isNeedSharding()</code> 方法,判断当前作业是否需要重分片。如果需要重新分片,就不要重复设置当前作业需要重新分片的标记。</li><li><p>调用 <code>ShardingService#hasShardingInfoInOfflineServers()</code> 方法,查询是否包含有分片节点的不在线服务器。<strong>永久</strong>数据节点 <code>/${JOB_NAME}/sharding/${ITEM_INDEX}/instance</code> 存储分配的作业节点主键( <code>${JOB_INSTANCE_ID}</code> ), <strong>不会</strong>随着作业节点因为各种原因断开后会话超时移除,而<strong>临时</strong>数据节点<code>/${JOB_NAME}/instances/${JOB_INSTANCE_ID}</code> <strong>会</strong>随着作业节点因为各种原因断开后超时会话超时移除。当查询到包含有分片节点的不在线的作业节点,设置需要重新分片的标记后进行重新分片,将其持有的作业分片分配给其它在线的作业节点。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// ShardingService.java</span></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 查询是包含有分片节点的不在线服务器.</span></div><div class="line"><span class="comment"> * </span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 是包含有分片节点的不在线服务器</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">hasShardingInfoInOfflineServers</span><span class="params">()</span> </span>{</div><div class="line"> List<String> onlineInstances = jobNodeStorage.getJobNodeChildrenKeys(InstanceNode.ROOT); <span class="comment">// `/${JOB_NAME}/instances/${JOB_INSTANCE_ID}`</span></div><div class="line"> <span class="keyword">int</span> shardingTotalCount = configService.load(<span class="keyword">true</span>).getTypeConfig().getCoreConfig().getShardingTotalCount();</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < shardingTotalCount; i++) {</div><div class="line"> <span class="keyword">if</span> (!onlineInstances.contains(jobNodeStorage.getJobNodeData(ShardingNode.getInstanceNode(i)))) { <span class="comment">// `/${JOB_NAME}/sharding/${ITEM_INDEX}/instance`</span></div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line">}</div></pre></td></tr></table></figure></li></ul></li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>芋道君:【动作:一脚踢开旁白君】,这是对前面解析的主节点选举和作业分片的复习!不是水更!<br>旁白君:你承认水…【动作:芋道君又来一记千年杀】</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_11_28/02.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. ReconcileService</a></li>
<li><a
</summary>
<category term="Elastic-Job-Lite" scheme="http://www.iocoder.cn/categories/Elastic-Job-Lite/"/>
</entry>
<entry>
<title>Elastic-Job-Lite 源码分析 —— 作业监听器</title>
<link href="http://www.iocoder.cn/Elastic-Job/job-listener/"/>
<id>http://www.iocoder.cn/Elastic-Job/job-listener/</id>
<published>2017-11-20T16:00:00.000Z</published>
<updated>2017-09-06T10:53:51.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. ElasticJobListener</a></li><li><a href="#">3. AbstractDistributeOnceElasticJobListener</a></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Lite 作业监听器</strong>。</p><p>涉及到主要类的类图如下( <a href="http://www.iocoder.cn/images/Elastic-Job/2017_11_21/01.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_11_21/01.png" alt=""></p><ul><li>绿色<strong>监听器</strong>接口 ElasticJobListener,每台作业节点均执行。</li><li>粉色<strong>监听器</strong>接口 AbstractDistributeOnceElasticJobListener,分布式场景中仅单一节点执行。</li><li>蓝色类在 <code>com.dangdang.ddframe.job.lite.internal.guarantee</code> 里,保证分布式任务全部开始和结束状态。 AbstractDistributeOnceElasticJobListener 通过 <code>guarantee</code> 功能,实现分布式场景中仅单一节点执行。</li></ul><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-ElasticJobListener"><a href="#2-ElasticJobListener" class="headerlink" title="2. ElasticJobListener"></a>2. ElasticJobListener</h1><p>ElasticJobListener,作业监听器接口,<strong>每台作业节点均执行</strong>。</p><blockquote><p>若作业处理作业服务器的文件,处理完成后删除文件,可考虑使用每个节点均执行清理任务。此类型任务实现简单,且无需考虑全局分布式任务是否完成,请尽量使用此类型监听器。</p></blockquote><p>接口代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ElasticJobListener</span> </span>{</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业执行前的执行的方法.</span></div><div class="line"><span class="comment"> * </span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> shardingContexts 分片上下文</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">beforeJobExecuted</span><span class="params">(<span class="keyword">final</span> ShardingContexts shardingContexts)</span></span>;</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业执行后的执行的方法.</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> shardingContexts 分片上下文</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">afterJobExecuted</span><span class="params">(<span class="keyword">final</span> ShardingContexts shardingContexts)</span></span>;</div><div class="line">}</div></pre></td></tr></table></figure><p>调用执行如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// AbstractElasticJobExecutor.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// ...省略无关代码</span></div><div class="line"> </div><div class="line"> <span class="comment">// 执行 作业执行前的方法</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> jobFacade.beforeJobExecuted(shardingContexts);</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Throwable cause) {</div><div class="line"> jobExceptionHandler.handleException(jobName, cause);</div><div class="line"> }</div><div class="line"> <span class="comment">// ...省略无关代码(执行 普通触发的作业)</span></div><div class="line"> <span class="comment">// ...省略无关代码(执行 被跳过触发的作业)</span></div><div class="line"> <span class="comment">// ...省略无关代码(执行 作业失效转移)</span></div><div class="line"> </div><div class="line"> <span class="comment">// ...执行 作业执行后的方法</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> jobFacade.afterJobExecuted(shardingContexts);</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Throwable cause) {</div><div class="line"> jobExceptionHandler.handleException(jobName, cause);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>JobFacade 对作业监听器简单封装进行调用。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// LiteJobFacade.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">beforeJobExecuted</span><span class="params">(<span class="keyword">final</span> ShardingContexts shardingContexts)</span> </span>{</div><div class="line"> <span class="keyword">for</span> (ElasticJobListener each : elasticJobListeners) {</div><div class="line"> each.beforeJobExecuted(shardingContexts);</div><div class="line"> }</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterJobExecuted</span><span class="params">(<span class="keyword">final</span> ShardingContexts shardingContexts)</span> </span>{</div><div class="line"> <span class="keyword">for</span> (ElasticJobListener each : elasticJobListeners) {</div><div class="line"> each.afterJobExecuted(shardingContexts);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>下文提到的 AbstractDistributeOnceElasticJobListener,也是这么调用。</p></li></ul><h1 id="3-AbstractDistributeOnceElasticJobListener"><a href="#3-AbstractDistributeOnceElasticJobListener" class="headerlink" title="3. AbstractDistributeOnceElasticJobListener"></a>3. AbstractDistributeOnceElasticJobListener</h1><p>AbstractDistributeOnceElasticJobListener,在分布式作业中只执行一次的监听器。</p><blockquote><p>若作业处理数据库数据,处理完成后只需一个节点完成数据清理任务即可。此类型任务处理复杂,需同步分布式环境下作业的状态同步,提供了超时设置来避免作业不同步导致的死锁,请谨慎使用。</p></blockquote><p><strong>创建</strong> AbstractDistributeOnceElasticJobListener 代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AbstractDistributeOnceElasticJobListener</span> <span class="keyword">implements</span> <span class="title">ElasticJobListener</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 开始超时时间</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">long</span> startedTimeoutMilliseconds;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 开始等待对象</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Object startedWait = <span class="keyword">new</span> Object();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 完成超时时间</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">long</span> completedTimeoutMilliseconds;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 完成等待对象</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Object completedWait = <span class="keyword">new</span> Object();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 保证分布式任务全部开始和结束状态的服务</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> GuaranteeService guaranteeService;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> TimeService timeService = <span class="keyword">new</span> TimeService();</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AbstractDistributeOnceElasticJobListener</span><span class="params">(<span class="keyword">final</span> <span class="keyword">long</span> startedTimeoutMilliseconds, <span class="keyword">final</span> <span class="keyword">long</span> completedTimeoutMilliseconds)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (startedTimeoutMilliseconds <= <span class="number">0L</span>) {</div><div class="line"> <span class="keyword">this</span>.startedTimeoutMilliseconds = Long.MAX_VALUE;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">this</span>.startedTimeoutMilliseconds = startedTimeoutMilliseconds;</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (completedTimeoutMilliseconds <= <span class="number">0L</span>) {</div><div class="line"> <span class="keyword">this</span>.completedTimeoutMilliseconds = Long.MAX_VALUE; </div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">this</span>.completedTimeoutMilliseconds = completedTimeoutMilliseconds;</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>超时参数 <code>startedTimeoutMilliseconds</code>、<code>completedTimeoutMilliseconds</code> 务必传递,避免作业不同步导致的死锁。</li></ul><p>👇下面,我们来看本文的<strong>重点</strong>:AbstractDistributeOnceElasticJobListener,在分布式作业中只执行一次:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">beforeJobExecuted</span><span class="params">(<span class="keyword">final</span> ShardingContexts shardingContexts)</span> </span>{</div><div class="line"> <span class="comment">// 注册作业分片项开始运行</span></div><div class="line"> guaranteeService.registerStart(shardingContexts.getShardingItemParameters().keySet());</div><div class="line"> <span class="comment">// 判断是否所有的分片项开始运行</span></div><div class="line"> <span class="keyword">if</span> (guaranteeService.isAllStarted()) {</div><div class="line"> <span class="comment">// 执行</span></div><div class="line"> doBeforeJobExecutedAtLastStarted(shardingContexts);</div><div class="line"> <span class="comment">// 清理启动信息</span></div><div class="line"> guaranteeService.clearAllStartedInfo();</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 等待</span></div><div class="line"> <span class="keyword">long</span> before = timeService.getCurrentMillis();</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">synchronized</span> (startedWait) {</div><div class="line"> startedWait.wait(startedTimeoutMilliseconds);</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> InterruptedException ex) {</div><div class="line"> Thread.interrupted();</div><div class="line"> }</div><div class="line"> <span class="comment">// 等待超时</span></div><div class="line"> <span class="keyword">if</span> (timeService.getCurrentMillis() - before >= startedTimeoutMilliseconds) {</div><div class="line"> <span class="comment">// 清理启动信息</span></div><div class="line"> guaranteeService.clearAllStartedInfo();</div><div class="line"> handleTimeout(startedTimeoutMilliseconds);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>调用 <code>GuaranteeService#registerStart(...)</code> 方法,注册作业分片项开始运行。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// GuaranteeService.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerStart</span><span class="params">(<span class="keyword">final</span> Collection<Integer> shardingItems)</span> </span>{</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> each : shardingItems) {</div><div class="line"> jobNodeStorage.createJobNodeIfNeeded(GuaranteeNode.getStartedNode(each));</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// GuaranteeNode.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">GuaranteeNode</span> </span>{</div><div class="line"> <span class="keyword">static</span> <span class="keyword">final</span> String ROOT = <span class="string">"guarantee"</span>;</div><div class="line"> <span class="keyword">static</span> <span class="keyword">final</span> String STARTED_ROOT = ROOT + <span class="string">"/started"</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">static</span> String <span class="title">getStartedNode</span><span class="params">(<span class="keyword">final</span> <span class="keyword">int</span> shardingItem)</span> </span>{</div><div class="line"> <span class="keyword">return</span> Joiner.on(<span class="string">"/"</span>).join(STARTED_ROOT, shardingItem);</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>Zookeeper 数据节点 <code>/${JOB_NAME}/guarantee/started/${ITEM_INDEX}</code> 为<strong>永久</strong>节点,存储空串( <code>""</code> )。为什么是<strong>永久</strong>节点呢?在 <code>GuaranteeService#isAllStarted()</code> 见分晓。</li></ul></li><li><p>调用 <code>GuaranteeService#isAllStarted()</code> 方法,判断是否所有的分片项开始运行。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 判断是否所有的任务均启动完毕.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@return</span> 是否所有的任务均启动完毕</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isAllStarted</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> jobNodeStorage.isJobNodeExisted(GuaranteeNode.STARTED_ROOT)</div><div class="line"> && configService.load(<span class="keyword">false</span>).getTypeConfig().getCoreConfig().getShardingTotalCount() == jobNodeStorage.getJobNodeChildrenKeys(GuaranteeNode.STARTED_ROOT).size();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>当 <code>/${JOB_NAME}/guarantee/started/</code> 目录下,所有作业分片项都开始运行,即运行总数等于作业分片总数( <code>JobCoreConfiguration.ShardingTotalCount</code> ),<strong>代表所有的任务均启动完毕</strong>。</li><li>等待所有任务启动过程中,不排除有作业节点会挂掉,如果 <code>/${JOB_NAME}/guarantee/started/${ITEM_INDEX}</code> 存储<strong>临时</strong>节点,会导致不能满足所有的分片项开始运行的条件。</li><li>等待过程中,如果调整作业分片总数( <code>JobCoreConfiguration.ShardingTotalCount</code> ),会导致异常。</li></ul></li><li><p>当不满足所有的分片项开始运行时,作业节点调用 <code>Object#wait(...)</code> 方法进行等待。该等待怎么结束等待?当满足所有的分片项开始运行的作业节点调用 <code>GuaranteeService#clearAllStartedInfo()</code> 时,StartedNodeRemovedJobListener 会监听到 <code>/${JOB_NAME}/guarantee/started/</code> 被删除,调用 <code>Object#notifyAll(...)</code> 方法进行唤醒全部。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// GuaranteeService.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 清理所有任务启动信息.</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">clearAllStartedInfo</span><span class="params">()</span> </span>{</div><div class="line"> jobNodeStorage.removeJobNodeIfExisted(GuaranteeNode.STARTED_ROOT);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// StartedNodeRemovedJobListener.java</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">StartedNodeRemovedJobListener</span> <span class="keyword">extends</span> <span class="title">AbstractJobListener</span> </span>{</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">dataChanged</span><span class="params">(<span class="keyword">final</span> String path, <span class="keyword">final</span> Type eventType, <span class="keyword">final</span> String data)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (Type.NODE_REMOVED == eventType && guaranteeNode.isStartedRootNode(path)) {</div><div class="line"> <span class="keyword">for</span> (ElasticJobListener each : elasticJobListeners) {</div><div class="line"> <span class="keyword">if</span> (each <span class="keyword">instanceof</span> AbstractDistributeOnceElasticJobListener) {</div><div class="line"> ((AbstractDistributeOnceElasticJobListener) each).notifyWaitingTaskStart();</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>调用 <code>#doBeforeJobExecutedAtLastStarted(...)</code> 方法,执行最后一个作业执行前的执行的方法,实现该抽象方法,完成自定义逻辑。<code>#doAfterJobExecutedAtLastCompleted(...)</code> 实现的方式一样,就不重复解析了。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// AbstractDistributeOnceElasticJobListener.java</span></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 分布式环境中最后一个作业执行前的执行的方法.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> shardingContexts 分片上下文</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">doBeforeJobExecutedAtLastStarted</span><span class="params">(ShardingContexts shardingContexts)</span></span>;</div><div class="line"> </div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment">* 分布式环境中最后一个作业执行后的执行的方法.</span></div><div class="line"><span class="comment">*</span></div><div class="line"><span class="comment">* <span class="doctag">@param</span> shardingContexts 分片上下文</span></div><div class="line"><span class="comment">*/</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">doAfterJobExecutedAtLastCompleted</span><span class="params">(ShardingContexts shardingContexts)</span></span>;</div></pre></td></tr></table></figure></li><li><p>整体流程如下图:<img src="http://www.iocoder.cn/images/Elastic-Job/2017_11_21/02.png" alt=""> </p></li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>旁白君:哎哟喂,AbstractDistributeOnceElasticJobListener 还不错哟。<br>芋道君:那必须必的。</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_11_21/03.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. ElasticJobListener</a></li>
<li>
</summary>
<category term="Elastic-Job-Lite" scheme="http://www.iocoder.cn/categories/Elastic-Job-Lite/"/>
</entry>
<entry>
<title>Elastic-Job-Lite 源码分析 —— 作业事件追踪</title>
<link href="http://www.iocoder.cn/Elastic-Job/job-event-trace/"/>
<id>http://www.iocoder.cn/Elastic-Job/job-event-trace/</id>
<published>2017-11-13T16:00:00.000Z</published>
<updated>2017-09-06T12:09:34.000Z</updated>
<content type="html"><![CDATA[<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p><ul><li><a href="#">1. 概述</a></li><li><a href="#">2. 作业事件总线</a></li><li><a href="#">3. 作业事件</a><ul><li><a href="#">3.1 作业状态追踪事件</a></li><li><a href="#">3.2 作业执行追踪事件</a></li><li><a href="#">3.3 作业事件数据库存储</a></li><li><a href="#">3.4 作业事件数据库查询</a></li></ul></li><li><a href="#">4. 作业监听器</a></li><li><a href="#">666. 彩蛋</a></li></ul><hr><p><img src="http://www.iocoder.cn/images/common/wechat_mp_2017_07_31.jpg" alt=""></p><blockquote><p>🙂🙂🙂关注<strong>微信公众号:【芋道源码】</strong>有福利: </p><ol><li>RocketMQ / MyCAT / Sharding-JDBC <strong>所有</strong>源码分析文章列表 </li><li>RocketMQ / MyCAT / Sharding-JDBC <strong>中文注释源码 GitHub 地址</strong> </li><li>您对于源码的疑问每条留言<strong>都</strong>将得到<strong>认真</strong>回复。<strong>甚至不知道如何读源码也可以请教噢</strong>。 </li><li><strong>新的</strong>源码解析文章<strong>实时</strong>收到通知。<strong>每周更新一篇左右</strong>。 </li><li><strong>认真的</strong>源码交流微信群。</li></ol></blockquote><hr><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>本文主要分享 <strong>Elastic-Job-Lite 作业事件追踪</strong>。</p><p>另外,<strong>Elastic-Job-Cloud 作业事件追踪</strong> 和 Elastic-Job-Lite 基本类似,不单独开一篇文章,记录在该文章里。如果你对 Elastic-Job-Cloud 暂时不感兴趣,可以跳过相应部分。</p><p>Elastic-Job 提供了事件追踪功能,可通过事件订阅的方式处理调度过程的重要事件,用于查询、统计和监控。Elastic-Job 目前订阅两种事件,基于<strong>关系型数据库</strong>记录事件。</p><p>涉及到主要类的类图如下( <a href="http://www.iocoder.cn/images/Elastic-Job/2017_11_14/01.png">打开大图</a> ):</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_11_14/01.png" alt=""></p><ul><li>以上类在 <code>com.dangdang.ddframe.job.event</code> 包,不仅为 Elastic-Job-Lite,而且为 Elastic-Job-Cloud 实现了事件追踪功能。</li><li>作业<strong>事件</strong>:粉色的类。</li><li>作业<strong>事件总线</strong>:黄色的类。</li><li>作业<strong>事件监听器</strong>:蓝色的类。 </li></ul><blockquote><p>你行好事会因为得到赞赏而愉悦<br>同理,开源项目贡献者会因为 Star 而更加有动力<br>为 Elastic-Job 点赞!<a href="https://github.com/dangdangdotcom/elastic-job/stargazers" rel="external nofollow noopener noreferrer" target="_blank">传送门</a></p></blockquote><h1 id="2-作业事件总线"><a href="#2-作业事件总线" class="headerlink" title="2. 作业事件总线"></a>2. 作业事件总线</h1><p>JobEventBus,作业事件总线,提供了注册监听器、发布事件两个方法。</p><p><strong>创建</strong> JobEventBus 代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">JobEventBus</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业事件配置</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> JobEventConfiguration jobEventConfig;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 线程池执行服务对象</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ExecutorServiceObject executorServiceObject;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 事件总线</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> EventBus eventBus;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 是否注册作业监听器</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> isRegistered;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">JobEventBus</span><span class="params">()</span> </span>{</div><div class="line"> jobEventConfig = <span class="keyword">null</span>;</div><div class="line"> executorServiceObject = <span class="keyword">null</span>;</div><div class="line"> eventBus = <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">JobEventBus</span><span class="params">(<span class="keyword">final</span> JobEventConfiguration jobEventConfig)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.jobEventConfig = jobEventConfig;</div><div class="line"> executorServiceObject = <span class="keyword">new</span> ExecutorServiceObject(<span class="string">"job-event"</span>, Runtime.getRuntime().availableProcessors() * <span class="number">2</span>);</div><div class="line"> <span class="comment">// 创建 异步事件总线</span></div><div class="line"> eventBus = <span class="keyword">new</span> AsyncEventBus(executorServiceObject.createExecutorService());</div><div class="line"> <span class="comment">// 注册 事件监听器</span></div><div class="line"> register();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>JobEventBus 基于 <a href="https://github.com/google/guava/wiki/EventBusExplained" rel="external nofollow noopener noreferrer" target="_blank">Google Guava EventBus</a>,在<a href="http://www.iocoder.cn/Sharding-JDBC/sql-execute">《Sharding-JDBC 源码分析 —— SQL 执行》「4.1 EventBus」</a>有详细分享。这里要注意的是 AsyncEventBus( <strong>异步事件总线</strong> ),注册在其上面的监听器是<strong>异步</strong>监听执行,事件发布无需阻塞等待监听器执行完逻辑,所以对性能不存在影响。</li><li><p>使用 JobEventConfiguration( 作业事件配置 ) 创建事件监听器,调用 <code>#register()</code> 方法进行注册监听。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> eventBus.register(jobEventConfig.createJobEventListener());</div><div class="line"> isRegistered = <span class="keyword">true</span>;</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> JobEventListenerConfigurationException ex) {</div><div class="line"> log.error(<span class="string">"Elastic job: create JobEventListener failure, error is: "</span>, ex);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>该方法是私有( <code>private</code> )方法,只能使用 JobEventConfiguration 创建事件监听器注册。当不传递该配置时,意味着不开启<strong>事件追踪</strong>功能。</li></ul></li></ul><p><strong>发布作业事件</strong></p><p>发布作业事件( JobEvent ) 代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// JobEventBus.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">post</span><span class="params">(<span class="keyword">final</span> JobEvent event)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (isRegistered && !executorServiceObject.isShutdown()) {</div><div class="line"> eventBus.post(event);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>在 Elaistc-Job-Lite 里,LiteJobFacade 对 <code>JobEventBus#post(...)</code> 进行封装,提供给作业执行器( AbstractElasticJobExecutor )调用( Elastic-Job-Cloud 实际也进行了封装 ):</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// LiteJobFacade.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">postJobExecutionEvent</span><span class="params">(<span class="keyword">final</span> JobExecutionEvent jobExecutionEvent)</span> </span>{</div><div class="line"> jobEventBus.post(jobExecutionEvent);</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">postJobStatusTraceEvent</span><span class="params">(<span class="keyword">final</span> String taskId, <span class="keyword">final</span> State state, <span class="keyword">final</span> String message)</span> </span>{</div><div class="line"> TaskContext taskContext = TaskContext.from(taskId);</div><div class="line"> jobEventBus.post(<span class="keyword">new</span> JobStatusTraceEvent(taskContext.getMetaInfo().getJobName(), taskContext.getId(),</div><div class="line"> taskContext.getSlaveId(), Source.LITE_EXECUTOR, taskContext.getType(), taskContext.getMetaInfo().getShardingItems().toString(), state, message));</div><div class="line"> <span class="keyword">if</span> (!Strings.isNullOrEmpty(message)) {</div><div class="line"> log.trace(message);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>TaskContext 通过 <code>#from(...)</code> 方法,对作业任务ID( <code>taskId</code> ) 解析,获取任务上下文。TaskContext 代码注释很完整,点击<a href="https://github.com/dangdangdotcom/elastic-job/blob/8926e94aa7c48dc635a36518da2c4b10194420a5/elastic-job-common/elastic-job-common-core/src/main/java/com/dangdang/ddframe/job/context/TaskContext.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>直接查看。</li></ul><h1 id="3-作业事件"><a href="#3-作业事件" class="headerlink" title="3. 作业事件"></a>3. 作业事件</h1><p>目前有两种作业事件( JobEvent ):</p><ul><li>JobStatusTraceEvent,作业状态追踪事件。</li><li>JobExecutionEvent,作业执行追踪事件。</li></ul><p>本小节分享两方面:</p><ul><li>作业事件<strong>发布时机</strong>。</li><li>Elastic-Job 基于<strong>关系型数据库</strong>记录事件的<strong>表结构</strong>。</li></ul><h2 id="3-1-作业状态追踪事件"><a href="#3-1-作业状态追踪事件" class="headerlink" title="3.1 作业状态追踪事件"></a>3.1 作业状态追踪事件</h2><p>JobStatusTraceEvent,作业状态追踪事件。</p><p>代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">JobStatusTraceEvent</span> <span class="keyword">implements</span> <span class="title">JobEvent</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 主键</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String id = UUID.randomUUID().toString();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业名称</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String jobName;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 原作业任务ID</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> String originalTaskId = <span class="string">""</span>;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业任务ID</span></div><div class="line"><span class="comment"> * 来自 {<span class="doctag">@link</span> com.dangdang.ddframe.job.executor.ShardingContexts#taskId}</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String taskId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 执行作业服务器的名字</span></div><div class="line"><span class="comment"> * Elastic-Job-Lite,作业节点的 IP 地址</span></div><div class="line"><span class="comment"> * Elastic-Job-Cloud,Mesos 执行机主键</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String slaveId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 任务来源</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Source source;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 任务执行类型</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ExecutionType executionType;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业分片项</span></div><div class="line"><span class="comment"> * 多个分片项以逗号分隔</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String shardingItems;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 任务执行状态</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> State state;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 相关信息</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String message;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 记录创建时间</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Date creationTime = <span class="keyword">new</span> Date();</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>ExecutionType,执行类型。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">enum</span> ExecutionType {</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 准备执行的任务.</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> READY,</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 失效转移的任务.</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> FAILOVER</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>Source,任务来源。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">enum</span> Source {</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Elastic-Job-Cloud 调度器</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> CLOUD_SCHEDULER,</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Elastic-Job-Cloud 执行器</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> CLOUD_EXECUTOR,</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * Elastic-Job-Lite 执行器</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> LITE_EXECUTOR</div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>State,任务执行状态。</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">enum</span> State {</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 开始中</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> TASK_STAGING,</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 运行中</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> TASK_RUNNING,</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 完成(正常)</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> TASK_FINISHED,</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 完成(异常)</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> TASK_ERROR,</div><div class="line"> </div><div class="line"> TASK_KILLED, TASK_LOST, TASK_FAILED, TASK_DROPPED, TASK_GONE, TASK_GONE_BY_OPERATOR, TASK_UNREACHABLE, TASK_UNKNOWN</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>Elastic-Job-Lite 使用 TASK_STAGING、TASK_RUNNING、TASK_FINISHED、TASK_ERROR 四种执行状态。</li><li>Elastic-Job-Cloud 使用所有执行状态。</li></ul></li></ul><p>关系数据库表 <code>JOB_STATUS_TRACE_LOG</code> 结构如下:</p><figure class="highlight sql"><table><tr><td class="code"><pre><div class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`JOB_STATUS_TRACE_LOG`</span> (</div><div class="line"> <span class="string">`id`</span> <span class="built_in">varchar</span>(<span class="number">40</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`job_name`</span> <span class="built_in">varchar</span>(<span class="number">100</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`original_task_id`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`task_id`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`slave_id`</span> <span class="built_in">varchar</span>(<span class="number">50</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`source`</span> <span class="built_in">varchar</span>(<span class="number">50</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`execution_type`</span> <span class="built_in">varchar</span>(<span class="number">20</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`sharding_item`</span> <span class="built_in">varchar</span>(<span class="number">100</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`state`</span> <span class="built_in">varchar</span>(<span class="number">20</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`message`</span> <span class="built_in">varchar</span>(<span class="number">4000</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`creation_time`</span> <span class="keyword">timestamp</span> <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</div><div class="line"> PRIMARY <span class="keyword">KEY</span> (<span class="string">`id`</span>),</div><div class="line"> <span class="keyword">KEY</span> <span class="string">`TASK_ID_STATE_INDEX`</span> (<span class="string">`task_id`</span>,<span class="string">`state`</span>)</div><div class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8 <span class="keyword">COLLATE</span>=utf8_bin</div></pre></td></tr></table></figure><ul><li><p>Elastic-Job-Lite 一次作业执行记录如下( <a href="http://www.iocoder.cn/images/Elastic-Job/2017_11_14/02.png">打开大图</a> ):</p><p> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_11_14/02.png" alt=""></p></li></ul><p><strong>JobStatusTraceEvent 在 Elastic-Job-Lite 发布时机</strong>:</p><ul><li><p>State.TASK_STAGING:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// AbstractElasticJobExecutor.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="comment">// 发布作业状态追踪事件(State.TASK_STAGING)</span></div><div class="line"> <span class="keyword">if</span> (shardingContexts.isAllowSendJobEvent()) {</div><div class="line"> jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_STAGING, String.format(<span class="string">"Job '%s' execute begin."</span>, jobName));</div><div class="line"> }</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>State.TASK_RUNNING:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// AbstractElasticJobExecutor.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(<span class="keyword">final</span> ShardingContexts shardingContexts, <span class="keyword">final</span> JobExecutionEvent.ExecutionSource executionSource)</span> </span>{</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="comment">// 发布作业状态追踪事件(State.TASK_RUNNING)</span></div><div class="line"> <span class="keyword">if</span> (shardingContexts.isAllowSendJobEvent()) {</div><div class="line"> jobFacade.postJobStatusTraceEvent(taskId, State.TASK_RUNNING, <span class="string">""</span>);</div><div class="line"> }</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line">}</div></pre></td></tr></table></figure></li><li><p>State.TASK_FINISHED、State.TASK_ERROR【第一种】:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// AbstractElasticJobExecutor.java</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="comment">// 跳过 存在运行中的被错过作业</span></div><div class="line"> <span class="keyword">if</span> (jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())) {</div><div class="line"> <span class="comment">// 发布作业状态追踪事件(State.TASK_FINISHED)</span></div><div class="line"> <span class="keyword">if</span> (shardingContexts.isAllowSendJobEvent()) {</div><div class="line"> jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_FINISHED, String.format(</div><div class="line"> <span class="string">"Previous job '%s' - shardingItems '%s' is still running, misfired job will start after previous job completed."</span>, jobName, </div><div class="line"> shardingContexts.getShardingItemParameters().keySet()));</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><ul><li><p>State.TASK_FINISHED、State.TASK_ERROR【第二种】:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// AbstractElasticJobExecutor.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(<span class="keyword">final</span> ShardingContexts shardingContexts, <span class="keyword">final</span> JobExecutionEvent.ExecutionSource executionSource)</span> </span>{</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> process(shardingContexts, executionSource);</div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> <span class="comment">// 根据是否有异常,发布作业状态追踪事件(State.TASK_FINISHED / State.TASK_ERROR)</span></div><div class="line"> <span class="keyword">if</span> (itemErrorMessages.isEmpty()) {</div><div class="line"> <span class="keyword">if</span> (shardingContexts.isAllowSendJobEvent()) {</div><div class="line"> jobFacade.postJobStatusTraceEvent(taskId, State.TASK_FINISHED, <span class="string">""</span>);</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">if</span> (shardingContexts.isAllowSendJobEvent()) {</div><div class="line"> jobFacade.postJobStatusTraceEvent(taskId, State.TASK_ERROR, itemErrorMessages.toString());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><p><strong>JobStatusTraceEvent 在 Elastic-Job-Cloud 发布时机</strong>:</p><p>Elastic-Job-Cloud 除了上文 Elastic-Job-Lite 会多一个场景下记录作业状态追踪事件( <strong>State.TASK_STAGING</strong> ),实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TaskLaunchScheduledService.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> JobStatusTraceEvent <span class="title">createJobStatusTraceEvent</span><span class="params">(<span class="keyword">final</span> TaskContext taskContext)</span> </span>{</div><div class="line"> TaskContext.MetaInfo metaInfo = taskContext.getMetaInfo();</div><div class="line"> JobStatusTraceEvent result = <span class="keyword">new</span> JobStatusTraceEvent(metaInfo.getJobName(), taskContext.getId(), taskContext.getSlaveId(),</div><div class="line"> Source.CLOUD_SCHEDULER, taskContext.getType(), String.valueOf(metaInfo.getShardingItems()), JobStatusTraceEvent.State.TASK_STAGING, <span class="string">""</span>);</div><div class="line"> <span class="comment">// 失效转移</span></div><div class="line"> <span class="keyword">if</span> (ExecutionType.FAILOVER == taskContext.getType()) {</div><div class="line"> Optional<String> taskContextOptional = facadeService.getFailoverTaskId(metaInfo);</div><div class="line"> <span class="keyword">if</span> (taskContextOptional.isPresent()) {</div><div class="line"> result.setOriginalTaskId(taskContextOptional.get());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>任务提交调度服务( TaskLaunchScheduledService )提交任务时,记录发布作业状态追踪事件(State.TASK_STAGING)。</li></ul><p>Elastic-Job-Cloud 根据 Mesos Master 通知任务状态变更,记录<strong>多种</strong>作业状态追踪事件,实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// SchedulerEngine.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">statusUpdate</span><span class="params">(<span class="keyword">final</span> SchedulerDriver schedulerDriver, <span class="keyword">final</span> Protos.TaskStatus taskStatus)</span> </span>{</div><div class="line"> String taskId = taskStatus.getTaskId().getValue();</div><div class="line"> TaskContext taskContext = TaskContext.from(taskId);</div><div class="line"> String jobName = taskContext.getMetaInfo().getJobName();</div><div class="line"> log.trace(<span class="string">"call statusUpdate task state is: {}, task id is: {}"</span>, taskStatus.getState(), taskId);</div><div class="line"> <span class="comment">//</span></div><div class="line"> jobEventBus.post(<span class="keyword">new</span> JobStatusTraceEvent(jobName, taskContext.getId(), taskContext.getSlaveId(), Source.CLOUD_SCHEDULER,</div><div class="line"> taskContext.getType(), String.valueOf(taskContext.getMetaInfo().getShardingItems()), State.valueOf(taskStatus.getState().name()), taskStatus.getMessage()));</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line">}</div></pre></td></tr></table></figure><h2 id="3-2-作业执行追踪事件"><a href="#3-2-作业执行追踪事件" class="headerlink" title="3.2 作业执行追踪事件"></a>3.2 作业执行追踪事件</h2><p>JobExecutionEvent,作业执行追踪事件。</p><p>代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">JobExecutionEvent</span> <span class="keyword">implements</span> <span class="title">JobEvent</span> </span>{</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 主键</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String id = UUID.randomUUID().toString();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 主机名称</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String hostname = IpUtils.getHostName();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * IP</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> String ip = IpUtils.getIp();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业任务ID</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String taskId;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业名字</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String jobName;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 执行来源</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ExecutionSource source;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业分片项</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> shardingItem;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 开始时间</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">private</span> Date startTime = <span class="keyword">new</span> Date();</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 结束时间</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> Date completeTime;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 是否执行成功</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> success;</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 执行失败原因</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Setter</span></div><div class="line"> <span class="keyword">private</span> JobExecutionEventThrowable failureCause;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><p>ExecutionSource,执行来源</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">enum</span> ExecutionSource {</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 普通触发执行</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> NORMAL_TRIGGER,</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 被错过执行</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> MISFIRE,</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 失效转移执行</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> FAILOVER</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><p>关系数据库表 <code>JOB_EXECUTION_LOG</code> 结构如下:</p><figure class="highlight sql"><table><tr><td class="code"><pre><div class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="string">`JOB_EXECUTION_LOG`</span> (</div><div class="line"> <span class="string">`id`</span> <span class="built_in">varchar</span>(<span class="number">40</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`job_name`</span> <span class="built_in">varchar</span>(<span class="number">100</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`task_id`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`hostname`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`ip`</span> <span class="built_in">varchar</span>(<span class="number">50</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`sharding_item`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`execution_source`</span> <span class="built_in">varchar</span>(<span class="number">20</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`failure_cause`</span> <span class="built_in">varchar</span>(<span class="number">4000</span>) <span class="keyword">COLLATE</span> utf8_bin <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`is_success`</span> <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`start_time`</span> <span class="keyword">timestamp</span> <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</div><div class="line"> <span class="string">`complete_time`</span> <span class="keyword">timestamp</span> <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">NULL</span>,</div><div class="line"> PRIMARY <span class="keyword">KEY</span> (<span class="string">`id`</span>)</div><div class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8 <span class="keyword">COLLATE</span>=utf8_bin</div></pre></td></tr></table></figure><ul><li><p>Elastic-Job-Lite 一次作业<strong>多作业分片项</strong>执行记录如下( <a href="http://www.iocoder.cn/images/Elastic-Job/2017_11_14/03.png">打开大图</a> ):</p><p> <img src="http://www.iocoder.cn/images/Elastic-Job/2017_11_14/03.png" alt=""></p></li></ul><p><strong>JobExecutionEvent 在 Elastic-Job-Lite 发布时机</strong>:</p><pre><code><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">process</span><span class="params">(<span class="keyword">final</span> ShardingContexts shardingContexts, <span class="keyword">final</span> <span class="keyword">int</span> item, <span class="keyword">final</span> JobExecutionEvent startEvent)</span> </span>{</div><div class="line"> <span class="comment">// 发布执行事件(开始)</span></div><div class="line"> <span class="keyword">if</span> (shardingContexts.isAllowSendJobEvent()) {</div><div class="line"> jobFacade.postJobExecutionEvent(startEvent);</div><div class="line"> }</div><div class="line"> JobExecutionEvent completeEvent;</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// 执行单个作业</span></div><div class="line"> process(<span class="keyword">new</span> ShardingContext(shardingContexts, item));</div><div class="line"> <span class="comment">// 发布执行事件(成功)</span></div><div class="line"> completeEvent = startEvent.executionSuccess();</div><div class="line"> <span class="keyword">if</span> (shardingContexts.isAllowSendJobEvent()) {</div><div class="line"> jobFacade.postJobExecutionEvent(completeEvent);</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Throwable cause) {</div><div class="line"> <span class="comment">// 发布执行事件(失败)</span></div><div class="line"> completeEvent = startEvent.executionFailure(cause);</div><div class="line"> jobFacade.postJobExecutionEvent(completeEvent);</div><div class="line"> <span class="comment">// ... 省略无关代码</span></div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></code></pre><p><strong>JobExecutionEvent 在 Elastic-Job-Cloud 发布时机</strong>:</p><p>和 Elastic-Job-Cloud 一致。</p><h2 id="3-3-作业事件数据库存储"><a href="#3-3-作业事件数据库存储" class="headerlink" title="3.3 作业事件数据库存储"></a>3.3 作业事件数据库存储</h2><p>JobEventRdbStorage,作业事件数据库存储。</p><p><strong>创建</strong> JobEventRdbStorage 代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line">JobEventRdbStorage(<span class="keyword">final</span> DataSource dataSource) <span class="keyword">throws</span> SQLException {</div><div class="line"> <span class="keyword">this</span>.dataSource = dataSource;</div><div class="line"> initTablesAndIndexes();</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">initTablesAndIndexes</span><span class="params">()</span> <span class="keyword">throws</span> SQLException </span>{</div><div class="line"> <span class="keyword">try</span> (Connection conn = dataSource.getConnection()) {</div><div class="line"> createJobExecutionTableAndIndexIfNeeded(conn);</div><div class="line"> createJobStatusTraceTableAndIndexIfNeeded(conn);</div><div class="line"> databaseType = DatabaseType.valueFrom(conn.getMetaData().getDatabaseProductName());</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>调用 <code>#createJobExecutionTableAndIndexIfNeeded(...)</code> 创建 <code>JOB_EXECUTION_LOG</code> 表和索引。</li><li>调用 <code>#createJobStatusTraceTableAndIndexIfNeeded(...)</code> 创建 <code>JOB_STATUS_TRACE_LOG</code> 表和索引。</li></ul><p><strong>存储</strong> JobStatusTraceEvent 代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// JobEventRdbStorage.java</span></div><div class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">addJobStatusTraceEvent</span><span class="params">(<span class="keyword">final</span> JobStatusTraceEvent jobStatusTraceEvent)</span> </span>{</div><div class="line"> String originalTaskId = jobStatusTraceEvent.getOriginalTaskId();</div><div class="line"> <span class="keyword">if</span> (State.TASK_STAGING != jobStatusTraceEvent.getState()) {</div><div class="line"> originalTaskId = getOriginalTaskId(jobStatusTraceEvent.getTaskId());</div><div class="line"> }</div><div class="line"> <span class="keyword">boolean</span> result = <span class="keyword">false</span>;</div><div class="line"> String sql = <span class="string">"INSERT INTO `"</span> + TABLE_JOB_STATUS_TRACE_LOG + <span class="string">"` (`id`, `job_name`, `original_task_id`, `task_id`, `slave_id`, `source`, `execution_type`, `sharding_item`, "</span> </div><div class="line"> + <span class="string">"`state`, `message`, `creation_time`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"</span>;</div><div class="line"> <span class="comment">// ... 省略你懂的代码</span></div><div class="line">}</div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">private</span> String <span class="title">getOriginalTaskId</span><span class="params">(<span class="keyword">final</span> String taskId)</span> </span>{</div><div class="line"> String sql = String.format(<span class="string">"SELECT original_task_id FROM %s WHERE task_id = '%s' and state='%s'"</span>, TABLE_JOB_STATUS_TRACE_LOG, taskId, State.TASK_STAGING);</div><div class="line"> <span class="comment">// ... 省略你懂的代码</span></div><div class="line"> <span class="keyword">return</span> original_task_id;</div><div class="line">}</div></pre></td></tr></table></figure><ul><li><code>originalTaskId</code>,原任务作业ID。<ul><li>Elastic-Job-Lite 暂未使用到该字段,存储空串( <code>""</code> )。</li><li>Elastic-Job-Cloud 在<strong>作业失效转移</strong>场景下使用该字段,存储失效转移的任务作业ID。</li></ul></li></ul><p><strong>存储</strong> JobExecutionEvent 代码如下: </p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// JobEventRdbStorage.java</span></div><div class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">addJobExecutionEvent</span><span class="params">(<span class="keyword">final</span> JobExecutionEvent jobExecutionEvent)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == jobExecutionEvent.getCompleteTime()) { <span class="comment">// 作业分片项执行开始</span></div><div class="line"> <span class="keyword">return</span> insertJobExecutionEvent(jobExecutionEvent);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">if</span> (jobExecutionEvent.isSuccess()) { <span class="comment">// 作业分片项执行完成(正常)</span></div><div class="line"> <span class="keyword">return</span> updateJobExecutionEventWhenSuccess(jobExecutionEvent);</div><div class="line"> } <span class="keyword">else</span> { <span class="comment">// 作业分片项执行完成(异常)</span></div><div class="line"> <span class="keyword">return</span> updateJobExecutionEventFailure(jobExecutionEvent);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>作业分片项执行完成进行的是<strong>更新</strong>操作。</li></ul><h2 id="3-4-作业事件数据库查询"><a href="#3-4-作业事件数据库查询" class="headerlink" title="3.4 作业事件数据库查询"></a>3.4 作业事件数据库查询</h2><p>JobEventRdbSearch,作业事件数据库查询,提供给运维平台调用查询数据。感兴趣的同学点击<a href="https://github.com/dangdangdotcom/elastic-job/blob/8283acf01548222f39f7bfc202a8f89d27728e6c/elastic-job-common/elastic-job-common-core/src/main/java/com/dangdang/ddframe/job/event/rdb/JobEventRdbSearch.java" rel="external nofollow noopener noreferrer" target="_blank">链接</a>直接查看。</p><h1 id="4-作业监听器"><a href="#4-作业监听器" class="headerlink" title="4. 作业监听器"></a>4. 作业监听器</h1><p>在上文我们看到,作业监听器通过传递作业事件配置( JobEventConfiguration )给作业事件总线( JobEventBus ) <strong>进行创建监听器,并注册监听器到事件总线</strong>。</p><p>我们来看下 Elastic-Job 提供的基于<strong>关系数据库</strong>的事件配置实现。</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// JobEventConfiguration.java</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">JobEventConfiguration</span> <span class="keyword">extends</span> <span class="title">JobEventIdentity</span> </span>{</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 创建作业事件监听器.</span></div><div class="line"><span class="comment"> * </span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span> 作业事件监听器.</span></div><div class="line"><span class="comment"> * <span class="doctag">@throws</span> JobEventListenerConfigurationException 作业事件监听器配置异常</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="function">JobEventListener <span class="title">createJobEventListener</span><span class="params">()</span> <span class="keyword">throws</span> JobEventListenerConfigurationException</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// JobEventRdbConfiguration.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">JobEventRdbConfiguration</span> <span class="keyword">extends</span> <span class="title">JobEventRdbIdentity</span> <span class="keyword">implements</span> <span class="title">JobEventConfiguration</span>, <span class="title">Serializable</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">transient</span> DataSource dataSource;</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> JobEventListener <span class="title">createJobEventListener</span><span class="params">()</span> <span class="keyword">throws</span> JobEventListenerConfigurationException </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> JobEventRdbListener(dataSource);</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> SQLException ex) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JobEventListenerConfigurationException(ex);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure><ul><li>JobEventRdbConfiguration,作业数据库事件配置。调用 <code>#createJobEventListener()</code> 创建作业事件数据库监听器( JobEventRdbListener )。</li></ul><p>JobEventRdbListener,作业事件数据库监听器。实现代码如下:</p><figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// JobEventListener.java</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">JobEventListener</span> <span class="keyword">extends</span> <span class="title">JobEventIdentity</span> </span>{</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业执行事件监听执行.</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> jobExecutionEvent 作业执行事件</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Subscribe</span></div><div class="line"> <span class="meta">@AllowConcurrentEvents</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">listen</span><span class="params">(JobExecutionEvent jobExecutionEvent)</span></span>;</div><div class="line"> </div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * 作业状态痕迹事件监听执行.</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> jobStatusTraceEvent 作业状态痕迹事件</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Subscribe</span></div><div class="line"> <span class="meta">@AllowConcurrentEvents</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">listen</span><span class="params">(JobStatusTraceEvent jobStatusTraceEvent)</span></span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// JobEventRdbListener.java</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">JobEventRdbListener</span> <span class="keyword">extends</span> <span class="title">JobEventRdbIdentity</span> <span class="keyword">implements</span> <span class="title">JobEventListener</span> </span>{</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> <span class="keyword">final</span> JobEventRdbStorage repository;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">JobEventRdbListener</span><span class="params">(<span class="keyword">final</span> DataSource dataSource)</span> <span class="keyword">throws</span> SQLException </span>{</div><div class="line"> repository = <span class="keyword">new</span> JobEventRdbStorage(dataSource);</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listen</span><span class="params">(<span class="keyword">final</span> JobExecutionEvent executionEvent)</span> </span>{</div><div class="line"> repository.addJobExecutionEvent(executionEvent);</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listen</span><span class="params">(<span class="keyword">final</span> JobStatusTraceEvent jobStatusTraceEvent)</span> </span>{</div><div class="line"> repository.addJobStatusTraceEvent(jobStatusTraceEvent);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ul><li>通过 JobEventRdbStorage 存储作业事件到关系型数据库。</li></ul><p><strong>如何自定义作业监听器?</strong></p><p>有些同学可能希望使用 ES 或者其他数据库存储作业事件,这个时候可以通过实现 JobEventConfiguration、JobEventListener 进行拓展。</p><p><strong>Elastic-Job-Cloud JobEventConfiguration 怎么配置?</strong></p><ul><li><p>Elastic-Job-Cloud-Scheduler:从 <code>conf/elastic-job-cloud-scheduler.properties</code> 配置文件读取如下属性,生成 JobEventConfiguration 配置对象。</p><ul><li><code>event_trace_rdb_driver</code></li><li><code>event_trace_rdb_url</code></li><li><code>event_trace_rdb_username</code></li><li><code>event_trace_rdb_password</code></li></ul></li><li><p>Elastic-Job-Cloud-Executor:通过接收到任务执行信息里读取JobEventConfiguration,实现代码如下:</p> <figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// TaskExecutor.java</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registered</span><span class="params">(<span class="keyword">final</span> ExecutorDriver executorDriver, <span class="keyword">final</span> Protos.ExecutorInfo executorInfo, <span class="keyword">final</span> Protos.FrameworkInfo frameworkInfo, <span class="keyword">final</span> Protos.SlaveInfo slaveInfo)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (!executorInfo.getData().isEmpty()) {</div><div class="line"> Map<String, String> data = SerializationUtils.deserialize(executorInfo.getData().toByteArray());</div><div class="line"> BasicDataSource dataSource = <span class="keyword">new</span> BasicDataSource();</div><div class="line"> dataSource.setDriverClassName(data.get(<span class="string">"event_trace_rdb_driver"</span>));</div><div class="line"> dataSource.setUrl(data.get(<span class="string">"event_trace_rdb_url"</span>));</div><div class="line"> dataSource.setPassword(data.get(<span class="string">"event_trace_rdb_password"</span>));</div><div class="line"> dataSource.setUsername(data.get(<span class="string">"event_trace_rdb_username"</span>));</div><div class="line"> jobEventBus = <span class="keyword">new</span> JobEventBus(<span class="keyword">new</span> JobEventRdbConfiguration(dataSource));</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>旁白君:瞎比比了这么长,能不能简单粗暴一点。<br>芋道君:是是是。</p><p><img src="http://www.iocoder.cn/images/Elastic-Job/2017_11_14/04.png" alt=""></p><p>道友,赶紧上车,分享一波朋友圈!</p>]]></content>
<summary type="html">
<p><strong>本文基于 Elastic-Job V2.1.5 版本分享</strong></p>
<ul>
<li><a href="#">1. 概述</a></li>
<li><a href="#">2. 作业事件总线</a></li>
<li><a href="#">
</summary>
<category term="Elastic-Job-Lite" scheme="http://www.iocoder.cn/categories/Elastic-Job-Lite/"/>
</entry>
</feed>