-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
538 lines (324 loc) · 403 KB
/
atom.xml
File metadata and controls
538 lines (324 loc) · 403 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Magical Cat</title>
<link href="/atom.xml" rel="self"/>
<link href="http://yoursite.com/"/>
<updated>2020-03-11T05:32:13.834Z</updated>
<id>http://yoursite.com/</id>
<author>
<name>F8F-1BearCat</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>基于Emconfig配置中心的CI发布流程</title>
<link href="http://yoursite.com/2020/03/11/%E5%9F%BA%E4%BA%8EEmconfig%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E7%9A%84CI%E5%8F%91%E5%B8%83%E6%B5%81%E7%A8%8B/"/>
<id>http://yoursite.com/2020/03/11/基于Emconfig配置中心的CI发布流程/</id>
<published>2020-03-11T05:22:18.428Z</published>
<updated>2020-03-11T05:32:13.834Z</updated>
<content type="html"><![CDATA[<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>整理了一下服务从测试到线上全量发布的流程,其中 CID 平台为基于 Jenkins 实现的持续集成平台,Emconfig 为自研的配置管理中心。</p><a id="more"></a><p><img src="https://i.loli.net/2020/03/11/GQ6X38AzlThv4gY.png" alt="基于Emconfig配置中心的CI发布流程.png"></p>]]></content>
<summary type="html">
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>整理了一下服务从测试到线上全量发布的流程,其中 CID 平台为基于 Jenkins 实现的持续集成平台,Emconfig 为自研的配置管理中心。</p>
</summary>
<category term="运维" scheme="http://yoursite.com/categories/%E8%BF%90%E7%BB%B4/"/>
<category term="CI/CD" scheme="http://yoursite.com/tags/CI-CD/"/>
<category term="配置中心" scheme="http://yoursite.com/tags/%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83/"/>
</entry>
<entry>
<title>Netty ChannelOption参数详解</title>
<link href="http://yoursite.com/2019/09/05/Netty%20ChannelOption%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3/"/>
<id>http://yoursite.com/2019/09/05/Netty ChannelOption参数详解/</id>
<published>2019-09-05T07:12:57.996Z</published>
<updated>2019-12-13T09:34:38.641Z</updated>
<content type="html"><![CDATA[<p><img src="https://i.loli.net/2018/11/19/5bf21c6726a92.png" alt="netty.png"></p><h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>在使用 Netty 进行客户端和服务端开发时,需要对 serverBootStrap 的 option 和 childOption 进行自定义的一些配置,具体的 Channel 配置参数可以查看 ChannelOption 里面声明的常量,按使用场景可以划分为 Netty 通用参数、Socket 套接字参数以及 IP 参数。</p><a id="more"></a><h2 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h2><p>以下代码就是在实际项目中一个 Netty server 的具体 ChannelOption 参数配置:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">serverBootstrap = serverBootstrap.childHandler(<span class="keyword">new</span> ListInitializer()) </span><br><span class="line"> .option(ChannelOption.SO_REUSEADDR, <span class="keyword">true</span>) </span><br><span class="line"> .option(ChannelOption.WRITE_BUFFER_WATER_MARK, <span class="keyword">new</span> WriteBufferWaterMark(ServerInfo.getInstance().getLowWaterMark(), ServerInfo.getInstance().getHighWaterMark())) </span><br><span class="line"> .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) </span><br><span class="line"> .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);</span><br></pre></td></tr></table></figure><p>ChannelOption 源码中定义的参数如下,当前使用的版本为 netty-all-4.1.29.Final:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<ByteBufAllocator> ALLOCATOR = valueOf(<span class="string">"ALLOCATOR"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<RecvByteBufAllocator> RCVBUF_ALLOCATOR = valueOf(<span class="string">"RCVBUF_ALLOCATOR"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<MessageSizeEstimator> MESSAGE_SIZE_ESTIMATOR = valueOf(<span class="string">"MESSAGE_SIZE_ESTIMATOR"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> CONNECT_TIMEOUT_MILLIS = valueOf(<span class="string">"CONNECT_TIMEOUT_MILLIS"</span>);</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@deprecated</span> Use {<span class="doctag">@link</span> MaxMessagesRecvByteBufAllocator}</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Deprecated</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> MAX_MESSAGES_PER_READ = valueOf(<span class="string">"MAX_MESSAGES_PER_READ"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> WRITE_SPIN_COUNT = valueOf(<span class="string">"WRITE_SPIN_COUNT"</span>);</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@deprecated</span> Use {<span class="doctag">@link</span> #WRITE_BUFFER_WATER_MARK}</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Deprecated</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> WRITE_BUFFER_HIGH_WATER_MARK = valueOf(<span class="string">"WRITE_BUFFER_HIGH_WATER_MARK"</span>);</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@deprecated</span> Use {<span class="doctag">@link</span> #WRITE_BUFFER_WATER_MARK}</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Deprecated</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> WRITE_BUFFER_LOW_WATER_MARK = valueOf(<span class="string">"WRITE_BUFFER_LOW_WATER_MARK"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<WriteBufferWaterMark> WRITE_BUFFER_WATER_MARK =</span><br><span class="line"> valueOf(<span class="string">"WRITE_BUFFER_WATER_MARK"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> ALLOW_HALF_CLOSURE = valueOf(<span class="string">"ALLOW_HALF_CLOSURE"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> AUTO_READ = valueOf(<span class="string">"AUTO_READ"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * If {<span class="doctag">@code</span> true} then the {<span class="doctag">@link</span> Channel} is closed automatically and immediately on write failure.</span></span><br><span class="line"><span class="comment"> * The default value is {<span class="doctag">@code</span> true}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> AUTO_CLOSE = valueOf(<span class="string">"AUTO_CLOSE"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> SO_BROADCAST = valueOf(<span class="string">"SO_BROADCAST"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> SO_KEEPALIVE = valueOf(<span class="string">"SO_KEEPALIVE"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> SO_SNDBUF = valueOf(<span class="string">"SO_SNDBUF"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> SO_RCVBUF = valueOf(<span class="string">"SO_RCVBUF"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> SO_REUSEADDR = valueOf(<span class="string">"SO_REUSEADDR"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> SO_LINGER = valueOf(<span class="string">"SO_LINGER"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> SO_BACKLOG = valueOf(<span class="string">"SO_BACKLOG"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> SO_TIMEOUT = valueOf(<span class="string">"SO_TIMEOUT"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> IP_TOS = valueOf(<span class="string">"IP_TOS"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<InetAddress> IP_MULTICAST_ADDR = valueOf(<span class="string">"IP_MULTICAST_ADDR"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<NetworkInterface> IP_MULTICAST_IF = valueOf(<span class="string">"IP_MULTICAST_IF"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Integer> IP_MULTICAST_TTL = valueOf(<span class="string">"IP_MULTICAST_TTL"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> IP_MULTICAST_LOOP_DISABLED = valueOf(<span class="string">"IP_MULTICAST_LOOP_DISABLED"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> TCP_NODELAY = valueOf(<span class="string">"TCP_NODELAY"</span>);</span><br><span class="line"></span><br><span class="line"><span class="meta">@Deprecated</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION =</span><br><span class="line"> valueOf(<span class="string">"DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> ChannelOption<Boolean> SINGLE_EVENTEXECUTOR_PER_GROUP =</span><br><span class="line"> valueOf(<span class="string">"SINGLE_EVENTEXECUTOR_PER_GROUP"</span>);</span><br></pre></td></tr></table></figure><p>源码中可以看到,参数<code>MAX_MESSAGES_PER_READ</code>、<code>WRITE_BUFFER_HIGH_WATER_MARK</code>、<code>WRITE_BUFFER_LOW_WATER_MARK</code>、<code>DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION</code> 已经弃用,下面简单的整理一下 ChannelOption 中参数的使用方法。</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><h3 id="Netty-通用参数"><a href="#Netty-通用参数" class="headerlink" title="Netty 通用参数"></a>Netty 通用参数</h3><ul><li><strong>ChannelOption.ALLOCATOR</strong></li></ul><p>ByteBuf 的分配器,默认值为 ByteBufAllocator.DEFAULT,在 4.0 版本中,默认的 allocator 为 UnpooledByteBufAllocator,4.1版本中增强了缓冲区泄漏追踪机制,默认的 allocator 就改为了 PooledByteBufAllocator。即 4.1 版本使用对象池,实现了重用缓冲区(可以直接只用这个配置)。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);</span><br><span class="line">bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);</span><br></pre></td></tr></table></figure><ul><li><strong>ChannelOption.RCVBUF_ALLOCATOR</strong></li></ul><p>用于 Channel 分配接受 Buffer 的分配器,默认值为 AdaptiveRecvByteBufAllocator.DEFAULT,是一个自适应的接受缓冲区分配器,能根据接受到的数据自动调节大小。可选值为FixedRecvByteBufAllocator,固定大小的接受缓冲区分配器。</p><ul><li><strong>ChannelOption.MESSAGE_SIZE_ESTIMATOR</strong></li></ul><p>消息大小估算器,默认为 DefaultMessageSizeEstimator.DEFAULT。估算 ByteBuf、ByteBufHolder 和 FileRegion 的大小,其中 ByteBuf 和 ByteBufHolder 为实际大小,FileRegion 估算值为0。该值估算的字节数在计算水位时使用,FileRegion 为0可知FileRegion 不影响高低水位。</p><ul><li><strong>ChannelOption.CONNECT_TIMEOUT_MILLIS</strong></li></ul><p>连接超时毫秒数,默认值 30000 毫秒。</p><ul><li><strong>ChannelOption.MAX_MESSAGES_PER_READ</strong></li></ul><p>【弃用,使用 MaxMessagesRecvByteBufAllocator】一次 Loop 读取的最大消息数,对于 ServerChannel 或者 NioByteChannel,默认值为 16,其他 Channel 默认值为1。默认值这样设置,是因为:ServerChannel 需要接受足够多的连接,保证大吞吐量,NioByteChannel 可以减少不必要的系统调用 select。</p><ul><li><strong>ChannelOption.WRITE_SPIN_COUNT</strong></li></ul><p>一个 Loop 写操作执行的最大次数,默认值为 16。也就是说,对于大数据量的写操作至多进行 16 次,如果 16 次仍没有全部写完数据,此时会提交一个新的写任务给 EventLoop,任务将在下次调度继续执行。这样,其他的写请求才能被响应不会因为单个大数据量写请求而耽误。</p><ul><li><strong>ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK</strong></li></ul><p>【弃用,使用 #WRITE_BUFFER_WATER_MARK】写高水位标记,默认值 64 KB。如果 Netty 的写缓冲区中的字节超过该值,Channel 的 isWritable() 返回 False。</p><ul><li><strong>ChannelOption.WRITE_BUFFER_LOW_WATER_MARK</strong></li></ul><p>【弃用,使用 #WRITE_BUFFER_WATER_MARK】写低水位标记,默认值 32 KB。当 Netty 的写缓冲区中的字节超过高水位之后若下降到低水位,则 Channe 的 isWritable() 返回 True。写高低水位标记使用户可以控制写入数据速度,从而实现流量控制。推荐做法是:每次调用 channl.write(msg) 方法首先调用 channel.isWritable() 判断是否可写。</p><ul><li><strong>ChannelOption.WRITE_BUFFER_WATER_MARK</strong></li></ul><p>写水位标记,使用方法见 <code>WRITE_BUFFER_HIGH_WATER_MARK</code> 和 <code>WRITE_BUFFER_LOW_WATER_MARK</code>。</p><ul><li><strong>ChannelOption.ALLOW_HALF_CLOSURE</strong></li></ul><p>一个连接的远端关闭时本地端是否关闭,默认值为 False。值为 False 时,连接自动关闭;为 True 时,触发 ChannelInboundHandler 的 userEventTriggered() 方法,事件为 ChannelInputShutdownEvent。</p><ul><li><strong>ChannelOption.AUTO_READ</strong></li></ul><p>自动读取,默认值为 True。Netty 只在必要的时候才设置关心相应的 I/O 事件。对于读操作,需要调用 channel.read() 设置关心的 I/O 事件为 OP_READ,这样若有数据到达才能读取以供用户处理。该值为 True 时,每次读操作完毕后会自动调用 channel.read(),从而有数据到达便能读取;否则,需要用户手动调用 channel.read()。需要注意的是:当调用 config.setAutoRead(boolean) 方法时,如果状态由 false 变为 true,将会调用 channel.read() 方法读取数据;由 true 变为 false,将调用 config.autoReadCleared() 方法终止数据读取。</p><ul><li><strong>ChannelOption.AUTO_CLOSE</strong></li></ul><p>自动关闭,默认值为 True。如果 True,则 Channel 在写入失败时立即自动关闭。</p><ul><li><strong>ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION</strong></li></ul><p>【弃用】DatagramChannel 注册的 EventLoop 即表示已激活。</p><ul><li><strong>ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP</strong></li></ul><p>单线程执行 ChannelPipeline 中的事件,默认值为 True。该值控制执行 ChannelPipeline 中执行 ChannelHandler 的线程。如果为 True,整个 pipeline 由一个线程执行,这样不需要进行线程切换以及线程同步,是 Netty4 的推荐做法;如果为 False,ChannelHandler 中的处理过程会由 Group 中的不同线程执行。</p><h3 id="Socket-套接字参数"><a href="#Socket-套接字参数" class="headerlink" title="Socket 套接字参数"></a>Socket 套接字参数</h3><ul><li><strong>ChannelOption.SO_BROADCAST</strong></li></ul><p>对应套接字选项中的 SO_BROADCAST,设置广播模式。允许或禁止发送广播数据,只有数据报套接字支持广播,并且是在支持广播消息的网络上才能使用。</p><ul><li><strong>ChannelOption.SO_KEEPALIVE</strong></li></ul><p>对应套接字选项中的 SO_KEEPALIVE,是否启动心跳包活机制,默认值为 False。该参数用于设置 TCP 连接,当设置该选项以后,TCP 会主动测试这个连接的状态,如果该连接长时间没有数据交流,TCP 会自动发送一个活动探测数据报文(keep-alive probe),来检测连接是否存活。可以将此功能视为TCP的心跳机制,需要注意的是:默认的心跳间隔是 7200s 即 2 小时。</p><ul><li><strong>ChannelOption.SO_SNDBUF</strong></li></ul><p>对应套接字选项中的 SO_SNDBUF,TCP数据接收缓冲区大小。接收缓冲区即 TCP 接收滑动窗口,用于保存网络协议站内收到的数据,直到应用程序读取成功。Linux操作系统可使用命令: cat /proc/sys/net/ipv4/tcp_rmem 查询其大小。一般情况下,该值可由用户在任意时刻设置,但当设置值超过 64KB 时,需要在连接到远端之前设置。</p><p>联系 SO_RCVBUF 参数来看,每个套接字都有一个发送缓冲区和一个接收缓冲区,TCP 设置这个两个选项注意顺序:对于客户端必须在调用 connect 之前,对于服务器端应该在调用 listen 之前,因为窗口选项是在建立连接时用 syn 分节与对端互换得到的。</p><p>TCP 套接字的缓冲区大小至少应该是 MSS 的4倍;MSS=MTU-40 头部,一般以太网卡 MTU 是1500;典型缓冲区默认大小是 8192 字节或者更大。对于一次发送大量数据,可以增加到48K,64K等,为了达到最佳性能,缓冲区可能至少要与BDP(带宽延迟乘积)一样大小。在实际的过程中发送数据和接收数据量比较大,提高接收缓冲区能够减少发送端的阻塞,避免 send()、recv() 不断的循环收发。</p><ul><li><strong>ChannelOption.SO_RCVBUF</strong></li></ul><p>对应于套接字选项中的SO_RCVBUF,TCP数据发送缓冲区大小。发送缓冲区即 TCP 发送滑动窗口,用于保存发送数据直到发送成功。Linux 操作系统可使用命令:cat /proc/sys/net/ipv4/tcp_smem 查询其大小。</p><ul><li><strong>ChannelOption.SO_REUSEADDR</strong></li></ul><p>对应套接字选项中的 SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口,默认值False。例如,某个服务占用了 TCP 的 8080 端口,其他服务再对这个端口进行监听就会报错, SO_REUSEADDR 这个参数就是用来解决这个问题的,该参数允许服务公用一个端口,这个在服务器程序中比较常用,例如某个进程非正常退出,对一个端口的占用可能不会立即释放,这时候如果不设置这个参数,其他进程就不能立即使用这个端口。而设置 SO_REUSEADDR,就允许在同一个端口上启动同一服务器的多个实例,只要每个实例绑定不同的本地地址即可。</p><ul><li><strong>ChannelOption.SO_LINGER</strong></li></ul><p>对应套接字选项中的 SO_LINGER,关闭Socket的延迟时间,默认值为-1,表示禁用该功能。Linux内核默认的处理方式是当用户调用 close() 方法的时候,函数返回,在可能的情况下,尽量发送数据,不一定保证会发生剩余的数据,造成了数据的不确定性,使用 SO_LINGER 可以阻塞 close() 的调用时间,直到数据完全发送。</p><p>具体配置方式:-1 表示 socket.close() 方法立即返回,但 OS 底层会将发送缓冲区全部发送到对端。0 表示 socket.close() 方法立即返回,OS 放弃发送缓冲区的数据直接向对端发送 RST 包,对端收到复位错误。非 0 整数值表示调用 socket.close() 方法的线程被阻塞直到延迟时间到或发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误。</p><ul><li><strong>ChannelOption.SO_BACKLOG</strong></li></ul><p>对应的是 TCP/IP 协议 listen 函数中的 backlog 参数,函数 listen(int socketfd,int backlog) 用来初始化服务端可连接队列。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定了用于临时存放已完成三次握手的请求队列的大小。如果未设置或者设置值为小于 1,则 Java 将会使用默认值50。</p><ul><li><strong>ChannelOption.SO_TIMEOUT</strong></li></ul><p>用于设置接受数据的等待的超时时间,单位为毫秒,默认值为 0,表示无限等待。</p><ul><li><strong>ChannelOption.TCP_NODELAY</strong></li></ul><p>对应于套接字选项中的 TCP_NODELAY,该参数的使用与 Nagle 算法有关,默认情况其是启用的。</p><p>在 TCP/IP 协议中,无论发送多少数据,总是需要在数据前面加上协议头,同时,对方接收到数据,也需要发送 ACK 表示确认。为了尽可能地利用网络宽带,TCP 总是希望尽可能的发送足够大的数据。Nagle 算法的目的就是为了尽可能的发送大块数据,避免网络中充斥这小块数据。虽然该方式有效提高网络的有效负载,但是却造成了延时。</p><p>TCP_NODELAY 就是用于关闭 Nagle 算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为 true 关闭 Nagle 算法;如果要减少发送次数减少网络交互,就设置为 false 启用 Nagle 算法,等累积一定大小后再发送。</p><h3 id="IP-参数"><a href="#IP-参数" class="headerlink" title="IP 参数"></a>IP 参数</h3><ul><li><strong>ChannelOption.IP_TOS</strong></li></ul><p>IP 参数,设置 IP 头部的 Type-of-Service 字段,用于描述 IP 包的优先级和 QoS 选项。</p><ul><li><strong>ChannelOption.IP_MULTICAST_ADDR</strong></li></ul><p>对应 IP 参数 IP_MULTICAST_IF,设置对应地址的网卡为多播模式。</p><ul><li><strong>ChannelOption.IP_MULTICAST_IF</strong></li></ul><p>对应 IP 参数 IP_MULTICAST_IF2,同上但支持 IPV6。</p><ul><li><strong>ChannelOption.IP_MULTICAST_TTL</strong></li></ul><p>IP 参数,多播数据报的 time-to-live 即存活跳数。</p><ul><li><strong>ChannelOption.IP_MULTICAST_LOOP_DISABLED</strong></li></ul><p>对应 IP 参数 IP_MULTICAST_LOOP,设置本地回环接口的多播功能。由于 IP_MULTICAST_LOOP返回 True 表示关闭,所以 Netty 加上后缀 _DISABLED 防止歧义。</p><p>附:</p><p><img src="https://img-blog.csdn.net/2018052114222193?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Ftb3NjeWts/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt=""></p><p> <img src="https://img-blog.csdn.net/20180521142238159?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Ftb3NjeWts/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt=""></p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p><a href="https://www.cnblogs.com/googlemeoften/p/6082785.html" target="_blank" rel="noopener">https://www.cnblogs.com/googlemeoften/p/6082785.html</a></p><p><a href="https://www.jianshu.com/p/0bff7c020af2" target="_blank" rel="noopener">https://www.jianshu.com/p/0bff7c020af2</a></p>]]></content>
<summary type="html">
<p><img src="https://i.loli.net/2018/11/19/5bf21c6726a92.png" alt="netty.png"></p>
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>在使用 Netty 进行客户端和服务端开发时,需要对 serverBootStrap 的 option 和 childOption 进行自定义的一些配置,具体的 Channel 配置参数可以查看 ChannelOption 里面声明的常量,按使用场景可以划分为 Netty 通用参数、Socket 套接字参数以及 IP 参数。</p>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="Netty" scheme="http://yoursite.com/tags/Netty/"/>
<category term="源码" scheme="http://yoursite.com/tags/%E6%BA%90%E7%A0%81/"/>
</entry>
<entry>
<title>Dynamo -Amazon’s Highly Available Key-value Store(译)</title>
<link href="http://yoursite.com/2019/08/13/Dynamo-Amazon%E2%80%99s%20Highly%20Available%20Key-value%20Store/"/>
<id>http://yoursite.com/2019/08/13/Dynamo-Amazon’s Highly Available Key-value Store/</id>
<published>2019-08-13T07:38:50.596Z</published>
<updated>2019-09-06T01:48:29.356Z</updated>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>中文译名:<strong>Dynamo:Amazon的高可用性的键-值存储系统</strong></p><p>论文于 2007 年首次发表,并在 Werner Vogels 的博客上得到推广,至今有着 4k+ 的引用量,是分布式存储系统中的一篇经典论文。</p><p>本文尝试对其进行简单翻译,受翻译水平所限,难免有翻译或理解错误,如有疑问烦请查阅原文:<a href="https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf" target="_blank" rel="noopener">原文地址</a>。</p><a id="more"></a><p>以下是译文。</p><p>[TOC]</p><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>超大规模下的可靠性问题是我们在 Amazon.com——世界上最大的电商之一——所要面对的最大挑战之一;即使是最轻微的故障也会造成严重的经济后果,并影响客户对我们的信任。Amazon.com 作为一个为全球提供 web 服务的平台,其底层的基础设施是由遍布全球的数据中心中成千上万的服务器和网络设备所组成的。在这种规模下,各种大大小小的部件故障持续发生,而我们面对这些故障时所采取的管理持久化状态的方式,驱动着软件系统的可靠性和可扩展的发展。</p><p>本文介绍了 Dynamo——一个高可用的 KV 存储系统——的设计和实现,Amazon 的一些核心服务就是借助 Dynamo 来提供“永远在线”的用户体验(“always-on” experience)的。为了达到这种级别的可用性,Dynamo 牺牲了在某些故障的场景下的一致性。它广泛地使用了对象版本控制(object versioning)和应用辅助的冲突解决(application-assisted conflict resolution)机制,为开发人员提供了一个可以使用的新接口。</p><h2 id="1-引言"><a href="#1-引言" class="headerlink" title="1. 引言"></a>1. 引言</h2><p>Amazon 是一个全球性的电商平台, 高峰期用户可达数千万,背后是靠分布在全球的若干数据中心中的成千上万的服务器来支撑。平台对性能、可靠性和效率方面有着严格的要求,并且为了支持持续增长,高度的可扩展性也是不可或缺的。其中可靠性是最重要的要求之一,因为即使是最轻微的故障也会造成严重的经济后果,并影响客户对我们的信任。</p><p>我们从运营 Amazon 平台的过程中总结出的经验之一是,一个系统的可靠性和可扩展性取决于它的应用状态是如何管理的。Amazon 使用的是一种由数百个服务器组成的高度去中心化、松耦合、面向服务的架构。这样的环境特别需要永远可用(always available)的存储技术。例如,即使磁盘故障,路由抖动,甚至是数据中心被龙卷风摧毁,用户应依然可以查看和添加商品到自己的购物车。因此,负责管理购物车的服务就必须始终能够对其数据存储进行读写,并且其数据需要跨多个数据中心可用。</p><p>在一个由数百万个组件组成的基础设施中,进行故障处理是我们习以为常的操作模式;任何时间都会有少部分但也有相当数量的服务器和网络组件发生故障。因此,Amazon 的软件系统需要以一种将故障处理视为正常情况的方式构建,不能因设备故障影响可用性或性能。</p><p>为了满足可靠性和可扩展性的需求,Amazon 开发了许多存储技术,其中 Amazon Simple storage Service(也可以从 Amazon 外部获得,称为 Amazon S3)可能是最为人熟知的。本文介绍了Dynamo 的设计与实现,这是另一个为 Amazon 平台构建的高可用性和可扩展的分布式数据存储。Dynamo 被用于管理对可靠性要求非常高的服务的状态,同时这些服务还要求能够严格控制可用性、一致性、成本效益和性能之间的权衡。Amazon 平台拥有非常多样化的应用,不同应用有着不同的存储需求。一部分应用需要存储技术具有足够的灵活性,以便在最具成本效益的方式下,让应用程序设计人员能够合理地配置数据存储,最终实现高可用性和性能保证之间的平衡。</p><p>Amazon服务平台中的许多服务只需要主键访问数据存储。对于许多服务,如提供畅销排行榜、购物车、客户的偏好、会话管理、session 管理、销售排名、商品目录等等,常见的使用关系数据库的模式会导致效率低下,而且限制了规模的扩展性和可用性。Dynamo 提供了一个简单的主键唯一(primary-key only)访问接口以满足这些应用的要求。</p><p>Dynamo 综合使用了一些众所周知的技术来实现可伸缩性和可用性:使用一致性哈希(consistent hashing)[10]对数据进行分散和复制,并且通过对象版本控制(object versioning)[12]促进了一致性。在更新期间,副本之间的一致性由类仲裁(quorum-like)技术和去中心化的副本同步协议(replica synchronization protocol)来维护。Dynamo 采用基于gossip的分布式故障检测和成员协议。Dynamo 是一个只需最少人工管理的、完全去中心化的系统。从 Dynamo 中添加和删除存储节点不需要任何手动分区(partition)或重新分配(redistribution)。</p><p>在过去的一年,Dynamo 已经成为 Amazon 电商平台很多核心服务的底层存储技术。在节假日购物高峰,它能够有效地扩容以支持极高的峰值负载,而不需要任何停机时间。例如,维护购物车的服务(Shopping Cart Service)能处理数千万个请求,这些请求会产生单日超过 300 万次的付款动作;管理会话状态的服务能处理数十万的并发活跃会话等。</p><p>这项工作对研究社区的主要贡献是评估了如何通过组合不同技术以实现一个高可用的系统。它论证了一个最终一致性(eventually-consistent)的存储系统可以被用于生产环境中有着苛刻要求的应用。它也对这些技术的调整进行了深入的分析,以满足生产环境的非常严格的性能要求。</p><p>本文的结构如下。Section 2 为背景,Section 3 为相关工作,Section 4 介绍了系统设计,Section 5 描述了实现,Section 6 详细介绍了在生产中运行 Dynamo 的经验和体会,Section 7 总结全文。在本文的许多地方也许可以适当有更多的信息,但出于适当保护 Amazon 的商业利益,需要减少一些细节。因此,第6节中的数据中心内部和跨数据中心之间的延迟(the intra- and inter-datacenter latencies)、第6.2节中的绝对请求速率(absolute request rates)以及第6.3节中的系统中断时间和工作负载(outage lengths and workloads),都只是概述而没有提供具体细节。</p><h2 id="2-背景"><a href="#2-背景" class="headerlink" title="2. 背景"></a>2. 背景</h2><p>Amazon 的电商平台由数百个服务组成,它们协同工作,提供了从推荐系统到订单处理,再到欺诈检测等一系列的功能。每个服务都对外提供明确的接口,并且能够通过网络访问。这些服务运行在遍布全球的数据中心中的数万台服务器组成的基础设施之上。其中有些服务是无状态的(例如,聚合其他服务的响应的服务),有些是有状态的(例如,在其存储在持久存储中的状态上,执行业务逻辑并生成响应的服务)。</p><p>传统的生产系统使用关系型数据库来存储状态。然而,对于许多更常见的状态持久性使用模式来说,关系型数据库远非理想的解决方案。因为这些服务大多只通过主键存储和检索数据,并不需要RDBMS 提供的复杂查询和管理功能。而这些多余的功能的运作需要昂贵的硬件和高技能人员,这就使其成为一个非常低效的解决方案。此外,现有的复制技术是有限的,而且通常是靠牺牲可用性来换一致性。尽管近年来已经取得了许多进展,但是要水平扩展(scale-out)或使用智能分区方案(smart partitioning schemes)来实现负载平衡仍然不是一件容易的事情。</p><p>本文介绍了 Dynamo,这个高可用的数据存储技术能够满足这些重要类型的服务的需求。Dynamo 有易用的 key/value 接口,是高可用的并有定义清晰的一致性窗口(clearly defined consistency window),资源利用率高,并且有易用的解决请求量或数据规模增长的水平扩展方案。每个使用Dynamo的服务都独立运行自己的Dynamo实例。</p><h3 id="2-1-系统假设与要求"><a href="#2-1-系统假设与要求" class="headerlink" title="2.1 系统假设与要求"></a>2.1 系统假设与要求</h3><p>这类服务的存储系统有以下要求:</p><p><strong>查询模型(Query Model)</strong>:通过一个主键唯一性标识对数据项进行简单的读写操作。状态存储为以唯一键索引的二进制对象(例如,blobs)。没有跨多个数据项的操作,也不需要关系模式(relational schema)。这一要求是考虑到,相当一部分 Amazon 的服务可以使用这个简单的查询模型,并不需要任何关系模式。Dynamo 的目标应用需要存储的对象都比较小(通常小于1MB)。</p><p><strong>ACID属性</strong>:ACID(Atomicity, Consistency, Isolation, Durability)是一组保证数据库事务可靠执行的属性。在数据库领域,对数据的单次逻辑操作称为事务。Amazon 的经验表明,保证ACID的数据存储往往可用性很差,这一点已被业界和学术界广泛认可 [5]。Dynamo 的目标应用能容忍较弱的一致性(ACID中的“C”),前提是可用性能够得到提升。Dynamo 不提供任何隔离保证,并且只允许单个 key 的更新。</p><p><strong>效率(Efficiency)</strong>:系统需要在普通硬件基础设施(commodity hardware infrastructure)上运行。Amazon 平台的服务对延迟有着严格的要求,描述服务的响应时间要求时以99.9百分位点(译者注,缩写为p999)为准。鉴于状态数据访问是服务的核心操作之一,存储系统必须满足那些严格的SLA (见 Section 2.2)。服务要有配置 Dynamo 的能力,使其最终能达到延时和吞吐量的要求。因此,就需要在性能、成本效率、可用性和持久性保证之间做权衡。</p><p><strong>其他假设</strong>:Dynamo 仅限在 Amazon 内部的服务上使用。因此我们假定它的操作环境是安全的,不需要考虑身份验证和授权等安全相关的需求。此外,由于每个服务都使用其不同的Dynamo实例,所以它的初始设计目标是扩展到数百台存储主机。我们将在后面的章节讨论 Dynamo 的可扩展性限制的问题,以及可能的解决方案。</p><h3 id="2-2-服务质量协议-Service-Level-Agreements,SLA"><a href="#2-2-服务质量协议-Service-Level-Agreements,SLA" class="headerlink" title="2.2 服务质量协议 (Service Level Agreements,SLA)"></a>2.2 服务质量协议 (Service Level Agreements,SLA)</h3><p>为了确保应用程序能够在有限时间内做出响应,平台中的每个依赖项的响应就需要有更严格的时限。客户端和服务端采用服务质量协议(SLA),该协议即为客户端和服务端在某些系统层面的指标上达成一致的一个正式协商合约。这些指标主要包括客户端对特定 API 的请求速率分布的期望值,以及在这些条件下的预期服务延迟。一个简单的 SLA 例子:服务端保证在 500 QPS 的峰值负载下,99.9% 的请求响应时间在 300ms 以内。</p><p>在 Amazon 的去中心化的、面向服务的基础设施中,SLA 扮演着重要角色。例如,对某个电商网站发送一个页面请求,通常需要渲染引擎通过发送请求到150多个服务来构建其响应。这些服务一般有多个依赖,而这些依赖又往往是其他服务,因而一个应用有着多层调用路径的情况并不少见。为了确保网页渲染引擎能在明确的时间限制内返回结果页面,调用链内的每个服务就都必须遵循合约中的性能指标。</p><p>图1展示了 Amazon 平台抽象的架构图,动态 web 内容由页面渲染组件(page rendering components)生成,而组件向下又会去调用一些其他的服务。一个服务可以使用不同的数据存储来管理它的状态数据,这些数据存储只能在各自的服务范围才能访问。一些服务充当聚合器的角色,通过聚合其他服务的数据来返回一个组合响应。通常情况下,聚合器服务是无状态的,尽管它们使用广泛的缓存(extensive caching)。</p><p><img src="https://docs.riak.com/images/dynamo/figure1.png" alt=""></p><p> 图 1:Amazon 平台的面向服务架构</p><p>行业内通常习惯于用平均数(average)、中位数(median)和期望方差(expected variance)来描述面向性能的 SLA。但是,在 Amazon 我们发现,如果我们的目标是打造一个让所有用户都有良好体验的系统,而不仅仅是大部分用户的话,以上这些指标是不够好的。例如,如果个性化推荐技术(personalization techniques)被广泛地使用,那么用户的访问历史越多,对该用户就需要做更多的处理,这将影响到数值分布高端区(high-end of the distribution)的性能(译者注,即响应时间的高百分位点,也称为尾部延迟(tail latencies))。基于平均数或中位数响应时间的 SLA 不能反映这一最有价值客户段的性能需求。为了解决这个问题,在 Amazon,SLA是基于分布的99.9百分位点来表达和测量的。选择99.9%而不是更高,是因为根据成本效益(cost-benefit)分析发现,再继续提高性能,所需的成本将大幅提高。在 Amazon 的生产环境中证实,相比于那些基于平均数或中位数定义的 SLA 的系统,该方法提供了更好的用户体验。</p><p>本文多次提到p999,这也反映了 Amazon 工程师从客户体验角度对性能的不懈追求。许多论文都是关于平均数的,所以我们在用作比较时也会引用。不过,Amazon的工程和优化都不是以平均数为关注点。某些技术,例如写协调器(write coordinators)的负载均衡选择,纯粹是基于p999来控制性能的。</p><p>存储系统在建立一个服务的SLA中通常扮演重要角色,特别是在业务逻辑相对轻量的场景下,正如许多 Amazon 服务的情况。因而状态管理就成为一个服务的 SLA 的主要组成部分。Dynamo 的主要设计考虑之一就是允许服务控制它们自己的系统属性,例如持久性和一致性,并让服务自己在功能,性能和成本效益之间进行权衡。</p><h3 id="2-3-设计考虑"><a href="#2-3-设计考虑" class="headerlink" title="2.3 设计考虑"></a>2.3 设计考虑</h3><p>传统上,商业系统中使用的数据复制(data replication)算法执行同步复制协调(synchronous replica coordination ),以提供一个强一致性的数据访问接口。为了达到这种程度的一致性,这些算法被迫牺牲了某些故障情况下的数据可用性。例如,当数据冲突时不去确定返回数据是否正确,而是直接使数据不可用,直到数据最终一致。从非常早期的复制数据库(replicated database)工作可以看出,当网络故障时,强一致性、数据高可用性是无法同时满足的 [2, 11]。因此,系统和应用程序需要知道,在什么场景下应该选择满足什么特性。</p><p>对于容易出现服务器和网络故障的系统,可以通过使用乐观复制(optimistic replication )技术来提高可用性,在后台将数据变动同步到其他节点,同时并发、断线(disconnected)也是可以容忍的。这种方法的问题在于它会导致更改冲突,需要检测并解决冲突。解决冲突的过程引入了两个问题:何时解决,谁来解决?Dynamo 被设计成最终一致(eventually consistent)的数据存储,即所有的更新最终会达到所有副本。</p><p>一个重要的设计考虑是决定何时去解决更新冲突,例如,是在读的时候解决冲突,还是在写的时候。许多传统数据存储在写的过程中解决冲突,从而保证读的复杂度相对简单 [7]。在这种系统中,如果在给定的时间内数据存储不能访问所有(或大部分)副本,写就会被拒绝。另一方面,Dynamo 的目标是设计一个“永远可写”(always writable)的数据存储(例如,一个对写操作高度可用的数据存储)。对于 Amazon 的许多服务来讲,拒绝用户的更新操作可能会导致糟糕的用户体验。比如,即使发生服务器或网络故障,购物车服务也必须允许用户仍然可以向购物车中添加或删除商品。这一要求迫使我们将解决冲突的复杂性推给读操作,以确保写操作永远不会被拒绝。</p><p>下一个需要考虑的问题是由谁来解决冲突。这可以通过数据存储或应用程序来完成。如果由数据仓库来执行,那么选择就相当有限。在这种情况下,数据仓库只能使用一些简单的策略,如“最后一次写有效”(last write wins) [22],来解决更新冲突。而另一方面,因为应用程序清楚数据结构(data schema),所以它可以决定最适合用户体验的冲突解决方法。例如,维护用户购物车的应用可以选择“合并”冲突的版本,并返回一个统一的购物车。尽管这样很灵活,但一些应用开发人员并不想自己实现一套冲突解决机制,他们选择将问题下放给数据存储,最终数据存储选择诸如“最后一次写有效”这种简单的策略去解决。</p><p>设计中需要考虑的其他设计原则:</p><p><strong>增量扩展性</strong>(Incremental scalability):Dynamo 应支持一次水平扩展一台存储主机(以下,简称为“节点“),而且对系统操作者和系统本身的影响很小。</p><p><strong>对称性</strong>(Symmetry):Dynamo 每个节点的职责应该是相同的;不应当出现某个/某些节点承担特殊角色或额外职责的情况。根据我们的经验,对称性简化了系统的配置和维护。</p><p><strong>去中心化</strong>(Decentralization):是对称性的扩展,系统设计应采用去中心化的、点对点的技术,而不是集中控制。在过去,集中控制的设计导致发生了很多系统中断(outages),我们的目标就是尽可能避免它。去中心化会使得系统更简单、更具扩展性和可用性。</p><p><strong>异构性</strong>(Heterogeneity):系统需要能够在其运行的基础设施中利用异构性。例如,负载的分布必须与各个服务器的能力成比例。样就可以一次只增加一个能力更强的新节点,而无需一次升级所有节点。</p><h2 id="3-相关工作"><a href="#3-相关工作" class="headerlink" title="3. 相关工作"></a>3. 相关工作</h2><h3 id="3-1-点对点系统(Peer-to-Peer-Systems)"><a href="#3-1-点对点系统(Peer-to-Peer-Systems)" class="headerlink" title="3.1 点对点系统(Peer to Peer Systems)"></a>3.1 点对点系统(Peer to Peer Systems)</h3><p>已经有几个点对点(peer-to-peer, P2P)系统研究过数据存储和分发的问题。第一代 P2P 系统,例如 Freenet 和 Gnutella,被主要用作文件共享系统。这些都是非结构化 P2P 网络( unstructured P2P networks)的例子,节点之间的 overlay 链路都是随意建立的。在这些网络中,一次查询请求通常泛洪到整个网络,以找到尽可能多的共享该数据的节点。P2P 系统演进到下一代,就是广为人知的结构化 P2P 网络(structured P2P networks)。这种网络采用了全局一致的协议,以确保任何节点都可以高效地将查询请求路由到那些具有所需数据的节点。诸如 Pastry [16] 和 Chord [20] 的系统使用路由机制,保证查询能在有限数量的跳(a bounded number of hops)内得到应答。</p><p>为了减少多跳(multi-hop)路由带来的额外延迟,一些 P2P 系统(例如 [14])采用了<br>O(1) 路由机制,即每个节点在本地维护足够多的路由信息,以便可以在固定数量的跳(a constant number of hops)内将(访问数据的)请求路由到合适的节点。</p><p>很多存储系统, 例如Oceanstore [9] 和 PAST [17],都是构建在这种路由 overlay 之上的。Oceanstore 提供了一个全局的、事务性的、持久的存储服务,支持对广泛分布的数据副本进行串行化更新。为了在避免广域锁(wide-are locking)固有的许多问题的同时支持并发更新,它使用了一 种基于冲突解决的更新模型。在[21]中介绍了冲突解决,被用于减少事务异常中止的数量。Oceanstore 通过处理一系列的更新,对更新整体排序,并按照顺序进行原子更新的方式解决冲突。它是为在未受信任的基础设施上进行数据复制的场景设计的。相比之下,PAST 在 Pastry 之上为持久的和不可变的对象提供了一个简单的抽象层。它假定应用程序可以在它之上建立必要的存储语义(如可变文件)。</p><h3 id="3-2-分布式文件系统与数据库"><a href="#3-2-分布式文件系统与数据库" class="headerlink" title="3.2 分布式文件系统与数据库"></a>3.2 分布式文件系统与数据库</h3><p>文件系统和数据库系统社区已经对分布式数据的性能、可用性和持久性进行了广泛研究。与 P2P 存储系统只支持扁平命名空间(flat namespace)相比,分布式文件系统通常都支持层级化(hierarchical )的命名空间。诸如 Ficus [15] 和 Coda [19] 的系统通过文件复制以牺牲一致性为代价来实现高可用。而更新冲突通常使用专门的冲突解决策略来处理。Farsite 系统 [1] 是一个不使用任何中央服务器(比如 NFS)的分布式文件系统。Farsite 使用复制来实现高度的可用性和可扩展性。谷歌文件系统(Google File System,GFS) [6] 是另一个分布式文件系统,它被用于承载 Google 内部应用的状态数据。GFS 的设计很简单,通过一个主节点(master)管理整个元数据,并维护数据被分片(chunk)并存储到分节点(chunkservers)的地址。Bayou 是一个分布式关系数据库系统,允许断线(disconnected)操作,并提供最终的数据一致性 [21]。</p><p>在这些系统中,Bayou、Coda 和 Ficus 都支持断线的情况下进行操作,并对网络分区(network partitions )和中断( outages)等问题具有很强的弹性。这些系统的不同之处在于如何解决冲突。例如,Coda 和 Ficus 在系统层面解决,而 Bayou 是在应用层面。不过,它们都保证最终一致性。</p><p>与这些系统类似,Dynamo 允许在发生网络分区的情况下继续执行读写操作,并通过不同的冲突解决机制来处理更新冲突。分布式块存储系统(distributed block storage system),例如 FAB [18],将大对象分割成较小的块(block),并以高可用的方式存储。与之相比,我们的场景更适合使用KV存储,因为:(a)它的定位就是存储相对较小的对象(大小 < 1M);(b)KV存储更容易针对每个应用进行配置。Antiquity 是一个广域(wide-area)分布式存储系统,用于处理多个服务器故障 [23]。它使用一个安全的日志来保证数据完整性,复制日志到多个服务器以达到持久性,并使用拜占庭容错协议(Byzantine fault tolerance protocols,译者注,参见拜占庭将军问题)保证数据的一致性。相较于 Antiquity,Dynamo 是为一个受信任的环境所构建的,所以数据的完整性和安全性并不是我们关注的重点。Bigtable 一个管理结构化数据的分布式文件系统,它维护了一张稀疏的多维有序映射表(sparse, multi-dimensional sorted map),并允许应用通过多重属性 [2] 访问它们的数据。相较于 Bigtable,Dynamo 的目标应用只需要以 key/value 方式访问数据,并主要关注高可用性,即使是在网络分区或服务器故障时,更新操作都不会被拒绝。</p><p>传统的复制型关系数据库系统都强调保证保证数据 副本的强一致性。虽然强一致性给应用编写者提供了方便的编程模型,但是这些系统在可扩展性和可用性方面受到了限制 [7]。正因为需要提供强一致性保证,这些系统将不能处理网络分区。</p><h3 id="3-3-讨论"><a href="#3-3-讨论" class="headerlink" title="3.3 讨论"></a>3.3 讨论</h3><p>与上述去中心化的存储系统相比,Dynamo 有着不同的目标需求。第一,Dynamo 主要针对那些需要“永远可写”数据存储的应用,更新操作不会因故障或并发写而被拒绝。这是 Amazon 许多应用的关键需求。第二,如前所述,Dynamo 是在一个假定所有节点都被信任的、单一管理域的基础设施之上构建的。第三,使用 Dynamo 的应用不需要分层化的命名空间(许多文件系统采用的规范),也不需要复杂的关系型 schema(传统数据库所支持)。第四,Dynamo是为延时敏感型应用程序设计的,至少99.9%的读写操作需要在几百毫秒内执行完成。为了满足这些严格的延时需求,就必须避免将请求在多节点之间进行路由的方式(这是一些分布式哈希表(distributed hash table,DHT)系统,如Chord和Pastry,采用的典型设计)。这是因为多跳(multi-hop)路由将增加响应时间的抖动性(variability),进而增加了高百分位的延迟。Dynamo可以被表述为零跳(zero-hop)的DHT,每个节点在本地维护了足够多的路由信息,能够将请求直接路由到合适节点。</p><h2 id="4-系统架构"><a href="#4-系统架构" class="headerlink" title="4. 系统架构"></a>4. 系统架构</h2><p>运行在生产环境的存储系统,其架构是很复杂的。除了实际的数据持久化组件之外,系统还需要具有可扩展的健壮的解决方案,用于负载均衡、成员管理(membership)和故障检测、故障恢复、副本同步、过载处理、状态转移、并发和任务调度、请求编组(marshalling)、请求路由(routing)、系统监控和告警,以及配置管理。描述每个解决方案的细节是不可能的,因此本文主要关注 Dynamo 中使用的核心分布式系统技术:分区(partitioning)、复制(replication)、版本化(versioning)、成员管理(membership)、故障处理(failure handling)和扩展(scaling)。表1 总结了 Dynamo 使用的技术及其各自的优点。</p><table><thead><tr><th>Problem</th><th>Technique</th><th>Advantage</th></tr></thead><tbody><tr><td>Partitioning</td><td>Consistent Hashing</td><td>Incremental Scalability</td></tr><tr><td>High Availability for writes</td><td>Vector clocks with reconciliation during reads</td><td>Version size is decoupled from update rates.</td></tr><tr><td>Handling temporary failures</td><td>Sloppy Quorum and hinted handoff</td><td>Provides high availability and durability guarantee when some of the replicas are not available.</td></tr><tr><td>Recovering from permanent failures</td><td>Anti-entropy using Merkle trees</td><td>Synchronizes divergent replicas in the background.</td></tr><tr><td>Membership and failure detection</td><td>Gossip-based membership protocol and failure detection.</td><td>Preserves symmetry and avoids having a centralized registry for storing membership and node liveness information.</td></tr></tbody></table><p> 表 1:Dynamo 中使用的技术及其优点的总结。</p><h3 id="4-1-系统接口"><a href="#4-1-系统接口" class="headerlink" title="4.1 系统接口"></a>4.1 系统接口</h3><p>Dynamo 通过一个简单的接口存储键值对象;它提供两个操作:get() 和 put() 。get(key) 方法定位到存储系统中 key 对应的对象副本,并返回单个对象或一个包含冲突的版本的对象列表,以及上下文(context)。put(key, context, object) 方法根据对应的 key 确定副本应该存放的位置,并将其写入磁盘。上下文编码了对调用者不透明的对象的系统元数据,还包含了诸如对象版本的一些信息。上下文信息是跟对象一起存储的,以便系统可以验证 put 请求中提供的上下文对象是否有效。</p><p>Dynamo 将调用者提供的 key 和对象都视为不透明的字节数组。它使用 MD5 哈希将 key 生成一个128位的标识符(id),并使用 id 来确定负责该 key 的存储节点。</p><h3 id="4-2-分区(Partitioning)算法"><a href="#4-2-分区(Partitioning)算法" class="headerlink" title="4.2 分区(Partitioning)算法"></a>4.2 分区(Partitioning)算法</h3><p>Dynamo 的关键设计要求之一是它必须支持增量扩展。这就需要一种机制来将数据动态划分到系统中的不同节点(例如,存储主机)上。Dynamo 的分区方案依赖于一致性哈希(consistent hashing)将负载分发到多个存储主机。在一致性哈希 [10] 中,哈希函数的输出范围被视为一个固定的圆形空间或称为“环”(即,最大 hash 值与最小 hash 值在环内首尾相接)。系统中的每个节点(译者注,服务器)都在这个空间中被分配了一个随机值,表示其在环上的“位置”。通过 hash 函数计算出数据项的 key 在环上的分布位置,然后顺时针遍历环以找到第一个位置大于该项位置的节点,从而将由 key 标识的每个数据项分配给节点。因此,每个节点负责环中当前节点与环上的前一个节点之间的区域。一致性哈希的主要优点是节点的添加或删除只会影响相邻的节点,而其他节点不受影响。</p><p>基础的一致哈希算法还存在一些问题。首先,环上每个节点的位置是随机分配的,这会导致数据和负载不能均匀分布。其次,基础的算法忽视了节点间性能的不同。为了解决这些问题,Dynamo 使用了一致性哈希的一个变体(类似于 [10,20] 中使用的哈希):每个节点不是映射到环上的一个单点,而是多个点。为此,Dynamo 使用了“虚拟节点”(virtual node)的概念。一个虚拟节点看上去和系统中一个普通节点一样,但每个节点是可以负责多个虚拟节点的。实际上,当一个新的节点添加到系统后,它会在环上被分配多个位置(以下简称“token”)。Dynamo 分区方案的进一步调优将会在Section 6 讨论。</p><p>使用虚拟节点有以下优点:</p><p>如果一个节点变得不可用(由于故障或例行维护),这个节点处理的负载将均匀分散在剩余的可用节点上。</p><p>当一个节点再次可用,或者向系统添加一个新节点时,新可用节点从其他每个可用节点接受大致相等的负载。</p><p>一个节点负责的虚拟节点的数量可以根据其容量决定,从而解决物理基础架构中的异构性。</p><h3 id="4-3-复制(Replication)"><a href="#4-3-复制(Replication)" class="headerlink" title="4.3 复制(Replication)"></a>4.3 复制(Replication)</h3><p>为了实现高可用性和持久性,Dynamo 将数据复制到多台机器上。每个数据项会被复制到 N 台机器,其中 N 是配置项 “per-instance” 的参数。每个 key k,会被分配一个 coordinator节点(前面章节所述)。coordinator节点掌控其负责范围内的数据的复制。它除了在本地存储其范围内的每个 key 外,还会复制到环上顺时针方向的 N-1 个后继节点。这导致了系统中,每个节点要负责从它自己往后的一共 N 个节点。在图2中,节点 B 除了在本地存储 key k 外,还会将其复制到 C 和 D 节点。节点 D 将存储落在(A,B]、(B,C] 和 (C,D] 范围 上的所有 key。</p><p><img src="https://docs.riak.com/images/dynamo/figure2.png" alt=""></p><p> 图 2:Dynamo 环中 key 的分区和复制。</p><p>负责存储特定 key 的节点列表称为优先列表(preference list)。该系统的设计是,正如将在 Section 4.8 中进行说明的,对于任何特定的 key,系统中的每个节点都可以决定哪些节点应该在这个列表中。为了应对节点故障的情况,优先列表会包含超过 N 个节点。需要注意的是,在使用虚拟节点时,存储一个 key 的 N 个后继节点,实际上对应的物理节点可能少于 N 个(即一个节点可能拥有前N个位置中的不止一个位置)。为了解决这个问题, key 的优先列表在构建时会跳过环上的一些位置,以确保该列表只包含不同的物理节点。</p><h3 id="4-4-数据版本化(Data-Versioning)"><a href="#4-4-数据版本化(Data-Versioning)" class="headerlink" title="4.4 数据版本化(Data Versioning)"></a>4.4 数据版本化(Data Versioning)</h3><p>Dynamo 提供最终一致性,允许将更新操作异步地传递给所有的副本。put() 调用可能在数据更新应用于所有副本之前返回给调用者,这可能导致后续 get() 操作获取不到最新数据。在没有故障的情况下,更新操作的传递时间有一个上限。但是,在某些故障场景下(例如,服务器宕机或网络分区),更新操作可能在很长一段时间内无法到达所有的副本。</p><p>Amazon 平台上有一类应用是可以容忍这种不一致性的,并且可以在这种条件下运行。例如,购物<br>车应用程序要求“添加到购物车”的请求永远不能被遗漏或拒绝。如果购物车的最新状态不可用,而用户对较旧版本的购物车进行了修改,那么该修改仍然有意义,应该保留。但同时它不应直接替代当前购物车不可用的状态,因为这不可用的状态中可能也有一些修改需要保留。需要注意的是,不<br>管是“添加到购物车”还是“从购物车删除”操作,都会被转换成是 Dynamo 的 put() 请求。当用户想要向购物车中添加(或从购物车中删除)某个商品,而最新版本又不可用时,该商品将被添加到旧版本(或从旧版本中删除),并由随后的步骤来协调处理更新冲突。</p><p>为了提供这种保证,Dynamo 将每次修改结果都视为一个新的、不可变的数据版本。它允许一个对象的多个版本同时出现在系统中。大多数情况下,新版本都包含老版本的数据,而且系统自己可以判断哪个是权威版本(syntactic reconciliation,语法协调)。然而,在故障与并发更新并存的情况下,可能会发生版本分叉(version branching),从而导致对象的版本冲突。在这种情况下,系统无法协调同一对象的多个版本,客户端必须执行协调,以便将数据演化的多个分支重新合并成一个(semantic reconciliation,语义协调)。一个典型的例子是”合并“用户购物车的多个不同版本。使用这种协调机制,”添加到购物车”操作就永远不会失败。但是,删除的商品可能会重新出现在购物车中。</p><p>需要重点留意的是,某些故障模式(failure mode)会导致系统拥有不止两个而是多个版本的相同数据。在存在网络分区和节点故障的情况下进行更新,可能会导致对象具有不同的版本子历史(sub-histories),需要系统在未来对此进行协调。这要求我们设计的应用程序明确承认同一数据的多版本存在的可能性(以便永远不会丢失任何更新)。</p><p>Dynamo 使用矢量时钟(vector clock)[12] 来捕捉同一对象不同版本之间的因果关系。矢量时钟实际上是一个 (node, counter) 列表。一个矢量时钟与每个对象的每个版本相关联。通过检查其矢量时钟,我们可以判断一个对象的两个版本是否在并行的分支上,或者是否有因果关系。如果第一个对象时钟上的计数器小于或等于第二个时钟中的所有节点,那么第一个时钟就是第二个的祖先,可以安全地删除。否则,这两个修改就被认为是有冲突的,需要协调冲突。</p><p>在 Dynamo 中,当客户端想要更新一个对象时,它必须指定将基于哪个版本进行更新。这是通过传递它从前面的读操作中获得的上下文来实现的,其中上下文包含矢量时钟信息。在处理读请求的时候,如果 Dynamo 访问到多个不能语法协调(syntactically reconciled)的版本分支,它将返回叶子上的所有对象,以及上下文中相应的版本信息。使用此上下文的更新被认为已经协调了不同的版本,多个分支被合并为一个唯一的新分支。</p><p><img src="https://docs.riak.com/images/dynamo/figure3.png" alt=""></p><p> 图 3:对象随时间的版本演化。</p><p>为了说明矢量时钟的使用,让我们考虑图3所示的例子。首先,客户端写入一个对象。处理这个 key 的写操作的节点(比如说 Sx)递增序列号,并用此创建对象的矢量时钟。至此系统拥有了对象 D1 及其对应的时钟 [(Sx, 1)] 。第二步,客户端更新该对象。假定还是由相同的节点处理这个请求。现在该系统也拥有了对象 D2 及其对应的时钟 [(Sx, 2)]。D2继承自D1,因此覆盖了D1,但是节点中或许存在还没有看到D2的D1的副本。第三步,让我们假设还是这个客户端再次更新了对象,但是由另一个服务器(比如Sy)处理该请求。现在该系统拥有了数据 D3 及其对应的时钟 [(Sx, 2),(Sy, 1)]。</p><p>接下来,假设另一个客户端读取 D2 并试图更新它,另一个节点(比如Sz)执行写入操作。系统现在有 D4(D2 的后代),其版本时钟是 [(Sx, 2),(Sz, 1)]。一个能意识到 D1 和 D2 存在的节点,在收到 D4 及其时钟时,能够确定 D1 和 D2 被这个新数据覆盖了,因此可以被垃圾回收掉。一个能意识到 D3 存在的节点,在接收D4时将会发现,它们之间不存在因果关系。换句话说,D3 和 D4 各自的改动并没<br>有反映在对方之中。因此这两个版本都应当被保留并提交给客户端,由客户端(在读时)执行语义协调。</p><p>现在假设一些客户端同时读取到了 D3 和 D4(上下文将反映是在读操作时找到了这两个值)。读操作返回的上下文综合了 D3 和 D4 的时钟,即 [(Sx, 2), (Sy, 1), (Sz, 1)]。如果客户端执行协调,且由节点 Sx 来协调这个写操作,Sx 将更新其时钟的序列号。新的数据 D5 将有以下时钟:[(Sx, 3), (Sy, 1), (Sz, 1)]。</p><p>关于矢量时钟的一个潜在问题是,如果许多服务器协调对一个对象的写,矢量时钟的大小可能会增长。实际上,这不太可能,因为写操作通常由优先列表中的前 N 个节点之一来处理。只有在网络分区或多个服务器故障的情况下,写请求才可以由不在优先列表的前N个的节点处理,从而使矢量时钟的大小增加。在这些情况下,最好限制矢量时钟的大小。为此,Dynamo 使用以下时钟截断方案(clock truncation scheme):伴随着每个 (node, counter) 对,Dynamo 存储一个时间戳记录节点更新数据项的最后一次时间。当矢量时钟中 (node, counter) 对的数目达到一个阈值(如 10),最早的一对将从时钟中删除。显然,由于无法精确地导出后代关系,这种截断方案可能导致和解效率低下。然而,这个问题还没有在生产中出现,因此这个问题还没有得到彻底的调查。</p><h3 id="4-5-get-和-put-的执行过程"><a href="#4-5-get-和-put-的执行过程" class="headerlink" title="4.5 get() 和 put() 的执行过程"></a>4.5 get() 和 put() 的执行过程</h3><p>Dynamo 中的任何存储节点都有资格接收客户端的任何对 key 的 get 和 put 操作。为了简单起见,本节我们将描述如何在无故障环境中执行这些操作,在下一节中,我们将描述如何在故障期间执行读写操作。</p><p>get 和 put 操作都是使用 Amazon 特定于基础设施的请求处理框架,并通过 HTTP 调用的。客户端有两种策略来选择节点:(1)通过一个普通的负载均衡器路由其请求,该负载均衡器将根据负载信息选择一个节点;(2)使用一个分区敏感(partition-aware)的客户端库,直接将请求路由到适当的 coordinator 节点。第一种方式的好处是客户端不必在其应用程序中链接(link)任何 Dynamo 相关的代码,第二种的好处是可以实现较低的延时,因为它跳过了一次潜在的转发步骤。</p><p>处理读或写请求的节点被称为 coordinator。通常,这是优先列表(preference list)中前 N 个节点中的第一个。如果通过负载均衡器接收请求,访问某个 key 的请求可能被路由到环上任意一个节点。在这种情况下,如果接收到请求的节点不在被请求的 key 的优先列表的前N个节点中,那它就不会处理这个请求。相反,该节点会将请求转发到优先列表前 N 个节点中的第一个。</p><p>读写操作涉及优先列表中的前 N 个健康节点,跳过那些宕机或不可访问的节点。当所有节点都健康时,将访问优先列表中前 N 个节点。当存在节点故障或网络分区时,将访问优先列表中编号较小的节点。</p><p>为了维护副本的一致性,Dynamo 使用了一种与仲裁系统(quorum systems)中所使用的类似的一致性协议。该协议有两个关键配置参数:R 和 W。R 是成功完成一次读操作所需的最小节点数。W 是成功完成一次写操作所需的最小节点数。设置 R 和 W,使 R + W > N,就得到了一个类似仲裁的系统。在此模型中,一次 get(或 put)操作的延迟是由 R(或 W)副本中最慢的一个决定。因此,R 和 W 通常被配置为小于 N,以提供更好的延迟。</p><p>在收到一个 key 的 put() 请求后,coordinator 会为新版本生成矢量时钟,并将新版本写入本地。然后,coordinator 将新版本(及新的矢量时钟)发送到 N 个排在最前面的可达节点。如果至少有 W-1 节点响应,则认为写入成功。</p><p>类似地,对于一个 get() 请求,coordinator 会从 N 个排在最前面的可达节点请求该 key 所有现存的数据版本,等待 R 个响应之后,返回结果给客户端。如果coordinator 最终收集了多个版本的数据,它将返回所有它认为没有因果关系的版本。不同版本将被协调,并将取代当前版本的协调版本写回。</p><h3 id="4-6-故障处理:提示移交(Hinted-Handoff)"><a href="#4-6-故障处理:提示移交(Hinted-Handoff)" class="headerlink" title="4.6 故障处理:提示移交(Hinted Handoff)"></a>4.6 故障处理:提示移交(Hinted Handoff)</h3><p>如果 Dynamo 使用传统的仲裁方法,那么在服务器故障和网络分区期间它将不可用,并且即使在最简单的故障情况下也会降低持久性。为了弥补这一点,它不严格执行仲裁,而是使用“草率的仲裁”(sloppy quorum);所有读和写操作都是在优先列表的前 N 个健康节点上执行的,它们可能不总是在散列环上遇到的前 N 个节点。</p><p>考虑图 2 中 Dynamo 的配置示例,其中 N=3。在本例中,如果节点 A 在写操作期间临时关闭或无法访问,那么通常存在于 A 上的一个副本现在将发送到节点 D。这样做是为了保证期望的可用性和持久性。发送到 D 的副本的元数据中会有一个提示(hint),表明哪个节点是副本的预期接收者(在本例中为 A)。含提示的副本将被接收节点保存在一个单独的本地数据库中,并定期扫描。在检测到 A 已恢复后,D 将尝试将副本发送回 A。发送成功后,D 将从本地数据库中将其删除,从而不会降低系统中的副本总数。</p><p>使用 hinted handoff,Dynamo 能确保读写操作不会因为节点或网络临时故障而失败。应用如果需要最高的可用性,可以将 W 设为 1,这就保证了只要系统中有一个节点将 key 持久地写入到本地存储,这次写操作就被接受了。因此,只有当系统中的所有节点都不可用时,写请求才会被拒绝。然而实际上,在生产环境中大部分 Amazon 的服务都将 W 设置得较大,以满足期望的持久性要求。Section 6将更详细地讨论 N、R 和 W 的配置。</p><p>一个高可用性的存储系统必须具备处理整个数据中心故障的能力。断电、冷却系统故障、网络故障和自然灾害都会导致整个数据中心发生故障。Dynamo 可以配置成每个对象都可以跨多个数据中心复制。本质上,一个 key 的优先列表是基于将存储节点分布到多个数据中心来构造的。这些数据中心之间通过高速网络连接。这种跨多个数据中心复制的方案允许我们在没有数据中断的情况下处理整个数据中心故障。</p><h3 id="4-7-持久故障处理:副本同步(Replica-synchronization)"><a href="#4-7-持久故障处理:副本同步(Replica-synchronization)" class="headerlink" title="4.7 持久故障处理:副本同步(Replica synchronization)"></a>4.7 持久故障处理:副本同步(Replica synchronization)</h3><p>在系统成员变动(churn)较低、节点故障很短暂的情况下,hinted handoff 能保证良好的工作。在某些情况下,在 hinted 副本移交回原来的副本节点之前,该副本就不可用了。为了解决这个问题,以及其他对持久性的威胁,Dynamo实现了一个反熵(anti-entropy,或称副本同步)协议来保持副本同步。</p><p>为了更快地检测副本之间的不一致性,以及最小化传输的数据量,使用了 Merkle 树 [13]。Merkle 树是一种哈希树,其中叶子节点是各个 key 的哈希值。树中较高的父节点是其子节点的哈希。Merkle 树的主要优点是可以独立检查树的每个分支,而无需节点下载载整个树或整个数据集。此外,Merkle 树有助于减少在检查副本之间的不一致性时所需传输的数据量。例如,如果两树根节点的哈希值相等,那么树中的叶子节点的值也相等,此时节点之间就不需要同步。如果不相等,则意味着某些副本的值是不同的。在这种情况下,两台节点就需要交换子节点的哈希值,这个过程一直持续到到达叶子节点,此时就可以识别出“不同步”的 key。Merkle 树最小化了同步所需传输的数据量,并减少了在反熵过程中执行的磁盘读取次数。</p><p>Dynamo 使用 Merkle 树实现反熵的过程如下:每个节点为每段 key range(一个虚拟节点所覆盖的 key 的集合)维护了一个单独的 Merkle 树。这允许节点之间可以比较 key range 内的 key 是否是最新的。在这个方案中,两个节点会交换他们都有的 key range 所对应的 Merkle 树的根节点。随后,使用上面描述的树遍历方案,节点可以判断它们是否有任何差异,并执行适当的同步操作。这种方案的缺点是,当有节点加入或离开系统时有许多 key range 会改变,从而需要重新对树进行计算。不过,这个问题可以通过第 6.2 节中描述的改进分区方案(partitioning scheme)来解决。</p><h3 id="4-8-成员管理和故障检测"><a href="#4-8-成员管理和故障检测" class="headerlink" title="4.8 成员管理和故障检测"></a>4.8 成员管理和故障检测</h3><h4 id="4-8-1-哈希环成员(Ring-Membership)"><a href="#4-8-1-哈希环成员(Ring-Membership)" class="headerlink" title="4.8.1 哈希环成员(Ring Membership)"></a>4.8.1 哈希环成员(Ring Membership)</h4><p>在 Amazon 的环境中,节点中断(由于故障和维护任务)通常情况下是暂时的,但也可能出现中断比较长的情况。一个节点中断少意味着这个节点永久性的离开了系统, 因此不应该导致 partition 分配的重新平衡或者修复无法访问的副本。同样,人工错误可能导致新的 Dynamo 节点的意外启动。因此,应当使用一个明确的机制来发起从Dynamo环中添加和删除节点。管理员使用命令行工具或浏览器连接到一个 Dynamo 节点,并下发成员变动(membership change)命令从环中加入或删除一个节点。负责处理这个请求的节点将成员变动及其对应的时间写入持久存储。成员变动会形成历史记录,因为一个节点可以被多次删除和重新添加。</p><p>一个基于 Gossip 的协议传播成员变动消息,并维护一份最终一致的成员关系视图。每个节点每秒会随机选择一对等节点进行通信,这两个节点会高效地协调他们持久化的成员变动历史。</p><p>当一个节点第一次启动时,它会选择它的 token 集(一致性哈希空间内的虚拟节点 ),并将节点映射到各自的 token 集。该映射关系会被持久化到磁盘上,最初只包含本地节点和token 集。存储在不同 Dynamo 节点上的映射关系,将在协调成员变动历史的通信过程中一同被协调。因此,分区(partitioning)和位置(placement)信息也通过基于 Gossip 的协议传播,每个存储节点都知道其对等节点处理的 token 范围。这使得每个节点可以将一个 key 的读/写操作直接发送给正确的节点集。</p><h4 id="4-8-2-外部发现(External-Discovery)"><a href="#4-8-2-外部发现(External-Discovery)" class="headerlink" title="4.8.2 外部发现(External Discovery)"></a>4.8.2 外部发现(External Discovery)</h4><p>上述机制可能会暂时导致 Dynamo 环的逻辑分区。例如,管理员可以先联系节点 A 将其加入到环,然后联系节点 B 将其加入到环。在这种情况下,节点 A 和 B 都会认为自己是环的一员,但都不会立即感知到对方。为了避免逻辑分区,一些 Dynamo 节点充当种子节点的角色。种子是通过外部机制(external mechanism)发现的节点,所有节点都知道它的存在。因为所有节点最终都会和种子节点协调成员信息,所以逻辑分区就几乎不可能发生了。种子可以从静态配置文件或者从一个配置中心获取。通常,种子节点具有 Dynamo 环上节点的全部功能。</p><h4 id="4-8-3-故障检测(Failure-Detection)"><a href="#4-8-3-故障检测(Failure-Detection)" class="headerlink" title="4.8.3 故障检测(Failure Detection)"></a>4.8.3 故障检测(Failure Detection)</h4><p>Dynamo 中的故障检测被用于避免,在 get() 和 put() 操作期间以及在传输分区和提示副本(hinted replica)时,尝试与不可到达的对等节点通信。为了避免通信失败的尝试,一个纯本地概念(pure local notion)的故障检测完全足够了:如果节点 B 没有响应节点 A 的消息(即使 B 可以应答 C 的消息),节点 A 可能会认为节点 B 故障。在 Dynamo 环中,当客户端请求以稳定的速率产生节点间通信时,一旦节点 B 无法对消息做出应答,节点 A 就能很快发现 B 不响应;然后节点 A 使用备用节点处理映射到 B 的分区的请求;A 定期重试 B,检查其是否恢复。在没有客户端请求来驱动两个节点之间的通信时,两个节点实际上都不需要知道另一个节点是否可访问和可响应。</p><p>去中心化故障检测协议使用简单的 Gossip 风格协议,使系统中的每个节点都可以感知到其他节点的加入(或离开)。有关去中心化的故障探测器和影响其准确性的参数的详细信息,感兴趣的读者可以参考 [8]。Dynamo 的早期设计中使用了去中心化的故障检测器来维护一个故障状态的全局一致视图 。后来发现,显式的节点加入和离开机制使得故障状态的全局视图变得多余了。这是因为,通过显式的节点加入和离开机制,节点可以感知其他节点持久性的添加和删除;而短暂的节点故障,可以在个别节点(转发请求时)无法与其他节点通信时被检测到。</p><h3 id="4-9-添加-移除存储节点"><a href="#4-9-添加-移除存储节点" class="headerlink" title="4.9 添加/移除存储节点"></a>4.9 添加/移除存储节点</h3><p>当一个新节点(比如 X)被添加到系统中时,它会被分配一些随机分布在环上的 token。对于每个分配给节点 X 的 key range,可能有一些节点(小于等于 N 个)当前已经在处理落在其 token range内的 key 了。由于将 key range 分配给了 X ,这些节点就不需要这些 key 了,而是将这些 key 转给 X。让我们考虑一个简单的引导(bootstrapping)场景,节点 X 被添加到图 2 所示环中的 A 和 B 之间。当 X 添加到系统后,它负责的 key 的范围为 (F, G],(G, A] 和 (A, X]。这样,节点 B、C 和 D 就不再需要将 key 存储在各自的范围内了。因此,在收到 X 确认后,B、C 和 D 会向 X 传输相应的 key。当从系统中删除一个节点时,key 的重新分配将以相反的过程进行。</p><p>实际运行经验表明,这种方法可以将 key 分布的负载均匀地分布在各个存储节点上,这对于满足延迟需求和确保快速引导(bootstrapping )是非常重要的。最后,通过在源和目标之间增加一轮确认,可以确保目标节点不会重复收到任何给定 key range。</p><h2 id="5-实现"><a href="#5-实现" class="headerlink" title="5. 实现"></a>5. 实现</h2><p>在 Dynamo 中,每个存储节点有三个主要的软件组件:请求协调、成员和故障检测,以及一个本地持久存储引擎。所有这些组件都是 Java 实现的。</p><p>Dynamo 的本地持久化存储组件支持插入不同的存储引擎。在使用的引擎包括:Berkeley Database (BDB) Transactional Data Store、BDB Java Edition、MySQL 和一个持久化备份存储的内存缓冲区。将其设计为一个可插拔的持久化组件的主要原因,是为了能根据应用程序的访问模式选择最适合的存储引擎。例如,BDB 通常可以处理几十 KB 的对象,而 MySQL 可以处理更大的对象。应用可以根据自己对象大小的分布选择合适的 Dynamo 本地持久化引擎。大多数 Dynamo 的生产实例都使用 BDB Transactional Data Store。</p><p>请求协调组件是构建在一个事件驱动的消息系统的基础之上的,其中消息处理 pipeline 被分成多个阶段,类似于 SEDA 架构 [24]。所有通信都基于 Java NIO channel 实现。coordinator 通过从一个或多个节点收集数据(读操作时),或向一个或多个节点存储数据(写操作时),从而代替客户端执行读写请求。每个客户端请求都会在收到这个请求的节点上创建一个状态机(state machine)。状态机包含识别 key 对应的节点、发送请求、等待响应、可能的重试处理、处理响应并将响应打包到客户机的所有逻辑。每个状态机实例只处理一个客户机请求。例如,一个写操作实现了以下状态机:(1)发送读请求到节点;(2)等待所需的最少数量响应;(3)如果在给定上限的时间内收到的响应数量太少,请求失败;(4)否则,收集所有数据的版本,并确定要返回的版本;(5)如果启用了版本控制,执行语法协调(syntactic reconciliation),并生成一个不透明的写上下文(write context),其中包含了所有剩余版本的矢量时钟。为了简单起见,省略了故障处理和重试状态。</p><p>读操作的响应发送给调用方之后,状态机将继续等待一小段时间,以接收任何未完成的响应。如果任何响应返回了过期版本,coordinator将使用最新版本更新这些节点。这个过程被称为读修复(read repair),因为它借此时机修复了那些错过了最新更新的副本,减少了反熵协议的操作。</p><p>如前所述,写请求是由优先列表中某个排名前 N 的节点来协调的。尽管总是选择前 N 节点中的第一个节点来协调是满足需要的,这样可以在一个位置将所有的写操作序列化,但是这种做法却会导致负载分布不均匀,从而违反了 SLA。这是因为请求负载不是均匀分布在对象之间的。为了解决这个问题,优先列表中的任何前 N 个节点都被允许协调写操作。特别是,由于每次写操作通常跟随在一个读操作之后,因此写操作的 coordinator 通常选择用存储在请求上下文信息中的前一次读操作响应最快的节点。这项优化使我们能够选中那个被前一次读操作读取过数据的节点,从而提高了达成 “read-your-writes” 一致性的概率。同时,这还降低了请求处理性能的抖动性,提高了 99.9 百分位点的性能。</p><h2 id="6-经验与教训"><a href="#6-经验与教训" class="headerlink" title="6. 经验与教训"></a>6. 经验与教训</h2><p>Dynamo 被几个具有不同配置的服务所使用。这些实例有着不同的版本协调逻辑和读/写仲裁(quorum)的特性。以下是 Dynamo 的主要使用模式:</p><ul><li><p><strong>业务逻辑特定的协调</strong>:这是 Dynamo 的一个流行用例。每个数据对象被复制到多个节点。当发生版本冲突时,客户端应用程序执行自己的协调逻辑。前面讨论的购物车服务就是这一类的典型例子。其业务逻辑是通过合并用户购物车的不同版本来协调对象。</p></li><li><p><strong>基于时间戳的协调</strong>:这种情况与前面相比只是和解机制不同。当发生版本冲突时,Dynamo 执行简单的基于时间戳的协调逻辑:“最后一次写胜出”(last write wins);例如,选择物理时间戳值最大的对象作为正确版本。该模式一个典型的例子是维护客户 session 信息的服务。</p></li><li><p><strong>高性能读引擎</strong>:虽然 Dynamo 被设计为一个“永远可写”(always writeable)的数据存储,但也有一些服务通过调整其仲裁(quorum)特性把它作为一个高性能读引擎来使用。通常,这些服务具有较高的读取请求速率但只有少量更新。在这种配置中,通常将 R 设为 1,W 设为 N。对于这些服务,Dynamo 提供了跨多个节点进行数据分区(partition)和复制的能力,从而提供了增量可扩展性。其中一些实例作为权威持久缓存(authoritative persistence cache),来缓存存储在更重量级的后端存储中的数据。</p></li></ul><p>Dynamo 的主要优点是它的客户端应用可以通过对 N、R 和 W 三个参数进行调优来达到期望的性能、可用性和持久性水平。例如,N的值决定了每个对象的持久性。Dynamo 用户使用的一个典型的 N 值是 3。</p><p>W 和 R 的值会影响对象的可用性、持久性和一致性。例如,如果 W 设置为 1,那么只要系统中至少还有一个节点可以成功处理写请求,那么写请求就不会被系统拒绝。不过,W 和 R 值较低会增加不一致的风险,因为写请求即使没有被大多数副本处理,仍然能够被视为成功并返回到客户端。当一次写请求即使只在少量节点上完成了持久化也会向客户端返回成功时,这也引入了一个持久性的风险窗口(vulnerability window)。</p><p>传统观点认为,持久性和可用性是密切相关的。然而,这里不一定是这样。例如,持久性的风险窗口可以通过增加 W 来减少。但这将增加请求被拒绝的机率(从而降低了可用性),因为这种情况下需要更多处于活动状态的存储主机来处理写请求。</p><p>Dynamo 的几个实例使用的常见 (N, R, W) 配置是 (3,2,2)。选择这些值是为了满足性能、持久性、一致性和可用性 SLA 的必要级别。</p><p>本节中介绍的所有数据都是从一套线上系统获得的,该系统的配置为 (3, 2, 2),运行着几百个具有相同硬件配置的节点。如前所述,Dynamo 的每个实例都包含位于多个数据中心的节点。这些数据中心通常是通过高速网络连接。回想一下,执行一次成功的 get(或 put)需要 R(或 W)个节点响应 coordinator 。显然,数据中心之间的网络延迟会影响响应时间,因此需要选择节点(及其数据中心位置)以满足应用程序的目标 SLA。</p><h3 id="6-1-平衡性能和持久性(Performance-and-Durability)"><a href="#6-1-平衡性能和持久性(Performance-and-Durability)" class="headerlink" title="6.1 平衡性能和持久性(Performance and Durability)"></a>6.1 平衡性能和持久性(Performance and Durability)</h3><p>虽然 Dynamo 的主要设计目标是建立一个高可用的数据存储,但性能也是在 Amazon 平台中同样重要的标准。如前所述,为了提供一致的用户体验,Amazon 的服务将性能目标设置为相对更高的百分位点(例如 99.9 或 99.99 百分位点)。Dynamo 中一个典型 SLA 是 99.9% 的读写请求在 300ms 内执行完成。</p><p>由于 Dynamo 运行在标准的商用硬件组件上,这些组件的 I/O 吞吐量远远低于高端企业级服务器,因此提供一致的高性能读写并不是一项简单的任务。多个存储节点参与读写操作使其更具挑战性,因为这些操作的性能是受 R 或 W 副本中最慢的副本限制的。图4显示了 30 天内 Dynamo 读写操作的平均延迟和 99.9 百分位的延迟。从图中可以看出,延迟表现出明显的日变化模式(diurnal pattern),这是因为传入请求的速率也是日变化模式的(例如,日间和夜间的请求速率有着显著差异)。另外,写延迟明显高于读延迟,因为写操作永远需要访问磁盘。同时可以看到,99.9 百分位延迟约为 200ms,比平均值高出一个数量级。这是因为 99.9 百分位的延迟受到几个因素,如请求负载、对象大小和位置模式(locality pattern)的变化影响。</p><p><img src="https://docs.riak.com/images/dynamo/figure4.png" alt=""></p><p>图 4:在 2006 年 12 月的请求高峰期,读写请求的平均延迟和 99.9 百分位延迟。x 轴上一个刻度为 12 小时。延迟遵循类似于请求率的日变化模式,并且 99.9 百分位延迟比平均值高一个数量级。</p><p>虽然这种性能水平对于很多服务是足够的,但是有些面向用户的服务对性能有着更高的要求。对于这些服务,Dynamo 能够牺牲持久性来保证性能。在这个优化中,每个存储节点会在主内存中维护一个对象缓冲区(buffer)。每个写操作都存储在 buffer 中,通过写线程定期将 buffer 写入存储。在此方案中,读取操作首先检查请求的 key 是否存在于缓冲区中。如果是,则从缓冲区而不是存储引擎读取对象。</p><p>这项优化使得高峰流量期间 99.9 百分位的延迟降低到原来的 1/5,即使是对于一个非常小的包含1000个对象的缓冲区(见图 5)。此外,如图中所示,写缓冲(write buffering)可以使较高百分位的延迟曲线变得平滑。显然,该方案是平衡持久性来提高性能的。在此方案中,服务崩溃可能会导致缓冲区队列中等待写入的数据丢失。为了降低持久性风险,将写操作细化为让 coordinator 从 N 个副本中选择一个进行“持久化写入”。因为 coordinator 只等待 W 个写操作响应,所以整体写操作性能不受单个副本执行持久写操作的性能的影响。</p><p><img src="https://docs.riak.com/images/dynamo/figure5.png" alt=""></p><p>图 5:在 24 小时内,缓冲写与非缓冲写之间 99.9 百分位延迟的性能比较。x 轴上一个刻度为一小时。</p><h3 id="6-2-确保均匀的负载分布(Uniform-Load-distribution)"><a href="#6-2-确保均匀的负载分布(Uniform-Load-distribution)" class="headerlink" title="6.2 确保均匀的负载分布(Uniform Load distribution)"></a>6.2 确保均匀的负载分布(Uniform Load distribution)</h3><p>Dynamo 使用一致性哈希将它的 key 空间在多个副本上进行分区(partition),以确保均匀的负载分布。假设key 的访问分布不是极度不平衡的,那么均匀的 key 分布就可以帮助我们实现均匀的负载分布。特别地,Dynamo 设计假定即使访问的分布存在明显的倾斜,只要在分布的热点端(popular end)有足够多的 key,同样可以通过分区实现处理热点 key 的负载均匀地分布到节点上。本节讨论 Dynamo 中所出现负载不均衡以及不同的分区策略对负载分布的影响。</p><p>为了研究负载不平衡及其与请求负载的关系,我们测量了每个节点在24小时内接收的请求总数,并将其划分为30分钟的间隔。在给定的时间窗口中,如果节点的请求负载与平均负载相差小于某个阈值(这里是 15%),则节点被认为是“平衡”(in-balance)的。否则该节点将被视为“不平衡”(out-of-balance)。图 6 展示了这段时间内“不平衡”节点的比例(以下简称“不平衡比例” - imbalance ratio)。为了便于参考,还绘制了这段时间内整个系统接收到的相应请求负载。从图中可以看出,不平衡比例随着负载的增大而减小。例如,低负载期间的不平衡比例高达 20%,而高负载期间降到了近 10%。直观上这可以解释为,在高负载下大量的热点 key 被访问时,由于 key 的均匀分布,负载也会平衡分布。然而,在低负载(负载是已测量峰值负载的八分之一)期间,只有很少的热点 key 被访问,从而导致更高的负载不平衡。</p><p><img src="https://docs.riak.com/images/dynamo/figure6.png" alt=""></p><p>图 6:不平衡节点的比例(即,请求负载高于平均系统负载的某个阈值的节点)及其相应的请求负载。x 轴上一个刻度为 30 分钟。</p><h4 id="策略-1:每个节点-T-个随机-token,按-token-值分区"><a href="#策略-1:每个节点-T-个随机-token,按-token-值分区" class="headerlink" title="策略 1:每个节点 T 个随机 token,按 token 值分区"></a>策略 1:每个节点 T 个随机 token,按 token 值分区</h4><p>这是部署在生产环境中的初始策略(在第 4.2 节介绍过)。在这个方案中,每个节点被分配 T 个 token(从哈希空间随机均匀地选择)。所有节点的 token 按照其在哈希空间中的值进行排序。相邻的两个 token 定义一个范围。最后一个 token 和第一个 token 组成一个范围,将哈希空间中的最大值和最小值“包装”起来。因为 token 是随机选择的,所以范围的大小不同。当有节点加入或离开系统时,token 集发生变化,导致范围范围也会更改。注意,维护每个节点的成员关系所需的空间随系统中节点的数量线性增加。</p><p>在使用此策略时,遇到了以下问题。第一,当一个新节点加入系统时,它需要从其他节点“窃取”其所需的 key range。然而,将 key range 移交给新节点的节点必须扫描其本地持久存储,以检索出适当的数据项集合。需要注意的是,在生产环境环境执行这种扫描操作是很棘手的,因为扫描是高度资源密集型的操作,为了不影响客户性能,需要在后台执行。这要求我们必须将新节点加入的引导任务(bootstrapping task)的优先级务调到最低。然而,这明显减慢了引导过程,尤其是购物高峰季节点每天处理数百万个请求时,引导过程几乎要花费一天的时间来完成。第二,当一个节点加入/离开系统时,许多节点处理的 key range 会发生变化,需要重新计算用于新范围的 Merkle 树,对于生产环境来说,这也不是一项简单的工作。最后,由于 key range 的随机性,无法方便地对整个 key 空间进行快照,这使得归档过程非常复杂。在这个方案中,归档整个 key 空间需要我们分别从每个节点检索 key,这是非常低效的。</p><p>这个策略的根本问题是,数据分区(partitioning)和数据安置(placement)的方案混在了一起。例如,在某些情况下,为了处理请求负载的增加,最好向系统添加更多的节点。但是,在这个场景中,不可能在不影响数据分区的情况下添加节点。理想情况下,最好使用独立的分区和安置方案。为此,我们评估了以下的策略:</p><h4 id="策略-2:每个节点-T-个随机-token,大小相等的分区"><a href="#策略-2:每个节点-T-个随机-token,大小相等的分区" class="headerlink" title="策略 2:每个节点 T 个随机 token,大小相等的分区"></a>策略 2:每个节点 T 个随机 token,大小相等的分区</h4><p>在此策略中,将哈希空间划分为 Q 个大小相等的分区/范围,每个节点分配 T 个随机 token。Q 通常设置为 Q >> N 和 Q >> S*T,其中 S 为系统节点数。在此策略中,token 仅用于构建将哈希空间中的值映射到有序节点列表的函数,而不不决定分区。一个分区(partition)是放置在从该分区的末尾开始顺时针遍历一致性hash环遇到的前N个独立的节点上。图 7 展示了当 N=3 时这一策略的情况。在本例中,节点 A、B、C 是在从包含 key k1 的分区的末尾遍历环时遇到的。这种策略的主要优点是:(1)将数据的分区和分区安置解耦;(2)允许在运行时更改安置方案。</p><p><img src="https://docs.riak.com/images/dynamo/figure7-small.png" alt=""></p><p>图 7:这三种策略中 key 的分区和安置。N=3,A、B 和 C 是在一致哈希环上构成 key k1 优先列表的三个独立节点。阴影区域表示节点 A、B 和 C 所构成的优先列表的 key range。黑色箭头表示各个节点的 token 位置。</p><h4 id="策略-3:每个节点-Q-S-个-token,大小相等的分区"><a href="#策略-3:每个节点-Q-S-个-token,大小相等的分区" class="headerlink" title="策略 3:每个节点 Q/S 个 token,大小相等的分区"></a>策略 3:每个节点 Q/S 个 token,大小相等的分区</h4><p>和策略 2 类似,该策略将哈希空间划分为大小相等的 Q 个分区,分区安置(placement of partition)与分区(partitioning)方案解耦。此外,每个节点都被分配 Q/S 个 token,其中 S 为系统节点数。当一个节点离开系统时,它的 token 会随机地分配给其他节点,以便保留这些属性。同样,当节点加入系统时,新节点将通过一种可以保留这种属性的方式从系统的其他节点“窃取” token。</p><p>使用一个配置为 S=30 和 N=3 的系统,评估这三种策略的效率。然而,由于不同的策略使用不同的配置对效率进行调优,因此很难公平地比较这些不同的策略。例如,策略 1 的负载分布特性取决于 token 的数量(即 T),而策略 3 取决于分区的数量(即 Q)。一个公平的比较方式是,在所有的策略都使用相同大小的空间维护成员信息时,测量它们的负载分布倾斜度(skew)。例如,在策略 1 中,每个节点需要维护环中所有节点的 token 位置,而在策略 3 中,每个节点需要维护分配到每个节点的分区有关的信息。</p><p>在下一个实验中,我们通过改变相关的参数 (T 和 Q) 对这些策略进行了评估。每个策略的负载均衡效率(load balancing efficiency )是根据每个节点需要维持的成员信息的大小的不同来测量,其中负载平衡效率是指每个节点服务的平均请求数与最热(hottest)节点服务的最大请求数之比。</p><p>结果如图 8 所示。从图中可以看出,策略 3 的负载均衡效率最好,策略 2 的负载均衡效率最差。在很短的时间内,策略 2 充当了将 Dynamo 实例从使用策略 1 迁移到使用策略 3 过程中的过渡策略。和策略 1 相比,策略 3 性能更好,并且在每个节点需要维持的信息的大小降低了三个数量级。虽然存储不是一个主要问题,但节点间会周期地 Gossip 成员信息,因此保持这些信息越紧凑越好。此外,策略3有利于且易于部署,原因包括:(1)更快的引导(bootstrapping)/恢复(recovery):由于分区范围是固定的,因此可以将其存放到单独的文件,这意味着一个分区可以作为一个整体通过转移文件被重新安置(避免了为定位特定数据而进行的随机访问)。这简化了引导和恢复的过程。(2)易于存档:定期对数据集进行存档是大多数 Amazon 存储服务的强制性要求。相反,在策略 1 中,token 是随机选取的,存档的时候需要从所有节点分别获取它们存储的 key 信息,这通常是低效和缓慢的。策略3的缺点是,更改节点成员关系时需要协调(coordination),以便维护分配所需的属性。</p><p><img src="https://docs.riak.com/images/dynamo/figure8.png" alt=""></p><p>图 8:30 个节点、N=3 且每个节点元数据量相同的系统,在不同策略下的负载分配效率比较。系统大小和副本数量的值是按照我们部署的大多数服务的典型配置。</p><h3 id="6-3-分歧版本:什么时候?有多少?"><a href="#6-3-分歧版本:什么时候?有多少?" class="headerlink" title="6.3 分歧版本:什么时候?有多少?"></a>6.3 分歧版本:什么时候?有多少?</h3><p>如前所述,Dynamo 被设计为通过牺牲一些一致性以换取可用性。为了准确理解不同故障对一致性的影响,需要详细考虑多个因素:中断时长、故障类型、组件可靠性、工作负载等。详细地展示这些数据超出了本文范围。不过,本节讨论了一个很好的总结指标:应用程序在实际生产环境中出现的分歧版本(divergent versions)的数量。</p><p>数据项的分歧版本在以下两种情况下会出现:第一种是当系统面临诸如节点故障、数据中心故障和网络分区等故障场景时。第二种是当系统处理单个数据项的大量并发写入线程,并且最终多个节点同时协调更新操作时。从易用性和效率的角度来看,最好在任何给定的时间都保证分歧版本的数量尽可能少。如果这些版本不能仅提高矢量时钟进行语法协调,则必须将它们交给业务逻辑执行语义协调。语义协调会给服务带来额外的负载,因此应当越少越好。</p><p>在下一个实验中,我们采集分析了 24 小时内返回到购物车服务的版本数量。在这段时间内,99.94% 的请求只看到一个版本;0.00057%的请求看到 2 个版本;0.00047% 的请求看到了 3 个版本,0.00009% 的请求看到了 4 个版本。这说明分歧版本出现的概率还是相当小的。</p><p>经验表明,分歧版本数量的增加不是因为故障而导致的,而是由于并发写操作数量的增加。并发写数量的增加通常是由繁忙的机器人(自动化客户端程序)触发的,而极少是人为导致的。由于其敏感性,在此就不做详细地讨论了。</p><h3 id="6-4-客户端驱动或服务端驱动的协调"><a href="#6-4-客户端驱动或服务端驱动的协调" class="headerlink" title="6.4 客户端驱动或服务端驱动的协调"></a>6.4 客户端驱动或服务端驱动的协调</h3><p>正如第 5 节中提到的,Dynamo 有一个请求协调组件,使用状态机来处理传入的请求。客户端请求会通过负载均衡器均匀地分发给环上的节点。任何 Dynamo 节点都可以作为读请求的 coordinator。另一方面,写请求将由 key 当前的优先列表中的节点来协调。有这种限制是因为,这些首选节点有创建一个新的版本戳(version stamp)的额外责任,该新版本戳在因果顺序上已经包含了被写操作更新的版本。注意,如果 Dynamo 的版本控制方案基于物理时间戳,那么任何节点都可以协调写请求。</p><p>另一种请求协调的方式是将状态机移到客户端节点。在这个方案中,客户端应用程序使用一个库(library)在本地执行请求协调。客户端定期选取一个随机 Dynamo 节点,并下载它当前的 Dynamo 成员状态视图。使用此信息,客户端可以确定任意 key 对应的优先列表由哪些节点集组成。读取请求可以在客户端节点上进行协调,从而避免了负载均衡器将请求分配给随机 Dynamo 节点时产生的额外网络跳(network hop)开销。如果 Dynamo 使用基于时间戳的版本控制,写操作要么转发给 key 优先列表中的一个节点,要么在本地被协调。</p><p>客户端驱动协调方法的一个重要优点是,不再需要一个负载平衡器来均匀分发客户端负载。通过向存储节点近乎均匀分配 key,隐式地保证了公平的负载分布。显然,此方案的效率取决于客户端成员信息的新鲜程度。当前,客户端每 10 秒随机轮询一个 Dynamo 节点,以获取成员更新。选择基于 pull 而不是基于 push 的方法,是因为前者在大量客户端的情况下具有更好的伸缩性,而且相较于在客户端,只需在服务端维护很少的状态信息。然而,在最坏的情况下,客户端可能持有长达 10 秒过期的成员信息。在这种情况下,如果客户端检测到其成员表过期(例如,当一些成员不可达时),它将立即更新其成员信息。</p><p>表 2 显示了相较于服务端驱动方法,使用客户端驱动的协调方法在 99.9 百分位点和平均值的延迟性能提升,测量周期为24小时。从表中可以看出,客户端驱动的协调方法使 99.9 百分位点延迟降低了至少30 毫秒,并将平均延迟降低了 3 到 4 毫秒。延迟降低是因为客户端驱动的方式消除了负载均衡器的开销,以及在将请求分配给随机节点时可能产生的额外网络跳。还可以看到,平均延迟远远低于 99.9 百分位点的延迟。这是因为 Dynamo 的存储引擎缓存和写缓冲区(write buffer)具有良好的命中率。此外,由于负载均衡器和网络会给延迟引入额外的抖动性,因此在响应时间方面对 99.9 百分位点的性能提升明显要高于平均值。</p><table><thead><tr><th></th><th>99.9th percentile read latency (ms)</th><th>99.9th percentile write latency (ms)</th><th>Average read latency (ms)</th><th>Average write latency (ms)</th></tr></thead><tbody><tr><td>Server-driven</td><td>68.9</td><td>68.5</td><td>3.9</td><td>4.02</td></tr><tr><td>Client-driven</td><td>30.4</td><td>30.4</td><td>1.55</td><td>1.9</td></tr></tbody></table><p> 表 2:客户端驱动和服务器驱动的协调方法的性能。</p><h3 id="6-5-平衡后台和前台任务"><a href="#6-5-平衡后台和前台任务" class="headerlink" title="6.5 平衡后台和前台任务"></a>6.5 平衡后台和前台任务</h3><p>除了正常的前台 put/get 操作外,每个节点还需要为副本同步和数据移交(由于提示或添加/删除节点)执行不同类型的后台任务。在早期的生产环境中,这些后台任务引发了资源争用问题,影响了常规 put 和 get 操作的性能。因此,必须确保后台任务只在常规关键操作不受显著影响的情况下运行。为此,后台任务都整合了一种许可控制机制(admission control mechanism)。每个后台任务都使用这个控制器来申请资源(例如数据库)的运行时时间片(runtime slices),这些资源在所有后台任务之间共享。使用基于前台任务监控性能的反馈机制,来改变后台任务可用的时间片数量。</p><p>在执行“前台” put/get 操作时,许可控制器会持续地监控资源访问的状况。监控指标包括磁盘操作延迟、锁竞争和事务超时导致的数据库访问失败,以及请求队列的等待时间。这些信息用于判断在给定的时间窗口之内的延迟(或故障)百分比是否接近所期望的阈值。例如,后台控制器检查数据库第 99 百分位数据库读取延迟(在过去 60 秒内)与预设的阈值(比如 50ms)的接近程度。控制器使用这种比较来为前台操作评估资源可用性。随后,它决定有多少时间片可用于后台任务,从而使用反馈回路(feedback loop)来限制后台任务的干扰(intrusiveness )。注意,在 [4] 中也研究过类似的后台任务管理问题。</p><h3 id="6-6-讨论"><a href="#6-6-讨论" class="headerlink" title="6.6 讨论"></a>6.6 讨论</h3><p>本节总结了在 Dynamo 实施和维护过程中取得的一些经验。许多 Amazon 的内部服务在过去的两年都使用了 Dynamo,它为其应用程序提供了相当高的可用性。特别是,应用程序的 99.9995% 的请求都收到成功的响应(无超时),并且到目前为止还没有发生数据丢失事件。</p><p>此外,Dynamo的主要优点是,它提供了必要的配置开关,可以根据根据自己的需求使用 (N,R,W) 三个参数对应用实例进行调优。与流行的商业数据存储不同,Dynamo 向开发人员公开了数据一致性和协调逻辑问题。一开始,有人可能会觉得这样会使应用逻辑变得更加复杂。然而,从历史上看,Amazon 平台就是为高可用设计的,许多应用程序被设计为能够处理可能出现的各种故障模式和不一致性问题。因此,移植这些应用程序到使用 Dynamo 是一项相对简单的任务。对于希望使用 Dynamo 的新应用程序,在开发初期需要进行一些分析,以选择满足业务需求的合适的冲突解决机制。最后,Dynamo采用完整成员关系模型(full membership model),其中每个节点都知道其对等节点承载的数据。为此,每个节点要主动将完整路由表传播(gossip)给系统内的其他节点。对于包含数百个节点的系统,该模型可以很好地工作。但是,将这样的设计扩展到运行成千上万个节点并不简单,因为维护路由表的开销会随着系统的大小的增加而增加。可以通过在 Dynamo 中引入层次扩展(hierarchical extensions)来克服这种限制。 O(1) 复杂度的的动态哈希树(DHT)系统(例如 [14])解决的就是这种问题。</p><h2 id="7-结束语"><a href="#7-结束语" class="headerlink" title="7. 结束语"></a>7. 结束语</h2><p>本文介绍了 Dynamo,一个具有高度可用性和可扩展性的数据存储系统,在 Amazon 电商平台被用于存储许多核心服务的状态数据。Dynamo 提供了所需的可用性和性能级别,并成功地处理了服务器故障、数据中心故障和网络分区。Dynamo 可以增量扩展,允许服务所有者根据当前请求负载进行动态伸缩。Dynamo 允许服务所有者通过调优 N、R 和 W 参数来定制存储系统,以满足他们所期望的性能、持久性和一致性 SLA。</p><p>过去一年 Dynamo 的生产使用表明,去中心化技术可以结合起来提供一个单一的高可用性系统。它在最具挑战性的应用环境之一中的成功表明,最终一致性的存储系统可以成为高可用性应用程序的一块基石。</p><h3 id="致谢"><a href="#致谢" class="headerlink" title="致谢"></a>致谢</h3><p>The authors would like to thank Pat Helland for his contribution to the initial design of Dynamo. We would also like to thank Marvin Theimer and Robert van Renesse for their comments. Finally, we would like to thank our shepherd, Jeff Mogul, for his detailed comments and inputs while preparing the camera ready version that vastly improved the quality of the paper.</p><h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><ol><li>Adya, et al. Farsite: federated, available, and reliable storage for an incompletely trusted environment. SIGOPS 2002</li><li>Bernstein, P.A., et al. An algorithm for concurrency control and recovery in replicated distributed databases. ACM Trans. on Database Systems, 1984</li><li>Chang, et al. <strong>Bigtable: a distributed storage system for structured data</strong>. In Proceedings of the 7th Conference on USENIX Symposium on Operating Systems Design and Implementation, 2006</li><li>Douceur, et al. Process-based regulation of low-importance processes. SIGOPS 2000</li><li>Fox, et al. Cluster-based scalable network services. SOSP, 1997</li><li>Ghemawat, et al. <strong>The Google file system</strong>. SOSP, 2003</li><li>Gray, et al. The dangers of replication and a solution. SIGMOD 1996</li><li>Gupta, et al. On scalable and efficient distributed failure detectors. In Proceedings of the Twentieth Annual ACM Symposium on Principles of Distributed Computing. 2001</li><li>Kubiatowicz, et al. OceanStore: an architecture for global-scale persistent storage. SIGARCH Comput. Archit. News, 2000</li><li>Karger, et al. Consistent hashing and random trees: distributed caching protocols for relieving hot spots on the World Wide Web. STOC 1997</li><li>Lindsay, et al. “Notes on Distributed Databases”, Research Report RJ2571(33471), IBM Research, 1979</li><li>Lamport, L. <strong>Time, clocks, and the ordering of events in a distributed system</strong>. ACM Communications, 1978</li><li>Merkle, R. A digital signature based on a conventional encryption function. Proceedings of CRYPTO, 1988</li><li>Ramasubramanian, et al. Beehive: O(1)lookup performance for power-law query distributions in peer-topeer overlays. In Proceedings of the 1st Conference on Symposium on Networked Systems Design and Implementation, , 2004</li><li>Reiher, et al. Resolving file conflicts in the Ficus file system. In Proceedings of the USENIX Summer 1994 Technical Conference, 1994</li><li>Rowstron, et al. Pastry: Scalable, decentralized object location and routing for large-scale peerto- peer systems. Proceedings of Middleware, 2001.</li><li>Rowstron, et al. Storage management and caching in PAST, a large-scale, persistent peer-to-peer storage utility. Proceedings of Symposium on Operating Systems Principles, 2001</li><li>Saito, et al. FAB: building distributed enterprise disk arrays from commodity components. SIGOPS 2004</li><li>Satyanarayanan, et al. Coda: A Resilient Distributed File System. IEEE Workshop on Workstation Operating Systems, 1987.</li><li>Stoica, et al. Chord: A scalable peer-to-peer lookup service for internet applications. SIGCOMM 2001</li><li>Terry, et al. Managing update conflicts in Bayou, a weakly connected replicated storage system. SOSP 1995</li><li>Thomas. A majority consensus approach to concurrency control for multiple copy databases. ACM Transactions on Database Systems, 1979.</li><li>Weatherspoon, et al. Antiquity: exploiting a secure log for wide-area distributed storage. SIGOPS 2007</li><li>Welsh, et al. SEDA: an architecture for well-conditioned, scalable internet services. SOSP 2001</li></ol>]]></content>
<summary type="html">
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>中文译名:<strong>Dynamo:Amazon的高可用性的键-值存储系统</strong></p>
<p>论文于 2007 年首次发表,并在 Werner Vogels 的博客上得到推广,至今有着 4k+ 的引用量,是分布式存储系统中的一篇经典论文。</p>
<p>本文尝试对其进行简单翻译,受翻译水平所限,难免有翻译或理解错误,如有疑问烦请查阅原文:<a href="https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf" target="_blank" rel="noopener">原文地址</a>。</p>
</summary>
<category term="译文" scheme="http://yoursite.com/categories/%E8%AF%91%E6%96%87/"/>
<category term="分布式" scheme="http://yoursite.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
<category term="分布式存储" scheme="http://yoursite.com/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E5%AD%98%E5%82%A8/"/>
</entry>
<entry>
<title>查看Linux内核版本及系统版本</title>
<link href="http://yoursite.com/2019/08/13/%E6%9F%A5%E7%9C%8BLinux%E5%86%85%E6%A0%B8%E7%89%88%E6%9C%AC%E5%8F%8A%E7%B3%BB%E7%BB%9F%E7%89%88%E6%9C%AC/"/>
<id>http://yoursite.com/2019/08/13/查看Linux内核版本及系统版本/</id>
<published>2019-08-13T06:55:35.379Z</published>
<updated>2019-08-13T07:26:48.751Z</updated>
<content type="html"><![CDATA[<p><img src="https://i.loli.net/2019/02/01/5c53e438b7367.png" alt="Linux.png"></p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>记录一下查看Linux内核版本及系统版本的几种命令。</p><a id="more"></a><h2 id="命令"><a href="#命令" class="headerlink" title="命令"></a>命令</h2><h3 id="查看Linux内核版本命令:"><a href="#查看Linux内核版本命令:" class="headerlink" title="查看Linux内核版本命令:"></a>查看Linux内核版本命令:</h3><ul><li><p><code>cat /proc/version</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@ZPVMLIST-YS ~]<span class="comment"># cat /proc/version</span></span><br><span class="line">Linux version 2.6.32-573.el6.x86_64 (mockbuild@c6b9.bsys.dev.centos.org) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC) ) <span class="comment">#1 SMP Thu Jul 23 15:44:03 UTC 2015</span></span><br></pre></td></tr></table></figure></li><li><p><code>uname -a</code></p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@ZPVMLIST-YS ~]<span class="comment"># uname -a</span></span><br><span class="line">Linux ZPVMLIST-YS 2.6.32-573.el6.x86_64 <span class="comment">#1 SMP Thu Jul 23 15:44:03 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux</span></span><br></pre></td></tr></table></figure></li><li><p><code>uname -sr</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@ZPVMLIST-YS ~]<span class="comment"># uname -sr</span></span><br><span class="line">Linux 2.6.32-573.el6.x86_64</span><br></pre></td></tr></table></figure></li></ul><h3 id="查看Linux系统版本的命令:"><a href="#查看Linux系统版本的命令:" class="headerlink" title="查看Linux系统版本的命令:"></a>查看Linux系统版本的命令:</h3><ul><li><p><code>lsb_release -a</code>,列出所有系统版本信息该命令适用于所有的Linux,包括Redhat、SuSE、Debian等发行版。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[root@ZPVMLIST-YS ~]<span class="comment"># lsb_release -a</span></span><br><span class="line">LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch</span><br><span class="line">Distributor ID: CentOS</span><br><span class="line">Description: CentOS release 6.7 (Final)</span><br><span class="line">Release: 6.7</span><br><span class="line">Codename: Final</span><br></pre></td></tr></table></figure></li><li><p><code>cat /etc/issue</code>,此命令也适用于所有的Linux发行版。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[root@ZPVMLIST-YS ~]<span class="comment"># cat /etc/issue</span></span><br><span class="line">CentOS release 6.7 (Final)</span><br><span class="line">Kernel \r on an \m</span><br></pre></td></tr></table></figure></li><li><p><code>cat /etc/redhat-release</code>,该命令只适合Redhat系的Linux。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@ZPVMLIST-YS ~]<span class="comment"># cat /etc/redhat-release</span></span><br><span class="line">CentOS release 6.7 (Final)</span><br></pre></td></tr></table></figure></li><li><p><code>file /bin/bash</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@ZPVMLIST-YS ~]<span class="comment"># file /bin/bash</span></span><br><span class="line">/bin/bash: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), <span class="keyword">for</span> GNU/Linux 2.6.18, stripped</span><br></pre></td></tr></table></figure></li><li><p><code>file /bin/cat</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@ZPVMLIST-YS ~]<span class="comment"># file /bin/cat</span></span><br><span class="line">/bin/cat: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), <span class="keyword">for</span> GNU/Linux 2.6.18, stripped</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<summary type="html">
<p><img src="https://i.loli.net/2019/02/01/5c53e438b7367.png" alt="Linux.png"></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>记录一下查看Linux内核版本及系统版本的几种命令。</p>
</summary>
<category term="Linux" scheme="http://yoursite.com/categories/Linux/"/>
<category term="Linux命令" scheme="http://yoursite.com/tags/Linux%E5%91%BD%E4%BB%A4/"/>
</entry>
<entry>
<title>介绍一种非侵入式运行期AOP解决方案—JVM-SANDBOX</title>
<link href="http://yoursite.com/2019/07/30/JVM-SANDBOX%E2%80%94%E9%9D%9E%E4%BE%B5%E5%85%A5%E5%BC%8F%E8%BF%90%E8%A1%8C%E6%9C%9FAOP%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
<id>http://yoursite.com/2019/07/30/JVM-SANDBOX—非侵入式运行期AOP解决方案/</id>
<published>2019-07-30T07:09:08.310Z</published>
<updated>2019-09-06T01:50:21.479Z</updated>
<content type="html"><![CDATA[<p><img src="https://github.com/alibaba/jvm-sandbox/wiki/img/BANNER.png" alt="sandbox"></p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>随着软件部署规模的扩大,系统功能的细化,系统间耦合度和链路复杂度不断加强。要继续保持现有规模系统的稳定性,需要实现并完善监控体系、故障定位分析、流量录制回放、强弱依赖检测、故障演练等支撑工具平台。</p><p>出于对服务器规模和业务稳定性的考量,这些配套工具平台要具备无侵入、实时生效、动态可插拔的特点。JVM-SANDBOX就是这样一种基于JVM的非侵入式运行期AOP解决方案,由阿里测试开发专家<strong>鸾伽</strong>(花名)开发,现已在Github上开源。</p><p>本文根据 GitHub 收录的文档简单整理,方便后面查看使用。</p><p>Github地址:<a href="https://github.com/alibaba/jvm-sandbox" target="_blank" rel="noopener">https://github.com/alibaba/jvm-sandbox</a></p><a id="more"></a><p>[TOC]</p><h2 id="项目介绍"><a href="#项目介绍" class="headerlink" title="项目介绍"></a>项目介绍</h2><h3 id="沙箱的特性"><a href="#沙箱的特性" class="headerlink" title="沙箱的特性"></a>沙箱的特性</h3><ul><li><p><strong>无侵入</strong>:目标应用无需重启也无需感知沙箱的存在</p></li><li><p><strong>类隔离</strong>:沙箱以及沙箱的模块不会和目标应用的类相互干扰</p></li><li><p><strong>可插拔</strong>:沙箱以及沙箱的模块可以随时加载和卸载,不会在目标应用留下痕迹</p></li><li><p><strong>多租户</strong>:目标应用可以同时挂载不同租户下的沙箱并独立控制</p></li><li><p><strong>高兼容</strong>:支持JDK[6,11]</p></li></ul><h3 id="沙箱常见应用场景"><a href="#沙箱常见应用场景" class="headerlink" title="沙箱常见应用场景"></a>沙箱常见应用场景</h3><p>要保持系统的稳定性,需要的能力有系统限流、故障模拟、信息监控、链路跟踪、问题定位、业务回归、风险评估等等模块</p><ul><li><strong>线上故障定位</strong>:无需重启,attach到目标机器上,获取系统和方法信息</li><li><strong>线上系统流控</strong>:为应用设定最大服务能力阈值,超过阈值自动,系统不提供服务,保证本系统的稳定,防止流量过大压垮系统</li><li><strong>线上故障模拟</strong>:向系统内注入故障,验证系统的稳定性</li><li><strong>流量录制回放</strong>:线上录制流量,线下回放,完成回归</li><li><strong>性能压测</strong>:使用录制流量,脱敏后,为压测提供流量</li><li><strong>链路跟踪</strong>:系统间和系统内链路跟踪</li><li><strong>动态日志打印</strong></li><li><strong>安全信息监测和脱敏</strong></li></ul><h3 id="实时无侵入AOP框架"><a href="#实时无侵入AOP框架" class="headerlink" title="实时无侵入AOP框架"></a>实时无侵入AOP框架</h3><p><strong>proxy方案</strong>:基于对目标方法采用代理模式,常见的AOP框架实现方案中,有静态代理和动态代理两种。但是无论是静态织入还是动态织入的代理模式都必须新增一个方法来完成,而JDK的规范中运行期禁止对类进行新增方法操作,从而限制了现有AOP方案只能在JVM启动时、或代码编译时生效。</p><p><strong>埋点方案</strong>:不修改原代码的情况下,动态将字节码注入到运行的类中,实现问题跟踪定位、数据获取等功能。</p><p>proxy的解决方案实现了标准API,减少重复投入,但是不能实时生效,灵活度不够;埋点方案与之相反。</p><p>JVM-Sandbox为AOP提供了一个新的实现方案:以插桩代替代理。基于JVMTI技术规范,为观察和改变代码运行结果提供了即插即用模块接口的容器。使用字节码增强技术使JVM-Sandbox的模块能在不违反JDK约束情况下实现对目标应用方法的无侵入运行时AOP拦截。</p><h2 id="快速开始"><a href="#快速开始" class="headerlink" title="快速开始"></a>快速开始</h2><h3 id="安装沙箱"><a href="#安装沙箱" class="headerlink" title="安装沙箱"></a>安装沙箱</h3><p>环境要求:</p><ol><li><p>JDK[6,11];</p></li><li><p>Linux/UNIX/MacOS;</p></li></ol><p>下载安装:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 下载最新版本的JVM-SANDBOX</span></span><br><span class="line">wget http://ompc.oss-cn-hangzhou.aliyuncs.com/jvm-sandbox/release/sandbox-stable-bin.zip</span><br><span class="line"></span><br><span class="line"><span class="comment"># 解压</span></span><br><span class="line">unzip sandbox-stable-bin.zip</span><br></pre></td></tr></table></figure><p>当前我使用的是<a href="https://github.com/alibaba/jvm-sandbox/releases/tag/1.2.1" target="_blank" rel="noopener">jvm-sandbox@1.2.1</a>版本,文件目录结构如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">./sandbox/</span><br><span class="line"> +--bin/</span><br><span class="line"> | `--sandbox.sh</span><br><span class="line"> |</span><br><span class="line"> +--cfg/</span><br><span class="line"> | +--sandbox-logback.xml</span><br><span class="line"> | +--sandbox.properties</span><br><span class="line"> | `--version</span><br><span class="line"> |</span><br><span class="line"> +--example/</span><br><span class="line"> | `--sandbox-debug-module.jar</span><br><span class="line"> |</span><br><span class="line"> +--install-local.sh</span><br><span class="line"> |</span><br><span class="line"> +--lib/</span><br><span class="line"> | +--sandbox-agent.jar</span><br><span class="line"> | +--sandbox-core.jar</span><br><span class="line"> | `--sandbox-spy.jar</span><br><span class="line"> |</span><br><span class="line"> +--module/</span><br><span class="line"> | `--sandbox-mgr-module.jar</span><br><span class="line"> |</span><br><span class="line"> +--provider/</span><br><span class="line"> | `--sandbox-mgr-provider.jar</span><br><span class="line"> |</span><br><span class="line"> `--sandbox-module/</span><br></pre></td></tr></table></figure><h3 id="启动沙箱"><a href="#启动沙箱" class="headerlink" title="启动沙箱"></a>启动沙箱</h3><p>沙箱有两种启动方式:<code>ATTACH</code>和<code>AGENT</code></p><ul><li><p><strong>ATTACH方式启动</strong></p><p>即插即用的启动模式,可以在不重启目标JVM的情况下完成沙箱的植入。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看进程号</span></span><br><span class="line">ps -aux |grep java</span><br><span class="line"></span><br><span class="line"><span class="comment"># 进入沙箱执行脚本</span></span><br><span class="line"><span class="built_in">cd</span> sandbox/bin</span><br><span class="line"></span><br><span class="line"><span class="comment"># 假设目标JVM进程 18233</span></span><br><span class="line">./sandbox.sh -p 18233</span><br></pre></td></tr></table></figure><p>注:<code>sandbox.sh</code>是整个沙箱的主要操作客户端,可以通过 <code>./sandbox.sh -h</code> 命令输出帮助信息。详见:<a href="https://github.com/alibaba/jvm-sandbox/wiki/CONFIG" target="_blank" rel="noopener">sandbox.sh命令详解</a></p></li></ul><ul><li><p>挂载成功后会提示</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233</span><br><span class="line"> NAMESPACE : default</span><br><span class="line"> VERSION : 1.2.1</span><br><span class="line"> MODE : ATTACH</span><br><span class="line"> SERVER_ADDR : 0.0.0.0</span><br><span class="line"> SERVER_PORT : 29071</span><br><span class="line"> UNSAFE_SUPPORT : ENABLE</span><br><span class="line"> SANDBOX_HOME : /opt/quote/sandbox/bin/..</span><br><span class="line"> SYSTEM_MODULE_LIB : /opt/quote/sandbox/bin/../module</span><br><span class="line"> USER_MODULE_LIB : /opt/quote/sandbox/sandbox-module;</span><br><span class="line"> SYSTEM_PROVIDER_LIB : /opt/quote/sandbox/bin/../provider</span><br><span class="line"> EVENT_POOL_SUPPORT : DISABLE</span><br></pre></td></tr></table></figure></li><li><p>查看挂载模块</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -l</span><br><span class="line">sandbox-info ACTIVE LOADED 0 0 0.0.4 luanjia@taobao.com</span><br><span class="line">sandbox-module-mgr ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com</span><br><span class="line">sandbox-control ACTIVE LOADED 0 0 0.0.3 luanjia@taobao.com</span><br><span class="line">total=3</span><br></pre></td></tr></table></figure></li><li><p>查看沙箱日志</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">cat ~/logs/sandbox/sandbox.log |more</span><br><span class="line">2019-08-06 17:43:19 default INFO </span><br><span class="line"> ___ ____ __ ____ _ _ _ ____ ____ _____ __</span><br><span class="line"> | \ \ / / \/ | / ___| / \ | \ | | _ \| __ ) / _ \ \/ /</span><br><span class="line"> _ | |\ \ / /| |\/| |____\___ \ / _ \ | \| | | | | _ \| | | \ /</span><br><span class="line">| |_| | \ V / | | | |_____|__) / ___ \| |\ | |_| | |_) | |_| / \</span><br><span class="line"> \___/ \_/ |_| |_| |____/_/ \_\_| \_|____/|____/ \___/_/\_\</span><br><span class="line">2019-08-06 17:43:19 default INFO initializing logback success. file=/opt/quote/sandbox/bin/../cfg/sandbox-logback.xml;</span><br><span class="line">...</span><br><span class="line">2019-08-06 17:43:20 default INFO initialized server. actual <span class="built_in">bind</span> to 0.0.0.0:36406</span><br><span class="line">2019-08-06 17:43:20 default INFO server[0.0.0.0:0] <span class="built_in">bind</span> success.</span><br></pre></td></tr></table></figure></li></ul><ul><li><p><strong>AGENT方式启动</strong></p><p>有些时候我们需要沙箱工作在应用代码加载之前,或者一次性渲染大量的类、加载大量的模块,此时如果用ATTACH方式加载,可能会引起目标JVM的卡顿或停顿(GC),这就需要启用到AGENT的启动方式。</p><p>假设SANDBOX被安装在了<code>/opt/quote/sandbox</code>,需要在JVM启动参数中增加上</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-javaagent:/opt/quote/sandbox/lib/sandbox-agent.jar</span><br></pre></td></tr></table></figure></li></ul><h3 id="卸载沙箱"><a href="#卸载沙箱" class="headerlink" title="卸载沙箱"></a>卸载沙箱</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -S</span><br><span class="line">jvm-sandbox[default] shutdown finished.</span><br></pre></td></tr></table></figure><h2 id="配置与使用"><a href="#配置与使用" class="headerlink" title="配置与使用"></a>配置与使用</h2><h3 id="沙箱配置"><a href="#沙箱配置" class="headerlink" title="沙箱配置"></a>沙箱配置</h3><p><strong>配置文件说明</strong>:</p><p>./sandbox/cfg/<br> 沙箱配置文件存放目录,里面存放了沙箱的所有配置文件</p><ul><li>./sandbox/cfg/version<br> 沙箱容器版本号</li><li>./sandbox/cfg/sandbox.properties<br> 存放沙箱容器的配置信息,配置文件只会在沙箱容器启动的时候加载一次。详见:<a href="#jump1">详细配置解释</a></li><li>./sandbox/cfg/sandbox-logback.xml<br> Logback日志配置</li></ul><p><span id="1"><strong>详细配置解释</strong></span>:</p><p>配置文件:./cfg/sandbox.properties</p><table><thead><tr><th>配置项</th><th>说明</th><th>默认值</th></tr></thead><tbody><tr><td>system_module</td><td>系统模块本地路径</td><td>./module</td></tr><tr><td>provider</td><td>强化服务提供包本地路径</td><td>./provider</td></tr><tr><td>user_module</td><td>用户模块本地路径</td><td>~/.sandbox-module</td></tr><tr><td>server.ip</td><td>沙箱HTTP服务IP</td><td>0.0.0.0</td></tr><tr><td>server.port</td><td>沙箱HTTP服务端口</td><td>0(随机端口)</td></tr><tr><td>unsafe.enable</td><td>是否允许增强JDK自带类</td><td>false</td></tr></tbody></table><ul><li><p>user_module</p><p>用户模块本地路径是一个多值通配符表达式,如果用户模块散落在本地多个不同的路径下,可以通过配置多个路径(<code>;</code>分割),亦或者可以配置通配符表达式。</p><p>这里,我将用户文件路径配置为:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">user_module=/opt/quote/sandbox/sandbox-module</span><br></pre></td></tr></table></figure></li><li><p>unsafe.enable</p><p>控制容器是否能让模块增强JDK自带的类,默认值为<code>FALSE</code>,即不允许增强JDK自带的类。</p></li></ul><h3 id="沙箱使用"><a href="#沙箱使用" class="headerlink" title="沙箱使用"></a>沙箱使用</h3><p><strong>快速入门</strong></p><p>快速开始写一个入门模块:<a href="https://github.com/alibaba/jvm-sandbox/wiki/FIRST-MODULE" target="_blank" rel="noopener">修复一个损坏了的钟</a></p><p><strong>进阶使用</strong></p><p>沙箱分发包中自带了实用工具的例子<code>./example/sandbox-debug-module.jar</code>,代码在沙箱的<code>sandbox-debug-module</code>模块。</p><table><thead><tr><th>例子</th><th>例子说明</th></tr></thead><tbody><tr><td><a href="https://github.com/alibaba/jvm-sandbox/blob/master/sandbox-debug-module/src/main/java/com/alibaba/jvm/sandbox/module/debug/DebugWatchModule.java" target="_blank" rel="noopener">DebugWatchModule.java</a></td><td>模仿GREYS的<code>watch</code>命令</td></tr><tr><td><a href="https://github.com/alibaba/jvm-sandbox/blob/master/sandbox-debug-module/src/main/java/com/alibaba/jvm/sandbox/module/debug/DebugTraceModule.java" target="_blank" rel="noopener">DebugTraceModule.java</a></td><td>模仿GREYES的<code>trace</code>命令</td></tr><tr><td><a href="https://github.com/alibaba/jvm-sandbox/blob/master/sandbox-debug-module/src/main/java/com/alibaba/jvm/sandbox/module/debug/DebugRalphModule.java" target="_blank" rel="noopener">DebugRalphModule.java</a></td><td>无敌破坏王,故障注入(延时、熔断、并发限流、TPS限流)</td></tr><tr><td><a href="https://github.com/alibaba/jvm-sandbox/blob/master/sandbox-debug-module/src/main/java/com/alibaba/jvm/sandbox/module/debug/LogExceptionModule.java" target="_blank" rel="noopener">LogExceptionModule.java</a></td><td>记录下你的应用都发生了哪些异常 $HOME/logs/sandbox/debug/exception-monitor.log</td></tr><tr><td><a href="https://github.com/alibaba/jvm-sandbox/blob/master/sandbox-debug-module/src/main/java/com/alibaba/jvm/sandbox/module/debug/LogServletAccessModule.java" target="_blank" rel="noopener">LogServletAccessModule.java</a></td><td>记录下你的应用的HTTP服务请求 $HOME/logs/sandbox/debug/servlet-access.log</td></tr></tbody></table><p>这里可以将<code>sandbox-debug-module.jar</code>包复制到上面配置的<code>/opt/quote/sandbox/sandbox-module</code>用户模块文件路径下,重新挂载后查看挂载模块如下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -l</span><br><span class="line">debug-ralph ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com</span><br><span class="line">debug-exception-logger ACTIVE LOADED 1 5 0.0.2 luanjia@taobao.com</span><br><span class="line">sandbox-info ACTIVE LOADED 0 0 0.0.4 luanjia@taobao.com</span><br><span class="line">sandbox-module-mgr ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com</span><br><span class="line">debug-trace ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com</span><br><span class="line">debug-lifecycle ACTIVE LOADED 0 0 0.0.1 luanjia@taobao.com</span><br><span class="line">sandbox-control ACTIVE LOADED 0 0 0.0.3 luanjia@taobao.com</span><br><span class="line">debug-watch ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com</span><br><span class="line">debug-servlet-access ACTIVE LOADED 2 2 0.0.2 luanjia@taobao.com</span><br><span class="line">total=9</span><br></pre></td></tr></table></figure><ul><li><p>不增强任何类,只为体验沙箱模块生命周期</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -d <span class="string">'debug-lifecycle/control'</span></span><br></pre></td></tr></table></figure></li><li><p>验证限流 并发请求数</p><p>class 执行类、method 指定方法、c 制定速度</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -d <span class="string">'debug-ralph/c-limit?class=<CLASS>&method=<METHOD>&c=<CONCURRENT>'</span></span><br></pre></td></tr></table></figure></li></ul><ul><li><p>验证限流 TPS</p><p>class 执行类、method 指定方法、r 制定速度</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -d <span class="string">'debug-ralph/r-limit?class=<CLASS>&method=<METHOD>&r=<RATE>'</span></span><br></pre></td></tr></table></figure></li><li><p>注入方法异常</p><p>访问直接抛出异常</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -d <span class="string">'debug-ralph/wreck?class=<CLASS>&method=<METHOD>&type=<EXCEPTION-TYPE>'</span></span><br></pre></td></tr></table></figure></li><li><p>注入方法延时</p><p>class 执行类、 method 指定方法、 delay 指定速度</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -d <span class="string">'debug-ralph/delay?class=<CLASS>&method=<METHOD>&delay=<DELAY(ms)>'</span></span><br></pre></td></tr></table></figure></li><li><p>trace</p><p>模仿Greys的trace命令,控制台输出方法调用链</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -d <span class="string">'debug-trace/trace?class=<CLASS>&method=<METHOD>'</span></span><br></pre></td></tr></table></figure></li><li><p>watch</p><p>模仿Greys的watch命令,匹配方法执行 <code>at</code> 方法执行 <code>watch</code> 观察点,OGNL表达式</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./sandbox.sh -p 18233 -d <span class="string">'debug-watch/watch?class=<CLASS>&method=<METHOD>&watch={return}&at=RETURN'</span></span><br></pre></td></tr></table></figure></li><li><p>流量录制回放</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 录制</span></span><br><span class="line"><span class="comment">## 1. 修改录制的HTTP的URL ~/.sandbox-module/cfg/repeater-config.json</span></span><br><span class="line"><span class="comment">### 配置URL</span></span><br><span class="line"><span class="string">"httpEntrancePatterns"</span>: [</span><br><span class="line"><span class="string">"^/regress/.*$"</span>,</span><br><span class="line"><span class="string">"^/.*$"</span></span><br><span class="line">]</span><br><span class="line"><span class="comment">### 配置 MOCK ⽅法点 【可以确定回放是否成功】</span></span><br><span class="line"><span class="string">"javaSubInvokeBehaviors"</span>: [</span><br><span class="line">{</span><br><span class="line"><span class="string">"classPattern"</span>: <span class="string">"xx.IndexService"</span>,</span><br><span class="line"><span class="string">"includeSubClasses"</span>: <span class="literal">false</span>,</span><br><span class="line"><span class="string">"methodPatterns"</span>: [</span><br><span class="line"><span class="string">"get"</span></span><br><span class="line">]</span><br><span class="line">}</span><br><span class="line"><span class="comment">## 2. 浏览器访问 (命令⼿动制定traceId和浏览量器访问)</span></span><br><span class="line">http://localhost:8080/index?a=2&b=x</span><br><span class="line"><span class="comment">## 3. 查看⽇志 ~/logs/sandbox/repeater/repeater.log</span></span><br><span class="line">broadcast success,traceId=127000001001156272798561510001ed,resp=success</span><br><span class="line"><span class="comment">## 3.1 查看录制⽂件信息 ~/.sandbox-module/repeater-data</span></span><br><span class="line"><span class="comment"># 回放</span></span><br><span class="line"><span class="comment">## ⽅式1 http _data 是hessian编码</span></span><br><span class="line">curl http://127.0.0.1:3658/sandbox/default/module/http/repeater/repeat?_data=</span><br><span class="line">bash sandbox.sh -p 18233 -d <span class="string">'repeater/repeat?_data='</span></span><br><span class="line"><span class="comment">## ⽅式2</span></span><br><span class="line">curl -s <span class="string">'http://localhost:8080/index?a=2&b=x'</span> -H <span class="string">'Repeat-TraceId-X:127000001001156274301881610001ed'</span></span><br><span class="line">curl -s <span class="string">'http://localhost:8080/index?a=2&b=x&Repeat-TraceId-X=127000001001156274301881610001ed'</span></span><br></pre></td></tr></table></figure><ul><li><p>基于JVM-Sandbox的录制/回放通用解决方案</p><p><a href="https://github.com/alibaba/jvm-sandbox-repeater" target="_blank" rel="noopener">jvm-sandbox-repeater</a>是JVM-Sandbox生态体系下的重要模块,它具备了JVM-Sandbox的所有特点,插件式设计便于快速适配各种中间件,封装请求录制/回放基础协议,也提供了通用可扩展的各种丰富API。</p></li></ul></li><li><p>记录异常日志</p></li><li><p>监听TCP数据包</p></li></ul><p><strong>编写你自己的模块</strong></p><p>我们可以根据需要编写自己的模块,参考资料如下:</p><p>沙箱实践介绍:<a href="https://github.com/alibaba/jvm-sandbox/wiki/EVENT-INTRODUCE" target="_blank" rel="noopener">https://github.com/alibaba/jvm-sandbox/wiki/EVENT-INTRODUCE</a></p><p>模块工程介绍:<a href="https://github.com/alibaba/jvm-sandbox/wiki/MODULE-LIFECYCLE" target="_blank" rel="noopener">https://github.com/alibaba/jvm-sandbox/wiki/MODULE-LIFECYCLE</a></p>]]></content>
<summary type="html">
<p><img src="https://github.com/alibaba/jvm-sandbox/wiki/img/BANNER.png" alt="sandbox"></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>随着软件部署规模的扩大,系统功能的细化,系统间耦合度和链路复杂度不断加强。要继续保持现有规模系统的稳定性,需要实现并完善监控体系、故障定位分析、流量录制回放、强弱依赖检测、故障演练等支撑工具平台。</p>
<p>出于对服务器规模和业务稳定性的考量,这些配套工具平台要具备无侵入、实时生效、动态可插拔的特点。JVM-SANDBOX就是这样一种基于JVM的非侵入式运行期AOP解决方案,由阿里测试开发专家<strong>鸾伽</strong>(花名)开发,现已在Github上开源。</p>
<p>本文根据 GitHub 收录的文档简单整理,方便后面查看使用。</p>
<p>Github地址:<a href="https://github.com/alibaba/jvm-sandbox" target="_blank" rel="noopener">https://github.com/alibaba/jvm-sandbox</a></p>
</summary>
<category term="Open Source" scheme="http://yoursite.com/categories/Open-Source/"/>
<category term="开发工具" scheme="http://yoursite.com/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
<category term="alibaba" scheme="http://yoursite.com/tags/alibaba/"/>
<category term="JVM" scheme="http://yoursite.com/tags/JVM/"/>
</entry>
<entry>
<title>介绍一款混沌实验注入工具—ChaosBlade</title>
<link href="http://yoursite.com/2019/06/24/ChaosBlade%E2%80%94%E6%B7%B7%E6%B2%8C%E5%AE%9E%E9%AA%8C%E6%B3%A8%E5%85%A5%E5%B7%A5%E5%85%B7/"/>
<id>http://yoursite.com/2019/06/24/ChaosBlade—混沌实验注入工具/</id>
<published>2019-06-24T02:59:47.485Z</published>
<updated>2019-08-13T07:04:17.232Z</updated>
<content type="html"><![CDATA[<p><img src="https://camo.githubusercontent.com/1db3a1edc0b49db279f18eabc97659660d982701/68747470733a2f2f6368616f73626c6164652e6f73732d636e2d68616e677a686f752e616c6979756e63732e636f6d2f646f632f696d6167652f6368616f73626c6164652d6c6f676f2e706e67" alt="chaosblade.png"></p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>混沌工程是在分布式系统上进行实验的学科,旨在提升系统容错性,建立系统抵御生产环境中发生不可预知问题的信心。Chaosblade(混沌之刃) 是阿里巴巴内部 MonkeyKing 对外开源的项目,其遵循混沌工程(Chaos Engineering)实验模型,提供丰富故障场景实现,特点是操作简洁、无侵入、扩展性强。</p><p>本文根据 GitHub 收录的文档简单整理,方便后面查看使用。</p><p>GitHub 地址:<a href="https://github.com/chaosblade-io" target="_blank" rel="noopener">https://github.com/chaosblade-io</a></p><a id="more"></a><h2 id="混沌实验模型"><a href="#混沌实验模型" class="headerlink" title="混沌实验模型"></a>混沌实验模型</h2><p>混沌实验举例:一个运行在 10.0.0.1 机器上的应用,调用<a href="mailto:com.example.HelloService@1.0.0" target="_blank" rel="noopener">com.example.HelloService@1.0.0</a> Dubbo 服务延迟 3s。</p><p>根据故障场景,可以统一成如下的混沌实验模型:</p><p><img src="https://user-images.githubusercontent.com/3992234/55319674-fd73b100-54a7-11e9-8a8c-15d0fb8f2758.png" alt="chaos_model"></p><p>使用 chaosblade 完成上例中的混沌实验注入命令如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">blade create dubbo delay --time 3000 --consumer --service com.example.HelloService --version 1.0.0</span><br></pre></td></tr></table></figure><p>结合混沌实验模型对 chaosblade 命令进行分析:</p><ul><li>Target:实验靶点,指实验发生的组件,本例中为 <code>dubbo</code>,即对 Dubbo 实施实验。</li><li>Scope:实验实施的范围,指具体触发实验的机器或者集群等。由于 chaosblade 是在单机执行的工具,所以 scope 默认为本机,不显式声明。</li><li>Matcher:实验规则匹配器,根据所配置的 Target,定义相关的实验匹配规则,可以配置多个。本例中为 <code>--consumer</code>、<code>--service</code>、<code>--version</code>,根据对应的匹配条件进行匹配。</li><li>Action:指实验模拟的具体场景,Target 不同,实施的场景也不一样。本例中为 <code>delay</code>、 <code>--time</code>,即执行延迟演练场景,并设置延迟时间。</li></ul><h2 id="工具介绍"><a href="#工具介绍" class="headerlink" title="工具介绍"></a>工具介绍</h2><p>Chaosblade 的 CLI 工具是 blade,下载最新的 <a href="https://github.com/chaosblade-io/chaosblade/releases" target="_blank" rel="noopener">release</a> 包解压后即可使用,无需编译。截至本文更新,最新的 Chaosblade 版本为 v0.1.0 。</p><p>执行 <code>./blade -h</code> 可以查看支持的命令选项,如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">[quote@chaosblade-0.1.0]$ ./blade -h</span><br><span class="line">An easy to use and powerful chaos engineering experiment toolkit</span><br><span class="line"></span><br><span class="line">Usage:</span><br><span class="line"> blade [<span class="built_in">command</span>]</span><br><span class="line"></span><br><span class="line">Available Commands:</span><br><span class="line"> create Create a chaos engineering experiment</span><br><span class="line"> destroy Destroy a chaos experiment</span><br><span class="line"> <span class="built_in">help</span> Help about any <span class="built_in">command</span></span><br><span class="line"> prepare Prepare to experiment</span><br><span class="line"> query Query the parameter values required <span class="keyword">for</span> chaos experiments</span><br><span class="line"> revoke Undo chaos engineering experiment preparation</span><br><span class="line"> status Query preparation stage or experiment status</span><br><span class="line"> version Print version info</span><br><span class="line"></span><br><span class="line">Flags:</span><br><span class="line"> -d, --debug Set client to DEBUG mode</span><br><span class="line"> -h, --<span class="built_in">help</span> <span class="built_in">help</span> <span class="keyword">for</span> blade</span><br><span class="line"></span><br><span class="line">Use <span class="string">"blade [command] --help"</span> <span class="keyword">for</span> more information about a <span class="built_in">command</span>.</span><br></pre></td></tr></table></figure><h3 id="介绍:"><a href="#介绍:" class="headerlink" title="介绍:"></a>介绍:</h3><ul><li>create:创建一个混沌演练实验,执行故障注入。如果注入成功,则返回实验的 uid,用于状态查询和销毁此实验使用。命令 <code>blade create [TARGET] [ACTION] [FLAGS]</code></li><li>destroy:销毁之前的混沌实验。命令 <code>blade destroy UID</code></li><li>prepare:混沌实验前的准备,比如演练 Java 应用,则需要挂载 java agent。如果挂载成功,返回挂载的 uid,用于状态查询或者撤销挂载使用。命令 <code>./blade prepare jvm -h</code> </li><li>query:查询混沌实验所需的参数值。</li><li>revoke:撤销之前混沌实验准备,比如卸载 java agent。命令 <code>blade revoke UID</code></li><li>status:查询混沌实验和混沌实验环境准备记录。命令 <code>blade status UID</code> 或者 <code>blade status --type create</code></li><li>version:打印版本信息。</li></ul><p>以上命令都支持首字母简写,且可通过 <code>--help</code> 查看命令帮助,如 <code>./blade c -h</code> 。查看<a href="https://github.com/chaosblade-io/chaosblade/wiki/%E6%96%B0%E6%89%8B%E6%8C%87%E5%8D%97" target="_blank" rel="noopener">新手指南</a>,快速上手使用。</p><h3 id="使用举例:"><a href="#使用举例:" class="headerlink" title="使用举例:"></a>使用举例:</h3><ul><li>blade create cpu fullload</li><li>blade destroy 7c1f7afc281482c8</li><li> blade status 7c1f7afc281482c8</li><li>blade prepare jvm –process dubbo.consumer</li><li> blade status –type prepare</li><li>blade create dubbo delay –time 3000 –service com.alibaba.demo.HelloService –methodname hello –consumer –process dubbo.consumer</li><li> blade create jvm throwCustomException –exception java.lang.Exception –classname com.example.controller.DubboController –methodname hello</li><li> blade revoke e669d57f079a00cc</li><li> blade query disk mount-point</li></ul><h3 id="混沌实验注入"><a href="#混沌实验注入" class="headerlink" title="混沌实验注入"></a>混沌实验注入</h3><p>目前支持的演练场景有操作系统类的 CPU、磁盘、进程、网络,Java 应用类的 Dubbo、MySQL、Servlet 和自定义类方法延迟或抛异常等以及杀容器、杀 Pod等,具体可执行 <code>./blade c -h</code> 查看:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">[quote@chaosblade-0.1.0]$ ./blade c -h</span><br><span class="line">Create a chaos engineering experiment</span><br><span class="line"></span><br><span class="line">Usage:</span><br><span class="line"> blade create [<span class="built_in">command</span>]</span><br><span class="line"></span><br><span class="line">Aliases:</span><br><span class="line"> create, c</span><br><span class="line"></span><br><span class="line">Examples:</span><br><span class="line">create dubbo delay --time 3000 --offset 100 --service com.example.Service --consumer</span><br><span class="line"></span><br><span class="line">Available Commands:</span><br><span class="line"> cpu Cpu experiment</span><br><span class="line"> disk Disk experiment</span><br><span class="line"> docker Execute a docker experiment</span><br><span class="line"> druid Druid experiment</span><br><span class="line"> dubbo dubbo experiment</span><br><span class="line"> http http experiment</span><br><span class="line"> jvm method</span><br><span class="line"> k8s Kubernetes experiment</span><br><span class="line"> mysql mysql experiment</span><br><span class="line"> network Network experiment</span><br><span class="line"> process Process experiment</span><br><span class="line"> script Script chaos experiment</span><br><span class="line"> servlet java servlet experiment</span><br><span class="line"></span><br><span class="line">Flags:</span><br><span class="line"> -h, --<span class="built_in">help</span> <span class="built_in">help</span> <span class="keyword">for</span> create</span><br><span class="line"></span><br><span class="line">Global Flags:</span><br><span class="line"> -d, --debug Set client to DEBUG mode</span><br><span class="line"></span><br><span class="line">Use <span class="string">"blade create [command] --help"</span> <span class="keyword">for</span> more information about a <span class="built_in">command</span>.</span><br></pre></td></tr></table></figure><p>动态脚本实现 Java 实验场景:</p><p>Chaosblade 从 <a href="https://github.com/chaosblade-io/chaosblade/releases/tag/0.1.0" target="_blank" rel="noopener">0.1.0</a> 版本开始支持编写脚本实现复杂的 Java 实验场景,脚本支持 Java 和 Groovy 实现。动态脚本场景支持指定脚本文件或者脚本内容(base64 编码后的内容)来调用。具体的使用帮助命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">[quote@chaosblade-0.1.0]$ ./blade create jvm script -h</span><br><span class="line">Dynamically execute custom scripts</span><br><span class="line"></span><br><span class="line">Usage:</span><br><span class="line"> blade create jvm script</span><br><span class="line"></span><br><span class="line">Flags:</span><br><span class="line"> --classname string The class name with package (required)</span><br><span class="line"> --effect-count string The count of chaos experiment <span class="keyword">in</span> effect</span><br><span class="line"> --effect-percent string The percent of chaos experiment <span class="keyword">in</span> effect</span><br><span class="line"> -h, --<span class="built_in">help</span> <span class="built_in">help</span> <span class="keyword">for</span> script</span><br><span class="line"> --methodname string The method name (required)</span><br><span class="line"> --process string Application process name</span><br><span class="line"> --script-content string The script contents</span><br><span class="line"> --script-file string The Script file full path</span><br><span class="line"> --script-name string The script name <span class="keyword">for</span> label, unnecessary</span><br><span class="line"> --script-type string The script file <span class="built_in">type</span>, java or groovy, default value is java</span><br><span class="line"> --timeout string <span class="built_in">set</span> timeout <span class="keyword">for</span> experiment</span><br><span class="line"></span><br><span class="line">Global Flags:</span><br><span class="line"> -d, --debug Set client to DEBUG mode</span><br></pre></td></tr></table></figure><p>主要参数介绍:</p><ul><li><p><code>classname</code>:必要参数,指定触发实验场景的类,注意,必须是实现类,不能是接口类。</p></li><li><p><code>methodname</code>:必要参数,指定触发实验场景的方法,静态、私有、公有、父类方法都支持。</p></li><li><p><code>script-type</code>:脚本类型,默认为 java。</p></li><li><p><code>script-file</code>:脚本文件,文件绝对路径。</p></li><li><p><code>script-content</code>:脚本内容,是 Base64 编码后的内容。</p></li></ul><ol><li>使用 <code>script-content</code> 指定演练脚本内容:</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./blade c jvm script --classname com.example.controller.DubboController --methodname call --script-content [Base64_content] --script-name exception</span><br></pre></td></tr></table></figure><ol start="2"><li>使用 <code>script-file</code> 参数指定文件演练:</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./blade c jvm script --classname com.example.controller.DubboController --methodname call --script-file [FILE_PATH] --script-name exception</span><br></pre></td></tr></table></figure><p>Java 脚本实现规范:</p><ul><li>必须创建一个类,对类名和包名没有要求,其中所依赖的类,必须是目标应用所具备的类。</li><li>必须添加 <code>public Object run(Map<String, Object> params)</code> 方法,其中 params 对象中包含目标方法参数,key 是参数索引下标,从 0 开始,比如目标方法是 <code>public String call(Object obj1, Object obj2){}</code>,则 <code>params.get("0")</code>则返回的是 <code>obj1</code> 对象,可以执行<code>params.put("0", <NEW OBJECT>)</code> 来修改目标方法参数(目标方法及 <code>--classname</code> 和 <code>--methodname</code> 所指定的类方法)。</li><li>上述方法返回的对象如果不为空,则会根据脚本中返回的对象来修改目标方法返回值,注意类型必须和目标方法返回值一致。如果上述方法返回 null,则不会修改目标方法返回值。</li></ul><h2 id="分布式实践"><a href="#分布式实践" class="headerlink" title="分布式实践"></a>分布式实践</h2><p>文档下载学习: <a href="https://github.com/chaosblade-io/awesome-chaosblade/tree/master/slides" target="_blank" rel="noopener">分布式服务架构下混沌工程实践</a></p>]]></content>
<summary type="html">
<p><img src="https://camo.githubusercontent.com/1db3a1edc0b49db279f18eabc97659660d982701/68747470733a2f2f6368616f73626c6164652e6f73732d636e2d68616e677a686f752e616c6979756e63732e636f6d2f646f632f696d6167652f6368616f73626c6164652d6c6f676f2e706e67" alt="chaosblade.png"></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>混沌工程是在分布式系统上进行实验的学科,旨在提升系统容错性,建立系统抵御生产环境中发生不可预知问题的信心。Chaosblade(混沌之刃) 是阿里巴巴内部 MonkeyKing 对外开源的项目,其遵循混沌工程(Chaos Engineering)实验模型,提供丰富故障场景实现,特点是操作简洁、无侵入、扩展性强。</p>
<p>本文根据 GitHub 收录的文档简单整理,方便后面查看使用。</p>
<p>GitHub 地址:<a href="https://github.com/chaosblade-io" target="_blank" rel="noopener">https://github.com/chaosblade-io</a></p>
</summary>
<category term="Open Source" scheme="http://yoursite.com/categories/Open-Source/"/>
<category term="开发工具" scheme="http://yoursite.com/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
<category term="alibaba" scheme="http://yoursite.com/tags/alibaba/"/>
</entry>
<entry>
<title>Shenandoah GC官方文档(译)</title>
<link href="http://yoursite.com/2019/06/21/Shenandoah%20GC%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%EF%BC%88%E8%AF%91%EF%BC%89/"/>
<id>http://yoursite.com/2019/06/21/Shenandoah GC官方文档(译)/</id>
<published>2019-06-21T05:53:19.964Z</published>
<updated>2019-09-06T01:50:38.231Z</updated>
<content type="html"><![CDATA[<p><img src="https://i.loli.net/2019/03/28/5c9c829850beb.jpg" alt="OpenJDK.jpg"></p><h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>Shenandoah是低停顿时间的垃圾收集器,通过与正在运行的Java程序并发地执行更多垃圾收集工作来缩短GC停顿时间。Shenandoah并发地完成大部分GC工作,包括并发整理,这意味着它的停顿时间不再与堆的大小成正比。收集200GB堆或2GB堆的垃圾应具有类似的低停顿行为。</p><p>本文对Shenandoah GC官方文档进行简单翻译,方便对该垃圾收集器的使用,原文参见: <a href="https://wiki.openjdk.java.net/display/shenandoah/Main" target="_blank" rel="noopener">Shenandoah wiki page</a>。</p><a id="more"></a><p>[TOC]</p><h2 id="综述"><a href="#综述" class="headerlink" title="综述"></a>综述</h2><p>Shenandoah是区域化的收集器,它将堆保持为region集合。</p><p>常规的Shenandoah GC周期如下所示:</p><p><img src="https://cr.openjdk.java.net/~shade/shenandoah/shenandoah-gc-cycle.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">GC(3) Pause Init Mark 0.771ms</span><br><span class="line">GC(3) Concurrent marking 76480M->77212M(102400M) 633.213ms</span><br><span class="line">GC(3) Pause Final Mark 1.821ms</span><br><span class="line">GC(3) Concurrent cleanup 77224M->66592M(102400M) 3.112ms</span><br><span class="line">GC(3) Concurrent evacuation 66592M->75640M(102400M) 405.312ms</span><br><span class="line">GC(3) Pause Init Update Refs 0.084ms</span><br><span class="line">GC(3) Concurrent update references 75700M->76424M(102400M) 354.341ms</span><br><span class="line">GC(3) Pause Final Update Refs 0.409ms</span><br><span class="line">GC(3) Concurrent cleanup 76244M->56620M(102400M) 12.242ms</span><br></pre></td></tr></table></figure><p>上面的阶段大致如下:</p><ol><li><strong>Init Mark(初始标记)</strong> :启动并发标记。为并发标记阶段准备堆和应用程序线程,然后扫描根集。这是周期中第一次停顿,主要是对根集的扫描消耗时间。因此,停顿持续时间取决于根集大小。</li><li><strong>Concurrent Marking(并发标记)</strong> :遍历堆,并追踪可达对象。此阶段与应用程序一起运行,其持续时间取决于堆中存活对象的数量和对象图的结构。由于应用程序可以在此阶段自由分配新数据,因此在并发标记期间堆占用率会上升。</li><li><strong>Final Mark(最终标记)</strong> :通过排空所有挂起的标记/更新队列并重新扫描根集来完成并发标记。它还通过确定要疏散的region(collection set),预先疏散一些根来初始化疏散,并且通常为下一阶段准备运行时间。这项工作的一部分可以在<strong>Concurrent precleaning</strong> (并发预清理)阶段并发完成。这是周期中的第二次停顿,最主要的时间消耗方是排空队列和扫描根集的过程。</li><li><strong>Concurrent Cleanup(并发清理)</strong> :回收当前垃圾region——即在并发标记之后检测到的没有存活对象的region。</li><li><strong>Concurrent Evacuation(并发疏散)</strong> :将对象从collection set复制到其他region。<em>这是与其他OpenJDK GC的主要区别。</em>此阶段再次与应用程序一起运行,因此应用程序可以自由分配。其持续时间取决于该GC周期选择的collection set的大小。</li><li><strong>Init Update Refs(初始更新引用)</strong> :初始化更新引用阶段。除了确保所有GC和应用程序线程都已完成疏散,然后为下一阶段准备GC之外,它几乎没有任何作用。这是周期中的第三次停顿,也是最短的停顿。</li><li><strong>Concurrent Update References(并发更新引用)</strong> :遍历堆,并更新对并发疏散极端移动对象的引用。 <em>这是与其他OpenJDK GC的主要区别。</em>它的持续时间取决于堆中的对象数,但不取决于对象图结构,因为它会线性扫描堆。此阶段与应用程序同时运行。</li><li><strong>Final Update Refs(最终更新引用)</strong> :通过重新更新现有根集来完成更新引用阶段。它还从collection set中回收region,因为现在堆没有对它们的(陈旧)对象的引用。这是周期中的最后一次停顿,其持续时间取决于根集的大小。</li><li><strong>Concurrent Cleanup(并发清理)</strong>:回收collection set region,这些region当前没有引用。</li></ol><h2 id="性能指南和诊断"><a href="#性能指南和诊断" class="headerlink" title="性能指南和诊断"></a>性能指南和诊断</h2><h3 id="总体思路"><a href="#总体思路" class="headerlink" title="总体思路"></a>总体思路</h3><p><strong>Heap sizes(堆大小):</strong> 与几乎所有其他GC的性能一样,Shenandoah性能取决于堆大小。当有足够的堆空间能够满足并发阶段运行(参见下面的”<strong>Failure Modes</strong>(失败模式)”部分)时的分配时,它应该能够更好地运行。并发阶段的时间与live data set (LDS)的大小——live data占用的空间——相关。因此,合理的堆大小取决于LDS和工作负载中的分配压力:对于给定的分配速率,较大的LDS需要同比例的较大的堆大小;对于给定的LDS,较大的分配率需要较大的堆大小。对于那些具有很小live data set和适度分配压力的工作负载,1~2GB的堆就表现得不错了。我们通常在各种工作负载上测试4~128GB的堆,其中LDS大小最高达到80%。不要害羞地尝试不同的堆大小来适配你的工作负载。</p><p><strong>Pauses(停顿):</strong> Shenandoah的停顿行为主要由根集操作主导:扫描和更新根。根集包括:局部变量,嵌入在生成的代码中的引用,interned Strings,类加载器的引用(例如,static final引用),JNI引用,JVMTI引用。拥有更大的根集通常意味着使用Shenandoah会有更长的停顿,除非具体的JDK版本具有同时执行部分工作的能力,并且Shenandoah能够使用它。二阶效应是:a)弱引用处理(在<strong>Final Mark</strong>(最终标记)阶段中发生),但仅适用于那些需要处理的引用; b)类的卸载和其他JDK清理(也会在<strong>Final Mark</strong>(最终标记)阶段时发生)。通过配置控制处理频率(包括完全禁用它)的其他选项和/或修改应用程序以更好地发挥作用,可以减轻这些二阶效应。</p><p><strong>Throughput(吞吐量):</strong> 由于Shenandoah是并发GC,它在收集周期中使用屏障来维护不变量。这些屏障可能会导致可测量的吞吐量损失。请参阅下面的诊断部分,了解如何剖析那里发生的事情。 一些用户报告说,通过自然地将并发GC工作卸载到备用和其他空闲核心,使由于屏障导致的吞吐量损失得到了弥补;换句话说,在某些情况下,它会提高应用程序+JVM利用率以获得更高的应用程序吞吐量。</p><p>在大多数情况下,停顿时间在0~10ms之内,吞吐量损失在0~15%之内。实际性能数据在很大程度上取决于实际应用,负载文件等。对于没有大量根,弱引用和/或class churn的应用程序,停顿可以在亚毫秒范围内。对于不会使堆变异很多,或者当前编译器对其进行了很好的优化的应用程序,屏障开销可能接近于零。本节的其余部分描述了使用Shenandoah测试和诊断性能行为的方法。如果您怀疑具体用例有什么问题,请告知开发人员。有可能,这是一个可管理的issue或直接的bug。</p><h3 id="基本配置"><a href="#基本配置" class="headerlink" title="基本配置"></a>基本配置</h3><p>基本配置和命令行选项:</p><ul><li><strong>-Xlog:gc</strong> (since JDK 9) or <strong>-verbose:gc</strong> (up to JDK 8):打印单独的GC计时</li><li><strong>-Xlog:gc+ergo</strong> (since JDK 9) or <strong>-XX:+PrintGCDetails</strong> (up to JDK 8):打印heuristics 决策,如果有异常值的话,打印异常值。</li><li><strong>-Xlog:gc+stats</strong> (since JDK 9) or <strong>-verbose:gc</strong> (up to JDK 8) :在运行结束时,在Shenandoah内部计时上打印汇总表。</li></ul><p>在启用日志记录的情况下运行几乎总是一个好主意。该汇总表传达了有关GC性能的重要信息,我们几乎不可避免地要求在性能错误报告中提供一个。Heuristics 日志对于确定GC异常值非常有用。</p><p>其他推荐的JVM选项包括: </p><ul><li><strong>-XX:+AlwaysPreTouch</strong>:将堆页面提交到内存中以减少latency hiccups。</li><li><strong>-Xmx == -Xms</strong> :使堆不可调整大小可以减少堆管理时的hiccups。对于Shenandoah,-Xms与其他收集器的相关性较低,因为它只将其视为“初始”堆大小(这可能在将来发生变化)。但是,与AlwaysPreTouch相结合,-Xmx == -Xms会在启动时提交所有内存,这可以避免在最终使用内存后出现hiccups。</li><li><strong>-XX:+UseTransparentHugePages</strong>: 这大大提高了大堆的性能。建议在Linux上将<em>/sys/kernel/mm/transparent_hugepage/enabled</em>和<em>/sys/kernel/mm/transparent_hugepage/defrag</em>设置为“<em>madvise</em>”。使用AlwaysPreTouch运行时,init/shutdown会更快,因为它将使用更大的页面进行预处理。它还将在启动时预先支付碎片整理成本。</li><li><strong>-XX:+UseNUMA</strong>:虽然Shenandoah尚未明确支持NUMA,但最好启用此功能以在多插槽主机上启用NUMA交叉存取。与AlwaysPreTouch相结合,它提供了比默认的开箱即用配置更好的性能。</li><li><strong>-XX:-UseBiasedLocking</strong>:在无竞争(偏向)锁定吞吐量与JVM根据需要启用和禁用它们的安全点之间存在权衡。对于面向延迟的工作负载,可以关闭偏向锁定。</li><li><strong>-XX:+DisableExplicitGC</strong>:从用户代码调用System.gc()强制Shenandoah执行STW Full GC,这对GC停顿不利;禁用此选项可以防止库执行此操作。还有一个替代 <strong>-XX:+ExplicitGCInvokesConcurrent</strong>,可以在System.gc()上强制并发循环而不是Full GC,建议您在System.gc()调用确实必要的情况下使用它。</li></ul><h3 id="Heuristics启发式方法"><a href="#Heuristics启发式方法" class="headerlink" title="Heuristics启发式方法"></a>Heuristics启发式方法</h3><p>Heuristics判断Shenandoah何时启动GC周期,以及它认为的该疏散的region。可以使用<strong>-XX:ShenandoahGCHeuristics = <name></name></strong>选择heuristics。一些heuristics方法接受配置参数,这可能有助于更好地为您的用例定制GC操作。可用的heuristics方法包括:</p><ol><li><p><strong>adaptive</strong> (default) 此启发式方法通过观察以前的GC周期,尝试启动下一个GC周期,以便在堆耗尽之前完成操作。</p><p>a. <strong>-XX:ShenandoahInitFreeThreshold=#</strong>: 触发”learning”集合的初始阈值。 </p><p>b. <strong>-XX:ShenandoahMinFreeThreshold=#</strong> :heuristics无条件触发GC的可用空间阈值。</p><p>c. <strong>-XX:ShenandoahAllocSpikeFactor=#</strong>:要预留多少堆来承担分配峰值。</p><p>d. <strong>-XX:ShenandoahGarbageThreshold=#</strong>:设置region在标记为可收集之前需要包含的垃圾百分比。</p></li><li><p><strong>static</strong> 此启发式决定基于堆占用和分配压力启动GC周期。该启发式配置选项如下:</p><p>a. <strong>-XX:ShenandoahFreeThreshold=#</strong>:设置启动GC周期时的空闲堆的百分比</p><p>b. <strong>-XX:ShenandoahAllocationThreshold=#</strong>:在新GC周期开始之前,设置自上一个GC周期以来分配的内存百分比。</p><p>c. <strong>-XX:ShenandoahGarbageThreshold=#</strong>:设置region在标记为collection之前需要包含的垃圾百分比。</p></li><li><p><strong>compact</strong> 该启发式连续运行GC周期,只要分配发生,就在上一个周期结束后立即开始下一个周期。这种启发式方法通常会产生吞吐量开销,但能提供最快速的空间回收。配置选项:</p><p>a. <strong>-XX:ConcGCThreads=#</strong>:减少并发GC线程的数量,以便为应用程序运行腾出更多空间。</p><p>b. <strong>-XX:ShenandoahAllocationThreshold=#</strong>:在启动另一个周期之前,设置自上一个GC周期以来分配的内存百分比。</p></li><li><p><strong>passive</strong> 这种启发式方法告诉GC完全被动。一旦可用内存耗尽,将触发Full Stop-The-World GC。这种启发式方法用于功能测试,但有时它可用于将GC屏障的性能异常等分(见下文),或计算应用程序中的实际live data size。</p></li><li><p><strong>aggressive</strong> 这种启发式方法告诉GC完全活跃。它将在前一个GC周期结束后立即启动新的GC周期(如“compact”),并且疏散所有存活对象。这种启发式方法对收集器本身的功能测试很有用。它会导致严重的性能损失。</p></li></ol><p>在某些周期中,Update References阶段与Concurrent Marking阶段合并,通过启发式方法裁决。可以使用<strong>-XX:ShenandoahUpdateRefsEarly=[on|off]</strong>强制启用/禁用Update References 。</p><h3 id="失败模式"><a href="#失败模式" class="headerlink" title="失败模式"></a>失败模式</h3><p>像Shenandoah这样的并发GC隐含地依赖于收集速度比应用程序分配速度更快。如果分配压力很高,并且在GC运行时没有足够的空间来分配,则最终会发生 <em>Allocation Failure</em> 。Shenandoah有一个优雅的降级阶梯,有助于在这些情况下幸存下来。阶梯包括:</p><ol><li><em>Pacing</em> (<strong>-XX:+ShenandoahPacing</strong>, enabled by default).当GC运行时,它知道需要完成多少GC工作,以及有多少可用空间可供应用程序使用。当GC进度不够快时,pacer会尝试停止分配线程。在正常情况下,GC收集的速度比应用程序分配的速度快,pacer自然不会停止。注意,pacing会引入通常分析工具中不可见的本地per-thread延迟。这就是为什么停止不是无限期的,它们受<strong>-XX:ShenandoahPacingMaxDelay=#ms</strong>的限制。在最大延时到期后,无论如何都会发生分配。大多数时候,轻度分配的峰值会被pacer吸收。当分配压力非常高时,pacer将无法应对,并且降级将进入下一步。<br><em>Usual latency induced: <10 ms</em></li><li><em>Degenerated GC</em> (<strong>-XX:+ShenandoahDegeneratedGC</strong>, enabled by default). 如果应用程序遇到分配失败,Shenandoah将陷入 stop-the-world 停顿,停止整个应用程序,并在停顿下继续GC周期。Degenerated GC 在stop-the-world 的情况下继续正在进行的“并发”周期。在许多情况下,分配失败发生在已完成大量GC工作、只剩一小部分GC工作等待完成之后,这就是STW停顿通常不大的原因。它将在GC日志、所有常见的监视和心跳线程中打印 GC pause:实际上,引发STW停顿的原因之一是使并发模式的失败可以清楚地被观察到。如果GC周期开始得太晚,或者发生了非常显着的分配峰值,将导致Degenerated GC。退化周期可能比并发周期更快,因为它不会与应用程序竞争资源,而且它使用<strong>-XX:ParallelGCThreads</strong>,而不是<strong>-XX:ConcCGThreads</strong>调整线程池大小。<br><em>Usual latency induced: <100 ms, but can be more, depending on the degeneration point</em></li><li><em>Full GC</em>.如果没有任何帮助,例如当Degenerated GC没有释放足够的内存时,将产生Full GC,并最大化地对堆进行整理。某些场景,比如异常碎片化的堆,以及实现性能bug和overlook,只能由Full GC修复。如果至少有一些内存可用,这个最后阶段的GC能够保证应用程序不会因OOM而失败。<br><em>Usual latency induced: >100 ms, but can be more, especially on a very occupied heap</em></li></ol><p>除了可以打印单个Degenerated GC和Full GC事件的常用GC日志之外,<strong>-Xlog:gc + stats</strong>将在运行结束时显示如下内容:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">Under allocation pressure, concurrent cycles may cancel, and either continue cycle</span><br><span class="line">under stop-the-world pause or result in stop-the-world Full GC. Increase heap size,</span><br><span class="line">tune GC heuristics, set more aggressive pacing delay, or lower allocation rate</span><br><span class="line">to avoid Degenerated and Full GC cycles.</span><br><span class="line"> </span><br><span class="line"> 4912 successful concurrent GCs</span><br><span class="line"> 0 invoked explicitly</span><br><span class="line"> </span><br><span class="line"> 3 Degenerated GCs</span><br><span class="line"> 3 caused by allocation failure</span><br><span class="line"> 3 happened at Update Refs</span><br><span class="line"> 0 upgraded to Full GC</span><br><span class="line"> </span><br><span class="line"> 0 Full GCs</span><br><span class="line"> 0 invoked explicitly</span><br><span class="line"> 0 caused by allocation failure</span><br><span class="line"> 0 upgraded from Degenerated GC</span><br><span class="line"> </span><br><span class="line">ALLOCATION PACING:</span><br><span class="line"> </span><br><span class="line">Max pacing delay is set for 10 ms.</span><br><span class="line"> </span><br><span class="line">Higher delay would prevent application outpacing the GC, but it will hide the GC latencies</span><br><span class="line">from the STW pause times. Pacing affects the individual threads, and so it would also be</span><br><span class="line">invisible to the usual profiling tools, but would add up to end-to-end application latency.</span><br><span class="line">Raise max pacing delay with care.</span><br><span class="line"> </span><br><span class="line">Actual pacing delays histogram:</span><br><span class="line"> From - To Count</span><br><span class="line"> 1 ms - 2 ms: 87</span><br><span class="line"> 2 ms - 4 ms: 142</span><br><span class="line"> 4 ms - 8 ms: 297</span><br><span class="line"> 8 ms - 16 ms: 1733</span><br><span class="line"> 16 ms - 32 ms: 21</span><br><span class="line"> 32 ms - 64 ms: 1</span><br></pre></td></tr></table></figure><p>从这一点来看,如果应用程序遇到以下任何一个降级步骤,可以尝试以下操作:</p><ul><li>为应用程序提供更多堆。满足在GC运行时更多的分配要求。</li><li>减少堆中的live data量。使GC周期更快地运行,并更好地应对分配。</li><li>削减分配压力。例如,减少分配线程的数量,或修复应用程序中的主要allocation hogs。</li><li>调整启发式算法,尽快启动GC周期。如果GC日志已经说GC正在运行连续周期,那么该项操作可能没什么用。</li><li>加快pacing延迟。这将导致更多的线程分配停滞,而不是升级到Degenerated和Full GC——注意,这仍然会给分配线程带来延迟!</li></ul><h3 id="性能分析"><a href="#性能分析" class="headerlink" title="性能分析"></a>性能分析</h3><p>性能分析方法:</p><ol><li><p>一些奇怪的性能行为——如分配失败GC或耗时的最终标记——可以通过heuristics issues解释,你可以使用<strong>-Xlog:gc + ergo</strong>配置。如果你有长时间运行的工作负载,在<a href="http://icedtea.classpath.org/hg/shenandoah-visualizer/" target="_blank" rel="noopener">Shenandoah Visualizer</a>下运行可以让你理解高级GC行为,有时类似奇怪的行为在SV下很明显。</p></li><li><p>一些性能差异可以用Shenandoah下更大的分配压力解释,因为它包含每个对象的转发指针。查看分配率以确定其是否有问题,并可以通过实验进一步证实(例如,增强对象可以减少与其他收集器之间的性能差异)。在某些情况下,较大的内存占用意味着退出CPU cache,寻找L1/L2/LLC遗漏的差异。</p></li><li><p>许多吞吐量差异可以用GC屏障开销来解释。当使用<strong>-XX:ShenandoahGCHeuristics=passive</strong>运行时,<em>这是启发式方法独有的</em>,正确性不需要障碍,因此启发式方法禁用它们。然后可以有选择地启用屏障,并查看哪些屏障正在影响吞吐量性能。“passive”启发式禁用的障碍列表列在GC输出中,如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ java -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=passive -Xlog:gc</span><br><span class="line">[0.002s][info][gc] Passive heuristics implies -XX:-ShenandoahSATBBarrier by default</span><br><span class="line">[0.002s][info][gc] Passive heuristics implies -XX:-ShenandoahKeepAliveBarrier by default</span><br><span class="line">[0.002s][info][gc] Passive heuristics implies -XX:-ShenandoahWriteBarrier by default</span><br><span class="line">[0.002s][info][gc] Passive heuristics implies -XX:-ShenandoahReadBarrier by default</span><br><span class="line">[0.002s][info][gc] Passive heuristics implies -XX:-ShenandoahStoreValReadBarrier by default</span><br><span class="line">[0.002s][info][gc] Passive heuristics implies -XX:-ShenandoahCASBarrier by default</span><br><span class="line">[0.002s][info][gc] Passive heuristics implies -XX:-ShenandoahAcmpBarrier by default</span><br><span class="line">[0.002s][info][gc] Passive heuristics implies -XX:-ShenandoahCloneBarrier by default</span><br><span class="line">[0.003s][info][gc] Using Shenandoah</span><br></pre></td></tr></table></figure></li><li><p>使用Linux perf可以轻松分析本机GC代码:</p><ol><li><p>Build OpenJDK with <strong>–with-native-debug-symbols=internal</strong>, this will get you the mapping to C++ code</p></li><li><p>Run the workload with <strong>perf record java …</strong> (plain profile) or <strong>perf record -g java …</strong> (call tree profile)</p></li><li><p>Open the report with <strong>perf report</strong></p></li><li><p>Navigate the report, and see where are suspiciously hot methods/paths are. Pressing <strong>“a”</strong>on the method usually gives a more detailed disassembly for it</p></li></ol></li><li><p>分析障碍代码需要启用PrintAssembly的构建。我们建议使用JMH -prof perfasm创建隔离的场景并查看Shenandoah下生成的代码。</p></li></ol><p>重要的是要明确GC停顿可能不是常规应用程序中响应时间的唯一重要来源。具有较大的GC停顿时间很快就会出现响应时间问题,但缺少长时间的GC停顿并不总是意味着良好的响应时间。排队延迟、网络延迟、其他服务延迟、OS调度程序抖动等都可能是影响因素。建议使用响应时间度量来运行Shenandoah,以全面了解系统中正在发生的事情,然后可以将其与GC停顿时间统计数据关联起来。</p><p>例如,这是一个带有jHiccup其中一个工作负载的示例报告:</p><p><img src="https://cr.openjdk.java.net/~shade/shenandoah/specjbb-preset4K-10min.png" alt=""></p><h2 id="功能诊断"><a href="#功能诊断" class="headerlink" title="功能诊断"></a>功能诊断</h2><p>本节介绍了可以诊断和/或调试Shenandoah的方法。</p><p>以下是缩小问题范围的步骤:</p><ol><li>使用 <strong>-XX:+ShenandoahVerify</strong>运行。这是针对GC bug的第一道防线,它在release和fastdebug构建中都可用。如果Verifier识别出一个问题,那么它很可能是GC bug。为了更好地诊断这一点,一个简单的复制器将是很方便的。在许多情况下,GC之前发生的事情很重要,例如GC所采取的最后操作。该历史记录通常记录在关联的hs_err_pidXXXX日志中,确保在报告bug时将其包含在内。</li><li>使用<strong>fastdebug build</strong>运行。在许多情况下,这将产生有意义的断言消息,指向GC检测到功能异常的最早时刻,而Shenandoah断言很多。这些构建可以通过添加<strong>–enable-debug</strong>来配置和重新构建生成。像往常一样,hs_err_pidXXXX.log方便地记录了有助于调查断言失败的环境和历史数据。</li><li>使用<strong>-XX:ShenandoahGCHeuristics=passive</strong>运行,它将仅执行stop-the-world GC,并避免执行大多数并发工作。如果问题在passive模式下消失,那么它一定是并发阶段和/或屏障中的bug。</li><li>使用不同的编译器运行:<strong>-Xint</strong>(仅限解释器),<strong>-XX:TieredStopAtLevel=1</strong>(仅限C1),<strong>-XX:-TieredCompilation</strong>(仅限解释器和C2)——剖析哪些模式失败,哪些没有。这将突出显示问题是在解释器、C1或C2中的屏障处理或优化。这通常有助于与fastdebug build相结合,因为编译器也会产生断言。</li><li>使用<strong>-XX:ShenandoahGCHeuristics=aggressive</strong>运行。这种启发式方法连续运行GC,并疏散所有非空region。由于Shenandoah并发执行大多数GC繁重工作,所以这不会阻止应用程序的执行,尽管在这种模式下GC将消耗更多的周期并降低应用程序的运行速度。注意,此模式下启用Verifier可能会将性能降低到不实用的水平。</li><li>使用<strong>-XX:+ShenandoahVerifyOptoBarriers</strong>(验证C2理想图中的屏障),<strong>-XX:VerifyStrictOopOperations</strong>(执行额外的检查来验证oop比较是否正确)添加更多验证。</li></ol><p>适用于Shenandoah的一般调试技术:</p><ol><li>在代码中围绕失败的断言放置日志语句,以便更好地理解问题。有了足够的日志记录,你就可以重新跟踪收集器中发生的所有事情。</li><li>在代码中可疑的部分周围添加更多的断言。查看shenandoahassert中的宏定义。hpp查看rich断言的可用性</li><li>附加一个本机调试器,例如gdb,通过请求VM在失败时使用<strong>-XX:OnError=”gdb - %p”</strong>执行外部操作(将%p替换为进程PID)</li><li>创建一个简单的复制器并交给Shenandoah开发人员。:)</li></ol><h2 id="构建,下载,安装,运行"><a href="#构建,下载,安装,运行" class="headerlink" title="构建,下载,安装,运行"></a>构建,下载,安装,运行</h2><p>自12以来,Shenandoah就在主线JDK中进行开发。除了主线构建之外,还有一些下游的构建可用于当前JDK。开发repos和builds之间的变更流程如下面的简化图所示。</p><p><img src="http://cr.openjdk.java.net/~shade/shenandoah/shenandoah-changes-flow.png" alt=""></p><p>如果你是早期采用者,尝试前沿构建应该在性能方面更有利可图,但可能会冒险暴露于尚未发现的bug。如果希望在实际部署中运行Shenandoah,则首选使用最稳定的版本。 </p>]]></content>
<summary type="html">
<p><img src="https://i.loli.net/2019/03/28/5c9c829850beb.jpg" alt="OpenJDK.jpg"></p>
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>Shenandoah是低停顿时间的垃圾收集器,通过与正在运行的Java程序并发地执行更多垃圾收集工作来缩短GC停顿时间。Shenandoah并发地完成大部分GC工作,包括并发整理,这意味着它的停顿时间不再与堆的大小成正比。收集200GB堆或2GB堆的垃圾应具有类似的低停顿行为。</p>
<p>本文对Shenandoah GC官方文档进行简单翻译,方便对该垃圾收集器的使用,原文参见: <a href="https://wiki.openjdk.java.net/display/shenandoah/Main" target="_blank" rel="noopener">Shenandoah wiki page</a>。</p>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="译文" scheme="http://yoursite.com/categories/Java/%E8%AF%91%E6%96%87/"/>
<category term="JVM" scheme="http://yoursite.com/tags/JVM/"/>
<category term="GC" scheme="http://yoursite.com/tags/GC/"/>
<category term="OpenJDK" scheme="http://yoursite.com/tags/OpenJDK/"/>
</entry>
<entry>
<title>G1GC官方文档(译)</title>
<link href="http://yoursite.com/2019/04/09/G1GC%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%EF%BC%88%E8%AF%91%EF%BC%89/"/>
<id>http://yoursite.com/2019/04/09/G1GC官方文档(译)/</id>
<published>2019-04-09T01:07:01.198Z</published>
<updated>2019-09-06T01:50:04.530Z</updated>
<content type="html"><![CDATA[<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>本文对G1GC官方文档进行了翻译,原文地址:<a href="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html" target="_blank" rel="noopener">Getting Started with the G1 Garbage Collector</a><br><a id="more"></a></p><p>[TOC]</p><h2 id="Java技术和JVM"><a href="#Java技术和JVM" class="headerlink" title="Java技术和JVM"></a>Java技术和JVM</h2><h3 id="Java概述"><a href="#Java概述" class="headerlink" title="Java概述"></a>Java概述</h3><h4 id="JRE-JDK"><a href="#JRE-JDK" class="headerlink" title="JRE JDK"></a>JRE JDK</h4><p>略</p><h4 id="Java虚拟机(JVM)"><a href="#Java虚拟机(JVM)" class="headerlink" title="Java虚拟机(JVM)"></a>Java虚拟机(JVM)</h4><p>Java虚拟机(JVM)是一种抽象计算机器。 JVM是一个程序,看起来像是编写在其中执行的程序的机器。这样,Java程序就被写入同一组接口和类库中。针对特定操作系统的每个JVM实现都将Java编程指令转换为在本地操作系统上运行的指令和命令。Java程序正是籍于此实现了平台独立性。</p><p>在Sun Microsystems,Inc完成的Java虚拟机的第一个原型实现模拟了由类似于当代个人数字助理(PDA)的手持设备托管的软件中的Java虚拟机指令集。Oracle当前的实现模拟移动,桌面和服务器设备上的Java虚拟机,但Java虚拟机不承担任何特定的实现技术,主机硬件或主机操作系统。它本身并不是解释,但也可以通过将其指令集编译为硅CPU来实现。它也可以用微代码实现或直接用硅实现。</p><p>Java虚拟机不知道Java编程语言,只知道特定的二进制格式,即类文件格式。类文件包含Java虚拟机指令(或字节码)和符号表,以及其他辅助信息。</p><p>出于安全考虑,Java虚拟机对类文件中的代码施加了强大的语法和结构约束。但是,任何具有可以用有效类文件表示的功能的语言都可以由Java虚拟机托管。由通用的,与机器无关的平台吸引,其他语言的实现者可以转向Java虚拟机作为其语言的交付工具。</p><h3 id="探索JVM体系结构"><a href="#探索JVM体系结构" class="headerlink" title="探索JVM体系结构"></a>探索JVM体系结构</h3><h4 id="Hotspot-架构"><a href="#Hotspot-架构" class="headerlink" title="Hotspot 架构"></a>Hotspot 架构</h4><p>HotSpot JVM拥有一个支持强大功能和基础的架构,并支持实现高性能和大规模可扩展性的能力。例如,HotSpot JVM JIT编译器生成动态优化。换句话说,他们在Java应用程序运行时做出优化决策,并生成针对底层系统架构的高性能本机机器指令。此外,通过其运行时环境和多线程垃圾收集器的成熟演进和持续工程,HotSpot JVM即使在最大的可用计算机系统上也能实现高可扩展性。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/gcslides/Slide1.png" alt="HotSpot JVM:Architecture"></p><p>JVM的主要组件包括类加载器,运行时数据区和执行引擎。</p><h4 id="Hotspot-关键组件"><a href="#Hotspot-关键组件" class="headerlink" title="Hotspot 关键组件"></a>Hotspot 关键组件</h4><p>以下图像突出显示了与性能相关的JVM的关键组件。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/gcslides/Slide2.png" alt="Key Hotaspot JVM Components"></p><p>在调整性能时,JVM有三个组件。堆是存储对象数据的位置。然后,此区域由启动时选择的垃圾收集器进行管理。 大多数调优选项都与调整堆大小和为您的情况选择最合适的垃圾收集器有关。JIT编译器对性能也有很大影响,但很少需要使用较新版本的JVM进行调优。</p><h3 id="性能基本知识"><a href="#性能基本知识" class="headerlink" title="性能基本知识"></a>性能基本知识</h3><p>通常在Java应用程序调优时,关注两个主要目标:响应性或吞吐量。下文将回顾这些概念。</p><h4 id="响应性"><a href="#响应性" class="headerlink" title="响应性"></a>响应性</h4><p>响应性是指应用程序或系统对请求数据响应的速度。例如:</p><pre><code>桌面UI响应事件的速度有多快网站返回页面的速度有多快返回数据库查询的速度有多快</code></pre><p>对于专注于响应性的应用程序,高停顿时间是不可接受的,侧重在短时间内做出回应。</p><h4 id="吞吐量"><a href="#吞吐量" class="headerlink" title="吞吐量"></a>吞吐量</h4><p>吞吐量关注在特定时间段内应用程序工作量的最大化。衡量吞吐量的示例:</p><pre><code>在给定时间内完成的交易数量。批处理程序可在一小时内完成的作业数。可在一小时内完成的数据库查询数。</code></pre><p>对于专注于吞吐量的应用程序,高停顿时间是可接受的。由于高吞吐量应用程序在较长时间内专注于基准测试,因此不需要考虑快速响应时间。</p><h2 id="G1垃圾收集器"><a href="#G1垃圾收集器" class="headerlink" title="G1垃圾收集器"></a>G1垃圾收集器</h2><h3 id="G1垃圾收集器-1"><a href="#G1垃圾收集器-1" class="headerlink" title="G1垃圾收集器"></a>G1垃圾收集器</h3><p>Garbage-First(G1)收集器是一种服务器风格的垃圾收集器,主要针对多处理器大内存的机器。它能高概率满足垃圾收集(GC)的停顿时间目标,同时实现高吞吐量。Oracle JDK 7 Update 4及更高版本完全支持G1垃圾收集器。G1收集器专为以下应用而设计:</p><ul><li>可以与CMS收集器等应用程序线程同时运行。</li><li>整理空闲内存时不会伴随长时间GC引起的高停顿。</li><li>需要GC停顿持续时间变得更加可预测。</li><li>不想牺牲很多吞吐量性能。</li><li>不需要更大的Java堆。</li></ul><p>G1被设计作为Concurrent Mark-Sweep Collector(CMS)的长期替代品。将G1与CMS进行比较,会发现存在以下差异使得G1成为更好的垃圾收集解决方案。一个区别是G1是“标记-整理”收集器,G1足够紧凑以完全避免使用细粒度的自由列表进行分配,而是依赖于区域。这大大简化了收集器的各个部分,并且主要消除了潜在的碎片问题。此外,G1提供比CMS收集器更可预测的垃圾收集暂停,并允许用户指定所需的暂停目标。</p><h4 id="G1操作概述"><a href="#G1操作概述" class="headerlink" title="G1操作概述"></a>G1操作概述</h4><p>旧的垃圾收集器(serial, parallel, CMS)都将堆分为三个部分:新生代、老年代和固定内存大小的永久代。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/HeapStructure.png" alt="Hotspot Heap Structure"></p><p>所有内存对象都在这三个部分之一结束生命周期。</p><p>G1收集器采用了不同的方法。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide9.png" alt="G1 Heap Allocation"></p><p><em>堆被分区为一组大小相等的堆region,每个region都是一个连续的虚拟内存区域。 某些region集具有与旧收集器中相同的角色(eden,survivor,old),但它们没有固定的大小。 这为内存使用提供了更大的灵活性。</em></p><p>执行垃圾收集时,G1以类似于CMS收集器的方式运行。G1执行并发全局标记阶段以确定整个堆中对象的活跃度。在标记阶段完成之后,G1知道哪些region基本上是空的。它首先在这些region上进行收集,这通常会产生大量的空闲内存。这就是为什么这种垃圾收集方法称为Garbage-First。顾名思义,G1将其收集和整理活动集中在堆的可能充满可回收对象(即垃圾)的区域。 G1使用暂停预测模型来满足用户定义的停顿时间目标,并根据指定的停顿时间目标选择要收集的区域数。</p><p>由G1确定适合回收的region,并使用疏散(evacuation)方式进行垃圾收集收集。G1将对象从堆的一个或多个region复制到堆上的单个region,并且在此过程中整理并释放内存。这种疏散在多处理器上并行执行,以减少停顿时间并提高吞吐量。因此,对于每次垃圾收集,G1会在用户定义的停顿时间内持续工作以减少碎片。这超出了以前两种方法的能力:CMS(Concurrent Mark Sweep)垃圾收集器不进行整理;ParallelOld垃圾收集仅执行整堆整理,这会导致相当长的停顿时间。</p><p>值得注意的是G1不是实时收集器。它以高概率但不是绝对确定性满足设定的停顿时间目标。基于先前收集过程中的数据,G1可以预估在用户指定的目标时间内收集多少region。因此,G1收集器对收集region的成本有一套相当准确的模型,它使用该模型来确定在停顿时间目标内时要收集哪些region和多少region。</p><p>注意:G1具有并发(与应用程序线程一起运行,例如,细化,标记,清理)和并行(多线程,例如,stop the world)阶段。Full GC仍然是单线程的,但如果正确调整,应用程序应该避免使用full GC。</p><h4 id="G1数据占位"><a href="#G1数据占位" class="headerlink" title="G1数据占位"></a>G1数据占位</h4><p>如果从ParallelOldGC或CMS收集器迁移到G1,你可能会看到更大的JVM进程大小。这主要与“accounting”数据结构有关,例如Remembered Sets和Collection Sets。</p><p><strong>Remembered Sets</strong>或RSet:跟踪对象引用到给定的region。堆中每个region都有一个RSet。RSet支持并行和独立收集region。RSets的总体占位影响小于5%。</p><p><strong>Collection Sets</strong>或CSets:将要在GC中收集的region集。在GC期间,CSet中的所有实时数据都被疏散(复制/移动)。区域集可以是Eden、survivor、和/或老年代。CSets对JVM的大小影响不到1%。</p><h4 id="推荐的G1使用场景"><a href="#推荐的G1使用场景" class="headerlink" title="推荐的G1使用场景"></a>推荐的G1使用场景</h4><p>第一个关注点是G1为运行需要低GC延迟大堆的应用程序的用户提供解决方案。这意味着堆大小约为6GB或更大,稳定且可预测的停顿时间低于0.5秒。</p><p>如果应用程序具有以下一个或多个特征,那么当前使用CMS或ParallelOldGC垃圾收集器运行的应用程序切换到G1后将得到提升。</p><ul><li>Full GC持续时间太长或太频繁。</li><li>对象分配率或提升率差异很大。</li><li>不期望的长GC或整理停顿(超过0.5到1秒)</li></ul><p><strong>注意:</strong>如果使用的是CMS或ParallelOldGC,并且应用程序没有经历长时间的GC停顿,那么使用当前的收集器就可以了。更改为G1收集器不是使用最新JDK的必要条件。</p><h2 id="回顾CMS垃圾收集"><a href="#回顾CMS垃圾收集" class="headerlink" title="回顾CMS垃圾收集"></a>回顾CMS垃圾收集</h2><h3 id="回顾传统GC及CMS"><a href="#回顾传统GC及CMS" class="headerlink" title="回顾传统GC及CMS"></a>回顾传统GC及CMS</h3><p>Concurrent Mark Sweep(CMS)收集器(也称为并发低停顿收集器)对老年代进行收集。它尝试通过与应用程序线程同时执行大部分垃圾收集工作,来最小化由于垃圾收集而导致的停顿。 通常,并发低停顿收集器不会复制或整理活动对象。无需移动活动对象即可完成垃圾收集。如果碎片成为问题,请分配更大的堆。</p><p><strong>注意:</strong>新生代的CMS收集器使用与并行收集器相同的算法。</p><h4 id="CMS收集阶段"><a href="#CMS收集阶段" class="headerlink" title="CMS收集阶段"></a>CMS收集阶段</h4><p>CMS收集器在堆的老年代上执行以下阶段:</p><table><thead><tr><th>Phase</th><th>Description</th></tr></thead><tbody><tr><td>(1) 初始标记 <em>(Stop the World Event)</em></td><td>老年代中的对象被“标记”为可达的,其中包括那些可以从新生代到达的对象。相较Minor GC的停顿时间,该阶段停顿时间通常较短。</td></tr><tr><td>(2) 并发标记</td><td>遍历可达对象的老年代对象图,与Java应用线程并发执行。从标记的对象开始扫描,并标记从GC roots可达的所有对象。Mutator线程在并发阶段2,3和5阶段执行,并且CMS在这些阶段中分配的任何对象(包括晋升的对象)立即被标记为存活对象。</td></tr><tr><td>(3) 重新标记 <em>(Stop the World Event)</em></td><td>查找并发标记阶段遗漏的对象,遗漏发生在并发收集器完成对该对象的跟踪之后,Java应用程序线程又进行了更新。</td></tr><tr><td>(4) 并发清理</td><td>收集在标记阶段标识为不可达的对象。死对象的收集将对象的空间添加到空闲列表以供稍后分配。此时可能会发生死对象的合并。请注意,不会移动存活对象。</td></tr><tr><td>(5) 重置</td><td>通过清除数据结构准备下一轮并发收集。</td></tr></tbody></table><h4 id="回顾垃圾收集步骤"><a href="#回顾垃圾收集步骤" class="headerlink" title="回顾垃圾收集步骤"></a>回顾垃圾收集步骤</h4><p>接下来,让我们一步一步查看CMS收集器操作。</p><ol><li><p><strong>CMS收集器堆结构</strong></p><p>堆被分成三个部分。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide1.png" alt="CMS Heap Structure">新生代分为Eden区和两个survivor区,老年代是一个连续的空间。对象在原地址上进行收集,除非有full GC,否则不会进行整理。</p></li><li><p><strong>CMS中Young GC工作原理</strong></p><p>新生代是浅绿色,老年代是蓝色。如果应用程序已运行一段时间,这就是CMS的样子,对象散落在老年代区域。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide2.png" alt="How Young GC works"></p><p>使用CMS,老年代对象在原地址上进行回收,没有移动操作。除非有full GC,否则不会进行整理。</p></li><li><p><strong>一次Young GC</strong></p><p>存活对象从Eden区和一个survivor区复制到另一个survivor区。任何已达到年龄阈值的老对象都将晋升到老年代。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide3.png" alt="Young Generation Collection"></p></li><li><p><strong>Young GC之后</strong></p><p>在Young GC之后,Eden区及其中一个survivor区被清空。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide4.png" alt="After Young GC"></p><p>新晋升的对象在图中以深蓝色显示,绿色对象是尚未晋升到老年代的幸存的新生代对象。</p></li><li><p><strong>CMS的老年代GC</strong></p><p>两次STW事件发生:初始标记和重新标记。当老年代达到一定的占用率时,CMS就会被启动。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide5.png" alt="Old Generation Collection with CMS"></p><p>(1)初始标记,短暂停顿阶段,对存活(可达的)对象进行标记。</p><p>(2)并发标记,在应用程序继续执行时并发地查找存活对象。</p><p>(3)重新标记,发现在前一阶段(2)并发标记期间遗漏的对象。</p></li><li><p><strong>老年代GC-并发清理</strong></p><p>在前一阶段未标记的对象将被回收,不进行整理。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide6.png" alt="Old Generation Collection - Concurrent Sweep"></p><p><strong>注意:</strong> Unmarked 对象 == Dead 对象</p></li><li><p><strong>老年代GC-清理之后</strong></p><p>在(4)清理阶段之后,可以看到已经释放了大量内存,并且没有进行任何整理。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide7.png" alt="Old Generation Collection - After Sweeping"></p><p>最后,CMS收集器将执行(5)重置阶段,并等待下一次达到GC阈值。</p></li></ol><h2 id="G1垃圾回收过程详解"><a href="#G1垃圾回收过程详解" class="headerlink" title="G1垃圾回收过程详解"></a>G1垃圾回收过程详解</h2><h3 id="The-G1-Garbage-Collector-Step-by-Step"><a href="#The-G1-Garbage-Collector-Step-by-Step" class="headerlink" title="The G1 Garbage Collector Step by Step"></a>The G1 Garbage Collector Step by Step</h3><p>G1收集器采用不同的方法来分配堆,下面的图片将逐步回顾G1系统。</p><ol><li><p><strong>G1 堆结构</strong></p><p>堆是一个分成许多固定大小region的内存区域。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide8.png" alt="G1 Heap Structure"></p><p>region大小由JVM在启动时选择,JVM通常将堆分为大约2000个region,大小从1到32Mb不等。</p></li><li><p><strong>G1 堆分配</strong></p><p>实际上,这些region被映射Eden、Survivor和老年代的逻辑表示。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide9.png" alt="G1 Heap Allocation"></p><p>图中的颜色显示哪个region与哪个角色相关联。存活对象从一个region疏散(即,复制或移动)到另一个region。region被设计为在停止或不停止所有其他应用程序线程的情况下并行收集。</p><p>如图所示,region可以分为Eden、Survivor和老年代region。此外,还有第四种被称为Humongous region的对象。这些region设计用于容纳大小为标准region大小的50%或更大的对象,它们存储为一组连续的region。 最后,最后一种类型的region将是堆的未使用region。</p><p><strong>注意:</strong>在撰写本文时,尚未优化收集humongous对象,因此应该避免创建此大小的对象。</p></li><li><p><strong>G1的Young GC原理</strong></p><p>堆被分成大约2000个region,最小大小为1Mb,最大大小为32Mb。蓝色region包含老年代对象,绿色region包含新生代对象。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide10.png" alt="Young Generation in G1"></p><p>请注意,region不需要像以前的垃圾收集器那样连续。</p></li><li><p><strong>G1的一次Young GC</strong></p><p>将存活对象疏散(即,复制或移动)到一个或多个survivor region。 如果满足年龄阈值,则将一些对象晋升为老年代region。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide11.png" alt="A Young GC in G1"></p><p>这是一次stop the world(STW)停顿,计算下一次Young GC的Eden大小和survivor大小。保留accounting信息以帮助计算大小,同时像停顿时间目标这样的事情被考虑在内。</p><p>这种方法可以很容易地调整region大小,使它们根据需要变大或变小。</p></li><li><p><strong>G1 Young GC结果</strong></p><p>存活对象已被疏散到survivor region或老年代region。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide12.png" alt="End of a Young GC with G1"></p><p>最近晋升的对象以深蓝色显示。深绿色为新生代幸存的survivor region。</p><p>对G1的新生代总结如下:</p><ul><li>堆是被分成region的单个内存空间。</li><li>新生代内存由一组非连续的region组成,这样可以在需要时轻松调整大小。</li><li>Young GC为STW事件,执行期间将停止所有应用程序线程。</li><li>Young GC使用多个线程并行完成。</li><li>存活对象被复制到新的survivor region或老年代region。</li></ul></li></ol><h3 id="G1的老年代收集"><a href="#G1的老年代收集" class="headerlink" title="G1的老年代收集"></a>G1的老年代收集</h3><h4 id="G1收集阶段-并发标记循环周期"><a href="#G1收集阶段-并发标记循环周期" class="headerlink" title="G1收集阶段 - 并发标记循环周期"></a>G1收集阶段 - 并发标记循环周期</h4><p>G1收集器在堆的老年代上执行以下阶段。请注意,某些阶段是新生代收集的一部分。</p><table><thead><tr><th>Phase</th><th>Description</th></tr></thead><tbody><tr><td>(1) 初始标记<em>(Stop the World Event)</em></td><td>这是一个STW事件。对G1来说初始标记可由一次普通的young GC完成。标记可能引用老年代对象的survivor region(root region)。</td></tr><tr><td>(2) Root Region 扫描</td><td>扫描引用老年代对象的survivor region,应用程序继续运行时会发生这种情况。 必须在young GC发生之前完成该阶段。</td></tr><tr><td>(3) 并发标记</td><td>在整个堆上查找存活对象,与应用程序并发执行。此阶段可被young GC中断。</td></tr><tr><td>(4) 重新标记<em>(Stop the World Event)</em></td><td>完成堆中存活对象的标记,使用名为snapshot-at-the-beginning(SATB)的算法,该算法比CMS收集器中使用的算法快得多。</td></tr><tr><td>(5) 清除<em>(Stop the World Event and Concurrent)</em></td><td>1 对存活对象和完全空闲region执行accounting。 (STW)2 清除Remembered Sets。 (STW)3 重置空region并将其返回到空闲列表。 (并发)</td></tr><tr><td>(<em>) 复制 </em>(Stop the World Event)*</td><td>STW停顿以疏散或复制存活对象到新的未使用region。新生代操作日志记录为<code>[GC pause (young)]</code>。新生代和老年代的混合收集日志记录为<code>[GC Pause (mixed)]</code>。</td></tr></tbody></table><h4 id="G1-Old-Generation-Collection-Step-by-Step"><a href="#G1-Old-Generation-Collection-Step-by-Step" class="headerlink" title="G1 Old Generation Collection Step by Step"></a>G1 Old Generation Collection Step by Step</h4><p>接下来看一下这些阶段是如何与G1收集器中的老年代进行交互的。</p><ol start="6"><li><p><strong>初始标记阶段</strong></p><p>存活对象的初始标记搭载在young GC上,在日志中,这被称为<code>GC pause (young)(inital-mark)</code>。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide13.png" alt="Initial Marking Phase"></p></li><li><p><strong>并发标记阶段</strong></p><p>如果找到空region(由“X”表示),则在重新标记阶段立即将它们移除。此外,计算确定活跃度的“accounting”信息。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide14.png" alt="Concurrent Marking Phase"></p></li><li><p><strong>重新标记阶段</strong></p><p>空region被移除并回收。现在计算所有region的区域活跃度。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide15.png" alt="Remark Phase"></p></li><li><p><strong>复制/清除阶段</strong></p><p>G1选择具有最低“活跃度”的region,这些region可以被最快收集。 然后,这些region与young GC同时收集。 这在日志中表示为<code>[GC pause (mixed)]</code>,即新生代与老年代同时被收集。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide16.png" alt="Copying/Cleanup Phase"></p></li><li><p><strong>After Copying/Cleanup Phase</strong></p><p>选择的region已经被收集并整理,如图中所示的深蓝色区域和深绿色区域。</p><p><img src="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/images/slide17.png" alt="After Copying/Cleanup Phase"></p></li></ol><h4 id="Summary-of-Old-Generation-GC"><a href="#Summary-of-Old-Generation-GC" class="headerlink" title="Summary of Old Generation GC"></a>Summary of Old Generation GC</h4><p>对G1老年代收集的总结如下:</p><ul><li><p>并发标记阶段</p><ul><li><p>在应用程序运行时并发计算活跃度信息。</p><ul><li><p>活跃度信息确定在疏散停顿期间那些region最适合回收。</p></li><li><p>没有类似于CMS的清理阶段。</p></li></ul></li></ul></li><li><p>重新标记阶段<br> - 使用 Snapshot-at-the-Beginning(SATB)算法,该算法比CMS使用的算法快得多。<br> - 完全空的region被回收。</p></li><li><p>复制/清除阶段<br> - 新生代和老年代同时被收回。<br> - 基于活跃度选择老年代区域。</p></li></ul><h2 id="命令行参数及最佳实践"><a href="#命令行参数及最佳实践" class="headerlink" title="命令行参数及最佳实践"></a>命令行参数及最佳实践</h2><h3 id="Command-Line-Options-and-Best-Practices"><a href="#Command-Line-Options-and-Best-Practices" class="headerlink" title="Command Line Options and Best Practices"></a>Command Line Options and Best Practices</h3><p>在本节中,我们来看看G1的各种命令行选项。</p><h4 id="基本命令行"><a href="#基本命令行" class="headerlink" title="基本命令行"></a>基本命令行</h4><p>要启用G1收集器,请使用:<code>-XX:+UseG1GC</code></p><p>下面是一个示例命令行,用于启动JDK演示和示例下载中包含的Java2Demo:<br> <strong>java -Xmx50m -Xms50m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar</strong> </p><h4 id="关键命令行开关"><a href="#关键命令行开关" class="headerlink" title="关键命令行开关"></a>关键命令行开关</h4><p><strong>-XX:+UseG1GC</strong> - 告诉JVM使用G1垃圾收集器。</p><p><strong>-XX:MaxGCPauseMillis=200</strong> - 设置最大GC停顿时间的目标。这是一个soft goal,JVM将尽最大努力实现它。因此,停顿时间的目标有时会无法实现。默认值是200毫秒。</p><p><strong>-XX:InitiatingHeapOccupancyPercent=45</strong> - 用于启动并发GC循环的(整个)堆占用的百分比。G1使用它根据整个堆的占用情况(而不仅仅是其中一个代)触发并发GC循环。值0表示“执行固定的GC循环”。默认值为45(即, 45%已满或已占用)。</p><h4 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h4><p>在使用G1时,您应该遵循一些最佳实践:</p><ol><li><p><strong>不要设置新生代的大小</strong></p><p>通过<code>-Xmn</code>显式地设置年轻代的大小会影响G1收集器的默认行为。</p><ul><li>G1将不再考虑收集的停顿时间目标。因此在本质上,设置新生代的大小会禁用停顿时间目标。</li><li>G1不再能够根据需要扩展和收缩新生代的空间。由于大小是固定的,所以不能对大小进行更改。</li></ul></li><li><p><strong>响应时间指标</strong></p><p>不使用平均响应时间(ART)作为设置<code>XX:MaxGCPauseMillis=<N></code>的指标,而是考虑设置将在90%或更多的时间内满足目标的值。这意味着90%发出请求的用户的响应时间不会超过目标。记住,停顿时间是一个目标,并不能保证总是能实现。</p></li><li><p><strong>什么是疏散失败?</strong></p><p>在GC期间,对于幸存者和提升的对象,当JVM用尽堆区域时发生的升级失败。 堆无法扩展,因为它已经处于最大值。 当使用 <code>-XX:+PrintGCDetails</code>时,GC日志中会显示此信息: <strong>tospace overflow</strong>。 这很贵!</p><ul><li>GC仍然需要继续,因此必须释放空间。</li><li>未成功复制的对象必须在适当的位置使用。</li><li>必须重新生成对CSet中的区域的RSets的任何更新。</li><li>所有这些步骤都很昂贵。</li></ul></li><li><p><strong>怎样避免疏散失败</strong></p><p>为避免疏散失败,请考虑以下选项。</p><ul><li>增加堆大小<br> 增加<strong>-XX:G1ReservePercent=n</strong>,默认值为10。<br> G1通过尝试释放保留存储器来创建假天花板,以防需要更多“空间”。</li><li>提前开始标记周期</li><li>使用<strong>-XX:ConcGCThreads=n</strong>选项增加标记线程的数量。</li></ul></li></ol><h4 id="G1-GC开关完整列表"><a href="#G1-GC开关完整列表" class="headerlink" title="G1 GC开关完整列表"></a>G1 GC开关完整列表</h4><p>这是G1 GC开关的完整列表,请记住使用上面列出的最佳实践。</p><table><thead><tr><th>Option and Default Value</th><th>Description</th></tr></thead><tbody><tr><td>-XX:+UseG1GC</td><td>Use the Garbage First (G1) Collector</td></tr><tr><td>-XX:MaxGCPauseMillis=n</td><td>Sets a target for the maximum GC pause time. This is a soft goal, and the JVM will make its best effort to achieve it.</td></tr><tr><td>-XX:InitiatingHeapOccupancyPercent=n</td><td>Percentage of the (entire) heap occupancy to start a concurrent GC cycle. It is used by GCs that trigger a concurrent GC cycle based on the occupancy of the entire heap, not just one of the generations (e.g., G1). A value of 0 denotes ‘do constant GC cycles’. The default value is 45.</td></tr><tr><td>-XX:NewRatio=n</td><td>Ratio of new/old generation sizes. The default value is 2.</td></tr><tr><td>-XX:SurvivorRatio=n</td><td>Ratio of eden/survivor space size. The default value is 8.</td></tr><tr><td>-XX:MaxTenuringThreshold=n</td><td>Maximum value for tenuring threshold. The default value is 15.</td></tr><tr><td>-XX:ParallelGCThreads=n</td><td>Sets the number of threads used during parallel phases of the garbage collectors. The default value varies with the platform on which the JVM is running.</td></tr><tr><td>-XX:ConcGCThreads=n</td><td>Number of threads concurrent garbage collectors will use. The default value varies with the platform on which the JVM is running.</td></tr><tr><td>-XX:G1ReservePercent=n</td><td>Sets the amount of heap that is reserved as a false ceiling to reduce the possibility of promotion failure. The default value is 10.</td></tr><tr><td>-XX:G1HeapRegionSize=n</td><td>With G1 the Java heap is subdivided into uniformly sized regions. This sets the size of the individual sub-divisions. The default value of this parameter is determined ergonomically based upon heap size. The minimum value is 1Mb and the maximum value is 32Mb.</td></tr></tbody></table>]]></content>
<summary type="html">
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>本文对G1GC官方文档进行了翻译,原文地址:<a href="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html" target="_blank" rel="noopener">Getting Started with the G1 Garbage Collector</a><br>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="译文" scheme="http://yoursite.com/categories/Java/%E8%AF%91%E6%96%87/"/>
<category term="JVM" scheme="http://yoursite.com/tags/JVM/"/>
<category term="GC" scheme="http://yoursite.com/tags/GC/"/>
</entry>
<entry>
<title>Spring Boot集成RocketMQ的设计与实现</title>
<link href="http://yoursite.com/2019/03/28/Spring%20Boot%E9%9B%86%E6%88%90RocketMQ%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0/"/>
<id>http://yoursite.com/2019/03/28/Spring Boot集成RocketMQ的设计与实现/</id>
<published>2019-03-28T02:10:19.653Z</published>
<updated>2019-03-29T02:50:52.136Z</updated>
<content type="html"><![CDATA[<p><img src="https://i.loli.net/2019/03/28/5c9c80dda5387.png" alt="RocketMQ.png"></p><h2 id="文章概要"><a href="#文章概要" class="headerlink" title="文章概要"></a>文章概要</h2><p>最近看了阿里中间件团队的几篇技术博客,详情见:<a href="http://jm.taobao.org/" target="_blank" rel="noopener">阿里中间件团队博客</a>,其中有关RocketMQ的几篇文章写得很好。还是那句话,学习本身就是一个不断获取知识然后投入实践的过程,本文就组内项目中使用的RocketMQ集成Spring Boot框架来实现消息发送消费的解决方案进行一个简单的梳理。鉴于自身当前对中间件优化方面还没有太深的接触,文中有可能会出现一些理解错误,难免不贻笑大方,所以权当成个人的学习笔记,方便记忆和以后的深入学习。</p><a id="more"></a><h2 id="设计实现"><a href="#设计实现" class="headerlink" title="设计实现"></a>设计实现</h2><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>为什么选择 Spring Boot集成RocketMQ作为行情组合计算服务的消息队列解决方案?迫于排版限制,其原因我放在了文章最后的补充目录下,下面是跳转链接:</p><p><a href="#jumpSpringAbout">关于Spring Boot</a></p><p><a href="#jumpCompare">Kafka、RocketMQ、RabbitMQ的比较</a></p><p>同时你也可以获取一些RocketMQ的简单介绍:</p><p><a href="#jumpRocketmqConcept">关于RocketMQ的一些概念</a></p><p>下面我将开门见山地对其实现细节进行介绍:</p><h3 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h3><ol><li>配置pom.xml文件</li></ol><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">exclusions</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-logging<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-autoconfigure<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">exclusions</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.rocketmq<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>rocketmq-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>${rocketmq.version}<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">exclusions</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>io.netty<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>netty-all<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.alibaba<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>fastjson<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">exclusion</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">exclusions</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><ol start="2"><li>spring-boot应用中对应的配置文件:src/main/resources/application.properties.</li></ol><p>生产端的配置文件application.properties</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 定义name-server地址</span><br><span class="line">rocketmq.cluster.nameServer.addr=localhost:9876</span><br><span class="line"># 定义发布者组名</span><br><span class="line">rocketmq.message.producerGroup=result-message-producer</span><br><span class="line"># 定义要发送的topic</span><br><span class="line">rocketmq.message.producer.topic=result-topic</span><br></pre></td></tr></table></figure><p>消费端的配置文件application.properties</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 定义name-server地址</span><br><span class="line">rocketmq.cluster.nameServer.addr=localhost:9876</span><br><span class="line"># 定义发布者组名</span><br><span class="line">rocketmq.message.result.consumerGroup=result-message-consumer</span><br><span class="line"># 定义要发送的topic</span><br><span class="line">rocketmq.message.result.topic=result-topic</span><br></pre></td></tr></table></figure><h3 id="消息生产端的设计实现"><a href="#消息生产端的设计实现" class="headerlink" title="消息生产端的设计实现"></a>消息生产端的设计实现</h3><p>生产端的Java代码如下:</p><p><strong>消息生产者类:</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MQPushProducer</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">MQPushProducer</span><span class="params">(String nameServers, String producerGroup, String topic, String tag, <span class="keyword">int</span> maxMessageSize)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">start</span><span class="params">()</span> </span>{</span><br><span class="line"> producer = <span class="keyword">new</span> DefaultMQProducer(producerGroup);</span><br><span class="line"> producer.setNamesrvAddr(nameServers);</span><br><span class="line"> producer.setMaxMessageSize(maxMessageSize);</span><br><span class="line"> producer.setRetryTimesWhenSendFailed(retryTimesWhenSendFailed);</span><br><span class="line"> producer.setSendMsgTimeout(sendMsgTimeout);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> producer.start();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">push</span><span class="params">(String tag, Collection<T> msgs)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (producer != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> SendResult result = producer.send(<span class="keyword">new</span> Message(topic, tag, JSONObject.toJSONString(msgs).getBytes()));</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">push</span><span class="params">(Collection<T> msgs)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (producer != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> SendResult result = producer.send(<span class="keyword">new</span> Message(topic, tag, JSONObject.toJSONString(msgs).getBytes()));</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>RocketMQ通过轮询所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。可以根据业务实现自己的MessageQueueSelector()发送顺序消息。</p><p>MQPushProducerOrderly类:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MQPushProducerOrderly</span><<span class="title">T</span>> <span class="keyword">extends</span> <span class="title">MQPushProducer</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">MQPushProducerOrderly</span><span class="params">(String nameServers, String producerGroup, String topic, String tag, <span class="keyword">int</span> maxMessageSize)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(nameServers, producerGroup, topic, tag, maxMessageSize);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">push</span><span class="params">(Collection<T> msgs)</span></span>{</span><br><span class="line"> <span class="keyword">if</span> (producer != <span class="keyword">null</span>){</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">for</span> (T msg : msgs){</span><br><span class="line"> <span class="keyword">byte</span>[] msgBytes = JSONObject.toJSONString(msg).getBytes();</span><br><span class="line"> <span class="comment">// RocketMQ通过MessageQueueSelector中实现的算法来确定消息发送到哪一个队列上</span></span><br><span class="line"><span class="comment">// RocketMQ默认提供了两种MessageQueueSelector实现:随机/Hash</span></span><br><span class="line"><span class="comment">// 可以根据业务实现自己的MessageQueueSelector来决定消息按照何种策略发送到消息队列中</span></span><br><span class="line"> SendResult result = producer.send(<span class="keyword">new</span> Message(topic, tag, msgBytes), <span class="keyword">new</span> MessageQueueSelector() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> MessageQueue <span class="title">select</span><span class="params">(List<MessageQueue> mqs, Message msg, Object arg)</span> </span>{</span><br><span class="line"> Integer id = (Integer) arg;</span><br><span class="line"> <span class="keyword">int</span> index = id % mqs.size();</span><br><span class="line"> <span class="keyword">return</span> mqs.get(index);</span><br><span class="line"> }</span><br><span class="line"> }, msg);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>消息生产端启动类:</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@ComponentScan</span>(basePackages = {<span class="comment">/*packages...*/</span>},</span><br><span class="line"> excludeFilters = {<span class="meta">@ComponentScan</span>.Filter(type = FilterType.CUSTOM, classes = ApplicationExcludeFilter.class)}</span><br><span class="line">)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ProducerApplication</span> <span class="keyword">implements</span> <span class="title">CommandLineRunner</span> </span>{</span><br><span class="line"> <span class="comment">//使用application.properties里定义的topic属性</span></span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${rocketmq.cluster.nameServer.addr}"</span>)</span><br><span class="line"> <span class="keyword">private</span> String nameServers;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${rocketmq.message.producer.topic}"</span>)</span><br><span class="line"> <span class="keyword">private</span> String resultTopic;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${rocketmq.message.producerGroup}"</span>)</span><br><span class="line"> <span class="keyword">private</span> String resultConsumerGroupName;</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"> </span><br><span 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">(String[] args)</span> </span>{</span><br><span class="line"> System.setProperty(CLIENT_LOG_USESLF4J,<span class="string">"true"</span>);</span><br><span class="line"> SpringApplication.run(ProducerApplication.class, args);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">(String... strings)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">//初始化发送生产者队列</span></span><br><span class="line">MQPushProducerOrderly<...> producer = <span class="keyword">new</span> MQPushProducerOrderly(nameServers, producerGroup, topic, tag, maxMessageSize);</span><br><span class="line"> <span class="comment">//整个应用生命周期内,只需要初始化1次</span></span><br><span class="line"> producer.start();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//启动消息计算发送主线程</span></span><br><span class="line"> ...</span><br><span class="line"> producer.push(msgs);</span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//清理资源,关闭网络连接,注销自己</span></span><br><span class="line"> producer.shutdown(); </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>关于@SpringBootApplication注解相关参见:<a href="#jumpSpringBootApplication">@SpringBootApplication 注解简析</a></p><p>在整个应用生命周期内,生产者需要调用一次start方法来初始化,初始化主要完成的任务有:</p><ol><li>如果没有指定namesrv地址,将会自动寻址;</li><li>启动定时任务:更新namesrv地址、从namsrv更新topic路由信息、清理已经挂掉的broker、向所有broker发送心跳等;</li><li>启动负载均衡的服务。</li></ol><p>如果Producer发送消息失败,会自动重试,重试的策略:</p><ol><li><p>重试次数 < retryTimesWhenSendFailed(可配置);</p></li><li><p>总的耗时(包含重试n次的耗时) < sendMsgTimeout(发送消息时传入的参数);</p></li><li><p>同时满足上面两个条件后,Producer会选择另外一个队列发送消息。</p></li></ol><h3 id="消息消费端设计实现"><a href="#消息消费端设计实现" class="headerlink" title="消息消费端设计实现"></a>消息消费端设计实现</h3><p>消息消费端代码如下:</p><p><strong>消息消费者类:</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">MQPushConsumer</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">MQPushConsumer</span><span class="params">(String nameServers, String consumerGroup, String topic, String tag, <span class="keyword">int</span> consumeMessageBatchMaxSize,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">int</span> consumeThreadMin, <span class="keyword">int</span> consumeThreadMax, <span class="keyword">int</span> pullBatchSize, MessageModel messageModel, ConsumeFromWhere offset)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> </span><br><span 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>{</span><br><span class="line"> DefaultMQPushConsumer consumer = <span class="keyword">new</span> DefaultMQPushConsumer(consumerGroup);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> consumer.setNamesrvAddr(nameServers);</span><br><span class="line"> consumer.subscribe(topic, tag);<span class="comment">//可订阅多个tag,但是一个消息只能有一个tag</span></span><br><span class="line"> consumer.setConsumeThreadMin(consumeThreadMin);</span><br><span class="line"> consumer.setConsumeThreadMax(consumeThreadMax);</span><br><span class="line"> consumer.setConsumeFromWhere(offset);</span><br><span class="line"> consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize);</span><br><span class="line"> consumer.setPullBatchSize(pullBatchSize);</span><br><span class="line"> consumer.setMessageModel(messageModel);</span><br><span class="line"> consumer.registerMessageListener(<span class="keyword">new</span> MessageListenerConcurrently() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ConsumeConcurrentlyStatus <span class="title">consumeMessage</span><span class="params">(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(!poll(list)){</span><br><span class="line"> <span class="keyword">return</span> ConsumeConcurrentlyStatus.RECONSUME_LATER;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> consumer.start();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>MQPushConsumerOrderly类:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">MQPushConsumerOrderly</span> <span class="keyword">extends</span> <span class="title">MQPushConsumer</span></span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">MQPushConsumerOrderly</span><span class="params">(String nameServers, String consumerGroup, String topic, String tag,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">int</span> consumeMessageBatchMaxSize, <span class="keyword">int</span> consumeThreadMin, <span class="keyword">int</span> consumeThreadMax,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">int</span> pullBatchSize, MessageModel messageModel, ConsumeFromWhere offset)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(nameServers, consumerGroup, topic, tag, consumeMessageBatchMaxSize, consumeThreadMin, consumeThreadMax, pullBatchSize, messageModel, offset);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span 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>{</span><br><span class="line"> DefaultMQPushConsumer consumer = <span class="keyword">new</span> DefaultMQPushConsumer(consumerGroup);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> consumer.setNamesrvAddr(nameServers);</span><br><span class="line"> consumer.subscribe(topic, tag);<span class="comment">//可订阅多个tag,但是一个消息只能有一个tag</span></span><br><span class="line"> consumer.setConsumeThreadMin(consumeThreadMin);</span><br><span class="line"> consumer.setConsumeThreadMax(consumeThreadMax);</span><br><span class="line"> consumer.setConsumeFromWhere(offset);</span><br><span class="line"> consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize);</span><br><span class="line"> consumer.setPullBatchSize(pullBatchSize);</span><br><span class="line"> consumer.setMessageModel(messageModel);</span><br><span class="line"> <span class="comment">//注册监听类的时候,不能使用匿名内部类。不然的话只消费一次消费者就挂了, 监听类要单独写。</span></span><br><span class="line"> MQMessageListenerOrderly listener = <span class="keyword">new</span> MQMessageListenerOrderly(<span class="keyword">this</span>);</span><br><span class="line"> consumer.registerMessageListener(listener);</span><br><span class="line"> consumer.start();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注册监听类的时候,不能使用匿名内部类。不然的话只消费一次消费者就挂了, 监听类要单独写。</p><p><strong>自定义监听类:</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MQMessageListenerOrderly</span> <span class="keyword">implements</span> <span class="title">MessageListenerOrderly</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ConsumeOrderlyStatus <span class="title">consumeMessage</span><span class="params">(List<MessageExt> msgs, ConsumeOrderlyContext context)</span> </span>{</span><br><span class="line"> <span class="comment">// 设置自动提交</span></span><br><span class="line"> context.setAutoCommit(<span class="keyword">true</span>);</span><br><span class="line"> <span class="keyword">for</span> (MessageExt msg : msgs) {</span><br><span class="line"> System.out.println(msg + <span class="string">",内容:"</span> + <span class="keyword">new</span> String(msg.getBody()));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> TimeUnit.SECONDS.sleep(<span class="number">5L</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"></span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> ConsumeOrderlyStatus.SUCCESS;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>消息消费端启动类:</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@ComponentScan</span>(basePackages = {<span class="comment">/*packages...*/</span>},</span><br><span class="line"> excludeFilters = {<span class="meta">@ComponentScan</span>.Filter(type = FilterType.CUSTOM, classes = ApplicationExcludeFilter.class)})</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConsumerApplication</span> <span class="keyword">implements</span> <span class="title">CommandLineRunner</span> </span>{</span><br><span class="line"> <span class="comment">//使用application.properties里定义的topic属性</span></span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${rocketmq.cluster.nameServer.addr}"</span>)</span><br><span class="line"> <span class="keyword">private</span> String nameServers;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${rocketmq.message.result.topic}"</span>)</span><br><span class="line"> <span class="keyword">private</span> String resultTopic;</span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${rocketmq.message.result.consumerGroup}"</span>)</span><br><span class="line"> <span class="keyword">private</span> String resultConsumerGroupName;</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line"> </span><br><span 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">(String[] args)</span> </span>{</span><br><span class="line"> System.setProperty(CLIENT_LOG_USESLF4J,<span class="string">"true"</span>);</span><br><span class="line"> SpringApplication.run(ConsumerApplication.class, args);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">(String... strings)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">//从MQ队列消费计算结果</span></span><br><span class="line"> consumer = <span class="keyword">new</span> MQPushConsumerOrderly(nameServers, consumerGroup, topic, tag, consumeMessageBatchMaxSize, consumeThreadMin,</span><br><span class="line"> consumeThreadMax, pullBatchSize, messageModel, offset) {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">poll</span><span class="params">(List<MessageExt> messageExts)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (MessageExt msg : messageExts) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> consumer.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上就是一个简单的使用Spring Boot框架集成RocketMQ实现基本的消息发送和接收的实例,在以后的工作中对RocketMQ的事务消费、消息存储有深入的理解再另行整理。</p><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><h4 id="关于Spring-Boot"><a href="#关于Spring-Boot" class="headerlink" title="关于Spring Boot"></a><span id="jumpSpringAbout">关于Spring Boot</span></h4><p>Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。</p><p>Spring Boot基于“约定大于配置”(Convention over configuration)这一理念来快速地开发、测试、运行和部署Spring应用,并能通过简单地与各种启动器(如 spring-boot-web-starter)结合,让应用直接以命令行的方式运行,不需再部署到独立容器中。这种简便直接快速构建和开发应用的过程,可以使用约定的配置并且简化部署,受到越来越多的开发者的欢迎。</p><h4 id="Kafka、RocketMQ、RabbitMQ的比较"><a href="#Kafka、RocketMQ、RabbitMQ的比较" class="headerlink" title="Kafka、RocketMQ、RabbitMQ的比较"></a><span id="jumpCompare">Kafka、RocketMQ、RabbitMQ的比较</span></h4><ul><li><p>Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务。</p><p>大型公司建议可以选用,如果有日志采集功能,肯定是首选kafka了。</p></li><li><p>RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。</p><p>结合erlang语言本身的并发优势,性能较好,社区活跃度也比较高,但是不利于做二次开发和维护。不过,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug。</p><p>如果你的数据量没有那么大,小公司优先选择功能比较完备的RabbitMQ。</p></li><li><p>RocketMQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。</p><p>天生为金融互联网领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。</p><p>RoketMQ在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ。</p></li></ul><h4 id="关于RocketMQ的一些概念"><a href="#关于RocketMQ的一些概念" class="headerlink" title="关于RocketMQ的一些概念"></a><span id="jumpRocketmqConcept">关于RocketMQ的一些概念</span></h4><p><strong>Producer</strong>:消息生产者,生产者的作用就是将消息发送到 MQ,生产者本身既可以产生消息,如读取文本信息等。也可以对外提供接口,由外部应用来调用接口,再由生产者将收到的消息发送到 MQ。</p><p><strong>Producer Group</strong>:生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组。在这里可以不用关心,只要知道有这么一个概念即可。</p><p><strong>Consumer</strong>:消息消费者,简单来说,消费 MQ 上的消息的应用程序就是消费者,至于消息是否进行逻辑处理,还是直接存储到数据库等取决于业务需要。</p><p><strong>Consumer Group</strong>:消费者组,和生产者类似,消费同一类消息的多个 consumer 实例组成一个消费者组。</p><p><strong>Topic</strong>:Topic 是一种消息的逻辑分类,比如说你有订单类的消息,也有库存类的消息,那么就需要进行分类,一个是订单 Topic 存放订单相关的消息,一个是库存 Topic 存储库存相关的消息。</p><p><strong>Message</strong>:Message 是消息的载体。一个 Message 必须指定 topic,相当于寄信的地址。Message 还有一个可选的 tag 设置,以便消费端可以基于 tag 进行过滤消息。也可以添加额外的键值对,例如你需要一个业务 key 来查找 broker 上的消息,方便在开发过程中诊断问题。</p><p><strong>Tag</strong>:标签可以被认为是对 Topic 进一步细化。一般在相同业务模块中通过引入标签来标记不同用途的消息。</p><p><strong>Broker</strong>:Broker 是 RocketMQ 系统的主要角色,其实就是前面一直说的 MQ。Broker 接收来自生产者的消息,储存以及为消费者拉取消息的请求做好准备。</p><p><strong>Name Server</strong>:Name Server 为 producer 和 consumer 提供路由信息。</p><p><strong>RocketMQ的概念模型如下:</strong> </p><p><img src="https://i.loli.net/2019/03/28/5c9c7b4c6f381.jpg" alt="RocketMQModel.jpg"></p><p><strong>RocketMQ的部署模型如下:</strong> </p><p><img src="https://i.loli.net/2019/03/28/5c9c7b4c651ee.jpg" alt="RocketMQBuild.jpg"></p><h4 id="SpringBootApplication-注解简析"><a href="#SpringBootApplication-注解简析" class="headerlink" title="@SpringBootApplication 注解简析"></a><span id="jumpSpringBootApplication">@SpringBootApplication 注解简析</span></h4><p>@SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan。</p><p>因为@SpringBootConfiguration ,@EnableAutoConfiguration,@ComponentScan这些注解一般都是一起使用来注解mian()方法所在的类,所以Spring Boot提供了一个统一的注解@SpringBootApplication。</p><ul><li><code>@SpringBootConfiguration</code>继承自<code>@Configuration</code>,二者功能也一致,标注当前类是配置类,并会将当前类内声明的一个或多个以<code>@Bean</code>注解标记的方法的实例纳入到Spring容器中,并且实例名就是方法名。</li><li>@EnableAutoConfiguration的作用启动自动的配置,@EnableAutoConfiguration注解的意思就是Springboot根据你添加的jar包来配置你项目的默认配置,比如根据spring-boot-starter-web,来判断你的项目是否需要添加了webmvc和tomcat,就会自动的帮你配置web项目中所需要的默认配置。在下面博客会具体分析这个注解,快速入门的demo实际没有用到该注解。</li><li>@ComponentScan,扫描当前包及其子包下被@Component,@Controller,@Service,@Repository注解标记的类并纳入到spring容器中进行管理。是以前的<code><context:component-scan></code>(以前使用在xml中使用的标签,用来扫描包配置的平行支持)。所以本demo中的User为何会被spring容器管理。</li></ul><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ol><li><a href="http://jm.taobao.org/2018/11/28/%E6%88%91%E7%94%A8%E8%BF%99%E7%A7%8D%E6%96%B9%E6%B3%95/#more" target="_blank" rel="noopener">如何在优雅地Spring 中实现消息的发送和消费</a></li><li><a href="http://jm.taobao.org/2018/11/06/%E6%BB%B4%E6%BB%B4%E5%87%BA%E8%A1%8C%E5%9F%BA%E4%BA%8ERocketMQ%E6%9E%84%E5%BB%BA%E4%BC%81%E4%B8%9A%E7%BA%A7%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E6%9C%8D%E5%8A%A1%E7%9A%84%E5%AE%9E%E8%B7%B5/#more" target="_blank" rel="noopener">滴滴出行基于RocketMQ构建企业级消息队列服务的实践</a></li><li><a href="http://www.10tiao.com/html/683/201811/2650718577/1.html" target="_blank" rel="noopener">一文讲透Apache RocketMQ技术精华</a></li><li><a href="https://www.jianshu.com/p/453c6e7ff81c" target="_blank" rel="noopener">分布式开放消息系统(RocketMQ)的原理与实践</a></li></ol>]]></content>
<summary type="html">
<p><img src="https://i.loli.net/2019/03/28/5c9c80dda5387.png" alt="RocketMQ.png"></p>
<h2 id="文章概要"><a href="#文章概要" class="headerlink" title="文章概要"></a>文章概要</h2><p>最近看了阿里中间件团队的几篇技术博客,详情见:<a href="http://jm.taobao.org/" target="_blank" rel="noopener">阿里中间件团队博客</a>,其中有关RocketMQ的几篇文章写得很好。还是那句话,学习本身就是一个不断获取知识然后投入实践的过程,本文就组内项目中使用的RocketMQ集成Spring Boot框架来实现消息发送消费的解决方案进行一个简单的梳理。鉴于自身当前对中间件优化方面还没有太深的接触,文中有可能会出现一些理解错误,难免不贻笑大方,所以权当成个人的学习笔记,方便记忆和以后的深入学习。</p>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="中间件" scheme="http://yoursite.com/categories/Java/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
<category term="消息队列" scheme="http://yoursite.com/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/"/>
<category term="Spring Boot" scheme="http://yoursite.com/tags/Spring-Boot/"/>
<category term="RocketMQ" scheme="http://yoursite.com/tags/RocketMQ/"/>
</entry>
<entry>
<title>OpenJDK12的新特性(下)</title>
<link href="http://yoursite.com/2019/03/26/OpenJDK12%E7%9A%84%E6%96%B0%E7%89%B9%E6%80%A7%EF%BC%88%E4%B8%8B%EF%BC%89/"/>
<id>http://yoursite.com/2019/03/26/OpenJDK12的新特性(下)/</id>
<published>2019-03-26T07:27:18.412Z</published>
<updated>2019-08-05T09:02:53.257Z</updated>
<content type="html"><![CDATA[<p><img src="https://i.loli.net/2019/03/28/5c9c829850beb.jpg" alt="OpenJDK.jpg"></p><h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>OpenJDK 12是由JSR 386在Java Community Process中指定的Java SE平台的版本12的开源参考实现,于2019年3月19日达到General Availability版本。GPL(General Public License)协议下的生产就绪二进制文件可从Oracle获得;其他供应商的二进制文件很快就会出现。</p><p>该版本的功能和时间表是通过JEP流程提出和跟踪的,并由JEP 2.0提案进行了修订。该版本使用JDK Release Process(JEP 3)生成发布。</p><p>本文根据OpenJDK 12的官方文档:<a href="http://openjdk.java.net/projects/jdk/12/" target="_blank" rel="noopener">OpenJDK 12</a>,对其新特性进行整理,受本人翻译水平所限,难免有翻译或理解错误,望不吝指正。上篇看这里:<a href="https://f8f-1bearcat.github.io/2019/03/26/OpenJDK12%E7%9A%84%E6%96%B0%E7%89%B9%E6%80%A7%EF%BC%88%E4%B8%8A%EF%BC%89/#jump334" target="_blank" rel="noopener">OpenJDK12的新特性(上)</a>。</p><a id="more"></a><h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><table><thead><tr><th>JEP</th><th>Features</th></tr></thead><tbody><tr><td>189:</td><td><a href="#jump189">Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)</a></td></tr><tr><td>230:</td><td><a href="#jump230">Microbenchmark Suite</a></td></tr><tr><td>325:</td><td><a href="#jump325">Switch Expressions (Preview)</a></td></tr><tr><td>334:</td><td><a href="#jump334">JVM Constants API</a></td></tr><tr><td>340:</td><td><a href="#jump340">One AArch64 Port, Not Two</a></td></tr><tr><td>341:</td><td><a href="#jump341">Default CDS Archives</a></td></tr><tr><td>344:</td><td><a href="#jump344">Abortable Mixed Collections for G1</a></td></tr><tr><td>346:</td><td><a href="#jump346">Promptly Return Unused Committed Memory from G1</a></td></tr></tbody></table><h4 id="JEP340-One-AArch64-Port-Not-Two"><a href="#JEP340-One-AArch64-Port-Not-Two" class="headerlink" title="JEP340: One AArch64 Port, Not Two"></a><span id="jump340">JEP340: One AArch64 Port, Not Two</span></h4><p><strong>摘要</strong>:</p><p>删除与arm64端口相关的所有源,同时保留32位ARM端口和64位aarch64端口。</p><p><strong>动机</strong>:</p><p>删除此端口将允许所有贡献者将他们的精力集中在单个64位ARM实现上,并消除维护两个端口所需的重复工作。</p><p><strong>描述</strong>:</p><p>JDK中存在两个64位ARM端口。 这些的主要来源是<code>src/hotspot/cpu/arm</code>和<code>open/src/hotspot/cpu/aarch64</code>目录。虽然这两个端口都产生了aarch64实现,但是为了这个JEP,我们将引用前者arm64,它由Oracle提供,后者是aarch64。</p><p>以下是将作为此JEP的一部分完成的任务:</p><ul><li>在<code>open/src/hotspot/cpu/arm</code>中删除所有与<code>arm64-specific</code>的源和与64位而不是32位构建相关的<code>#ifdefs</code>;</li><li>扫描与此端口相关的<code>#ifdefs</code>的剩余JDK源;</li><li>删除构建此端口的build option,使aarch64端口成为64位ARM体系结构的默认构建;</li><li>验证剩余的32位ARM端口是否继续构建并运行一致性测试。</li></ul><h4 id="JEP341-Default-CDS-Archives"><a href="#JEP341-Default-CDS-Archives" class="headerlink" title="JEP341: Default CDS Archives"></a><span id="jump341">JEP341: Default CDS Archives</span></h4><p><strong>摘要</strong>:</p><p>在64位平台上使用默认类列表,增强JDK build process以生成 class data-sharing(CDS)存档。</p><p><strong>目标</strong>:</p><ul><li>改善开箱即用的启动时间</li><li>消除用户运行<code>-Xshare:dump</code>以从CDS中受益的需要</li></ul><p><strong>非目标</strong>:</p><ul><li>我们将仅为native builds生成默认存档,而不是针对交叉编译的构建。</li><li>我们将仅为64位版本生成默认存档,稍后可能会添加对32位版本的支持。</li></ul><p><strong>动机</strong>:</p><p>自JDK 8u40以来,许多增强功能被添加到基本CDS特性中。启用CDS提供的启动时间和内存共享优势显着增加。 使用JDK 11早期访问版本14在Linux/x64上完成的测量显示运行HelloWorld的启动时间缩短了32%。 在其他64位平台上,已观察到类似或更高的启动性能增益。</p><p>目前,JDK映像包括在lib目录中构建时生成的默认类列表。想要利用CDS的用户,即使只使用JDK中提供的默认类列表,也必须运行<code>java -Xshare:dump</code>作为额外步骤。此选项已记录在案,但许多用户并未意识到这一点。</p><p><strong>描述</strong>:</p><p>修改JDK构建以在链接图像后运行<code>java -Xshare:dump</code>,(可以包括附加的命令行选项以微调GC堆大小等,以便为常见情况获得更好的内存布局)将生成的CDS存档保留在<code>lib/server</code>目录中。</p><p>用户将自动受益于CDS功能,因为默认情况下为JDK 11(JDK-8197967)中的服务VM启用了<code>-Xshare:auto</code>。 要禁用CDS,请使用<code>-Xshare:off</code>运行。</p><p>具有更高级要求的用户(例如,使用包括应用程序类、不同GC配置等的自定义类列表)仍然可以像以前一样创建自定义CDS存档。</p><p><strong>选择</strong>:略</p><p><strong>测试</strong>:</p><p>启用CDS的现有自动化测试已足够。</p><h4 id="JEP344-Abortable-Mixed-Collections-for-G1"><a href="#JEP344-Abortable-Mixed-Collections-for-G1" class="headerlink" title="JEP344: Abortable Mixed Collections for G1"></a><span id="jump344">JEP344: Abortable Mixed Collections for G1</span></h4><p><strong>摘要</strong>:</p><p>如果G1 mixed 收集可能超过停顿目标,则使其可以中止。</p><p><strong>非目标</strong>:</p><p>使G1中的所有停顿都可以中止。</p><p><strong>动机</strong>:</p><p>G1的目标之一是满足用户提供的暂停时间目标的条件的时候暂停其收集。G1使用高级分析引擎来选择在收集期间要完成的工作(这部分基于应用程序行为),此选择的结果是一组称为<em>collection set</em>的区域。一旦确定了collection set并且已经开始收集工作,则G1必须不停地收集collection set的所有region中的所有存活的对象。如果heuristics选择过大的collection set,则此行为可能导致G1超过停顿时间目标,例如,如果应用程序的行为发生变化,使得heuristics选择在”陈旧“数据进行处理,则可能发生这种情况。特别是在mixed收集期间可以观察到这种情况,期间collection set通常会包含很多old region。需要一种机制来检测heuristics何时反复为收集选择错误的工作量,如果此现象存在的话,则让G1逐步递增地执行收集工作,并且可以在每个步骤之后中止收集。这种机制将允许G1更易于满足停顿时间目标。</p><p><strong>描述</strong>:</p><p>如果G1发现collection set选择heuristics重复选择错误的区域数,则切换到更复杂的方式来执行mixed收集:将collection set拆分为两个部分:强制部分和可选部分。强制性部分包括G1不能递增地处理的collection set的部分(例如young regions),但也可以包含old region以提高效率。例如,预计的collection set的80%构成强制性部分,剩余的20%(仅由old region组成)构成可选部分。</p><p>G1完成强制部分的收集后,如果还有剩余时间,G1会以更细粒度的级别开始收集可选部分。此可选部分的集合粒度取决于剩余的时间量,一次最多只能到一个region。完成对可选collection set所有部分的收集后,G1可以根据剩余时间决定停止收集。</p><p>随着预测再次变得更准确,一次收集的可选部分变得越来越小,直到强制部分再次包括所有collection set(即,G1完全依赖于其heuristics)。如果预测再次变得不准确,则下一次收集将再次包含强制和可选部分。</p><p><strong>选择</strong>:</p><ul><li><p>改进分析引擎和heuristics,以便他们不会做出错误的预测。由于heuristics方法依赖于之前应用程序的行为,因此无法获得100%准确的heuristics方法。但是,改进的heuristics方法将自动减少对此机制的需求。</p></li><li><p>始终使用与预测有关的“安全边际”。例如,如果预测返回x,则始终使用0.8 * x(20%的安全范围)。这可能在大多数情况下都有效,但是当预测有效时会导致sub-optimal 性能,因为这意味着G1只会使用80%的停顿目标。</p></li><li><p>重用现有的疏散失败机制来中止mixed收集。这已被拒绝作为替代方案,因为在这样的中止时,不能保证该次收集释放任何region。我们提出的机制通过回收region基础(G1的空间回收粒度)上的collection set的空间来保证空间回收的进展。</p></li></ul><p><strong>测试</strong>:</p><p>构成实现的各个C++部分应该使用C++单元测试进行测试。由于可中止的mixed收集代码将是G1 GC的组成部分,因此只需运行现有测试即可执行代码。</p><p><strong>风险和假设</strong>:</p><ul><li><p>将collection set拆分为必需和可选部分时,需要为可选collection set部分维护一些其他数据。 这会产生轻微的CPU开销,小于1%,并且仅适用于使用可选collection set部分的mixed收集。</p></li><li><p>使用可选collection set部分的mixed收集期间的本机内存使用量也可能增加。这是因为在mixed收集期间必须跟踪到可选部分中的区域的一些附加传入指针。</p></li></ul><h4 id="JEP346-Promptly-Return-Unused-Committed-Memory-from-G1"><a href="#JEP346-Promptly-Return-Unused-Committed-Memory-from-G1" class="headerlink" title="JEP346: Promptly Return Unused Committed Memory from G1"></a><span id="jump346">JEP346: Promptly Return Unused Committed Memory from G1</span></h4><p><strong>摘要</strong>:</p><p>增强G1垃圾收集器,以便在空闲时自动将Java堆内存返回给操作系统。</p><p><strong>非目标</strong>:</p><ul><li><p>在Java进程之间共享已提交但空的页面。应将内存返回(未提交)到操作系统。</p></li><li><p>回馈内存的过程不需要节省CPU资源,也不需要是瞬时的。</p></li><li><p>使用不同的方法返回除可用内存以外的内存。</p></li><li><p>支持除G1之外的其他收集器。</p></li></ul><p><strong>成功标准</strong>:</p><p>如果应用程序活动非常低,G1应该在合理的时间段内释放未使用的Java堆内存。</p><p><strong>动机</strong>:</p><p>目前G1垃圾收集器可能无法及时将已提交的Java堆内存返回给操作系统。G1仅在full GC或并发周期内从Java堆返回内存。由于G1很难完全避免full GC,并且只触发基于Java堆占用和分配活动的并发周期,因此除非在外部强制执行,否则在许多情况下它不会返回Java堆内存。</p><p>在使用资源支付的容器环境中,这种行为特别不利。即使在VM由于不活动而仅使用其分配的内存资源的一小部分的阶段,G1也将保留所有Java堆。这导致客户始终为所有资源付费,云提供商无法充分利用其硬件。</p><p>如果VM能够检测到Java堆的利用率不足(“空闲”阶段),并在此期间自动减少其堆使用量,则两者都将受益。</p><p>Shenandoah和OpenJ9的GenCon收集器已经提供了类似的功能。</p><p>在<a href="http://www.gsd.inesc-id.pt/~rbruno/publications/rbruno-ismm18.pdf" target="_blank" rel="noopener">Bruno et al., section 5.5</a>中使用原型进行的测试表明,基于在白天为HTTP请求提供服务的Tomcat服务器的实际利用情况(夜间大部分空闲),此解决方案可以将Java VM提交的内存量减少85%。</p><p><strong>描述</strong>:</p><p>为了实现向操作系统返回最大内存量的目标,G1将在应用程序不活动期间定期尝试继续或触发并发周期以确定整体Java堆使用情况。这将导致它自动将Java堆的未使用部分返回给操作系统。(可选)在用户控制下,可以执行full GC以最大化返回的内存量。</p><p>如果以下两者都发生,应用程序将被视为非活动状态,并且G1会触发定期垃圾收集:</p><ul><li><p>自任何先前的垃圾收集停顿以来已超过<code>G1PeriodicGCInterval</code>毫秒,此时没有正在进行的并发周期。值为零表示禁用快速回收内存的定期垃圾收集。</p></li><li><p>JVM主机系统(例如容器)上的<code>getloadavg()</code>调用返回的平均一分钟系统负载值低于<code>G1PeriodicGCSystemLoadThreshold</code>。如果<code>G1PeriodicGCSystemLoadThreshold</code>为零,则忽略此条件。</p></li></ul><p>如果不满足这些条件中的任何一个,则取消当前的预期定期垃圾收集。下次<code>G1PeriodicGCInterval</code>时间过去时,将重新考虑定期垃圾收集。</p><p>周期性垃圾收集的类型由<code>G1PeriodicGCInvokesConcurrent</code>选项的值确定:如果设置,G1继续或启动并发周期,否则G1执行full GC。在任一收集过程的末尾,G1调整当前的Java堆大小,可能会将内存返回给操作系统。新的Java堆大小由用于调整Java堆大小的现有配置确定,包括但不限于<code>MinHeapFreeRatio</code>,<code>MaxHeapFreeRatio</code>以及最小和最大堆大小配置。</p><p>默认情况下,G1在此定期垃圾回收期间启动或继续并发循环。这最大限度地减少了应用程序的中断,但与完整收集相比,最终可能无法返回尽可能多的内存。</p><p>由此机制触发的任何垃圾收集都使用<code>G1 Periodic Collection</code>进行标记。此类日志示例如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">(1) [6.084s][debug][gc,periodic ] Checking for periodic GC.</span><br><span class="line"> [6.086s][info ][gc ] GC(13) Pause Young (Concurrent Start) (G1 Periodic Collection) 37M->36M(78M) 1.786ms</span><br><span class="line">(2) [9.087s][debug][gc,periodic ] Checking for periodic GC.</span><br><span class="line"> [9.088s][info ][gc ] GC(15) Pause Young (Prepare Mixed) (G1 Periodic Collection) 9M->9M(32M) 0.722ms</span><br><span class="line">(3) [12.089s][debug][gc,periodic ] Checking for periodic GC.</span><br><span class="line"> [12.091s][info ][gc ] GC(16) Pause Young (Mixed) (G1 Periodic Collection) 9M->5M(32M) 1.776ms</span><br><span class="line">(4) [15.092s][debug][gc,periodic ] Checking for periodic GC.</span><br><span class="line"> [15.097s][info ][gc ] GC(17) Pause Young (Mixed) (G1 Periodic Collection) 5M->1M(32M) 4.142ms</span><br><span class="line">(5) [18.098s][debug][gc,periodic ] Checking for periodic GC.</span><br><span class="line"> [18.100s][info ][gc ] GC(18) Pause Young (Concurrent Start) (G1 Periodic Collection) 1M->1M(32M) 1.685ms</span><br><span class="line">(6) [21.101s][debug][gc,periodic ] Checking for periodic GC.</span><br><span class="line"> [21.102s][info ][gc ] GC(20) Pause Young (Concurrent Start) (G1 Periodic Collection) 1M->1M(32M) 0.868ms</span><br><span class="line">(7) [24.104s][debug][gc,periodic ] Checking for periodic GC.</span><br><span class="line"> [24.104s][info ][gc ] GC(22) Pause Young (Concurrent Start) (G1 Periodic Collection) 1M->1M(32M) 0.778ms</span><br></pre></td></tr></table></figure><p>在上面的示例中,使用<code>G1PeriodicGCInterval</code>为3000ms运行,在步骤(1)G1中,在应用程序不活动之后,启动并发周期,如(<code>Concurrent Start</code>)和(<code>G1 Periodic Collection</code>)所示。 该并发周期最初返回一些内存,通过从(1)到(2)的容量数(78M)和(32M)的减少来表示。 在(2)到(4)之间的间隔中,触发更多的周期性收集,这次触发mixed collection以对堆进行整理。(5)到(7)启动并发周期,因为G1策略确定当时在老年代中没有足够的垃圾来启动mixed GC阶段。在这种情况下,定期垃圾收集(5)到(7)不会进一步缩小堆,因为已经达到最小堆大小。</p><p>在应用程序不活动期间对对象活跃性的改变(例如,由于软引用到期)可以在空闲期间触发进一步缩小已提交的Java堆。</p><p><strong>选择</strong>:</p><p>可以从VM外部实现类似的功能,例如,通过jcmd工具或注入VM的一些代码。这有隐藏的成本:假设使用cron-based任务执行检查,如果节点上有数百或数千个容器,这可能意味着堆压缩操作由许多这些容器同时执行, 导致主机上出现非常大的CPU峰值。</p><p>另一种替代方法是Java代理,它自动附加到每个Java进程。然后,当容器在不同的时间开始时,检查的时间会自然分配,而且由于没有启动任何新进程,因此在CPU上的成本更低。然而,这种方法为用户增加了显著的复杂性,这可能会阻碍采用。</p><p>给定的用例,及时缩小Java堆,被认为是一个相当常见的用例,需要在VM中提供特殊支持。</p><p><strong>风险和假设</strong>:</p><p>在配置的默认值中,我们禁用此功能。 这导致延迟或吞吐量敏感应用程序的VM行为没有意外更改。启用后,我们假设通常需要将Java堆内存返回给操作系统,并且所产生的并发周期或其继续对应用程序吞吐量的影响可以忽略不计。</p><p>启用此功能后,VM将在上述条件下运行这些定期收集,而不管其他option如何。 例如,VM可以假设如果用户将<code>-Xms</code>设置为-<code>Xmx</code>和其他(组合)选项以获得最小且一致的垃圾收集暂停。出于一致性原因,情况并非如此。</p><p>如果定期垃圾收集仍然会太干扰程序执行,我们提供控制以让决策考虑整个系统CPU负载,或让用户完全禁用定期垃圾收集。</p>]]></content>
<summary type="html">
<p><img src="https://i.loli.net/2019/03/28/5c9c829850beb.jpg" alt="OpenJDK.jpg"></p>
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>OpenJDK 12是由JSR 386在Java Community Process中指定的Java SE平台的版本12的开源参考实现,于2019年3月19日达到General Availability版本。GPL(General Public License)协议下的生产就绪二进制文件可从Oracle获得;其他供应商的二进制文件很快就会出现。</p>
<p>该版本的功能和时间表是通过JEP流程提出和跟踪的,并由JEP 2.0提案进行了修订。该版本使用JDK Release Process(JEP 3)生成发布。</p>
<p>本文根据OpenJDK 12的官方文档:<a href="http://openjdk.java.net/projects/jdk/12/" target="_blank" rel="noopener">OpenJDK 12</a>,对其新特性进行整理,受本人翻译水平所限,难免有翻译或理解错误,望不吝指正。上篇看这里:<a href="https://f8f-1bearcat.github.io/2019/03/26/OpenJDK12%E7%9A%84%E6%96%B0%E7%89%B9%E6%80%A7%EF%BC%88%E4%B8%8A%EF%BC%89/#jump334" target="_blank" rel="noopener">OpenJDK12的新特性(上)</a>。</p>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="译文" scheme="http://yoursite.com/categories/Java/%E8%AF%91%E6%96%87/"/>
<category term="JVM" scheme="http://yoursite.com/tags/JVM/"/>
<category term="OpenJDK" scheme="http://yoursite.com/tags/OpenJDK/"/>
</entry>
<entry>
<title>OpenJDK12的新特性(上)</title>
<link href="http://yoursite.com/2019/03/26/OpenJDK12%E7%9A%84%E6%96%B0%E7%89%B9%E6%80%A7%EF%BC%88%E4%B8%8A%EF%BC%89/"/>
<id>http://yoursite.com/2019/03/26/OpenJDK12的新特性(上)/</id>
<published>2019-03-26T07:27:06.771Z</published>
<updated>2019-08-05T09:02:46.792Z</updated>
<content type="html"><![CDATA[<p><img src="https://i.loli.net/2019/03/28/5c9c829850beb.jpg" alt="OpenJDK.jpg"></p><h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>OpenJDK 12是由JSR 386在Java Community Process中指定的Java SE平台的版本12的开源参考实现,于2019年3月19日达到General Availability版本。GPL(General Public License)协议下的生产就绪二进制文件可从Oracle获得;其他供应商的二进制文件很快就会出现。</p><p>该版本的功能和时间表是通过JEP流程提出和跟踪的,并由JEP 2.0提案进行了修订。该版本使用JDK Release Process(JEP 3)生成发布。</p><p>本文根据OpenJDK 12的官方文档:<a href="http://openjdk.java.net/projects/jdk/12/" target="_blank" rel="noopener">OpenJDK 12</a>,对其新特性进行整理,受本人翻译水平所限,难免有翻译或理解错误,望不吝指正。</p><a id="more"></a><h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><table><thead><tr><th>JEP</th><th>Features</th></tr></thead><tbody><tr><td>189:</td><td><a href="#jump189">Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)</a></td></tr><tr><td>230:</td><td><a href="#jump230">Microbenchmark Suite</a></td></tr><tr><td>325:</td><td><a href="#jump325">Switch Expressions (Preview)</a></td></tr><tr><td>334:</td><td><a href="#jump334">JVM Constants API</a></td></tr><tr><td>340:</td><td><a href="#jump340">One AArch64 Port, Not Two</a></td></tr><tr><td>341:</td><td><a href="#jump341">Default CDS Archives</a></td></tr><tr><td>344:</td><td><a href="#jump344">Abortable Mixed Collections for G1</a></td></tr><tr><td>346:</td><td><a href="#jump346">Promptly Return Unused Committed Memory from G1</a></td></tr></tbody></table><p><strong>注意</strong>:因篇幅限制,下文将对OpenJDK 12的新特性:189、230、325、334进行整理,剩余特性的整理请见下篇:<a href="https://f8f-1bearcat.github.io/2019/03/26/OpenJDK12%E7%9A%84%E6%96%B0%E7%89%B9%E6%80%A7%EF%BC%88%E4%B8%8B%EF%BC%89/#more" target="_blank" rel="noopener">OpenJDK12的新特性(下)</a>。</p><h4 id="JEP189-Shenandoah-A-Low-Pause-Time-Garbage-Collector-Experimental"><a href="#JEP189-Shenandoah-A-Low-Pause-Time-Garbage-Collector-Experimental" class="headerlink" title="JEP189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)"></a><span id="jump189">JEP189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)</span></h4><p>低GC停顿的垃圾收集器(Experimental版本)</p><p><strong>摘要</strong>:</p><p>添加一个名为Shenandoah的新垃圾收集(GC)算法,在执行GC动作时可以通过并发的方式让Java程序继续执行,从而缩短Stop_The_World的时间。使用Shenandoah的停顿时间与堆大小无关,这意味着无论堆是200 MB还是200 GB,都将具有相同的稳定停顿时间。</p><p><strong>非目标</strong>:</p><p>这个GC算法并不是万能的,还有其他的垃圾收集算法会优先考虑吞吐量或者内存占用,而不是响应性。Shenandoah算法适用于那些注重响应性和可预测的停顿时间的应用,它并不能解决所有的JVM停顿问题,那些由于GC之外的其他原因,如达到安全点的时间(TTSP)或者监控通胀,导致的停顿时间超出了此JEP的范围。</p><p><strong>成功标准</strong>:</p><p>保持一致的较短的GC停顿时间。</p><p><strong>描述</strong>:</p><p>现代机器拥有比以往更多的内存和更多的处理器。服务水平协议(SLA)应用程序保证响应时间为10-500毫秒。为了满足该目标的低端,我们需要足够高效垃圾收集算法以允许程序在可用内存中运行,而且也要能做到永远不会中断正在运行的程序超过几毫秒。 Shenandoah是OpenJDK开源的低停顿收集器,旨在让我们更接近这些目标。</p><p>Shenandoah通过牺牲并发cpu周期和空间以优化停顿时间。我们为每个Java对象添加了一个间接指针,使得GC线程进行Java堆整理时能够与用户线程并发执行。由于整个GC过程中耗时最长的并发标记和整理过程收集器线程与用户线程可以一起工作,所以只需要在扫描线程堆栈以枚举根节点时停顿Java执行线程。</p><p>Shenandoah算法在<a href="https://www.researchgate.net/publication/306112816_Shenandoah_An_open-source_concurrent_compacting_garbage_collector_for_OpenJDK" target="_blank" rel="noopener">this PPPJ2016 paper</a>中有详细描述。</p><p>Shenandoah已经应用并且将由Red Hat提供aarch64和amd64支持。</p><p>在OpenJDK Shenandoah项目中的Shenandoah开发已经完成。在<a href="https://wiki.openjdk.java.net/display/shenandoah/Main" target="_blank" rel="noopener">Shenandoah wiki page</a>页面上查看有关当前开发流程,实现细节和可用性的更多详细信息。</p><p><strong>选择</strong>:</p><p>Zing/Azul有一个更低停顿的收集器,但是这项工作还没有贡献给OpenJDK。</p><p>ZGC有基于彩色指针的低停顿收集器, 我们期待比较两种策略的表现。</p><p>G1执行一些平行和并发工作,但它不执行并发回收(编者注:G1的筛选回收阶段,从Sun公司透漏出的信息来看,其实是支持与用户线程并发执行的,但是由于只会有一部分Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率)。</p><p>CMS执行并发标记,但它在停顿时执行年轻代复制,并且从不整理老年代, 这导致花费更多时间来管理老年代中的可用空间以及碎片问题(编者注:CMS是基于“标记-清除”算法实现的收集器,这就意味着收集结束的时候将有大量的空间碎片产生)。</p><p><strong>建立和调用</strong>:</p><p>作为实验性功能,Shenandoah要求在命令行中配置<code>-XX:+UnlockExperimentalVMOptions</code>参数,构建系统会自动禁用不受支持的配置。下游构建者可以选择在其他支持的平台上使用<code>--with-jvm-features = -shenandoahgc</code>禁用构建Shenandoah。</p><p>要启用/使用Shenandoah GC,需要使用以下JVM选项:<code>-XX:+UnlockExperimentalVMOptions</code> <code>-XX:+UseShenandoahGC</code>。</p><p>想要获取有关如何设置和调整Shenandoah GC的更多信息,请参阅<a href="https://wiki.openjdk.java.net/display/shenandoah/Main" target="_blank" rel="noopener">Shenandoah wiki</a>页面。</p><p><strong>测试</strong>:</p><p>Red Hat已经为我们的重要应用程序进行了大量测试。我们开发了许多Shenandoah特定的jtreg测试。Shenandoah从Fedora 24开始在Fedora中传送,并在Rhel 7.4中作为技术预览。使用<code>-XX:+UseShenandoahGC</code>运行标准的OpenJDK测试就足够了。</p><p><strong>风险和假设</strong>:</p><p>GC接口(JEP 304)已集成在JDK 11中,之后对GC接口进行了许多扩展和改进,这样可以最大限度地降低将Shenanodah添加到OpenJDK源代码库的风险。除此之外,任何无法合理编址的Shenandoah-specific代码路径都将受到<code>#ifdef INCLUDE_SHENANDOAHGC</code>或类似机制的保护。Shenandoah GC最初将被标记为实验性功能,因此在配置参数方面除<code>-XX:+UseShenandoahGC</code>之外还需要<code>-XX:+UnlockExperimentalVMOptions</code>。</p><h4 id="JEP230-Microbenchmark-Suite"><a href="#JEP230-Microbenchmark-Suite" class="headerlink" title="JEP230: Microbenchmark Suite"></a><span id="jump230">JEP230: Microbenchmark Suite</span></h4><p>Microbenchmark 套件</p><p><strong>摘要</strong>:</p><p>在JDK源代码中添加一套基本的microbenchmarks ,使开发人员可以轻松运行现有的microbenchmarks 并创建新的microbenchmarks 。</p><p><strong>目标</strong>:</p><ul><li>基于[Java Microbenchmark Harness(JMH)];<a href="http://openjdk.java.net/projects/code-tools/jmh" target="_blank" rel="noopener">[1]</a></li><li>稳定且经过调整的benchmarks,针对持续性能测试<br> - 在feature release以及non-feature releases的Feature Complete milestone之后提供稳定且不移动的套件<br> - 支持与先前JDK版本的适用测试比较</li><li>简易<ul><li>轻松添加新benchmarks</li><li>在APIs和options更改、不推荐使用或在开发期间删除时,可以轻松更新测试</li><li>易于构建</li><li>易于查找和运行benchmark</li></ul></li><li>支持JMH更新</li><li>在组件中包含大约一百个benchmarks的初始集</li></ul><p><strong>非目标</strong>:</p><ul><li><p>为新的JDK功能提供benchmarks不是目标,为新功能添加benchmarks是这些项目的一部分。</p></li><li><p>创建一套完整的benchmarks来覆盖JDK中所有内容不是目标。随着时间的推移,该套件将继续通过新编写的benchmarks,或通过专门针对扩展其覆盖范围的协作进行扩展。</p></li><li><p>为处理microbenchmarks中的二进制依赖提供解决方案不是目标,稍后可能会添加对此的支持。</p></li></ul><p><strong>描述</strong>:</p><p>microbenchmark套件将与JDK源代码位于一个目录下,并且在构建时将生成单个JAR文件。协同定位将简化在开发期间添加和定位benchmarks。在运行benchmarks时,JMH提供强大的过滤功能,允许用户仅运行当前感兴趣的benchmarks,确切的位置仍有待确定。</p><p>Benchmarking 通常需要与早期build甚至release版本进行比较,因此microbenchmarks 必须支持JDK(N),用于针对新JDK和JDK(N-1)中的特性,以及存在于早期release版本中的特性的benchmarks 。 这意味着,对于JDK 12,结构和构建脚本必须支持JDK 12和JDK 11的编译benchmarks 。benchmarks 将进一步使用描述他们正在测试的JDK区域的Java包名称进行划分。</p><p>建议使用以下目录结构:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">jdk/jdk</span><br><span class="line"> .../make/test (Shared folder for Makefiles)</span><br><span class="line"> .../test (Shared folder for functional tests)</span><br><span class="line"> .../micro/org/openjdk/bench</span><br><span class="line"> .../java (subdirectories similar to JDK packages and modules)</span><br><span class="line"> .../vm (subdirectories similar to HotSpot components)</span><br></pre></td></tr></table></figure><p>microbenchmark套件的构建将与普通的JDK构建系统集成。它将是一个单独的目标,在正常的JDK构建期间不会执行,以便为开发人员和其他对构建微基准套件不感兴趣的人保持较低的构建时间。要构建微基准套件,用户必须专门运行make build-microbenchmark或类似工具。另外,将支持使用<code>make test TEST =“micro:regexp”</code>运行基准测试。有关如何设置本地环境的说明将记录在<code>docs / testing.md | html</code>中。</p><p>benchmark将完全依赖于JMH,就像某些单元测试依赖于TestNG或jtreg一样,所以虽然对JMH的依赖是新的,但是构建的其他部分具有相似的依赖性。与jtreg相比的一个区别是JMH在构建期间都被使用,并且被打包为生成的JAR文件的一部分。</p><p>microbenchmark套件中的benchmark集将从<code>JMH JDK Microbenchmarks</code>项目中导入。这些构成了一组已在内部使用的经过调整和测试的microbenchmark。一个悬而未决的问题是,是将整个独立项目整体迁移到共处套件,还是将其作为更长寿命回归测试的stabilization forest。</p><p>但是,任何用户仍然希望确保其他参数(例如执行机器和JDK)在进行分析时是稳定且可比较的。在通常情况下,我们希望benchmark能够在不到一分钟的时间内完成整个运行。这不是大型或长期运行benchmark的包装框架;目标是提供一套快速且有针对性的benchmark。在某些特殊情况下,benchmark可能需要更长时间的预热或运行时间才能获得稳定的结果,但应尽可能避免这种情况。套件的目标不是充当大型工作负载的通用包装器;相反,意图是从更大的benchmark中提取关键组件或方法,并仅将该部分强调为microbenchmark。</p><p>作为该项目的一部分,将创建<a href="https://wiki.openjdk.java.net" target="_blank" rel="noopener">wiki.openjdk.java.net</a>上的新页面,以帮助解释如何开发新benchmarks并描述添加benchmark的要求。这些要求将要求遵守编码标准、可重现的性能,以及明确的benchmark测量文档及其测量内容。</p><p><strong>选择</strong>:</p><p>继续将microbenchmark套件维护为一个单独的项目<a href="http://openjdk.java.net/projects/code-tools/jmh-jdk-microbenchmarks/" target="_blank" rel="noopener">[2]</a>。</p><p>协同定位简化了为新功能添加benchmark,特别是在所有新功能开发的大部分在项目存储库(Valhalla,Amber等)中完成的世界中。 在单独的项目模型中被证明特别复杂的情况是测试对javac本身的更改,需要使用每个相应的JDK显式重建benchmark套件。 协同定位可以更优雅地解决这个特定用例,同时不禁止使用预先构建的benchmark捆绑包来在较长时间段内对稳定测试进行性能跟踪。</p><p><strong>测试</strong>:</p><p>作为常规性能测试的一部分,性能团队将验证microbenchmark测试,以确保仅添加稳定、调整好的和准确的microbenchmark测试。 还将根据具体情况对基准进行评估和分析,以确保其测试预期的功能。所有测试必须在所有适用的平台上运行多次,以确保它们稳定。</p><h4 id="JEP325-Switch-Expressions-Preview"><a href="#JEP325-Switch-Expressions-Preview" class="headerlink" title="JEP325: Switch Expressions (Preview)"></a><span id="jump325">JEP325: Switch Expressions (Preview)</span></h4><p><strong>摘要</strong>:</p><p>扩展switch语句,以便它可以用作语句或表达式,并且这两种形式都可以使用“traditional”或“simplified”的作用域,都可以实现对程序的流程控制。 这些更改将简化日常编码,并为在switch语句中使用模式匹配(JEP 305)做好准备。 </p><p><strong>动机</strong>:</p><p>当我们准备增强Java编程语言以支持模式匹配(JEP 305)时,现有switch语句的一些不规则性(长期以来一直是用户的烦恼)成为我们工作的障碍。这包括switch blocks的默认控制流行为(fall through),switch blocks的默认作用域,还包括对switch使用时仅作为语句,尽管作为表达式的话实现multi-way conditionals通常更自然。</p><p>当前Java的switch语句设计紧随C和C++等语言,并且默认支持fall-through语义。虽然这种传统的控制流通常用于编写low-level代码(例如用于二进制编码的解析器),但随着switch用于higher-level的contexts,其error-prone的性质开始超过其灵活性。</p><p>例如,在下面的代码中,许多break语句使它不必要地冗长,并且这种视觉干扰经常掩盖难以debug的错误,比如缺少break语句将导致发生意外的fall-through错误。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> (day) {</span><br><span class="line"> <span class="keyword">case</span> MONDAY:</span><br><span class="line"> <span class="keyword">case</span> FRIDAY:</span><br><span class="line"> <span class="keyword">case</span> SUNDAY:</span><br><span class="line"> System.out.println(<span class="number">6</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> TUESDAY:</span><br><span class="line"> System.out.println(<span class="number">7</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> THURSDAY:</span><br><span class="line"> <span class="keyword">case</span> SATURDAY:</span><br><span class="line"> System.out.println(<span class="number">8</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> WEDNESDAY:</span><br><span class="line"> System.out.println(<span class="number">9</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们将引入一种新形式的switch label,写成“case L - >”,用来表示如果标签匹配则只执行标签右侧的代码。 例如, 前面的代码现在可以写成下面这种形式:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> (day) {</span><br><span class="line"> <span class="keyword">case</span> MONDAY, FRIDAY, SUNDAY -> System.out.println(<span class="number">6</span>);</span><br><span class="line"> <span class="keyword">case</span> TUESDAY -> System.out.println(<span class="number">7</span>);</span><br><span class="line"> <span class="keyword">case</span> THURSDAY, SATURDAY -> System.out.println(<span class="number">8</span>);</span><br><span class="line"> <span class="keyword">case</span> WEDNESDAY -> System.out.println(<span class="number">9</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>(此示例还使用多个case 标签:我们支持在单个switch label中多个标签以逗号分隔开。)</p><p>“case L - >”开关标签右侧的代码仅限于表达式、块或(为方便起见)throw语句。 这具有令人满意的结果,如果一条分支(arm)引入局部变量,则它必须包含在该分支的块中,不在 switch block中的任何其他分支的作用域内。 这消除了“traditional”作用域的switch block的另一个烦恼,即局部变量的范围是整个switch block。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> (day) {</span><br><span class="line"> <span class="keyword">case</span> MONDAY:</span><br><span class="line"> <span class="keyword">case</span> TUESDAY:</span><br><span class="line"> <span class="keyword">int</span> temp = ...</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> WEDNESDAY:</span><br><span class="line"> <span class="keyword">case</span> THURSDAY:</span><br><span class="line"> <span class="keyword">int</span> temp2 = ... <span class="comment">// Why can't I call this temp?</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">int</span> temp3 = ... <span class="comment">// Why can't I call this temp?</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>许多现有的switch语句本质上是switch表达式的模拟,其中每个arm分配给一个公共目标变量或返回一个值:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> numLetters;</span><br><span class="line"><span class="keyword">switch</span> (day) {</span><br><span class="line"> <span class="keyword">case</span> MONDAY:</span><br><span class="line"> <span class="keyword">case</span> FRIDAY:</span><br><span class="line"> <span class="keyword">case</span> SUNDAY:</span><br><span class="line"> numLetters = <span class="number">6</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> TUESDAY:</span><br><span class="line"> numLetters = <span class="number">7</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> THURSDAY:</span><br><span class="line"> <span class="keyword">case</span> SATURDAY:</span><br><span class="line"> numLetters = <span class="number">8</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> WEDNESDAY:</span><br><span class="line"> numLetters = <span class="number">9</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"Wat: "</span> + day);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这种作为语句进行表述是拐弯抹角的、重复的、并且容易出错。上面的代码旨在每天为numLetters赋一个值。直说的话,使用一个switch表达式将更清晰、更安全:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> numLetters = <span class="keyword">switch</span> (day) {</span><br><span class="line"> <span class="keyword">case</span> MONDAY, FRIDAY, SUNDAY -> <span class="number">6</span>;</span><br><span class="line"> <span class="keyword">case</span> TUESDAY -> <span class="number">7</span>;</span><br><span class="line"> <span class="keyword">case</span> THURSDAY, SATURDAY -> <span class="number">8</span>;</span><br><span class="line"> <span class="keyword">case</span> WEDNESDAY -> <span class="number">9</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>反过来,将switch扩展到支持表达式会引发一些额外的需求,例如扩展流分析(表达式必须始终计算值或突然完成),并允许switch表达式的某些case arm抛出异常而不是产生值。</p><p><strong>描述</strong>:</p><p>除了“traditional”的switch block之外,我们还建议添加一个新的“simplified”形式,使用新的“case L - >”switch标签。如果标签匹配,则只执行箭头标签右侧的表达式或语句,并且没有fall through。例如,给定方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">howMany</span><span class="params">(<span class="keyword">int</span> k)</span> </span>{</span><br><span class="line"> <span class="keyword">switch</span> (k) {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1</span> -> System.out.println(<span class="string">"one"</span>);</span><br><span class="line"> <span class="keyword">case</span> <span class="number">2</span> -> System.out.println(<span class="string">"two"</span>);</span><br><span class="line"> <span class="keyword">case</span> <span class="number">3</span> -> System.out.println(<span class="string">"many"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">howMany(<span class="number">1</span>);</span><br><span class="line">howMany(<span class="number">2</span>);</span><br><span class="line">howMany(<span class="number">3</span>);</span><br></pre></td></tr></table></figure><p>得到以下输出:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">one</span><br><span class="line">two</span><br><span class="line">many</span><br></pre></td></tr></table></figure><p>我们将扩展switch语句,以便它可以另外作为表达式来使用。 在常见情况下,switch表达式如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">T result = <span class="keyword">switch</span> (arg) {</span><br><span class="line"> <span class="keyword">case</span> L1 -> e1;</span><br><span class="line"> <span class="keyword">case</span> L2 -> e2;</span><br><span class="line"> <span class="keyword">default</span> -> e3;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>switch表达式是多边表达式,如果目标类型已知,则将此类型下推到每个arm中。如果已知,switch表达式的类型是其目标类型;如果未知,则通过组合每个case arm的类型来计算独立的类型。</p><p>大多数switch表达式在“case L - >”开关标签的右侧都有一个表达式。 如果需要一个完整的块,我们扩展了break语句以获取一个参数,该参数成为封闭的switch表达式的值。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">int j = switch (day) {</span><br><span class="line"> case MONDAY -> 0;</span><br><span class="line"> case TUESDAY -> 1;</span><br><span class="line"> default -> {</span><br><span class="line"> int k = day.toString().length();</span><br><span class="line"> int result = f(k);</span><br><span class="line"> break result;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>与switch语句一样,switch表达式也可以使用带有“case L:”switch标签的“traditional”switch block。在这种情况下,将使用break with value语句生成值:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> result = <span class="keyword">switch</span> (s) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"Foo"</span>: </span><br><span class="line"> <span class="keyword">break</span> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"Bar"</span>:</span><br><span class="line"> <span class="keyword">break</span> <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> System.out.println(<span class="string">"Neither Foo nor Bar, hmmm..."</span>);</span><br><span class="line"> <span class="keyword">break</span> <span class="number">0</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>两种形式的break(有值和无值)类似于方法中return的两种形式。两种形式的return都会立即终止方法的执行;在非void方法中,还必须提供一个值,该值被赋予方法的调用者。</p><p>switch表达式的case必须是详尽的,对于任何可能的值,必须有匹配的switch标签。实际上,这通常意味着只需要一个default子句;但是,如果枚举switch表达式涵盖了所有已知情况,default子句可以由编译器插入,即枚举定义在编译时和运行时之间发生了变化。</p><p>此外,switch表达式必须正常使用值完成,或抛出异常。编译器检查每个switch标签是否匹配,然后可以产生一个值。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> i = <span class="keyword">switch</span> (day) {</span><br><span class="line"> <span class="keyword">case</span> MONDAY -> {</span><br><span class="line"> System.out.println(<span class="string">"Monday"</span>); </span><br><span class="line"> <span class="comment">// ERROR! Block doesn't contain a break with value</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">default</span> -> <span class="number">1</span>;</span><br><span class="line">};</span><br><span class="line">i = <span class="keyword">switch</span> (day) {</span><br><span class="line"> <span class="keyword">case</span> MONDAY, TUESDAY, WEDNESDAY: </span><br><span class="line"> <span class="keyword">break</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">default</span>: </span><br><span class="line"> System.out.println(<span class="string">"Second half of the week"</span>);</span><br><span class="line"> <span class="comment">// ERROR! Group doesn't contain a break with value</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>控制语句、break、return和continue不能跳过switch表达式,如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">z: </span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < MAX_VALUE; ++i) {</span><br><span class="line"> <span class="keyword">int</span> k = <span class="keyword">switch</span> (e) { </span><br><span class="line"> <span class="keyword">case</span> <span class="number">0</span>: </span><br><span class="line"> <span class="keyword">break</span> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line"> <span class="keyword">break</span> <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">default</span>: </span><br><span class="line"> <span class="keyword">continue</span> z; </span><br><span class="line"> <span class="comment">// ERROR! Illegal jump through a switch expression </span></span><br><span class="line"> };</span><br><span class="line"> ...</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>同时,我们可以扩展switch以支持先前不允许的原始类型(及其装箱类型),例如float,double和long。</p><h4 id="JEP334-JVM-Constants-API"><a href="#JEP334-JVM-Constants-API" class="headerlink" title="JEP334: JVM Constants API"></a><span id="jump334">JEP334: JVM Constants API</span></h4><p><strong>摘要</strong>:</p><p>引入API来规范key class-file和run-time artifacts(特别是可从常量池加载的常量)的标称描述。</p><p><strong>动机</strong>:</p><p>每个Java类文件都有一个常量池,用于存储类中字节码指令的操作数。从广义上讲,常量池中的条目描述了运行时构件(如类和方法)或简单值(如字符串和整数)。所有这些条目都称为可加载常量,因为它们可以作为ldc指令的操作数(“加载常量”)。它们也可能出现在invokedynamic指令的bootstrap方法的静态参数列表中。执行ldc或invokedynamic指令会将可装入常量解析为标准Java类型的“实时”值,例如Class,String或int。</p><p>操作类文件的程序需要对字节码指令进行建模,然后再加载可加载的常量。但是,使用标准Java类型来规范可加载常量是不合适的。描述字符串(CONSTANT_String_info条目)的可加载常量可能是可以接受的,因为生成“实时”String对象很简单,但是对于描述类(CONSTANT_Class_info条目)的可加载常量是有问题的,因为生成“实时”类对象依赖于类加载的正确性和一致性。不幸的是,类加载有许多环境依赖和失败模式:所需的类不存在或者请求者可能无法访问;类加载的结果因上下文而异;装载类有副作用;有时类加载可能根本不可能(例如当所描述的类尚不存在或者不可加载时,如在编译那些相同的类期间,或在jlink-time转换期间)。</p><p>因此,处理可加载常量的程序如果能够以纯粹的名义符号形式操作类和方法,以及不太知名的构件(如方法句柄和动态计算常量),则会更简单:</p><ul><li><p>字节码解析和生成库必须以符号形式描述类和方法句柄。如果没有标准机制,它们必须求助于ad-hoc机制,无论是描述符类型(如ASM的Handle)还是字符串元组(方法所有者,方法名称,方法描述符),或者ad-hoc(和容易出错)的编码成一个字符串。</p></li><li><p>如果可以在符号域中工作而不是使用“实时”类和方法句柄,则通过旋转字节码(例如LambdaMetafactory)操作的invokedynamic的bootstraps会更简单。</p></li><li><p>编译器和脱机转换器(例如jlink插件)需要描述无法加载到正在运行的VM的类的类和成员。编译器插件(例如注释处理器)同样需要用符号术语来描述程序元素。</p></li></ul><p>这些类型的库和工具都将受益于使用单一,标准的方式来描述可加载常量。</p><p><strong>描述</strong>:</p><p>我们在新的java.lang.invoke.constant包中定义了一系列基于值的符号引用(JVMS 5.1)类型,能够描述每种可加载常量。符号引用描述了纯粹标称形式的可加载常量,与类加载或可访问性上下文分开。 有些类可以作为自己的符号引用(例如String);对于可链接常量,我们定义了一系列符号引用类型(ClassDesc,MethodTypeDesc,MethodHandleDesc和DynamicConstantDesc),它们包含描述这些常量的标称信息。</p>]]></content>
<summary type="html">
<p><img src="https://i.loli.net/2019/03/28/5c9c829850beb.jpg" alt="OpenJDK.jpg"></p>
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>OpenJDK 12是由JSR 386在Java Community Process中指定的Java SE平台的版本12的开源参考实现,于2019年3月19日达到General Availability版本。GPL(General Public License)协议下的生产就绪二进制文件可从Oracle获得;其他供应商的二进制文件很快就会出现。</p>
<p>该版本的功能和时间表是通过JEP流程提出和跟踪的,并由JEP 2.0提案进行了修订。该版本使用JDK Release Process(JEP 3)生成发布。</p>
<p>本文根据OpenJDK 12的官方文档:<a href="http://openjdk.java.net/projects/jdk/12/" target="_blank" rel="noopener">OpenJDK 12</a>,对其新特性进行整理,受本人翻译水平所限,难免有翻译或理解错误,望不吝指正。</p>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="译文" scheme="http://yoursite.com/categories/Java/%E8%AF%91%E6%96%87/"/>
<category term="JVM" scheme="http://yoursite.com/tags/JVM/"/>
<category term="OpenJDK" scheme="http://yoursite.com/tags/OpenJDK/"/>
</entry>
<entry>
<title>日志分析接口协议的设计与实现</title>
<link href="http://yoursite.com/2019/03/25/%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90%E6%8E%A5%E5%8F%A3%E5%8D%8F%E8%AE%AE%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0/"/>
<id>http://yoursite.com/2019/03/25/日志分析接口协议设计与实现/</id>
<published>2019-03-25T09:35:20.701Z</published>
<updated>2019-03-26T03:26:54.937Z</updated>
<content type="html"><![CDATA[<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p><img src="https://cdn2.iconfinder.com/data/icons/productivity-social-vol-2/512/checkmark_approve-128.png" alt="Choice"><br>通过日志信息对RPC服务调用的用户进行监控,提高系统的稳定性。本文简要记录了一下日志分析接口协议的设计与实现.<br><a id="more"></a></p><h2 id="实现细节"><a href="#实现细节" class="headerlink" title="实现细节"></a>实现细节</h2><h3 id="HTTP接口"><a href="#HTTP接口" class="headerlink" title="HTTP接口"></a>HTTP接口</h3><p>请求方式: <code>post</code><br>请求参数: </p><ul><li>HTTP header<br><code>Content-Type=application/json (必须)</code></li><li><p>HTTP body</p><pre><code>{</code></pre><p> “log_project_name”:”testhttp”,<br> “log_message_type”:”request”,<br> “log_format”:”default”,<br> “log_from”:”172.0.0.1”,<br> “log_content”:”2017-06-28 22:39:44,959 [P58,] communicationInfo - {\”cid\”:\”1111\”,\”clientIP\”:\”10.10.10.10\”,\”request\”:\”simpleSearch\”,\”timeAll\”:107,\”respLength\”:55555}”<br> }<br> </p></li></ul><p>body中为JSON字符串<br>JSON参数</p><blockquote><p>log_project_name:项目名称 同一个项目使用同一个名称<br>log_message_type:日志类型request (请求日志),error(错误日志)<br>log_format:日志格式default<br>log_from:日志来源机器每台机器写自己的IP作为一个标示<br>log_content:根据日志类型发送的日志内容(日志内容格式请见下文)</p></blockquote><h3 id="日志文件"><a href="#日志文件" class="headerlink" title="日志文件"></a>日志文件</h3><p>读取日志文件(日志内容格式请见下文)</p><h3 id="日志格式"><a href="#日志格式" class="headerlink" title="日志格式"></a>日志格式</h3><p>日志格式为两种</p><h4 id="日志格式为-request-解析类型为-default-日志为一行"><a href="#日志格式为-request-解析类型为-default-日志为一行" class="headerlink" title="日志格式为 request 解析类型为 default 日志为一行"></a>日志格式为 <code>request</code> 解析类型为 <code>default</code> 日志为一行</h4><p>示例:</p><pre><code>2017-06-21 22:37:44,959 - {"cid":"1111","clientIP":"10.10.10.10","request":"simpleSearch","timeAll":107, "respLength":55555}</code></pre><p>请求日志内容格式 分为3块</p><ol><li>时间开头格式必须为 <code>yyyy-MM-dd HH:mm:ss,SSS</code></li><li>分隔符 <code>-</code></li><li>JSON内容<blockquote><p>cid:用户ID<br> clientIP:客户端IP<br> request:请求业务类型<br> timeAll:耗时<br> respLength:返回大小</p></blockquote></li></ol><p><strong>注意:json中字段为必填字段若没有值则使用空字符串</strong></p><h4 id="日志格式为-error-解析类型为-default-日志可以为多行"><a href="#日志格式为-error-解析类型为-default-日志可以为多行" class="headerlink" title="日志格式为 error 解析类型为 default 日志可以为多行"></a>日志格式为 <code>error</code> 解析类型为 <code>default</code> 日志可以为多行</h4><p>示例</p><pre><code>2017-06-19 22:54:58,151 InfoThreadPool requestInfo http://www.eastmoney.com/ open error</code></pre><p>错误日志内容格式 分为2块</p><ol><li>时间开头格式必须为<code>yyyy-MM-dd HH:mm:ss,SSS</code></li><li>错误内容<br>注意:错误日志读取时,只匹配日志开头是否为此格式的时间,最多读取500行,当行中出现<code>eastmoney</code>关键字时,停止读取。</li></ol><h3 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h3><h4 id="每一台服务器的访问情况"><a href="#每一台服务器的访问情况" class="headerlink" title="每一台服务器的访问情况"></a>每一台服务器的访问情况</h4><ul><li><code>LoginHandler</code>每次登录输出日志</li><li>日志内容字段<blockquote><p>cid:用户ID<br> clientIP:客户端IP<br> request:请求业务类型<br> timeAll:耗时<br> respLength:返回大小</p></blockquote></li></ul><h4 id="每个用户所做的操作"><a href="#每个用户所做的操作" class="headerlink" title="每个用户所做的操作"></a>每个用户所做的操作</h4><ul><li>调用Handler的doHandle()时输出日志<blockquote><p>cid:用户ID<br> clientIP:客户端IP<br> request:请求业务类型<br> timeAll:耗时<br> respLength:返回大小</p></blockquote></li></ul><p>count_info</p><pre><code>机房,服务器类型,服务器ip,服务器端口,用户数,时间,备注area,server_type,server_ip,server_port,count,time,desc</code></pre><p>user_log</p><pre><code>用户id,客户端ip,客户端端口,服务器ip,服务器端口,请求id,时间,备注clientid,remote_server,remote_port,server_ip,server_port,request_id,time,desc</code></pre><h4 id="文件名"><a href="#文件名" class="headerlink" title="文件名"></a>文件名</h4><p><strong>LIST_10.10.10.10_80-1818-443_sys.2017110620.log</strong></p><ul><li><p>request_id为 <strong>-1</strong>,预留位desc赋值为当前用户数(链接数),每分钟打印一次</p><blockquote><p>2017-10-30 13:26:00.574 - {“clientid”:””,”remote_server”:””,”remote_port”:””,”server_port”:””,”request_id”:-1,”desc”:”500”}</p></blockquote></li><li><p>request_id为 <strong>-2</strong>,预留位desc赋值为链接断线原因</p><blockquote><p>2017-10-30 13:26:00.574 - {“clientid”:”22000000000”,”remote_server”:”10.10.10.10”,”remote_port”:”30040”,”server_port”:”1818”,”request_id”:-2,”desc”:0}</p></blockquote> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">byte</span> UNKNOWN = <span class="number">0</span>; <span class="comment">//未知</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">byte</span> WRITE_WAITE_MANY_TIMES = <span class="number">1</span>; <span class="comment">//写等待次数过多</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">byte</span> FORCE_NO_CANCEL_REQUEST = <span class="number">2</span>; <span class="comment">//强制去除,客户端没有发送取消请求</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">byte</span> CHANNEL_GROUP_CHECK = <span class="number">3</span>; <span class="comment">//ChannelGroup检查关闭</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">byte</span> HANDLER_EXCEPTION = <span class="number">4</span>; <span class="comment">//Handler exceptionCaught触发</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">byte</span> HANDLER_CLOSED = <span class="number">5</span>; <span class="comment">//Handler Closed触发</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">byte</span> CHANNEL_EXCEPTION = <span class="number">6</span>; <span class="comment">//Handler Closed触发</span></span><br></pre></td></tr></table></figure></li><li><p>正常打印请求信息</p><blockquote><p>2017-10-30 13:26:00.574 - {“clientid”:”22000000000”,”remote_server”:”10.10.10.10”,”remote_port”:”30040”,”server_port”:”1818”,”request_id”:16066,”desc”:””}</p></blockquote></li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>logback的使用和logback.xml详解:<a href="http://www.cnblogs.com/warking/p/5710303.html" target="_blank" rel="noopener">http://www.cnblogs.com/warking/p/5710303.html</a><br>logback.xml配置:<a href="http://blog.csdn.net/qq173684423/article/details/53611906" target="_blank" rel="noopener">http://blog.csdn.net/qq173684423/article/details/53611906</a><br>logback配置详解<a href="http://blog.csdn.net/haidage/article/details/6794509" target="_blank" rel="noopener">http://blog.csdn.net/haidage/article/details/6794509</a></p>]]></content>
<summary type="html">
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p><img src="https://cdn2.iconfinder.com/data/icons/productivity-social-vol-2/512/checkmark_approve-128.png" alt="Choice"><br>通过日志信息对RPC服务调用的用户进行监控,提高系统的稳定性。本文简要记录了一下日志分析接口协议的设计与实现.<br>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="性能调优" scheme="http://yoursite.com/tags/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98/"/>
<category term="RPC" scheme="http://yoursite.com/tags/RPC/"/>
<category term="logback" scheme="http://yoursite.com/tags/logback/"/>
</entry>
<entry>
<title>同步、异步、阻塞、非阻塞IO</title>
<link href="http://yoursite.com/2019/01/29/%E5%90%8C%E6%AD%A5%E3%80%81%E5%BC%82%E6%AD%A5%E3%80%81%E9%98%BB%E5%A1%9E%E3%80%81%E9%9D%9E%E9%98%BB%E5%A1%9EIO/"/>
<id>http://yoursite.com/2019/01/29/同步、异步、阻塞、非阻塞IO/</id>
<published>2019-01-29T06:58:00.260Z</published>
<updated>2019-01-31T06:43:45.956Z</updated>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>最近在整理过去一段时间内Netty性能调优的工作,涉及到了Netty的NIO/Epoll等异步非阻塞通信,索性复习一下常见的几种IO模型。看到CSDN上这篇文章写得很清晰易懂,<a href="https://blog.csdn.net/historyasamirror/article/details/5778378" target="_blank" rel="noopener">IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)</a>,在此基础上整理了一下,方便记忆及以后查看。</p><a id="more"></a><h2 id="IO模型"><a href="#IO模型" class="headerlink" title="IO模型"></a>IO模型</h2><p>IO发生时会经历两个阶段:</p><ul><li><p>等待数据</p></li><li><p>将数据从内核复制到用户空间</p></li></ul><p>不同的IO模型就是根据在这两个阶段所做的处理不同进行区分的。</p><h3 id="Blocking-IO"><a href="#Blocking-IO" class="headerlink" title="Blocking IO"></a>Blocking IO</h3><p><img src="https://i.loli.net/2019/01/30/5c5151d73a80d.jpg" alt="blocking IO.jpg"></p><p><strong>阻塞IO模型(BIO)</strong>:应用进程调用recvfrom,kernel开始了IO的第一阶段:等待数据,等到数据准备好了,kernel开始第二阶段:将数据从内核复制到用户空间。在IO的这两个阶段执行期间,应用进程一直处于block状态,直到kernel返回结果,应用进程阻塞才解除。</p><p>举例:你在美团上点了一份外卖,下单后外卖小哥开始取餐(阶段一),取到餐后再进行配送(阶段二),期间你一直站在门口等(block状态),直到收到外卖才继续自己的事情。</p><h3 id="Nonblocking-IO"><a href="#Nonblocking-IO" class="headerlink" title="Nonblocking IO"></a>Nonblocking IO</h3><p><img src="https://i.loli.net/2019/01/30/5c5151d7b62f1.jpg" alt="non-blocking IO.jpg"></p><p><strong>非阻塞IO模型(NIO)</strong>:应用进程调用recvfrom,kernel开始了IO的第一阶段:等待数据,如果kernel中的数据还没有准备好,就返回应用进程EWOULDBLOCK,应用进程收到返回后会一直调用recvfrom询问kernel,直到数据准备好,在此期间应用进程处于非block状态;然后kernel将数据从内核复制到用户空间,并block应用进程,直到成功返回后阻塞状态解除。所以非阻塞IO和阻塞IO不是完全对立的,在IO第二阶段二者均处于block状态。</p><p>举例:你在美团上点了一份外卖,下单后外卖小哥开始取餐(阶段一),在取餐期间你坐在屋里一遍看剧一边不断打电话给外卖小哥询问取到了没,直到小哥说他拿到了;这时小哥取到餐后进行配送(阶段二),你也起身站在门口等(block状态),直到收到外卖。</p><h3 id="IO-multiplexing"><a href="#IO-multiplexing" class="headerlink" title="IO multiplexing"></a>IO multiplexing</h3><p><img src="https://i.loli.net/2019/01/30/5c5151d7af63b.jpg" alt="IO multiplexing.jpg"></p><p><strong>IO复用模型</strong>:应用进程调用select,通过select可以不断的轮询所负责的所有socket,当某个socket的数据准备好了,select就会返回信息,然后用户态进程调用recvfrom,将数据从内核复制到用户空间。对于每一个socket,一般都设置成为non-blocking,但是整个应用进程受阻于select调用;同样,在IO的第二阶段,应用进程也是出于block状态。</p><p>如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于单个process就可以同时处理多个网络连接的IO。</p><p>Linux关于IO复用的使用,有三种不同的API,select、poll和epoll,关于这三种API的实现分析,可以参考下这篇文章:<a href="https://www.cnblogs.com/Anker/p/3265058.html" target="_blank" rel="noopener">select、poll、epoll之间的区别总结[整理]</a>。</p><p>举例:这次你在美团上一次点了好几份外卖,美团的消息系统(select等API调用)推给你说等有外卖取到了就发消息给你,让你留意消息(block状态),然后几个外卖小哥就到不同的店去取餐(阶段一)。等取到餐了,如果你用的是select或poll版本的美团,消息推给你说你有订单已经取到了,但是不告诉你是哪家;如果你用的是epoll版本的美团,消息推给你说你有订单已经取到了,而且还告诉你是碳烤羊排。收到消息之后你就到门口去等(block状态),外卖小哥进行配送(阶段二),直到收到外卖才继续自己的事情。</p><h3 id="Asynchronous-IO"><a href="#Asynchronous-IO" class="headerlink" title="Asynchronous IO"></a>Asynchronous IO</h3><p><img src="https://i.loli.net/2019/01/30/5c5151d704ff1.jpg" alt="Asynchronous IO.jpg"></p><p><strong>异步IO模型(AIO)</strong>:应用进程调用aio_read,kernel收到请求后会立刻返回,等数据准备好,并且复制到用户空间后执行事先指定好的函数,整个过程应用进程都处于非block状态。</p><p>举例:你发现自己出去等太大笨蛋了,在美团上点了一份外卖,看到下单成功后就去看剧了。外卖小哥开始取餐(阶段一),取到餐后再进行配送(阶段二),最后送到你门口,整个过程你都在干自己的事情,没有一直等着(非block状态)。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><h3 id="IO模型对比"><a href="#IO模型对比" class="headerlink" title="IO模型对比"></a>IO模型对比</h3><p><img src="https://i.loli.net/2019/01/30/5c5151d774bba.jpg" alt="IO Models.jpg"></p><p>因为信号驱动式IO(signal-driven I/O)使用较少,正文中没提及。</p><p><strong>阻塞IO和非阻塞IO</strong>:调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。</p><p><strong>同步IO和异步IO</strong>:synchronous IO做”IO operation”的时候会将process阻塞,blocking IO,non-blocking IO,IO multiplexing因为都阻塞于recvfrom调用,都属于synchronous IO。</p><p><strong>非阻塞IO和异步IO</strong>:在数据准备阶段,non-blocking IO中进程不会被block,但是它仍然要求进程去主动的check,而asynchronous IO中应用进程进程将整个IO操作交给kernel来完成。</p>]]></content>
<summary type="html">
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>最近在整理过去一段时间内Netty性能调优的工作,涉及到了Netty的NIO/Epoll等异步非阻塞通信,索性复习一下常见的几种IO模型。看到CSDN上这篇文章写得很清晰易懂,<a href="https://blog.csdn.net/historyasamirror/article/details/5778378" target="_blank" rel="noopener">IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)</a>,在此基础上整理了一下,方便记忆及以后查看。</p>
</summary>
<category term="网络" scheme="http://yoursite.com/categories/%E7%BD%91%E7%BB%9C/"/>
<category term="IO" scheme="http://yoursite.com/tags/IO/"/>
</entry>
<entry>
<title>读懂Young GC日志</title>
<link href="http://yoursite.com/2018/12/18/%E8%AF%BB%E6%87%82Young%20GC%E6%97%A5%E5%BF%97/"/>
<id>http://yoursite.com/2018/12/18/读懂Young GC日志/</id>
<published>2018-12-18T06:46:08.526Z</published>
<updated>2019-08-05T09:06:05.748Z</updated>
<content type="html"><![CDATA[<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>在进行JVM调优时我们经常需要通过日志信息来分析G1GC的性能,本文就日志中可供收集的数据和信息进行了简单介绍。<br><a id="more"></a></p><h2 id="Young-GC日志"><a href="#Young-GC日志" class="headerlink" title="Young GC日志"></a>Young GC日志</h2><p>下面选取了SZVPC线上报价服务的一段Young GC日志:<br><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">{Heap before GC invocations=<span class="number">38623</span> (full <span class="number">4</span>):</span><br><span class="line"> garbage-first heap total <span class="number">25165824</span>K, used <span class="number">11321508</span>K [<span class="number">0x00000001c0000000</span>, <span class="number">0x00000001c040c000</span>, <span class="number">0x00000007c0000000</span>)</span><br><span class="line"> region size <span class="number">4096</span>K, <span class="number">307</span> young (<span class="number">1257472</span>K), <span class="number">26</span> survivors (<span class="number">106496</span>K)</span><br><span class="line"> Metaspace used <span class="number">23093</span>K, capacity <span class="number">23236</span>K, committed <span class="number">29440</span>K, reserved <span class="number">1075200</span>K</span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">space</span> <span class="title">used</span> 2444<span class="title">K</span>, <span class="title">capacity</span> 2510<span class="title">K</span>, <span class="title">committed</span> 3328<span class="title">K</span>, <span class="title">reserved</span> 1048576<span class="title">K</span></span></span><br><span class="line">2018-12-18T11:19:27.084+0800: 671040.790: [GC pause (G1 Evacuation Pause) (young), 0.3639131 secs]</span><br><span class="line"> [Parallel Time: <span class="number">354.4</span> ms, GC Workers: <span class="number">13</span>]</span><br><span class="line"> [<span class="function">GC Worker <span class="title">Start</span> <span class="params">(ms)</span>: Min: 671040790.4, Avg: 671040790.6, Max: 671040790.8, Diff: 0.4]</span></span><br><span class="line"><span class="function"> [Ext Root <span class="title">Scanning</span> <span class="params">(ms)</span>: Min: 0.8, Avg: 1.0, Max: 1.2, Diff: 0.4, Sum: 13.0]</span></span><br><span class="line"><span class="function"> [Update <span class="title">RS</span> <span class="params">(ms)</span>: Min: 22.6, Avg: 23.0, Max: 23.6, Diff: 1.0, Sum: 299.6]</span></span><br><span class="line"><span class="function"> [Processed Buffers: Min: 24, Avg: 32.5, Max: 39, Diff: 15, Sum: 422]</span></span><br><span class="line"><span class="function"> [Scan <span class="title">RS</span> <span class="params">(ms)</span>: Min: 201.4, Avg: 202.0, Max: 202.4, Diff: 1.0, Sum: 2625.9]</span></span><br><span class="line"><span class="function"> [Code Root <span class="title">Scanning</span> <span class="params">(ms)</span>: Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.0, Sum: 0.2]</span></span><br><span class="line"><span class="function"> [Object <span class="title">Copy</span> <span class="params">(ms)</span>: Min: 126.9, Avg: 127.2, Max: 127.8, Diff: 0.9, Sum: 1653.5]</span></span><br><span class="line"><span class="function"> [<span class="title">Termination</span> <span class="params">(ms)</span>: Min: 0.0, Avg: 0.6, Max: 0.9, Diff: 0.9, Sum: 8.3]</span></span><br><span class="line"><span class="function"> [Termination Attempts: Min: 1, Avg: 278.6, Max: 349, Diff: 348, Sum: 3622]</span></span><br><span class="line"><span class="function"> [GC Worker <span class="title">Other</span> <span class="params">(ms)</span>: Min: 0.0, Avg: 0.1, Max: 0.3, Diff: 0.2, Sum: 1.6]</span></span><br><span class="line"><span class="function"> [GC Worker <span class="title">Total</span> <span class="params">(ms)</span>: Min: 353.8, Avg: 354.0, Max: 354.2, Diff: 0.4, Sum: 4602.1]</span></span><br><span class="line"><span class="function"> [GC Worker <span class="title">End</span> <span class="params">(ms)</span>: Min: 671041144.5, Avg: 671041144.6, Max: 671041144.7, Diff: 0.2]</span></span><br><span class="line"><span class="function"> [Code Root Fixup: 0.0 ms]</span></span><br><span class="line"><span class="function"> [Code Root Purge: 0.0 ms]</span></span><br><span class="line"><span class="function"> [Clear CT: 1.7 ms]</span></span><br><span class="line"><span class="function"> [Other: 7.8 ms]</span></span><br><span class="line"><span class="function"> [Choose CSet: 0.0 ms]</span></span><br><span class="line"><span class="function"> [Ref Proc: 0.6 ms]</span></span><br><span class="line"><span class="function"> [Ref Enq: 0.0 ms]</span></span><br><span class="line"><span class="function"> [Redirty Cards: 2.5 ms]</span></span><br><span class="line"><span class="function"> [Humongous Register: 0.2 ms]</span></span><br><span class="line"><span class="function"> [Humongous Reclaim: 0.1 ms]</span></span><br><span class="line"><span class="function"> [Free CSet: 3.0 ms]</span></span><br><span class="line"><span class="function"> [Eden: 1124.0<span class="title">M</span><span class="params">(<span class="number">1124.0</span>M)</span>->0.0<span class="title">B</span><span class="params">(<span class="number">1132.0</span>M)</span> Survivors: 104.0M->96.0M Heap: 10.8<span class="title">G</span><span class="params">(<span class="number">24.0</span>G)</span>->9968.2<span class="title">M</span><span class="params">(<span class="number">24.0</span>G)</span>]</span></span><br><span class="line"><span class="function">Heap after GC invocations</span>=<span class="number">38624</span> (full <span class="number">4</span>):</span><br><span class="line"> garbage-first heap total <span class="number">25165824</span>K, used <span class="number">10207396</span>K [<span class="number">0x00000001c0000000</span>, <span class="number">0x00000001c040c000</span>, <span class="number">0x00000007c0000000</span>)</span><br><span class="line"> region size <span class="number">4096</span>K, <span class="number">24</span> young (<span class="number">98304</span>K), <span class="number">24</span> survivors (<span class="number">98304</span>K)</span><br><span class="line"> Metaspace used <span class="number">23093</span>K, capacity <span class="number">23236</span>K, committed <span class="number">29440</span>K, reserved <span class="number">1075200</span>K</span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">space</span> <span class="title">used</span> 2444<span class="title">K</span>, <span class="title">capacity</span> 2510<span class="title">K</span>, <span class="title">committed</span> 3328<span class="title">K</span>, <span class="title">reserved</span> 1048576<span class="title">K</span></span></span><br><span class="line"><span class="class">}</span></span><br><span class="line"> [Times: user=4.64 sys=0.00, real=0.36 secs]</span><br></pre></td></tr></table></figure></p><h4 id="日志信息详解:"><a href="#日志信息详解:" class="headerlink" title="日志信息详解:"></a>日志信息详解:</h4><ul><li><p><strong>garbage-first heap total 25165824K, used 11321508K [0x00000001c0000000, 0x00000001c040c000, 0x00000007c0000000)</strong></p><p>可以看出GC的类型为G1,Heap的总大小为25165824K,已使用11321508K。</p></li><li><p><strong>region size 4096K, 307 young (1257472K), 26 survivors (106496K)</strong></p><p>每个region的大小为4096K,新生代307个region,survivor区26个region。</p></li><li><p><strong>Metaspace used 23093K, capacity 23236K, committed 29440K, reserved 1075200K<br> class space used 2444K, capacity 2510K, committed 3328K, reserved 1048576K</strong></p><p>java8去掉了永久区(Permanent),新增元数据区(Metaspace)。</p></li><li><p><strong>671040.790: [GC pause (G1 Evacuation Pause) (young), 0.3639131 secs]</strong></p><p>G1停顿清理(young) regions,即新生代minor GC。本次停顿在JVM启动后671040.790 s后触发,且停顿持续时间为0.3639131 s,根据系统时间(wall clock time)测定。</p></li><li><p><strong>[Parallel Time: 354.4 ms, GC Workers: 13]</strong></p><p>收集过程是多线程并发执行的,日志表明在354.4ms(real time)时间内,下面的活动是由13个线程并行完成的:</p><ol><li><p><code>GC Worker Start (ms)</code></p><p>收集线程开始的时间,匹配停顿开始时的时间戳。使用的是相对时间,Min是最早开始时间,Avg是平均开始时间,Max是最晚开始时间,Diff是Max-Min。 如果Min和Max差别很大,那么可能表明使用了太多线程或者机器上的其他进程正在从JVM内部的垃圾收集进程中窃取CPU时间。</p></li><li><p><code>Ext Root Scanning (ms)</code></p><p>扫描外部(非heap)roots如类加载器、JNI引用、JVM 系统roots等的耗时,Sum是Cpu time。</p></li><li><p><code>Update RS (ms)</code></p><p>每个线程更新Remembered Set的耗时。</p></li><li><p><code>Scan RS (ms)</code></p><p>扫描CS中的region对应的RSet,因为RSet是points-into,所以这样实现避免了扫描old generadion region,但是会产生float garbage。</p></li><li><p><code>Code Root Scanning (ms)</code></p><p>扫描code roots如本地变量的耗时。code roots指的是经过JIT编译后的代码里,引用了heap中的对象。引用关系保存在RSet中。</p></li><li><p><code>Object Copy (ms)</code></p><p>从收集region拷贝存活的对象到新region的耗时。</p></li><li><p><code>Termination (ms)</code></p><p>工作线程确保它们能够安全地停止并且没有更多的工作要做最终终止所用的耗时。在结束前,它会检查其他线程是否还有未扫描完的引用,如果有,则”偷”过来,完成后再申请结束,这个时间是线程之前互相同步所花费的时间。</p><ul><li><p><code>Termination Attempts</code></p><p>工作线程尝试终止的次数,如果实际还有更多的工作要做,那么尝试就会失败,现在终止还为时过早。</p></li></ul></li><li><p><code>GC Worker Other (ms)</code></p><p>花费在其他杂项(不值得在日志中单独占用一个部分)上的时间。</p></li><li><p><code>GC Worker Total (ms)</code></p><p>所用工作线程耗时之和。</p></li><li><p><code>GC Worker End (ms)</code></p><p>每个线程结束的时间戳,通常它们应该大致相等。</p></li></ol></li><li><p><strong>[Code Root Fixup: 0.0 ms]</strong></p><p>用来将code root修正到正确的evacuate之后的对象位置所花费的时间。</p></li><li><p><strong>[Code Root Purge: 0.0 ms]</strong></p><p>清除code root的耗时,code root中的引用已经失效,不再指向Region中的对象,所以需要被清除。</p></li><li><p><strong>[Clear CT: 1.7 ms]</strong></p><p>清除RSet的card table的耗时,这个任务在serial模式下执行。</p></li><li><p><strong>[Other: 7.8 ms]</strong></p><p>下面所列的其他事项共耗时7.8ms:</p><ol><li><p><code>[Choose CSet: 0.0 ms]</code></p><p>查找Collection Set的region的耗时;</p></li><li><p><code>[Ref Proc: 0.6 ms]</code></p><p>Reference Process, 处理引用对象的耗时;</p></li><li><p><code>[Ref Enq: 0.0 ms]</code></p><p>Reference enqueue,引用对象排队到引用队列的耗时;</p></li><li><p><code>[Redirty Cards: 2.5 ms]</code>略</p></li><li><p><code>[Humongous Register: 0.2 ms]</code>略</p></li><li><p><code>[Humongous Reclaim: 0.1 ms]</code>略</p></li><li><p><code>[Free CSet: 3.0 ms]</code></p><p>清理Collection Set数据结构的耗时;</p></li></ol></li><li><p><strong>[Eden: 1124.0M(1124.0M)->0.0B(1132.0M) Survivors: 104.0M->96.0M Heap: 10.8G(24.0G)->9968.2M(24.0G)]</strong></p><p>分别代表:停顿前后Eden区已用内存和容量;停顿前后Survivor区使用的内存;停顿前后整个Heap已用内存和容量。</p></li><li><p><strong>[Times: user=4.64 sys=0.00, real=0.36 secs]</strong></p><p>整个GC事件的持续时间,从以下不同的类别来测量:</p><ol><li><p><strong>user</strong> - 本次垃圾收集中GC线程所消耗的总体CPU时间;</p></li><li><p><strong>sys</strong> - OS调用或等待系统事件的耗时;</p></li><li><p><strong>real</strong> - 应用程序停止的时间(Clock time)。在GC活动可并行执行的情况下,这个数值理想上应为:(user时间 + sys时间)/ GC所用的线程数,在这个case里线程数是13。要注意的是,有些活动不能并行执行,因此该数值总会超过这个比例一定数量。</p></li></ol></li></ul><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p><a href="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html" target="_blank" rel="noopener">Getting Started with the G1 Garbage Collector</a></p><p><a href="https://blogs.oracle.com/poonam/understanding-g1-gc-logs" target="_blank" rel="noopener">Understanding G1 GC Logs</a></p><p><a href="https://plumbr.io/handbook/garbage-collection-algorithms-implementations/g1/evacuation-pause-fully-young" target="_blank" rel="noopener">JAVA GARBAGE COLLECTION HANDBOOK</a></p>]]></content>
<summary type="html">
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>在进行JVM调优时我们经常需要通过日志信息来分析G1GC的性能,本文就日志中可供收集的数据和信息进行了简单介绍。<br>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="性能调优" scheme="http://yoursite.com/tags/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98/"/>
<category term="JVM" scheme="http://yoursite.com/tags/JVM/"/>
<category term="GC" scheme="http://yoursite.com/tags/GC/"/>
</entry>
<entry>
<title>JDK命令行工具</title>
<link href="http://yoursite.com/2018/12/13/JDK%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7/"/>
<id>http://yoursite.com/2018/12/13/JDK命令行工具/</id>
<published>2018-12-13T02:10:02.308Z</published>
<updated>2019-01-31T09:11:08.478Z</updated>
<content type="html"><![CDATA[<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>前一阵接口服务性能调优的时候,零零星星地用到一些JDK自带的命令行工具。当服务部署到生产环境时,有一些性能诊断工具因为环境原因可能会受到限制,但是通过使用这些JDK自带的监控工具,我问可以直接在应用程序中实现强大的监控分析功能。这些工具在<strong>《深入理解Java虚拟机》</strong>和<strong>《Java性能权威指南》</strong>中都有系统性的介绍,本文也权当是一篇学习笔记,顺便对之前调优过程中的使用做一下梳理。</p><a id="more"></a><h2 id="常用的JDK命令行工具"><a href="#常用的JDK命令行工具" class="headerlink" title="常用的JDK命令行工具"></a>常用的JDK命令行工具</h2><table><thead><tr><th style="text-align:left">请求ID</th><th style="text-align:left">主要作用</th></tr></thead><tbody><tr><td style="text-align:left">jps</td><td style="text-align:left">JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程</td></tr><tr><td style="text-align:left">jstat</td><td style="text-align:left">JVM Statistics Monitoring Tool,用于收集Hotspot虚拟机各方面的运行数据</td></tr><tr><td style="text-align:left">jinfo</td><td style="text-align:left">Configuration Info for Java,显示虚拟机配置信息</td></tr><tr><td style="text-align:left">jmap</td><td style="text-align:left">JVM Memory Map,生成虚拟机的内存转储快照,生成heapdump文件</td></tr><tr><td style="text-align:left">jhat</td><td style="text-align:left">JVM Heap Dump Browser,用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户在浏览器上查看分析结果</td></tr><tr><td style="text-align:left">jstack</td><td style="text-align:left">JVM Stack Trace,显示虚拟机的线程快照</td></tr><tr><td style="text-align:left">jcmd</td><td style="text-align:left">打印Java进程所涉及的基本类、线程和VM信息</td></tr><tr><td style="text-align:left">jconsole</td><td style="text-align:left">提供JVM活动的图形化视图,包括线程的使用、类的使用和GC活动</td></tr><tr><td style="text-align:left">jvisualvm</td><td style="text-align:left">监控JVM的GUI工具,可用来剖析运行的应用,分析JVM堆转储</td></tr></tbody></table><p>这些工具可以广泛用于以下领域:</p><ul><li>基本的VM信息</li><li>线程信息</li><li>类信息</li><li>实时GC分析</li><li>堆转储的事后处理</li><li>JVM的性能分析</li></ul><h2 id="常用命令介绍"><a href="#常用命令介绍" class="headerlink" title="常用命令介绍"></a>常用命令介绍</h2><h3 id="jps"><a href="#jps" class="headerlink" title="jps"></a>jps</h3><p><strong>命令格式</strong>:</p><p><code>jps [ options ] [ hostid ]</code></p><p><strong>options</strong>:</p><p><code>-q</code> 只输出本地虚拟机进程ID,省略主类名 </p><p><code>-m</code> 输出虚拟机进程启动时传递给main()函数的参数</p><p><code>-l</code> 输出进程执行的主类的全名 </p><p><code>-v</code> 输出虚拟机进程启动时的JVM参数 </p><h3 id="jstat"><a href="#jstat" class="headerlink" title="jstat"></a>jstat</h3><p><strong>命令格式</strong>:</p><p><code>jstat [ option vmid [interval[s|ms] [count]] ]</code></p><p><strong>options</strong>:</p><p><code>-class</code>监视类装载,卸载数量,总空间以及类装载所耗费的时间</p><p><code>-gc</code> 监视Java堆状况,包括Eden区,两个survivor区,老年代,永久代的容量,已用空间,GC时间合计等信息</p><p><code>-gccapacity</code> 内容与-gc基本相同,但主要输出Java堆各个区域的最大最小空间</p><p><code>-gcutil</code> 内容与-gc基本相同,但主要关注已使用空间占总空间的百分比</p><p><code>-gccause</code> 内容与-gcutil基本相同,但主要关注已使用空间占总空间的百分比,并输出导致上一次GC的原因</p><p><code>-gcnew</code> 监视新生代GC情况</p><p><code>-gcnewcapacity</code> 内容与-gcnew基本相同,但主要输出使用到的最大最小空间</p><p><code>-gcold</code> 监视老年代GC情况</p><p><code>-gcoldcapacity</code> 内容与-gcnew基本相同,但主要输出使用到的最大最小空间</p><p><code>-gcpermcapacity</code> 输出永久代使用到的最大最小空间</p><p><code>-complier</code> 输出JIT 编译器编译过的方法耗时的信息</p><p><code>-printcompliter</code> 输出已经被JIT编译的方法</p><h3 id="jinfo"><a href="#jinfo" class="headerlink" title="jinfo"></a>jinfo</h3><p><strong>命令格式</strong>:</p><p><code>jinfo [ option ] pid</code></p><h3 id="jmap"><a href="#jmap" class="headerlink" title="jmap"></a>jmap</h3><p><strong>命令格式</strong>:</p><p><code>jinfo [ option ] vmid</code></p><p><strong>options</strong>:</p><p><code>-dump</code> 生成Java堆转储快照,格式为: -dump:[live , ]format=b , file=,其中live子参数 说明只dump出存活的对象</p><p><code>-finalizerinfo</code> 显示在F-Queue中等待Finalizer线程执行finalize方法的对象</p><p><code>-heap</code> 显示Java堆详细信息,如使用哪种回收器,参数配置,分代状况等 </p><h3 id="jhat"><a href="#jhat" class="headerlink" title="jhat"></a>jhat</h3><p><strong>命令格式</strong>:</p><p><code>jhat heapdumpFileName</code></p><h3 id="jatack"><a href="#jatack" class="headerlink" title="jatack"></a>jatack</h3><p><strong>命令格式</strong>:</p><p><code>jstack [ option ] vmid</code></p><p><strong>options</strong>:</p><p><code>-F</code> 当正常输出的请求不被响应时,强制输出线程堆栈</p><p><code>-l</code> 除堆栈外,显示关于锁的附加信息</p><p><code>-m</code> 如果调用到本地方法的话,可以显示C/C++的堆栈</p>]]></content>
<summary type="html">
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>前一阵接口服务性能调优的时候,零零星星地用到一些JDK自带的命令行工具。当服务部署到生产环境时,有一些性能诊断工具因为环境原因可能会受到限制,但是通过使用这些JDK自带的监控工具,我问可以直接在应用程序中实现强大的监控分析功能。这些工具在<strong>《深入理解Java虚拟机》</strong>和<strong>《Java性能权威指南》</strong>中都有系统性的介绍,本文也权当是一篇学习笔记,顺便对之前调优过程中的使用做一下梳理。</p>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="性能调优" scheme="http://yoursite.com/tags/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98/"/>
<category term="JVM" scheme="http://yoursite.com/tags/JVM/"/>
</entry>
<entry>
<title>线上服务CPU负载高问题排查</title>
<link href="http://yoursite.com/2018/12/06/%E7%BA%BF%E4%B8%8A%E6%9C%8D%E5%8A%A1CPU%E8%B4%9F%E8%BD%BD%E9%AB%98%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5/"/>
<id>http://yoursite.com/2018/12/06/线上服务CPU负载高问题排查/</id>
<published>2018-12-06T05:49:15.410Z</published>
<updated>2019-07-31T08:17:30.558Z</updated>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>最近在进行接口服务优化,前面对处理大量数据的线程进行了优化。当优化后的版本部署到灰度服务器上时,原以为CPU占用率会明显地降低,然而事实却狠狠地打了脸,非但该服务的CPU占用及负载没有降低,反而远远差于上次优化之前版本及线上的服务。是优化方向错了,还是引入了新的问题?<br><a id="more"></a></p><h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><p>通过运维中心<code>zabbix</code>面板查看该机房上所有的报价服务负载情况,新部署接口服务的负载情况如下图红线所示:<br><img src="https://i.loli.net/2018/12/06/5c08d1299cb5d.png" alt="load_before.png"><br>可以发现在上午11:00~14:00时间段内,该接口服务的负载明显高于其他几台服务。CPU利用率的情况与负载类似。</p><h2 id="排查过程"><a href="#排查过程" class="headerlink" title="排查过程"></a>排查过程</h2><h3 id="一、比较用户数量"><a href="#一、比较用户数量" class="headerlink" title="一、比较用户数量"></a>一、比较用户数量</h3><p>5个服务都部署在SZVPC机房,并且可以确定服务运行环境是相同的,那么首先需要确认是否是该服务用户量过大导致的。</p><p>登录CRT,通过<code>netstat -antp|grep 'port'|wc -l</code>命令查看服务器上的实时用户连接数。对比该机房的5个报价服务发现,所有服务的实时用户连接数大体相等,不存在优化版本服务用户连接远大于其他几台服务的情况。到这里可以得出结论是新部署的服务本身有问题。</p><h3 id="二、定位高CPU占用线程"><a href="#二、定位高CPU占用线程" class="headerlink" title="二、定位高CPU占用线程"></a>二、定位高CPU占用线程</h3><p>1、在上一篇的CPU高负载排查过程中,我们定位到是组合和账户的数据处理线程CPU占用率高。那首先,可以确定下上次优化有没有解决这两个线程的CPU占用问题。</p><p>通过Arthas进入到当前服务进程,执行<code>thread -n 5 -i 1000</code>命令来查看当前CPU占用最高的几个线程。分析堆栈信息,可以看到改完之后的服务组合账户线程的CPU占用率已经降低了,至少前5个线程里面不见他们的踪影。</p><p><img src="https://i.loli.net/2018/12/13/5c11f7ed83ec2.png" alt="thread5.png"></p><p>2、排除了组合账户线程的影响,下一步就要定位到底是什么线程占用CPU最高。通过<code>$ top</code>命令,查看CPU实时使用情况(注:图为后面补加的,数据可能有出入):<br><img src="https://i.loli.net/2018/12/07/5c09d1aa96940.png" alt="top_load.png"><br>可以看出pid为26810的进程CPU占用率竟然达到了1000%,而这个进程就是接口服务的进程。</p><p>3、经典的命令行工具,如<em>ps</em>或<em>top</em>,都可以用来显示线程级别的信息,下面列举了这两种方法的使用。</p><p>方法一:通过<code>$ ps -mp <pid> -o tid,THREAD,time</code>命令,查看线程列表。到这里确认tid为8592…的这三个线程CPU占用率最高,且占用CPU的时间也很长。</p><p><img src="https://i.loli.net/2018/12/07/5c09de2a7f8c5.png" alt="thread.png"></p><p>方法二:通过<code>$ top -H -p <pid></code>命令,查看线程列表,得出的结论与方法一一致。</p><p> <img src="https://i.loli.net/2018/12/07/5c09de2511265.png" alt="top_thread.png"></p><p>4、定位到线程tid之后,就可以用<code>jstack</code>命令查看线程具体的堆栈信息了,不过<code>jstack</code>命令中线程id使用16进制,因而首先需要通过 <code>$ echo "obase=16;<tid>"|bc</code>或 <code>$ printf "%x\n" <tid></code>命令将tid转为16进制。</p><p><img src="https://i.loli.net/2018/12/07/5c09de1cb3c4b.png" alt="16.png"></p><p>以tid8592为例,转为16进制后为2190,再通过<code>$ jstack <pid> |grep <tid> -C 20</code>命令查看该线程的堆栈信息。</p><p><img src="https://i.loli.net/2018/12/07/5c09de3041b0a.png" alt="thread_stack.png"></p><h3 id="三、分析"><a href="#三、分析" class="headerlink" title="三、分析"></a>三、分析</h3><p>Gang worker是JVM 用于做新生代垃圾回收(monir gc)的一个线程,#号后面是线程编号。从线程的堆栈信息可以看出是JVM的GC线程一直在占用大量CPU.定位到垃圾收集器的问题,可以通过<code>jstat -gcutil <pid> 1000 100</code>命令统计GC回收的情况(没发现有什么异常)。</p><p><img src="https://i.loli.net/2018/12/13/5c11f7d2e9c4d.png" alt="gcutil.png"></p><p>对应的参数如下:</p><ul><li><strong>S0:</strong>幸存1区当前使用比例</li><li><strong>S1:</strong>幸存2区当前使用比例</li><li><strong>E:</strong>伊甸园区使用比例</li><li><strong>O:</strong>老年代使用比例</li><li><strong>M:</strong>元数据区使用比例</li><li><strong>CCS:</strong>压缩使用比例</li><li><strong>YGC:</strong>年轻代垃圾回收次数</li><li><strong>FGC:</strong>老年代垃圾回收次数</li><li><strong>FGCT:</strong>老年代垃圾回收消耗时间</li><li><strong>GCT:</strong>垃圾回收消耗总时间</li></ul><p>通过<code>jinfo -flags <pid></code>命令,查看该服务器上的JVM内存参数设置,看到了这一条配置:<code>-Xmx12g -XX:MaxDirectMemorySize=18g</code>,原来之前的服务版本中已经不使用直接内存,而是全都使用堆内存,而这里JVM配置却没有改过来,由此推测是服务的内存分配不够用导致频繁GC,进而引起CPU负载过高。</p><p><img src="https://i.loli.net/2018/12/13/5c11f7f40440c.png" alt="jinfo.png"></p><h3 id="四、后续"><a href="#四、后续" class="headerlink" title="四、后续"></a>四、后续</h3><p>修改JVM的配置,将最大堆内存设置为<code>-Xmx=24g</code>,重启服务运行一段时候后发现CPU负载迅速降了下来,并一直低于线上服务的负载。</p><p><img src="https://i.loli.net/2018/12/06/5c08d1312b7aa.png" alt="load_after.png"></p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>1 有关JVM调优相关的可以参考这一系列博客,写的很详尽:<a href="https://www.cnblogs.com/wyb628/p/8566337.html" target="_blank" rel="noopener">Java内存泄露分析系列</a>。</p><p>2 这次问题解决也是凑巧去看了一眼JVM参数,本身GC回收具体什么地方占用了CPU,迫于时间关系实际上没有准确定位到,后面再研究吧。</p>]]></content>
<summary type="html">
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>最近在进行接口服务优化,前面对处理大量数据的线程进行了优化。当优化后的版本部署到灰度服务器上时,原以为CPU占用率会明显地降低,然而事实却狠狠地打了脸,非但该服务的CPU占用及负载没有降低,反而远远差于上次优化之前版本及线上的服务。是优化方向错了,还是引入了新的问题?<br>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="性能调优" scheme="http://yoursite.com/tags/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98/"/>
<category term="JVM" scheme="http://yoursite.com/tags/JVM/"/>
<category term="Linux命令" scheme="http://yoursite.com/tags/Linux%E5%91%BD%E4%BB%A4/"/>
</entry>
<entry>
<title>Netty源码之writeAndFlush()流程与异步</title>
<link href="http://yoursite.com/2018/12/06/Netty%E6%BA%90%E7%A0%81%E4%B9%8BwriteAndFlush()%E6%B5%81%E7%A8%8B%E4%B8%8E%E5%BC%82%E6%AD%A5/"/>
<id>http://yoursite.com/2018/12/06/Netty源码之writeAndFlush()流程与异步/</id>
<published>2018-12-06T01:14:11.376Z</published>
<updated>2019-03-25T06:36:30.985Z</updated>
<content type="html"><![CDATA[<p><img src="https://i.loli.net/2018/11/19/5bf21c6726a92.png" alt="netty.png"></p><h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>挖坑待填…</p><p>本文旨在对Netty中writeAndFlush()方法的线程安全及并发问题简单谈下自己的理解,同时对channel.writeAndFlush()和ctx.writeAndFlush()两个方法通过阅读源码进行比较。</p><a id="more"></a>]]></content>
<summary type="html">
<p><img src="https://i.loli.net/2018/11/19/5bf21c6726a92.png" alt="netty.png"></p>
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>挖坑待填…</p>
<p>本文旨在对Netty中writeAndFlush()方法的线程安全及并发问题简单谈下自己的理解,同时对channel.writeAndFlush()和ctx.writeAndFlush()两个方法通过阅读源码进行比较。</p>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="Netty" scheme="http://yoursite.com/tags/Netty/"/>
<category term="源码" scheme="http://yoursite.com/tags/%E6%BA%90%E7%A0%81/"/>
</entry>
<entry>
<title>Linux之sed用法</title>
<link href="http://yoursite.com/2018/12/05/Linux%E4%B9%8Bsed%E7%94%A8%E6%B3%95/"/>
<id>http://yoursite.com/2018/12/05/Linux之sed用法/</id>
<published>2018-12-05T01:33:22.380Z</published>
<updated>2019-02-01T06:16:32.856Z</updated>
<content type="html"><![CDATA[<p><img src="https://i.loli.net/2019/02/01/5c53e438b7367.png" alt="Linux.png"></p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Linux sed命令是利用script来处理、编辑文本文件。</p><a id="more"></a><h2 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h2><p><code>sed [OPTION]... {script-only-if-no-other-script} [input-file]...</code></p><h4 id="参数说明"><a href="#参数说明" class="headerlink" title="参数说明"></a>参数说明</h4><ul><li>-n, –quiet, –silent<br> suppress automatic printing of pattern space</li><li>-e script, –expression=script<br> add the script to the commands to be executed</li><li><p>-f script-file, –file=script-file<br> add the contents of script-file to the commands to be executed</p><p> –follow-symlinks<br> follow symlinks when processing in place; hard links<br> will still be broken.</p></li><li>-i[SUFFIX], –in-place[=SUFFIX]<br> edit files in place (makes backup if extension supplied).<br> The default operation mode is to break symbolic and hard links.<br> This can be changed with –follow-symlinks and –copy.</li><li>-c, –copy<br> use copy instead of rename when shuffling files in -i mode.<br> While this will avoid breaking links (symbolic or hard), the<br> resulting editing operation is not atomic. This is rarely<br> the desired mode; –follow-symlinks is usually enough, and<br> it is both faster and more secure.</li><li>-l N, –line-length=N<br> specify the desired line-wrap length for the `l’ command<br> –posix<br> disable all GNU extensions.</li><li>-r, –regexp-extended<br> use extended regular expressions in the script.</li><li>-s, –separate<br> consider files as separate rather than as a single continuous<br> long stream.</li><li>-u, –unbuffered<br> load minimal amounts of data from the input files and flush<br> the output buffers more often</li></ul><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>查看某段时间的服务日志:<code>sed -n '/1970-01-01 01:01/,$p' 1.log |more</code></p>]]></content>
<summary type="html">
<p><img src="https://i.loli.net/2019/02/01/5c53e438b7367.png" alt="Linux.png"></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Linux sed命令是利用script来处理、编辑文本文件。</p>
</summary>
<category term="Linux" scheme="http://yoursite.com/categories/Linux/"/>
<category term="Linux命令" scheme="http://yoursite.com/tags/Linux%E5%91%BD%E4%BB%A4/"/>
</entry>
<entry>
<title>浅谈OGNL表达式</title>
<link href="http://yoursite.com/2018/11/30/%E6%B5%85%E8%B0%88OGNL%E8%A1%A8%E8%BE%BE%E5%BC%8F/"/>
<id>http://yoursite.com/2018/11/30/浅谈OGNL表达式/</id>
<published>2018-11-30T02:29:36.063Z</published>
<updated>2018-11-30T02:33:36.421Z</updated>
<content type="html"><![CDATA[<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>挖坑…</p><p>OGNL特殊用法请参考:<a href="https://github.com/alibaba/arthas/issues/71" target="_blank" rel="noopener">https://github.com/alibaba/arthas/issues/71</a><br>OGNL表达式官方指南:<a href="https://commons.apache.org/proper/commons-ognl/language-guide.html" target="_blank" rel="noopener">https://commons.apache.org/proper/commons-ognl/language-guide.html</a></p><a id="more"></a>]]></content>
<summary type="html">
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>挖坑…</p>
<p>OGNL特殊用法请参考:<a href="https://github.com/alibaba/arthas/issues/71" target="_blank" rel="noopener">https://github.com/alibaba/arthas/issues/71</a><br>OGNL表达式官方指南:<a href="https://commons.apache.org/proper/commons-ognl/language-guide.html" target="_blank" rel="noopener">https://commons.apache.org/proper/commons-ognl/language-guide.html</a></p>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="OGNL" scheme="http://yoursite.com/tags/OGNL/"/>
</entry>
<entry>
<title>Logback以及Log4j2性能测试对比</title>
<link href="http://yoursite.com/2018/11/28/Logback%E4%BB%A5%E5%8F%8ALog4j2%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E5%AF%B9%E6%AF%94/"/>
<id>http://yoursite.com/2018/11/28/Logback以及Log4j2性能测试对比/</id>
<published>2018-11-28T01:06:32.907Z</published>
<updated>2019-03-25T08:24:06.663Z</updated>
<content type="html"><![CDATA[<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>挖坑待填…</p><p>在进行RPC服务调优时,我们往往下意识地会从框架、JVM、业务代码等方向思考,但却往往会忽略线上服务日志打印对服务性能的影响。实际上,在某些情况下,选取合适的日志框架、有效的日志打印策略,将会给服务带来很大的性能提升。本文将尝试对常用的日志框架进行对比分析,从而服务于我们的服务性能调优工作。</p><a id="more"></a><h2 id="内容"><a href="#内容" class="headerlink" title="内容"></a>内容</h2>]]></content>
<summary type="html">
<h2 id="文章摘要"><a href="#文章摘要" class="headerlink" title="文章摘要"></a>文章摘要</h2><p>挖坑待填…</p>
<p>在进行RPC服务调优时,我们往往下意识地会从框架、JVM、业务代码等方向思考,但却往往会忽略线上服务日志打印对服务性能的影响。实际上,在某些情况下,选取合适的日志框架、有效的日志打印策略,将会给服务带来很大的性能提升。本文将尝试对常用的日志框架进行对比分析,从而服务于我们的服务性能调优工作。</p>
</summary>
<category term="Java" scheme="http://yoursite.com/categories/Java/"/>
<category term="Logback" scheme="http://yoursite.com/tags/Logback/"/>
<category term="Log4j2" scheme="http://yoursite.com/tags/Log4j2/"/>
</entry>
</feed>