Repository navigation
Expand file tree
/
Copy pathatom.xml
More file actions
556 lines (359 loc) · 225 KB
/
Copy pathatom.xml
File metadata and controls
556 lines (359 loc) · 225 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
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>奋斗不息</title>
<subtitle>态度决定一切</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://yefan813.github.io/"/>
<updated>2020-06-15T02:57:01.716Z</updated>
<id>https://yefan813.github.io/</id>
<author>
<name>Evan</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>秒杀系统设计思考</title>
<link href="https://yefan813.github.io/2020/06/15/%E7%A7%92%E6%9D%80%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%80%83/"/>
<id>https://yefan813.github.io/2020/06/15/%E7%A7%92%E6%9D%80%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%80%83/</id>
<published>2020-06-15T02:55:18.000Z</published>
<updated>2020-06-15T02:57:01.716Z</updated>
<content type="html"><![CDATA[<p>总结一下秒杀系统架构设计的一些思考</p><a id="more"></a><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-06-15-%E7%A7%92%E6%9D%80%E7%B3%BB%E7%BB%9F.png" alt=""></p>]]></content>
<summary type="html">
<p>总结一下秒杀系统架构设计的一些思考</p>
</summary>
</entry>
<entry>
<title>分布式事务解决方案</title>
<link href="https://yefan813.github.io/2020/05/22/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
<id>https://yefan813.github.io/2020/05/22/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
<published>2020-05-22T04:18:24.000Z</published>
<updated>2020-05-22T04:26:04.766Z</updated>
<content type="html"><![CDATA[<p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%871.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%872.png" alt=""></p><a id="more"></a><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%873.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%874.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%875.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%876.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%877.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%878.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%879.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8710.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8711.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8712.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8713.png" alt=""><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8714.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8715.png" alt=""><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8716.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8717.png" alt=""><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8718.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8719.png" alt=""></p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%8720.png" alt=""></p><p><a href="https://github.com/yefan813/DistributionTrasaction-" target="_blank" rel="noopener">PPT下载地址</a></p>]]></content>
<summary type="html">
<p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%871.png" alt=""></p>
<p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-%E5%B9%BB%E7%81%AF%E7%89%872.png" alt=""></p>
</summary>
</entry>
<entry>
<title>MySql核心知识</title>
<link href="https://yefan813.github.io/2020/05/22/Mysql%E6%A0%B8%E5%BF%83%E7%9F%A5%E8%AF%86/"/>
<id>https://yefan813.github.io/2020/05/22/Mysql%E6%A0%B8%E5%BF%83%E7%9F%A5%E8%AF%86/</id>
<published>2020-05-22T03:46:47.000Z</published>
<updated>2020-05-22T03:54:27.440Z</updated>
<content type="html"><![CDATA[<p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-MySql%E6%A0%B8%E5%BF%83%E7%9F%A5%E8%AF%86.png" alt=""></p>]]></content>
<summary type="html">
<p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-05-22-MySql%E6%A0%B8%E5%BF%83%E7%9F%A5%E8%AF%86.png" alt=""></p>
</summary>
<category term="MySql" scheme="https://yefan813.github.io/tags/MySql/"/>
</entry>
<entry>
<title>记一次生产异常问题排查过程</title>
<link href="https://yefan813.github.io/2020/05/20/%E8%AE%B0%E4%B8%80%E6%AC%A1%E7%94%9F%E4%BA%A7%E5%BC%82%E5%B8%B8%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5%E8%BF%87%E7%A8%8B/"/>
<id>https://yefan813.github.io/2020/05/20/%E8%AE%B0%E4%B8%80%E6%AC%A1%E7%94%9F%E4%BA%A7%E5%BC%82%E5%B8%B8%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5%E8%BF%87%E7%A8%8B/</id>
<published>2020-05-20T06:59:34.000Z</published>
<updated>2020-05-20T08:16:50.022Z</updated>
<content type="html"><![CDATA[<h1 id="账号中心异常日志问题排查"><a href="#账号中心异常日志问题排查" class="headerlink" title="账号中心异常日志问题排查"></a>账号中心异常日志问题排查</h1><p>今天上午发现调用账号中心接口异常,于是立马去线上查看日志发现了大量的异常信息,异常日志如下:<br><img src="https://ws3.sinaimg.cn/large/006tNc79gy1fyzjeapsjtj31ez0u0wud.jpg" alt=""><br><img src="https://ws4.sinaimg.cn/large/006tNc79gy1fyzjp2hwa4j327y0m4wn7.jpg" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure</span><br></pre></td></tr></table></figure><p>于是上网查了下相关文章,给出如下解释</p><p>MySQL默认连接存活时长为28800秒,即8小时。如果在wait_timeout期间内,数据库连接(java.sql.Connection)一直处于等待状态,MySQL就将该连接关闭。此时,数据库连接池仍然合法地持有该连接,当用该连接来进行数据库操作时,就报上述错误。</p><p><em>查了下线上账号中心库的 wait_timeout时间为259200s=3天</em></p><p>确实从异常日志文件也能看到异常信息:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 1,536,593,656 milliseconds ago. The last packet sent successfully to the server was 1,536,593,656 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.</span><br></pre></td></tr></table></figure></p><p>从日志文件看出有些数据库操作正常的,有些查询是异常的,所有猜测大概是数据库连接池中部分链接其实已经断开。</p><p>其中异常日志也给出了原因和响应的解决办法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.</span><br></pre></td></tr></table></figure><p>方法一:<br>即使用autoReconnect=true来避免面这个这个问题,但是查了下配置文件中发现已经配置了此参数</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xxxauth.datasource.url=jdbc:mysql://xxxxx?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&rewriteBatchedStatements=TRUE&useSSL=false</span><br></pre></td></tr></table></figure><p>既然已经配置了那为什么还有这个问题,于是查了想相关文档<br>autoReconnect=true这个参数对 mysql5以上的版本不生效,现在公司 mysql 使用的都是5.6版本,所以这个方法不行。</p><p>方法二:<br>将数据库 wait_timeout调大,但是就算调大可能链接空闲等待的时间还是会超出等待时间还是不能解决实际问题。</p><p>方法三:<br>数据库连接池配置:testOnBorrow、testOnReturn、testWhileIdle属性,意义分别是取得、返回对象和空闲时,是否进行对象有效性检查,默认都是False关闭状态。只要都设置为True,并提供validationQuery语句即可保证数据库连接始终有效。<br>检查了下账号中心相关配置<br><img src="https://ws3.sinaimg.cn/large/006tNc79gy1fyzkv8yl3pj318c0eqjuw.jpg" alt=""><br>账号中心是有配置但是为啥还是有这个问题?<br>于是 debug 了下DataSourceConfig这个类加载过程发现这么些个配置文件竟然只加载了前四项<br>有图有真相<br><img src="https://ws1.sinaimg.cn/large/006tNc79gy1fyzkwmregnj31dm0u0tqr.jpg" alt=""></p><p>xxxauth.datasource.tomcat开头的都解析不了咯。<br>继续看,发现如下代码<br><img src="https://ws1.sinaimg.cn/large/006tNc79gy1fyzl0epyazj31fp0u0toe.jpg" alt=""><br>系统会在家 prefix + “.” + reflaxName的的配置信息,而这里的prefix是在类注解是这样的</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@ConfigurationProperties(prefix = "xxxauth.datasource")</span><br></pre></td></tr></table></figure><p>所以只会加载xxxauth.datasource.test-on-borrow这样的配置加载不到xxxauth.datasource.tomcat.test-on-borrow类似这样的配置。</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><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">xxxauth.datasource.driver-class-name=com.mysql.jdbc.Driver</span><br><span class="line">xxxauth.datasource.test-on-borrow=true</span><br><span class="line">xxxauth.datasource.test-while-idle=true</span><br><span class="line">xxxauth.datasource.max-active=30</span><br><span class="line">xxxauth.datasource.test-on-return=true</span><br><span class="line">xxxauth.datasource.test-on-connect=true</span><br><span class="line">xxxauth.datasource.initial-size=10</span><br><span class="line">xxxauth.datasource.min-idle=10</span><br><span class="line">xxxauth.datasource.validation-query=SELECT 1</span><br><span class="line">xxxauth.datasource.validation-interval=5000</span><br><span class="line">xxxauth.datasource.log-validation-errors=true</span><br></pre></td></tr></table></figure><p>再次 debug 发现 这些配置以及加载到了<br><img src="https://ws3.sinaimg.cn/large/006tNc79gy1fyzl7w8uh3j323c0u0tnr.jpg" alt=""></p>]]></content>
<summary type="html">
<h1 id="账号中心异常日志问题排查"><a href="#账号中心异常日志问题排查" class="headerlink" title="账号中心异常日志问题排查"></a>账号中心异常日志问题排查</h1><p>今天上午发现调用账号中心接口异常,于是立马去线上查看日志发现
</summary>
</entry>
<entry>
<title>RocketMQ数据存储结构详解</title>
<link href="https://yefan813.github.io/2020/04/01/RocketMQ%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84%E8%AF%A6%E8%A7%A3/"/>
<id>https://yefan813.github.io/2020/04/01/RocketMQ%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84%E8%AF%A6%E8%A7%A3/</id>
<published>2020-04-01T09:47:33.000Z</published>
<updated>2020-04-01T10:02:42.804Z</updated>
<content type="html"><![CDATA[<h1 id="1-RocketMQ消息存储结构"><a href="#1-RocketMQ消息存储结构" class="headerlink" title="1 RocketMQ消息存储结构"></a>1 RocketMQ消息存储结构</h1><p><strong>Broker 整体架构图</strong><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-065911.png" alt=""></p><a id="more"></a><p><strong>消息体结构</strong><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-071537.png" alt=""></p><p><a href="https://github.com/apache/rocketmq/blob/master/docs/cn/design.md" target="_blank" rel="noopener">官网地址</a></p><h2 id="1-1-commitLog-(类似Mysql的redolog)"><a href="#1-1-commitLog-(类似Mysql的redolog)" class="headerlink" title="1.1 commitLog (类似Mysql的redolog)"></a>1.1 commitLog (类似Mysql的redolog)</h2><p>消息存放的物理文件,是消息主体以及元数据存储的主体。<strong>每个broker上的 CommitLog被本机所有的Consumequeue共享</strong>,用于存储Producer端写入的消息主体内容,消息内容不是定长的,文件<strong>顺序写,随机读</strong>。单个文件大小默认1G 可以配置,文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是顺序写入日志文件,当文件满了,写入下一个文件。</p><blockquote><p>commitlog存储单元结构图<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-073520.png" alt=""></p></blockquote><h2 id="1-2-ConsumeQueue"><a href="#1-2-ConsumeQueue" class="headerlink" title="1.2 ConsumeQueue"></a>1.2 ConsumeQueue</h2><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-072604.png" alt=""><br>消息消费队列,引入的目的主要是提高消息消费的性能,由于RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要遍历commitlog文件中根据topic检索消息是非常低效的。Consumer即可根据ConsumeQueue来查找待消费的消息。其中,ConsumeQueue(逻辑消费队列)作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。consumequeue文件可以看成是基于topic的commitlog索引文件,故consumequeue文件夹的组织方式如下:topic/queue/file三层组织结构,具体存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同样consumequeue文件采取定长设计,每一个条目共20个字节,分别为8字节的commitlog物理偏移量、4字节的消息长度、8字节tag hashcode,单个文件由30W个条目组成,可以像数组一样随机访问每一个条目,每个ConsumeQueue文件大小约5.72M;</p><p><strong>consumerQueue 存储单元格结构</strong><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-080408.png" alt=""></p><h2 id="1-3-IndexFile"><a href="#1-3-IndexFile" class="headerlink" title="1.3 IndexFile"></a>1.3 IndexFile</h2><p>消息索引文件,Index 索引文件提供了对 CommitLog 进行数据检索,提供通过 key 或者时间区间来查询 CommitLog 中的消息的方法。在实际的物理存储上,文件名则是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为400M,一个IndexFile可以保存 2000W个索引</p><p>IndexFile结构分析<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-080600.png" alt=""></p><p><strong>IndexHead</strong>: </p><ul><li>beginTimestamp:该索引文件包含消息的最小存储时间</li><li>endTimestamp:该索引文件包含消息的最大存储时间</li><li>beginPhyoffset:该索引文件中包含消息的最小物理偏移量(commitlog 文件偏移量)</li><li>endPhyoffset:该索引文件中包含消息的最大物理偏移量(commitlog 文件偏移量)</li><li>hashSlotCount:hashslot个数,并不是 hash 槽使用的个数,在这里意义不大,</li><li>indexCount:已使用的 Index 条目个数</li></ul><p><strong>Hash 槽</strong>:</p><ul><li>一个 IndexFile 默认包含 500W 个 Hash 槽,每个 Hash 槽存储的是落在该 Hash 槽的 hashcode 最新的 Index 的索引</li></ul><p><strong>Index 条目列表</strong>:</p><ul><li>hashcode:key 的 hashcode</li><li>phyoffset:消息对应的物理偏移量</li><li>timedif:该消息存储时间与第一条消息的时间戳的差值,小于 0 表示该消息无效</li><li>preIndexNo:该条目的前一条记录的 Index 索引,hash 冲突时,根据该值构建链表结构</li></ul><h2 id="1-5-消息发送到消息消费数据流转"><a href="#1-5-消息发送到消息消费数据流转" class="headerlink" title="1.5 消息发送到消息消费数据流转"></a>1.5 消息发送到消息消费数据流转</h2><ol><li>Producer 将消息发送到 Broker 后,Broker 会采用同步或者异步的方式把消息写入到 CommitLog。RocketMQ 所有的消息都会存放在 CommitLog 中,为了保证消息存储不发生混乱,对 CommitLog 写之前会加锁,同时也可以使得消息能够被顺序写入到 CommitLog,只要消息被持久化到磁盘文件 CommitLog,那么就可以保证 Producer 发送的消息不会丢失<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-084704.png" alt=""></li><li>CommitLog 持久化后,会把里面的消息 Dispatch 到对应的 Consume Queue 上,Consume Queue 相当于 Kafka 中的 Partition,是一个逻辑队列,存储了这个 Queue 在 CommitLog 中的起始 Offset,log 大小和 MessageTag 的 hashCode<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-084805.png" alt=""></li><li>当消费者进行消息消费时,会先读取 ConsumerQueue,逻辑消费队列 ConsumeQueue 保存了指定 Topic 下的队列消息在 CommitLog 中的起始物理偏移量 Offset,消息大小、和消息 Tag 的 HashCode 值。<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-084857.png" alt=""></li><li>直接从 ConsumerQueue 中读取消息是没有数据的,真正的消息主体在 CommitLog 中,所以还需要从 CommitLog 中读取消息。<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-084930.png" alt=""></li></ol><h2 id="1-6-消息存储流程代码流程"><a href="#1-6-消息存储流程代码流程" class="headerlink" title="1.6 消息存储流程代码流程"></a>1.6 消息存储流程代码流程</h2><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-085936.png" alt=""></p><ul><li>Broker端收到消息后,将消息原始信息保存在CommitLog文件对应的MappedFile中,然后异步刷新到磁盘</li><li>ReputMessageServie线程异步的将CommitLog中MappedFile中的消息保存到ConsumerQueue和IndexFile中</li><li>ConsumerQueue和IndexFile只是原始文件的索引信息</li></ul><h1 id="2-同步刷盘和异步刷盘"><a href="#2-同步刷盘和异步刷盘" class="headerlink" title="2 同步刷盘和异步刷盘"></a>2 同步刷盘和异步刷盘</h1><p><strong>刷盘流程</strong><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-025441.png" alt=""></p><ul><li>producer发送给broker的消息保存在MappedFile中,然后通过刷盘机制同步到磁盘中</li><li>刷盘分为同步刷盘和异步刷盘</li><li>异步刷盘后台线程按一定时间间隔执行</li><li>同步刷盘也是生产者-消费者模型。broker保存消息到MappedFile后,创建GroupCommitRequest请求放入列表,并阻塞等待。后台线程从列表中获取请求并刷新磁盘,成功刷盘后通知等待线程。</li></ul><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-091530.png" alt=""><br>(1) 同步刷盘:如上图所示,只有在消息真正持久化至磁盘后RocketMQ的Broker端才会真正返回给Producer端一个成功的ACK响应。同步刷盘对MQ消息可靠性来说是一种不错的保障,但是性能上会有较大影响,一般适用于金融业务应用该模式较多。</p><p>(2) 异步刷盘:能够充分利用OS的PageCache的优势,只要消息写入PageCache即可将成功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量。</p><p>读取消息的ConsumeQueue文件也会加载到PageCache,读PageCache和内存速度差不多。</p><h2 id="2-1-刷盘方法调用流程"><a href="#2-1-刷盘方法调用流程" class="headerlink" title="2.1 刷盘方法调用流程"></a>2.1 刷盘方法调用流程</h2><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-090226.png" alt=""></p><ul><li>producer发送给broker的消息保存在MappedFile中,然后通过刷盘机制同步到磁盘中</li><li>刷盘分为同步刷盘和异步刷盘</li><li>异步刷盘后台线程按一定时间间隔执行</li><li>同步刷盘也是生产者-消费者模型。broker保存消息到MappedFile后,创建GroupCommitRequest请求放入列表,并阻塞等待。后台线程从列表中获取请求并刷新磁盘,成功刷盘后通知等待线程</li></ul><h1 id="3-同步复制和异步复制"><a href="#3-同步复制和异步复制" class="headerlink" title="3 同步复制和异步复制"></a>3 同步复制和异步复制</h1><h2 id="3-1-异步复制"><a href="#3-1-异步复制" class="headerlink" title="3.1 异步复制"></a>3.1 异步复制</h2><p>消息写到 Broker 后,直接返回客户端成功,消息数据异步到 Slave 节点<br><strong>优点</strong>:性能高,不需要等到消息同步直接返回。适合能容忍消息丢失的场景<br><strong>缺点</strong>:这种可能会导致消息丢失</p><h2 id="3-2-同步复制"><a href="#3-2-同步复制" class="headerlink" title="3.2 同步复制"></a>3.2 同步复制</h2><p>消息写到Broker 后,需要将消息同步复制到 Slave 节点才返回成功<br><strong>优点</strong>:能够保证消息绝对不丢失,保证高可用,这种适合和金融相关的业务<br><strong>缺点</strong>:性能不高,因为需要将消息同步到 slave 才能能返回成功</p><h1 id="4-高可用机制"><a href="#4-高可用机制" class="headerlink" title="4 高可用机制"></a>4 高可用机制</h1><h2 id="4-1-Broker-集群"><a href="#4-1-Broker-集群" class="headerlink" title="4.1 Broker 集群"></a>4.1 Broker 集群</h2><table><thead><tr><th>名称</th><th>描述</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>单个 Master</td><td>这种方式风险较大,一旦Broker 重启或者宕机时,会导致整个服务不可用,不建议线上环境使用</td><td>~</td><td>~</td></tr><tr><td>多 Master 模式</td><td>一个集群无 Slave,全是 Master,例如 2 个 Master 或者 3 个 Master, 消息分别写到不同的 master 节点上</td><td>配置简单,单个Master 宕机或重启维护对应用无影响,在磁盘配置为 RAID10 时,即使机器宕机不可恢复情况下,由与 RAID10 磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢)。性能最高</td><td>单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性收到影响</td></tr><tr><td>多 Master 多 Slave 模式,异步复制</td><td>每个 Master 配置一个 Slave,有多对Master-Slave,HA 采用异步复制方式,主备有短暂消息延迟,毫秒级。消息写入 Master 节点,异步复制到 Slave节点</td><td>即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,因为 Master 宕机后,消费者仍然可以从 Slave 消费,此过程对应用透明。不需要人工干预。性能同多 Master 模式几乎一样</td><td>Master 宕机,磁盘损坏情况,会丢失少量消息</td></tr><tr><td>多 Master 多 Slave 模式,同步双写</td><td>每个 Master 配置一个 Slave,有多对Master-Slave,HA 采用同步双写方式,master和slave都写成功,向应用返回成功。</td><td>数据与服务都无单点,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高</td><td>性能比异步复制模式略低,大约低 10%左右,发送单个消息的 RT 会略高。目前主宕机后,备机不能自动切换为主机,后续会支持自动切换功能。</td></tr></tbody></table><h2 id="4-2-Dledger"><a href="#4-2-Dledger" class="headerlink" title="4.2 Dledger"></a>4.2 Dledger</h2><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-140505.png" alt=""></p><p>支持故障转移,自动将 slave 节点提升为 master 提供服务</p>]]></content>
<summary type="html">
<h1 id="1-RocketMQ消息存储结构"><a href="#1-RocketMQ消息存储结构" class="headerlink" title="1 RocketMQ消息存储结构"></a>1 RocketMQ消息存储结构</h1><p><strong>Broker 整体架构图</strong><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-31-065911.png" alt=""></p>
</summary>
<category term="消息队列,MQ,RocketMQ" scheme="https://yefan813.github.io/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%EF%BC%8CMQ%EF%BC%8CRocketMQ/"/>
</entry>
<entry>
<title>Kafka数据存储结构详解</title>
<link href="https://yefan813.github.io/2020/04/01/Kafka%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84%E8%AF%A6%E8%A7%A3/"/>
<id>https://yefan813.github.io/2020/04/01/Kafka%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84%E8%AF%A6%E8%A7%A3/</id>
<published>2020-04-01T09:44:59.000Z</published>
<updated>2020-04-20T12:16:19.396Z</updated>
<content type="html"><![CDATA[<h1 id="Kafka存储结构详解"><a href="#Kafka存储结构详解" class="headerlink" title="Kafka存储结构详解"></a>Kafka存储结构详解</h1><h2 id="1-Topic"><a href="#1-Topic" class="headerlink" title="1 Topic"></a>1 Topic</h2><ul><li>用一个Topic的消息可以分布在一个或者多个broker上</li><li>一个 Topic 包含一个或者多个 partation</li><li>每个消息都属于且仅属于一个Topic</li><li>Producer 发布数据时,必须指定该消息发布到哪一个 Topic 上</li><li>Consumer 订阅消息时,必须制定订阅哪个 Topic</li></ul><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-082344.png" alt=""><br><a id="more"></a></p><h2 id="2-Partition"><a href="#2-Partition" class="headerlink" title="2 Partition"></a>2 Partition</h2><ul><li>物理概念,一个 partition 只分布在一个 broker 上(不考虑备份)</li><li>一个 partition 物理上对应一个文件夹</li><li>一个 partition 包含多个 segment(逻辑上的概念,不存在具体物理文件)</li><li>一个 segment 对应一个文件</li><li>segment由一个个不变记录组成</li><li>记录只会被 append 到 segment,不会被单独修改或者删除</li><li>清除过期日志时,直接删除一个或者多个 segment</li></ul><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-065909.png" alt=""></p><p><strong>topic名称-分区数命名</strong><br><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></pre></td><td class="code"><pre><span class="line">//分布在不同的broker节点上 </span><br><span class="line">test-topic-0 </span><br><span class="line">test-topic-1 </span><br><span class="line">test-topic-2</span><br></pre></td></tr></table></figure></p><p><strong>partition文件存储方式</strong><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-075742.png" alt=""></p><h3 id="疑问:-为什么需要分区"><a href="#疑问:-为什么需要分区" class="headerlink" title="疑问: 为什么需要分区"></a>疑问: 为什么需要分区</h3><p>为了性能考虑,如果不分区每个topic的消息只存在一个broker上,那么所有的消费者都是从这个broker上消费消息,那么单节点的broker成为性能的瓶颈,如果有分区的话生产者发过来的消息分别存储在各个broker不同的partition上,这样消费者可以并行的从不同的broker不同的partition上读消息,实现了水平扩展。 </p><p style="color:red">总结一句话:可以多个 broker 同事操作,提高并行度</p><h3 id="2-2-分区文件下存了哪些东西"><a href="#2-2-分区文件下存了哪些东西" class="headerlink" title="2.2 分区文件下存了哪些东西"></a>2.2 分区文件下存了哪些东西</h3><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-082719.png" alt=""></p><h2 id="3-Segment"><a href="#3-Segment" class="headerlink" title="3 Segment"></a>3 Segment</h2><p>segment 是个逻辑上的概念,并不存在真实的 segment 文件<br>Segment 是由一个 .index 和 一个 .log文件组成的。所以从可看到一个上图一个 partition 存在一个或者多个 segment。</p><ul><li>.index 文件是索引文件用来快速的查找真实消息数据</li><li>.log 文件是用来存储数据的文件<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-074720.png" alt=""><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-084529.png" alt=""></li></ul><h3 id="疑问:-为什么有了-Partition-还需要-segment"><a href="#疑问:-为什么有了-Partition-还需要-segment" class="headerlink" title="疑问: 为什么有了 Partition 还需要 segment"></a>疑问: 为什么有了 Partition 还需要 segment</h3><p>通过上面的图,可以了解到一个 partition 下存在多个 segment,一个 segment 由有一个.index和一个.log文件组成,如果不用这种方式,那可以使用一个.index和一个.log文件组成(类似RocketMQ中使用CommitLog文件来保存所有的数据文件,由多个 indexfile 来存储索引文件)。这样的坏处是,随着消息的不断写入这个文件,由于kafka的消息不会做更新操作都是顺序写入的,如果做消息清理的时候只能删除文件的前面部分删除,不符合kafka顺序写入的设计,如果多个segment的话那就比较方便了,直接删除整个文件即可保证了每个segment的顺序写入。</p><p style="color:red">总结一句话:为了提高写入的效率,以及方便清除不需要的数据</p><h3 id="3-1-index文件内部存储了哪些数据"><a href="#3-1-index文件内部存储了哪些数据" class="headerlink" title="3.1 .index文件内部存储了哪些数据"></a>3.1 .index文件内部存储了哪些数据</h3><p>存储了对应数据文件的部分offset,以及 position(表示具体消息存储在log中的物理地址)。可以看待 offset 并不是连续的,而是每隔 6 个 offset 存储一条索引数据。<br><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">offset: 1049 position: 16205 </span><br><span class="line">offset: 1065 position: 32410 </span><br><span class="line">offset: 1081 position: 48615 </span><br><span class="line">offset: 1097 position: 64820 </span><br><span class="line">offset: 1113 position: 81025 </span><br><span class="line">offset: 1129 position: 97230</span><br></pre></td></tr></table></figure></p><h4 id="疑问:-为什么-index-文件中这些-offset-不是连续的编号呢?"><a href="#疑问:-为什么-index-文件中这些-offset-不是连续的编号呢?" class="headerlink" title="疑问: 为什么 index 文件中这些 offset 不是连续的编号呢?"></a>疑问: 为什么 index 文件中这些 offset 不是连续的编号呢?</h4><p>因为index文件中并没有为数据文件中的每条消息都建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。这种存储方式叫做稀疏索引<br>也可以配置成稠密索引,带来的问题就是索引文件太大。但是查找效率会高一点</p><p></p><p style="color:red">总结一句话:减小 index 文件大小,可以将 index 文件内容加载到内存中,从而减小占用内存空间大小</p><p></p><h3 id="3-2-log文件存储了那些数据"><a href="#3-2-log文件存储了那些数据" class="headerlink" title="3.2 .log文件存储了那些数据"></a>3.2 .log文件存储了那些数据</h3><p>log数据文件中并不是直接存储数据,而是通过许多的message组成,message包含了实际的消息数据。<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-091728.png" alt=""><br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-091807.png" alt=""></p><h2 id="4-消费者如何根据-offset-查找-message"><a href="#4-消费者如何根据-offset-查找-message" class="headerlink" title="4 消费者如何根据 offset 查找 message"></a>4 消费者如何根据 offset 查找 message</h2><p>假如我们想要读取offset=1066的message,需要通过下面2个步骤查找。</p><ol><li>查找segment file<br>00000000000000000000.index表示最开始的文件,起始偏移量(offset)为0.第二个文件00000000000000001018.index的消息量起始偏移量为1019 = 1018 + 1.同样,第三个文件00000000000000002042.index的起始偏移量为2043=2042 + 1,其他后续文件依次类推,以起始偏移量命名并排序这些文件,只要根据offset 二分查找文件列表,就可以快速定位到具体文件。 当offset=1066时定位到00000000000000001018.index|log</li><li>通过segment file查找message<br>通过第一步定位到segment file,当offset=1066时,依次定位到00000000000000001018.index的元数据物理位置和00000000000000001018.log的物理偏移地址,此时我们只能拿到1065的物理偏移地址,然后再通过00000000000000001018.log顺序查找直到offset=1066为止。每个message都有固定的格式很容易判断是否是下一条消息</li></ol><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-092404.png" alt=""></p>]]></content>
<summary type="html">
<h1 id="Kafka存储结构详解"><a href="#Kafka存储结构详解" class="headerlink" title="Kafka存储结构详解"></a>Kafka存储结构详解</h1><h2 id="1-Topic"><a href="#1-Topic" class="headerlink" title="1 Topic"></a>1 Topic</h2><ul>
<li>用一个Topic的消息可以分布在一个或者多个broker上</li>
<li>一个 Topic 包含一个或者多个 partation</li>
<li>每个消息都属于且仅属于一个Topic</li>
<li>Producer 发布数据时,必须指定该消息发布到哪一个 Topic 上</li>
<li>Consumer 订阅消息时,必须制定订阅哪个 Topic</li>
</ul>
<p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-04-01-082344.png" alt=""><br>
</summary>
<category term="消息队列,MQ,Kafka" scheme="https://yefan813.github.io/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97-MQ-Kafka/"/>
</entry>
<entry>
<title>Netty入门</title>
<link href="https://yefan813.github.io/2020/03/26/Netty%E5%85%A5%E9%97%A8/"/>
<id>https://yefan813.github.io/2020/03/26/Netty%E5%85%A5%E9%97%A8/</id>
<published>2020-03-26T12:53:58.000Z</published>
<updated>2020-04-01T10:02:34.519Z</updated>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在介绍 Netty之前一定要先搞明白同步和异步、阻塞和非阻塞、BIO、NIO、AIO 都是什么,然后有哪些优缺点,再来看 Netty 出现的原因,有哪些优势,使用场景是什么。这样循序渐进的学习,更容易理解。</p><a id="more"></a><h1 id="同步和异步、阻塞和非阻塞"><a href="#同步和异步、阻塞和非阻塞" class="headerlink" title="同步和异步、阻塞和非阻塞"></a>同步和异步、阻塞和非阻塞</h1><h2 id="同步和异步"><a href="#同步和异步" class="headerlink" title="同步和异步"></a>同步和异步</h2><h4 id="同步和异步关注的是消息通信机制"><a href="#同步和异步关注的是消息通信机制" class="headerlink" title="同步和异步关注的是消息通信机制"></a>同步和异步关注的是消息通信机制</h4><p><strong>同步</strong>:同步,就是在发出一个<strong>调用</strong>时,在没有得到结果之前,该<strong>调用</strong>就不返回。但是一旦调用返回,就得到返回值了。</p><blockquote><p>举例:你去收发室问老大爷有没快递,老大爷说你等等,我找找,然后你就等啊等啊,可能 5s 分钟就找到了,可能一天才找到,你就一直等着知道大爷回答你</p></blockquote><p><strong>异步</strong>:异步则是相反,<strong>调用</strong>在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在<strong>调用</strong>发出后,<strong>被调用者</strong>通过状态、通知来通知调用者,或通过回调函数处理这个调用</p><blockquote><p>举例:你去收发室问老大爷有没快递,老大爷就说我找一下,找到了我给你打电话。然后你就直接走了,干自己的事去了</p></blockquote><h2 id="阻塞和非阻塞"><a href="#阻塞和非阻塞" class="headerlink" title="阻塞和非阻塞"></a>阻塞和非阻塞</h2><h4 id="阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态"><a href="#阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态" class="headerlink" title="阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态."></a>阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.</h4><p><strong>阻塞</strong>:是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回</p><blockquote><p>举例:你去收发室问大爷有没快递,在大爷没回复你之前,你就把自己‘挂起’啥都不干,知道大爷回复你。</p></blockquote><p><strong>非阻塞</strong>:在不能立刻得到结果之前,该调用不会阻塞当前线程</p><blockquote><p>举例:你去收发室问大爷有没快递,然后你就跑去玩了,时不时的过来问一下是否有结果。</p></blockquote><h1 id="BIO-NIO-AIO是什么"><a href="#BIO-NIO-AIO是什么" class="headerlink" title="BIO/NIO/AIO是什么"></a>BIO/NIO/AIO是什么</h1><h2 id="BIO(Block-IO)"><a href="#BIO(Block-IO)" class="headerlink" title="BIO(Block IO)"></a>BIO(Block IO)</h2><p>同步阻塞,服务器实现模式是一个连接一个线程,即客户端有连接请求是服务度就需要启动一个线程进行处理。<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-27-084253.png" alt=""></p><p><strong>优点</strong>:编写简单,小请求量可以接受</p><p><strong>缺点</strong>:针对高并发,超过100000的并发连接来说该方案并不可取,它所需要的线程资源太多,而且任何时候都可能存在大量线程处于阻塞状态,等待输入或者输出数据就绪,整个方案性能太差。</p><p>代码示例:<br><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></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">BioServer</span> </span>{</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 class="keyword">throws</span> IOException </span>{</span><br><span class="line"> ServerSocket serverSocket = <span class="keyword">new</span> ServerSocket();</span><br><span class="line"> System.out.println(<span class="string">"启动服务器..."</span>);</span><br><span class="line"> serverSocket.bind(<span class="keyword">new</span> InetSocketAddress(<span class="string">"127.0.0.1"</span>, <span class="number">8888</span>));</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> System.out.println(<span class="string">"等待客户端链接..."</span>);</span><br><span class="line"> <span class="comment">//accept()方法阻塞,直到有新的连接</span></span><br><span class="line"> <span class="keyword">final</span> Socket socket = serverSocket.accept();</span><br><span class="line"> System.out.println(<span class="string">"客户已连接,创建新的线程处理"</span>);</span><br><span class="line"> <span class="keyword">new</span> Thread(()->{</span><br><span class="line"> handle(socket);</span><br><span class="line"> }).start();</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">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">handle</span><span class="params">(Socket socket)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">byte</span>[] bytes = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"读数据.."</span>);</span><br><span class="line"> <span class="comment">//read block method</span></span><br><span class="line"> <span class="keyword">int</span> len = socket.getInputStream().read(bytes);</span><br><span class="line"> System.out.println(<span class="keyword">new</span> String(bytes,<span class="number">0</span>,len));</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"写数据.."</span>);</span><br><span class="line"> <span class="comment">//write block method</span></span><br><span class="line"> socket.getOutputStream().write(bytes,<span class="number">0</span>,len);</span><br><span class="line"> socket.getOutputStream().flush();</span><br><span class="line"> }<span class="keyword">catch</span> (IOException ex) {</span><br><span class="line"> ex.printStackTrace();</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><br><span class="line">-------</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">Client</span> </span>{</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 class="keyword">throws</span> IOException </span>{</span><br><span class="line"> Socket socket = <span class="keyword">new</span> Socket(<span class="string">"127.0.0.1"</span>, <span class="number">8888</span>);</span><br><span class="line"> socket.getOutputStream().write(<span class="string">"hello server"</span>.getBytes());</span><br><span class="line"> socket.getOutputStream().flush();</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"write over, wait for msg back"</span>);</span><br><span class="line"> <span class="keyword">byte</span>[] bytes = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];</span><br><span class="line"> <span class="keyword">int</span> len = socket.getInputStream().read(bytes);</span><br><span class="line"> System.out.println(<span class="keyword">new</span> String(bytes,<span class="number">0</span>,len));</span><br><span class="line"> socket.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2 id="NIO(No-Block-IO)"><a href="#NIO(No-Block-IO)" class="headerlink" title="NIO(No-Block IO)"></a>NIO(No-Block IO)</h2><p>同步非阻塞,服务器实现是一个线程处理多个请求连接,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮训到连接有 IO 请求就进行处理</p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-27-084320.png" alt=""></p><p>NIO核心组件:</p><ol><li>Channel: 对应JDK底层的Socket,它除了包含基本的I/O操作,如 bind(),connect(),read(),write()之外</li><li>Buffer: 缓存 Buffer</li><li>Selector:是 Java 非阻塞 I/O实现的关键,将通道Channel注册在 Selector上,如果某个通道 Channel发送 读或写事件,这个Channel处于就绪状态,会被Selector轮询出来,进而进行后续I/O操作</li></ol><p><strong>优点</strong>:使用 Java NIO可以让我们使用较少的线程处理很多连接,较少线程意味着减少了线程创建内存分配和线程上下文切换带来的开销。</p><p><strong>缺点</strong>:编程复杂,需要处理各种问题,API 使用难度大,在高负载下可靠和高效地处理和调度I/O操作是一项繁琐而且容易出错的任务,使用 NIO编程很容易出错。</p><p>代码示例:单线程处理连接<br><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><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</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">NioServer</span> </span>{</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 class="keyword">throws</span> IOException </span>{</span><br><span class="line"> ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();</span><br><span class="line"> serverSocketChannel.socket().bind(<span class="keyword">new</span> InetSocketAddress(<span class="string">"127.0.0.1"</span>, <span class="number">8888</span>));</span><br><span class="line"> <span class="comment">// set block is false</span></span><br><span class="line"> serverSocketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"server is started, listen on :"</span> + serverSocketChannel.getLocalAddress());</span><br><span class="line"> Selector selector = Selector.open();</span><br><span class="line"> <span class="comment">//注册监听客户端连接事件</span></span><br><span class="line"> <span class="comment">//注册 accept 监听事件</span></span><br><span class="line"> serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> <span class="comment">//轮训查看,阻塞方法。监听是否有事件发生</span></span><br><span class="line"> System.out.println(<span class="string">"轮训查看是否有监听事件发生..."</span>);</span><br><span class="line"> selector.select();</span><br><span class="line"> <span class="comment">//监听到有哪些 key 事件发生</span></span><br><span class="line"> System.out.println(<span class="string">"监听到事件发生..."</span>);</span><br><span class="line"> Set<SelectionKey> keys = selector.selectedKeys();</span><br><span class="line"> Iterator<SelectionKey> iterator = keys.iterator();</span><br><span class="line"> <span class="keyword">while</span>(iterator.hasNext()) {</span><br><span class="line"> SelectionKey key = iterator.next();</span><br><span class="line"> <span class="comment">//需要将 key remove 掉不然下次轮训还会处理</span></span><br><span class="line"> iterator.remove();</span><br><span class="line"> handle(key);</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><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">handle</span><span class="params">(SelectionKey key)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (key.isAcceptable()) {</span><br><span class="line"> <span class="comment">//获得 channel</span></span><br><span class="line"> System.out.println(<span class="string">"accept事件发生,建立一条通道"</span>);</span><br><span class="line"> ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//建立 channel</span></span><br><span class="line"> serverSocketChannel.accept();</span><br><span class="line"> <span class="comment">//设置通道是否阻塞</span></span><br><span class="line"> serverSocketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"> <span class="comment">//在通道上放置一个 read 的监听事件</span></span><br><span class="line"> System.out.println(<span class="string">"在 channel 通道上注册 read 事件..."</span>);</span><br><span class="line"> serverSocketChannel.register(key.selector(),SelectionKey.OP_READ);</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(key.isReadable()) {</span><br><span class="line"> System.out.println(<span class="string">"read事件发生..."</span>);</span><br><span class="line"> SocketChannel socketChannel = (SocketChannel) key.channel();</span><br><span class="line"> <span class="comment">//分配内存</span></span><br><span class="line"> ByteBuffer buffer = ByteBuffer.allocate(<span class="number">512</span>);</span><br><span class="line"> buffer.clear();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//从通道读取数据</span></span><br><span class="line"> System.out.println(<span class="string">"读取数据..."</span>);</span><br><span class="line"> <span class="keyword">int</span> len = socketChannel.read(buffer);</span><br><span class="line"> <span class="keyword">if</span> (len != -<span class="number">1</span>) {</span><br><span class="line"> System.out.println(<span class="keyword">new</span> String(buffer.array(),<span class="number">0</span>,len));</span><br><span class="line"> }</span><br><span class="line"> ByteBuffer bufferToWrite = ByteBuffer.wrap(<span class="string">"hello client"</span>.getBytes());</span><br><span class="line"> <span class="comment">//向通道写数据</span></span><br><span class="line"> socketChannel.write(bufferToWrite);</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> e.printStackTrace();</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><br><span class="line">----------</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">Client</span> </span>{</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 class="keyword">throws</span> IOException </span>{</span><br><span class="line"> Socket socket = <span class="keyword">new</span> Socket(<span class="string">"127.0.0.1"</span>, <span class="number">8888</span>);</span><br><span class="line"> socket.getOutputStream().write(<span class="string">"hello server"</span>.getBytes());</span><br><span class="line"> socket.getOutputStream().flush();</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"write over, wait for msg back"</span>);</span><br><span class="line"> <span class="keyword">byte</span>[] bytes = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];</span><br><span class="line"> <span class="keyword">int</span> len = socket.getInputStream().read(bytes);</span><br><span class="line"> System.out.println(<span class="keyword">new</span> String(bytes,<span class="number">0</span>,len));</span><br><span class="line"> socket.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>代码示例:线程池处理连接<br><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><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</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">NioPoolServer</span> </span>{</span><br><span class="line"> ExecutorService pool = Executors.newFixedThreadPool(<span class="number">50</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Selector selector;</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">initServer</span><span class="params">(<span class="keyword">int</span> port)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();</span><br><span class="line"> <span class="comment">// set block is false</span></span><br><span class="line"> serverSocketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"> serverSocketChannel.socket().bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">8888</span>));</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"server is started, listen on :"</span> + serverSocketChannel.getLocalAddress());</span><br><span class="line"> selector = Selector.open();</span><br><span class="line"> <span class="comment">//注册监听客户端连接事件</span></span><br><span class="line"> <span class="comment">//注册 accept 监听事件</span></span><br><span class="line"> serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"> System.out.println(<span class="string">"server run success!"</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">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> NioPoolServer server = <span class="keyword">new</span> NioPoolServer();</span><br><span class="line"> server.initServer(<span class="number">8000</span>);</span><br><span class="line"> server.listen();</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">listen</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> System.out.println(<span class="string">"selector 轮训是否有事件.."</span>);</span><br><span class="line"> <span class="comment">// block method</span></span><br><span class="line"> selector.select();</span><br><span class="line"> Set<SelectionKey> keys = selector.selectedKeys();</span><br><span class="line"> Iterator<SelectionKey> iterator = keys.iterator();</span><br><span class="line"> <span class="keyword">while</span>(iterator.hasNext()) {</span><br><span class="line"> SelectionKey key = iterator.next();</span><br><span class="line"> iterator.remove();</span><br><span class="line"> handle(key);</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><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">handle</span><span class="params">(SelectionKey key)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="keyword">if</span>(key.isAcceptable()) {</span><br><span class="line"> System.out.println(<span class="string">"有客户端链接.."</span>);</span><br><span class="line"> ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();</span><br><span class="line"> SocketChannel channel = serverSocketChannel.accept();</span><br><span class="line"> channel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"> channel.register(key.selector(),SelectionKey.OP_READ);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(key.isReadable()) {</span><br><span class="line"> System.out.println(<span class="string">"客户端写入数据..."</span>);</span><br><span class="line"> key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));</span><br><span class="line"> pool.execute(<span class="keyword">new</span> ThreadHandleChannel(key));</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="class"><span class="keyword">class</span> <span class="title">ThreadHandleChannel</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> SelectionKey selectionKey;</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="title">ThreadHandleChannel</span><span class="params">(SelectionKey selectionKey)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.selectionKey = selectionKey;</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">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"读取客户端传入数据..."</span>);</span><br><span class="line"> SocketChannel socketChannel = (SocketChannel) selectionKey.channel();</span><br><span class="line"> <span class="comment">//分配内存</span></span><br><span class="line"> ByteBuffer buffer = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"> ByteOutputStream byteOutputStream = <span class="keyword">new</span> ByteOutputStream();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//从通道读取数据</span></span><br><span class="line"> <span class="keyword">int</span> size = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> ((size = socketChannel.read(buffer)) > <span class="number">0</span>) {</span><br><span class="line"> buffer.flip();</span><br><span class="line"> byteOutputStream.write(buffer.array(),<span class="number">0</span>,size);</span><br><span class="line"> buffer.clear();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">byte</span>[] content = byteOutputStream.toByteArray();</span><br><span class="line"></span><br><span class="line"> ByteBuffer writeBuffer = ByteBuffer.allocate(content.length);</span><br><span class="line"> writeBuffer.put(content);</span><br><span class="line"> writeBuffer.flip();</span><br><span class="line"> <span class="comment">//write data</span></span><br><span class="line"> socketChannel.write(writeBuffer);</span><br><span class="line"> <span class="keyword">if</span>(size == -<span class="number">1</span>){</span><br><span class="line"> socketChannel.close();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_READ);</span><br><span class="line"> selectionKey.selector().wakeup();</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> System.out.println(e.getMessage());</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><br></pre></td></tr></table></figure></p><h2 id="AIO(Async-IO)"><a href="#AIO(Async-IO)" class="headerlink" title="AIO(Async IO)"></a>AIO(Async IO)</h2><p>异步非阻塞,结果返回回调接口</p><p>示例代码:<br><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></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">AioServer</span> </span>{</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 class="keyword">throws</span> IOException, InterruptedException </span>{</span><br><span class="line"></span><br><span class="line"> ExecutorService executorService = Executors.newCachedThreadPool();</span><br><span class="line"> AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService,<span class="number">2</span>);</span><br><span class="line"> AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);</span><br><span class="line"> serverSocketChannel.bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">8888</span>));</span><br><span class="line"> <span class="comment">//此处非阻塞,执行完成后就干下一步,返回结果会调用CompletionHandler#completed来处理</span></span><br><span class="line"> serverSocketChannel.accept(<span class="keyword">null</span>, <span class="keyword">new</span> CompletionHandler<AsynchronousSocketChannel, Object>() {</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">completed</span><span class="params">(AsynchronousSocketChannel channel, Object attachment)</span> </span>{</span><br><span class="line"> serverSocketChannel.accept(<span class="keyword">null</span>,<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> System.out.println(channel.getRemoteAddress());</span><br><span class="line"> ByteBuffer allocate = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"> channel.read(allocate, allocate, <span class="keyword">new</span> CompletionHandler<Integer, ByteBuffer>() {</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">completed</span><span class="params">(Integer result, ByteBuffer attachment)</span> </span>{</span><br><span class="line"> attachment.flip();</span><br><span class="line"> System.out.println(<span class="keyword">new</span> String(attachment.array(),<span class="number">0</span>,result));</span><br><span class="line"> channel.write(ByteBuffer.wrap(<span class="string">"hello client"</span>.getBytes()));</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">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</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">failed</span><span class="params">(Throwable exc, Object attachment)</span> </span>{</span><br><span class="line"> exc.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h1 id="Netty是什么"><a href="#Netty是什么" class="headerlink" title="Netty是什么"></a>Netty是什么</h1><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>Netty是一款异步的事件驱动的网络应用程序框架,支持快速开发可维护的高性能的面向协议的服务器和客户端。Netty主要是对java的nio包进行的封装。本质是一个NIO框架,用于服务器通信相关的多种应用场景</p><blockquote><p><strong>事件驱动</strong>:例如 Client 端发送的是个连接操作或者读请求或者断开连接,Netty 服务端可以读这几种不同的事件做定制的处理</p></blockquote><h2 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h2><ul><li><p>对 NIO 进行封装,开发者不需要关注 NIO 的底层原理,只需要调用 Netty 组件就能够完成工作。</p></li><li><p>对网络调用透明,从 Socket 建立 TCP 连接到网络异常的处理都做了包装。</p></li><li><p>对数据处理灵活, Netty 支持多种序列化框架,通过“ChannelHandler”机制,可以自定义“编/解码器”。</p></li><li><p>对性能调优友好,Netty 提供了线程池模式以及 Buffer 的重用机制(对象池化),不需要构建复杂的多线程模型和操作队列。</p></li></ul><h2 id="核心组件"><a href="#核心组件" class="headerlink" title="核心组件"></a>核心组件</h2><ul><li>EventLoop:线程,负责处理IO 实践</li><li>EventLoopGroup:线程池,包含了多个NioEventLoop</li><li>Channel:类似 Socket 的抽象,提供了 Socket 的,read(),wirte(),flush()等操作,可以通过 Channel 获取这个 Channel 绑定到的NioEventLoop</li></ul><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-27-083706.png" alt=""></p><ul><li>Bootstrap:客户端的引导类应用程序网络层配置提供容器</li><li>ServerBootstrap:服务端的引导类应用程序网络层配置提供容器</li><li>ChannelHandler:从应用开发者看来,ChannelHandler是最重要的组件,其中存放用来处理进站和出站数据的用户逻辑。ChannelHandler的方法被网络事件触发,ChannelHandler可以用于几乎任何类型的操作,如将数据从一种格式转换为另一种格式或处理抛出的异常。例如,其子接口ChannelInboundHandler,接受进站的事件和数据以便被用户定义的逻辑处理,或者当响应所连接的客户端时刷新ChannelInboundHandler的数据</li><li>ChannelPipeline:ChannelPipeline为ChannelHandler链提供了一个容器并定义了用于沿着链传播入站和出站事件流的API。当创建Channel时,会自动创建一个附属的ChannelPipeline<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-27-083753.png" alt=""></li></ul><h3 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h3><p>统一的API,适用于不同的协议(阻塞和非阻塞)<br>基于灵活、可扩展的事件驱动模型<br>高度可定制的线程模型<br>可靠的无连接数据Socket支持(UDP)</p><h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>更好的吞吐量,低延迟<br>更低的资源消耗<br>最少的内存复制</p><h3 id="健壮性"><a href="#健壮性" class="headerlink" title="健壮性"></a>健壮性</h3><p>不再因过快、过慢或超负载连接导致OutOfMemoryError<br>不再有在高速网络环境下NIO读写频率不一致的问题</p><h3 id="安全性:"><a href="#安全性:" class="headerlink" title="安全性:"></a>安全性:</h3><p>完整的SSL/TLS和STARTTLS的支持<br>可用于受限环境下,如 Applet 和OSGI</p><h3 id="易用:"><a href="#易用:" class="headerlink" title="易用:"></a>易用:</h3><p>详实的Javadoc和大量的示例集</p><p>示例代码:<br>server<br><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><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</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">EchoServer</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> port;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">EchoServer</span><span class="params">(<span class="keyword">int</span> port)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.port = port;</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 class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="keyword">new</span> EchoServer(<span class="number">8888</span>).start();</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 class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="keyword">final</span> EchoServerHandler serverHandler = <span class="keyword">new</span> EchoServerHandler();</span><br><span class="line"> <span class="comment">//创建EventLoopGroup,处理事件</span></span><br><span class="line"> EventLoopGroup boss = <span class="keyword">new</span> NioEventLoopGroup();</span><br><span class="line"> EventLoopGroup worker = <span class="keyword">new</span> NioEventLoopGroup();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> ServerBootstrap b = <span class="keyword">new</span> ServerBootstrap();</span><br><span class="line"> b.group(boss,worker)</span><br><span class="line"> <span class="comment">//指定所使用的NIO传输 Channel</span></span><br><span class="line"> .channel(NioServerSocketChannel.class)</span><br><span class="line"> <span class="comment">//使用指定的端口设置套接字地址</span></span><br><span class="line"> .localAddress(<span class="keyword">new</span> InetSocketAddress(port))</span><br><span class="line"> <span class="comment">//添加一个EchoServerHandler到子Channel的ChannelPipeline</span></span><br><span class="line"> .childHandler(<span class="keyword">new</span> ChannelInitializer<SocketChannel>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(SocketChannel socketChannel)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">//EchoServerHandler标志为@Shareable,所以我们可以总是使用同样的实例</span></span><br><span class="line"> socketChannel.pipeline().addLast(serverHandler);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="comment">//异步的绑定服务器,调用sync()方法阻塞等待直到绑定完成</span></span><br><span class="line"> ChannelFuture future = b.bind().sync();</span><br><span class="line"> future.channel().closeFuture().sync();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">//关闭EventLoopGroup,释放所有的资源</span></span><br><span class="line"> group.shutdownGracefully().sync();</span><br><span class="line"> worker.shutdownGracefully().sync();</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="meta">@ChannelHandler</span>.Sharable <span class="comment">//标识一个 ChannelHandler可以被多个Channel安全地共享</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EchoServerHandler</span> <span class="keyword">extends</span> <span class="title">ChannelInboundHandlerAdapter</span> </span>{</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">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> ByteBuf buffer = (ByteBuf) msg;</span><br><span class="line"> <span class="comment">//将消息记录到控制台</span></span><br><span class="line"> System.out.println(<span class="string">"Server received: "</span> + buffer.toString(CharsetUtil.UTF_8));</span><br><span class="line"> <span class="comment">//将接受到消息回写给发送者</span></span><br><span class="line"> ctx.write(buffer);</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">channelReadComplete</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line">hbv ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)</span><br><span class="line"> .addListener(ChannelFutureListener.CLOSE);</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">exceptionCaught</span><span class="params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">//打印异常栈跟踪</span></span><br><span class="line"> cause.printStackTrace();</span><br><span class="line"> <span class="comment">//关闭该Channel</span></span><br><span class="line"> ctx.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>Client<br><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></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">EchoClient</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String host;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> port;</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="title">EchoClient</span><span class="params">(String host, <span class="keyword">int</span> port)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.host = host;</span><br><span class="line"> <span class="keyword">this</span>.port = port;</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 class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> EventLoopGroup group = <span class="keyword">new</span> NioEventLoopGroup();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Bootstrap b = <span class="keyword">new</span> Bootstrap();</span><br><span class="line"> b.group(group)</span><br><span class="line"> .channel(NioSocketChannel.class)</span><br><span class="line"> .remoteAddress(<span class="keyword">new</span> InetSocketAddress(host, port))</span><br><span class="line"> .handler(<span class="keyword">new</span> ChannelInitializer<SocketChannel>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(SocketChannel socketChannel)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> socketChannel.pipeline().addLast(<span class="keyword">new</span> EchoClientHandler());</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> ChannelFuture channelFuture = b.connect().sync();</span><br><span class="line"> channelFuture.channel().closeFuture().sync();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> group.shutdownGracefully().sync();</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 class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="keyword">new</span> EchoClient(<span class="string">"127.0.0.1"</span>, <span class="number">8888</span>).start();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EchoClientHandler</span> <span class="keyword">extends</span> <span class="title">SimpleChannelInboundHandler</span><<span class="title">ByteBuf</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">protected</span> <span class="keyword">void</span> <span class="title">channelRead0</span><span class="params">(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> System.out.println(<span class="string">"Client received: "</span>+byteBuf.toString());</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">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> ctx.writeAndFlush(Unpooled.copiedBuffer(<span class="string">"Netty rocks"</span>,CharsetUtil.UTF_8));</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">exceptionCaught</span><span class="params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> cause.printStackTrace();</span><br><span class="line"> ctx.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h1 id="Netty能够做什么"><a href="#Netty能够做什么" class="headerlink" title="Netty能够做什么"></a>Netty能够做什么</h1><ul><li>远程服务调用,高性能网络通信如 Dubbo</li><li>开发异步、非阻塞的 TCP 网络应用程序;</li><li>开发异步、非阻塞的 UDP 网络应用程序;</li><li>Flink,Cassandra,Spark 等</li><li>开发异步文件传输应用程序;</li><li>开发异步 HTTP 服务端和客户端应用程序;</li><li>提供对多种编解码框架的集成,包括谷歌的 Protobuf、Jbossmarshalling、Java 序列化、压缩编解码、XML 解码、字符串编解码等,这些编解码框架可以被用户直接使用;</li><li>提供形式多样的编解码基础类库,可以非常方便的实现私有协议栈编解码框架的二次定制和开发;</li><li>基于职责链模式的 Pipeline-Handler 机制,用户可以非常方便的对网络事件进行拦截和定制;</li><li>所有的 IO 操作都是异步的,用户可以通过 Future-Listener 机制主动 Get 结果或者由 IO 线程操作完成之后主动 Notify 结果,用户的业务线程不需要同步等待</li><li>IP 黑白名单控制;</li><li>打印消息码流;</li><li>流量控制和整形;</li><li>性能统计;</li></ul>]]></content>
<summary type="html">
<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在介绍 Netty之前一定要先搞明白同步和异步、阻塞和非阻塞、BIO、NIO、AIO 都是什么,然后有哪些优缺点,再来看 Netty 出现的原因,有哪些优势,使用场景是什么。这样循序渐进的学习,更容易理解。</p>
</summary>
<category term="Netty,NIO" scheme="https://yefan813.github.io/tags/Netty-NIO/"/>
</entry>
<entry>
<title>如何实现一个短链接服务 </title>
<link href="https://yefan813.github.io/2020/03/23/%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%9F%AD%E9%93%BE%E6%8E%A5%E6%9C%8D%E5%8A%A1/"/>
<id>https://yefan813.github.io/2020/03/23/%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%9F%AD%E9%93%BE%E6%8E%A5%E6%9C%8D%E5%8A%A1/</id>
<published>2020-03-23T10:29:02.000Z</published>
<updated>2020-04-01T09:57:31.261Z</updated>
<content type="html"><![CDATA[<h1 id="实现一个短连接服务"><a href="#实现一个短连接服务" class="headerlink" title="实现一个短连接服务"></a>实现一个短连接服务</h1><table><thead><tr><th style="text-align:left">所用技术</th><th style="text-align:left">功能</th><th style="text-align:left">完成度</th></tr></thead><tbody><tr><td style="text-align:left">Spring Boot</td><td style="text-align:left">框架</td><td style="text-align:left">已完成</td></tr><tr><td style="text-align:left">MySQL</td><td style="text-align:left">数据存储</td><td style="text-align:left">已完成</td></tr><tr><td style="text-align:left">Redis</td><td style="text-align:left">二级缓存</td><td style="text-align:left">X</td></tr><tr><td style="text-align:left">OpenResty</td><td style="text-align:left">代理 + 一级缓存</td><td style="text-align:left">已完成</td></tr><tr><td style="text-align:left">EhCache</td><td style="text-align:left">三级缓存</td><td style="text-align:left">已完成</td></tr><tr><td style="text-align:left">布隆过滤器</td><td style="text-align:left">判断hash是否存在</td><td style="text-align:left">已完成</td></tr></tbody></table><p><a href="https://github.com/yefan813/short-url.git" target="_blank" rel="noopener">代码地址</a><br><a id="more"></a></p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li>短链接好处</li><li>短链跳转的原理</li><li>锻炼生成的几种方式</li></ul><h2 id="短链接好处"><a href="#短链接好处" class="headerlink" title="短链接好处"></a>短链接好处</h2><p><img src="https://img2018.cnblogs.com/blog/725429/201911/725429-20191109192512207-1372819004.png" alt=""></p><h3 id="为啥要使用短链接"><a href="#为啥要使用短链接" class="headerlink" title="为啥要使用短链接"></a>为啥要使用短链接</h3><ul><li>大家可能都收到过这样的推广短信,点击上面链接后会跳转到指定的地址,大家有没想过别后的实现细节</li><li>我们经常需要将链接转成二维码的形式分享给他人,如果是长链的话二维码密集难识别,短链就不存在这个问题了,如图示</li><li>链接太长在有些平台上无法自动识别为超链接</li></ul><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line">原始网址:https://yefan813.github.io/</span><br><span class="line">短网址:http://xxxx.cn/Vvux2</span><br></pre></td></tr></table></figure><p>大家可能都收到过这样的推广短信,点击上面链接后会跳转到指定的地址,大家有没想过背后的实现细节</p><h2 id="短链跳转的原理"><a href="#短链跳转的原理" class="headerlink" title="短链跳转的原理"></a>短链跳转的原理</h2><p> 我们可以从这些短链接请求看下原理<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-23-103507.png" alt=""><br>我们可以看到请求的http状态码是 302(重定向) 和 Location值具体跳转目标地址,浏览器拿到了得到这个长链接,发起重定向请求到目标地址</p><p>整体交互流程如下<br><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-23-103324.png" alt=""></p><p>这里有个问题http状态码, 301和302 有什么区别</p><ul><li>301: 代表 永久重定向,也就是说第一次请求拿到长链接后,下次浏览器再去请求短链的话,不会向短网址服务器请求了,而是直接从浏览器的缓存里拿,这样在 server 层面就无法获取到短网址的点击数了,如果这个链接刚好是某个活动的链接,也就无法分析此活动的效果。所以我们一般不采用 301</li><li>302: 代表 临时重定向,也就是说每次去请求短链都会去请求短网址服务器(除非响应中用 Cache-Control 或 Expired 暗示浏览器缓存),这样就便于 server 统计点击数,所以虽然用 302 会给 server 增加一点压力,但在数据异常重要的今天,这点代码是值得的,所以推荐使用 302</li></ul><h2 id="短链接生成的几种方式"><a href="#短链接生成的几种方式" class="headerlink" title="短链接生成的几种方式"></a>短链接生成的几种方式</h2><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-24-062346.png" alt=""></p><p>通过观察发现上面提到的短链接能发现,它是由固定的短链接域名 + 长链接映射成的一串字母组成,那么长链接怎么才能映射成一串字母呢?</p><h3 id="哈希算法"><a href="#哈希算法" class="headerlink" title="哈希算法"></a>哈希算法</h3><p> 哈希算法非常多,我们如何选择呢? 很多人可能会想到MD5, SHA等算法,但是我们选择 hash 算法的时候需要考虑的,生成的性能,<br>和产生冲突的概率。我们不回去考虑反向解码的性能或者难度,因为我们之直接将生成的 hash 字符串和长链接映射,不需反向生成。</p><p> 我们项目中使用Google 出品的 MurmurHash 算法,MurmurHash 是一种非加密型哈希函数,适用于一般的哈希检索操作。与其它流行的哈希函数相比,对于规律性较强的 key,MurmurHash 的随机分布特征表现更良好</p><p> 非加密意味着着相比 MD5,SHA 这些函数它的性能肯定更高(实际上性能是 MD5 等加密算法的十倍以上),也正是由于它的这些优点,所以虽然它出现于 2008,但目前已经广泛应用到 Redis、MemCache、Cassandra、HBase、Lucene 等众多著名的软件中</p><p> MurmurHash 提供了两种长度的哈希值,32 bit,128 bit,为了让网址尽可通地短,我们选择 32 bit 的哈希值,32 bit 能表示的最大值近 43 亿,对于中小型公司的业务而言绰绰有余。</p><p> 对长链(<a href="https://yefan813.github.io/)做">https://yefan813.github.io/)做</a> MurmurHash 计算,得到的哈希值为 849756688,于是我们现在得到的短链为 固定短链域名+哈希值 = <a href="http://xxxx.cn/849756688" target="_blank" rel="noopener">http://xxxx.cn/849756688</a></p><h4 id="1-如何让短网址更短?"><a href="#1-如何让短网址更短?" class="headerlink" title="1.如何让短网址更短?"></a>1.如何让短网址更短?</h4><p> 不过,你可能已经看出来了,通过 MurmurHash 算法得到的短网址还是很长啊,而且跟我们开头那个网址的格式好像也不一样。别着急,我们只需要稍微改变一个哈希值的表示方法,就可以轻松把短网址变得更短些。</p><p> 我们可以将 10 进制的哈希值,转化成更高进制的哈希值,这样哈希值就变短了。我们知道,16 进制中,我们用 A~E,来表示 10~15。在网址 URL 中,常用的合法字符有 0~9、a~z、A~Z 这样 62 个字符。为了让哈希值表示起来尽可能短,我们可以将 10 进制的哈希值转化成 62 进制。具体的计算过程,我写在这里了。最终用 62 进制表示的短网址就是<br> <img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-24-062239.png" alt=""></p><h3 id="2-如何解决哈希冲突"><a href="#2-如何解决哈希冲突" class="headerlink" title="2.如何解决哈希冲突"></a>2.如何解决哈希冲突</h3><p> 解决思路,根据长链接生成hash字符串,然后去查找数据库会出现以下几种情况</p><ol><li>数据库不存在:这时候直接插入字符串和对应的长链接即可。</li><li>数据库存在:对应的长链接一样,这时说明其他人已经将次长链接生成了对应的短链接,这时候直接返回即可。</li><li>数据库存在:对应的长链接不一样,则说明冲突了,此时可以将长链接尾部,拼接一串特殊字符,比如“[DUPLICATED]”,然后跟再重新计算哈希值,两次哈希计算都冲突的概率,显然是非常低的。假设出现非常极端的情况,又发生冲突了,我们可以再拼接字符串,再计算哈希值。然后把计算得到的哈希值,跟原始网址拼接了特殊字符串之后的文本,一并存储在 MySQL 数据库中</li></ol><h3 id="压测一下看看性能"><a href="#压测一下看看性能" class="headerlink" title="压测一下看看性能"></a>压测一下看看性能</h3><p>其实这里没有做任何的缓存,所以请求全部落到数据库,很容易就能想到不能支持高并发,但还是压测一下装个 X</p><blockquote><p>100线程 并发执行生成短链接接口</p></blockquote><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-23-134741.png" alt=""></p><blockquote><p>短链接跳转接口结果和上面差不多</p></blockquote><h3 id="短链接跳转需要查询数据库,这时候其实可以将查询到的数据放入到本地缓存(三级缓存)还可以放入到-redis(二级缓存)-,也可以放入到-Nginx(一级缓存)"><a href="#短链接跳转需要查询数据库,这时候其实可以将查询到的数据放入到本地缓存(三级缓存)还可以放入到-redis(二级缓存)-,也可以放入到-Nginx(一级缓存)" class="headerlink" title="短链接跳转需要查询数据库,这时候其实可以将查询到的数据放入到本地缓存(三级缓存)还可以放入到 redis(二级缓存) ,也可以放入到 Nginx(一级缓存)"></a>短链接跳转需要查询数据库,这时候其实可以将查询到的数据放入到本地缓存(三级缓存)还可以放入到 redis(二级缓存) ,也可以放入到 Nginx(一级缓存)</h3><h4 id="加入本地缓存-Ehcache"><a href="#加入本地缓存-Ehcache" class="headerlink" title="加入本地缓存 Ehcache"></a>加入本地缓存 Ehcache</h4><blockquote><p>服务使用 Ehcache 后,压测短链接跳转结果</p></blockquote><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-23-164034.png" alt=""></p><h4 id="加入-Redis-缓存"><a href="#加入-Redis-缓存" class="headerlink" title="加入 Redis 缓存"></a>加入 Redis 缓存</h4><p>TODO</p><h4 id="加入-OpenResty"><a href="#加入-OpenResty" class="headerlink" title="加入 OpenResty"></a>加入 OpenResty</h4><p>TODO</p><h3 id="3-如何优化哈希算法生成短连接的性能"><a href="#3-如何优化哈希算法生成短连接的性能" class="headerlink" title="3.如何优化哈希算法生成短连接的性能"></a>3.如何优化哈希算法生成短连接的性能</h3><p>TODO</p><h2 id="利用-ID-生成器生成短链接"><a href="#利用-ID-生成器生成短链接" class="headerlink" title="利用 ID 生成器生成短链接"></a>利用 ID 生成器生成短链接</h2><p>TODO</p><h2 id="阶段一:实现一个简单的短连接服务"><a href="#阶段一:实现一个简单的短连接服务" class="headerlink" title="阶段一:实现一个简单的短连接服务"></a>阶段一:实现一个简单的短连接服务</h2><ol><li>根据URL生成短连接</li><li>实现短连接的跳转</li></ol><p>问题:</p><ol><li>如何解决Hash冲突问题</li><li>如何支持高性能短连接结构设计,例如:如何支持大量的并发生成短连接请求</li></ol><h2 id="阶段二:实现一个高性能的短链接服务器"><a href="#阶段二:实现一个高性能的短链接服务器" class="headerlink" title="阶段二:实现一个高性能的短链接服务器"></a>阶段二:实现一个高性能的短链接服务器</h2><ol><li>支持大并发访问生成短链接</li></ol>]]></content>
<summary type="html">
<h1 id="实现一个短连接服务"><a href="#实现一个短连接服务" class="headerlink" title="实现一个短连接服务"></a>实现一个短连接服务</h1><table>
<thead>
<tr>
<th style="text-align:left">所用技术</th>
<th style="text-align:left">功能</th>
<th style="text-align:left">完成度</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">Spring Boot</td>
<td style="text-align:left">框架</td>
<td style="text-align:left">已完成</td>
</tr>
<tr>
<td style="text-align:left">MySQL</td>
<td style="text-align:left">数据存储</td>
<td style="text-align:left">已完成</td>
</tr>
<tr>
<td style="text-align:left">Redis</td>
<td style="text-align:left">二级缓存</td>
<td style="text-align:left">X</td>
</tr>
<tr>
<td style="text-align:left">OpenResty</td>
<td style="text-align:left">代理 + 一级缓存</td>
<td style="text-align:left">已完成</td>
</tr>
<tr>
<td style="text-align:left">EhCache</td>
<td style="text-align:left">三级缓存</td>
<td style="text-align:left">已完成</td>
</tr>
<tr>
<td style="text-align:left">布隆过滤器</td>
<td style="text-align:left">判断hash是否存在</td>
<td style="text-align:left">已完成</td>
</tr>
</tbody>
</table>
<p><a href="https://github.com/yefan813/short-url.git" target="_blank" rel="noopener">代码地址</a><br>
</summary>
<category term="短链接,短网址,302,Spring Boot,Redis" scheme="https://yefan813.github.io/tags/%E7%9F%AD%E9%93%BE%E6%8E%A5-%E7%9F%AD%E7%BD%91%E5%9D%80-302-Spring-Boot-Redis/"/>
</entry>
<entry>
<title>数据库,缓存一致性方案</title>
<link href="https://yefan813.github.io/2020/03/21/%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%8C%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%E6%96%B9%E6%A1%88/"/>
<id>https://yefan813.github.io/2020/03/21/%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%8C%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%E6%96%B9%E6%A1%88/</id>
<published>2020-03-21T14:54:39.000Z</published>
<updated>2020-04-21T07:29:46.506Z</updated>
<content type="html"><![CDATA[<p><a href="https://github.com/yefan813/eshop-inventory.git" target="_blank" rel="noopener">代码地址</a></p><p><strong>问题1</strong>:如果是先修改数据库,再删除缓存,如果删除缓存失败,那么会导致数据库中的数据是新数据,缓存中的是旧数据<br>,数据不一致。</p><p><strong>解决思路</strong>:先删除缓存,再修改数据库,如果删除缓存成功了,修改数据库失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致,因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中</p><h1 id="Cache-Aside-Pattern"><a href="#Cache-Aside-Pattern" class="headerlink" title="Cache Aside Pattern"></a>Cache Aside Pattern</h1><ul><li><p>读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应</p></li><li><p>更新的时候,先操作数据库,再删除缓存</p></li></ul><blockquote><p>1.如果先删除缓存,可能因为并发太大,数据库还没更新老的数据又被另外一个查询放入了缓存,这时候缓存是老数据,数据库是新数据。数据不一致。</p><p>2.但是如果先操作数据库,后删除缓存,如果删除缓存失败。缓存是老数据,数据库是新数据,但是删除缓存失败的概率比上面并发造成数据不一致的概率要小得多。但是具体还是要结合场景来考虑</p></blockquote><h1 id="为什么是删除缓存,而不是更新缓存"><a href="#为什么是删除缓存,而不是更新缓存" class="headerlink" title="为什么是删除缓存,而不是更新缓存"></a>为什么是删除缓存,而不是更新缓存</h1><a id="more"></a><p>1,因为很多时候缓存不是直接数据库直接取出来的值,很可能做了逻辑运算以后再存入数据库<br>如果这类的数据要更新,则需要将相关的数据都查出来再去计算更新,这样代价太高了。</p><p>2,如果你频繁修改一个缓存涉及的多个表,那么这个缓存会被频繁的更新,频繁的更新缓存,但是问题在于,这个缓存到底会不会被频繁访问到?</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></pre></td><td class="code"><pre><span class="line">举个例子,一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次,100 次; 但是这个缓存在 1 分钟内就被读取了 1 次,有大量的冷数据</span><br><span class="line"></span><br><span class="line">热冷数据的 28 黄金法则:20% 的数据,占用了 80% 的访问量</span><br><span class="line"></span><br><span class="line">实际上,如果你只是删除缓存的话,那么 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低</span><br><span class="line"></span><br><span class="line">每次数据过来,就只是删除缓存,然后修改数据库,如果这个缓存,在 1 分钟内只是被访问了 1 次,那么只有那 1 次缓存是要被重新计算的,用缓存才去算缓存</span><br></pre></td></tr></table></figure><blockquote><p>其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算<br>mybatis、hibernate 就是懒加载思想</p></blockquote><p><strong>问题2</strong>:一个比较复杂的数据不一致问题<br>1,数据发生了变更,先删除缓存,然后要去修改数据库,此时还没修改<br>2,一个并发请求过来,去读缓存,发现缓存空了,然后去查询数据库,查到了修改前的值,放到缓存<br>这时候出现数据库和缓存不一致。</p><p><strong><em>解决思路</em></strong>: 导致这种情况出现的原因是读写并发请求造成的,根据某个规则比如商品 ID 相同的请求路由到一台服务器上,可以尝试将读请求和更新请求进行串行化处理,</p><p><img src="http://test-pic-yefan.oss-cn-beijing.aliyuncs.com/2020-03-21-143455.png" alt=""></p><p><strong><em>具体流程如下</em></strong>:</p><ul><br><li>更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部的队列中</li><br><li>读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据 + 更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部的队列中</li><br><li>一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行</li><br></ul><p>这样的话,一个数据变更的操作,先执行,删除缓存,然后再去更新数据库,但是还没完成更新<br>此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成<br>待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中<br>如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;<br>如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值</p><h3 id="需要优化的点"><a href="#需要优化的点" class="headerlink" title="需要优化的点"></a>需要优化的点</h3><p>1,一个队列中已经存在一个更新操作 + 一个读取操作,则后续的读请求可以尝试等待一段时间从缓存读取,因为前面的更新会删除缓存,后面的读取请求会将<br>再次放入到缓存中,后续的读请求可以尝试等待一段时间从缓存读取,如果等待时间过了再尝试从数据库去拿</p><h2 id="此方案的风险点"><a href="#此方案的风险点" class="headerlink" title="此方案的风险点"></a><b style="color:red">此方案的风险点</b></h2><p>1,可能 数据更新很频繁,导致队列中积压了大量更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库。所以务必通过一些模拟真实的测试,看看更新数据的频繁是怎样的</p><p>2,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要部署多个服务,每个服务分摊一些数据的更新操作</p><p>3, 会存在热点数据都打到同一台机器上的,可能造成某台机器压力过大的问题。</p>]]></content>
<summary type="html">
<p><a href="https://github.com/yefan813/eshop-inventory.git" target="_blank" rel="noopener">代码地址</a></p>
<p><strong>问题1</strong>:如果是先修改数据库,再删除缓存,如果删除缓存失败,那么会导致数据库中的数据是新数据,缓存中的是旧数据<br>,数据不一致。</p>
<p><strong>解决思路</strong>:先删除缓存,再修改数据库,如果删除缓存成功了,修改数据库失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致,因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中</p>
<h1 id="Cache-Aside-Pattern"><a href="#Cache-Aside-Pattern" class="headerlink" title="Cache Aside Pattern"></a>Cache Aside Pattern</h1><ul>
<li><p>读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应</p>
</li>
<li><p>更新的时候,先操作数据库,再删除缓存</p>
</li>
</ul>
<blockquote>
<p>1.如果先删除缓存,可能因为并发太大,数据库还没更新老的数据又被另外一个查询放入了缓存,这时候缓存是老数据,数据库是新数据。数据不一致。</p>
<p>2.但是如果先操作数据库,后删除缓存,如果删除缓存失败。缓存是老数据,数据库是新数据,但是删除缓存失败的概率比上面并发造成数据不一致的概率要小得多。但是具体还是要结合场景来考虑</p>
</blockquote>
<h1 id="为什么是删除缓存,而不是更新缓存"><a href="#为什么是删除缓存,而不是更新缓存" class="headerlink" title="为什么是删除缓存,而不是更新缓存"></a>为什么是删除缓存,而不是更新缓存</h1>
</summary>
<category term="Mysql,Redis" scheme="https://yefan813.github.io/tags/Mysql-Redis/"/>
</entry>
<entry>
<title>Dubbo和Spring Cloud优缺点</title>
<link href="https://yefan813.github.io/2020/03/13/Dubbo%E5%92%8CSpring-Cloud%E4%BC%98%E7%BC%BA%E7%82%B9/"/>
<id>https://yefan813.github.io/2020/03/13/Dubbo%E5%92%8CSpring-Cloud%E4%BC%98%E7%BC%BA%E7%82%B9/</id>
<published>2020-03-13T08:36:33.000Z</published>
<updated>2020-03-13T08:40:31.924Z</updated>
<content type="html"><![CDATA[<p><img src="https://tva1.sinaimg.cn/large/00831rSTgy1gcse3kp0nvj30wj0u0wko.jpg" alt=""></p>]]></content>
<summary type="html">
<p><img src="https://tva1.sinaimg.cn/large/00831rSTgy1gcse3kp0nvj30wj0u0wko.jpg" alt=""></p>
</summary>
<category term="Dubbo, Spring Boot" scheme="https://yefan813.github.io/tags/Dubbo-Spring-Boot/"/>
</entry>
<entry>
<title>Spring Boot自定义starter</title>
<link href="https://yefan813.github.io/2020/03/06/SpringBoot%E8%87%AA%E5%AE%9A%E4%B9%89starter/"/>
<id>https://yefan813.github.io/2020/03/06/SpringBoot%E8%87%AA%E5%AE%9A%E4%B9%89starter/</id>
<published>2020-03-06T12:52:13.000Z</published>
<updated>2020-04-01T10:03:08.124Z</updated>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Spring Boot已经为很多的开源项目提供了很多的 starter项目,你也可以开发你自定义的 starter。再开始之前让我们先理解下Spring Boot是如何自动配置的,如果你已经知道 Spring Boot自动配置的过程可以直接调到创建自定义starter步骤。</p><a id="more"></a><h2 id="Spring-Boot-自动配置"><a href="#Spring-Boot-自动配置" class="headerlink" title="Spring Boot 自动配置"></a>Spring Boot 自动配置</h2><p>当你启动 Spring Boot 应用的时候,Spring Boot 会检测一个特殊的文件,它是 spring-boot-autoconfigure 这个包内的META-INF/spring.factories</p><p><img src="https://tva1.sinaimg.cn/large/00831rSTgy1gckiuxcwefj30pk09q402.jpg" alt=""></p><p>它里面的内容我们重点关注这里<br><img src="https://tva1.sinaimg.cn/large/00831rSTgy1gckivm3o4hj31500qq7d6.jpg" alt=""></p><p>这里是利用的spring SPI提供的扩展机制。</p><p>这里简单介绍下 SPI (全称Service Provider Interface),是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。</p><p>通俗一点讲就是SPI 就是服务方提供接口,具体的实现由其他方来实现,只需要将实现了的类全路径加入到文件中,服务方启动的时候就会把这些文件内容解析出来,由于配置了类全路径地址,直接利用反射机制将这些类初始化提供使用。</p><h2 id="开始创建自定义的starter"><a href="#开始创建自定义的starter" class="headerlink" title="开始创建自定义的starter"></a>开始创建自定义的starter</h2><ul><li>创建一个Spring Boot 项目,在pom.xml中引入</li></ul><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></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-autoconfigure<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.2.2.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><ul><li>创建一个类 。该类starter 引入后自动加入到 Spring 容器中</li></ul><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></pre></td><td class="code"><pre><span class="line">public class HelloService {</span><br><span class="line"> private String prefix;</span><br><span class="line"> private String suffix;</span><br><span class="line"> </span><br><span class="line"> public HelloService() {</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public HelloService(String prefix, String suffix) {</span><br><span class="line"> this.prefix = prefix;</span><br><span class="line"> this.suffix = suffix;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public String sayHello(String word) {</span><br><span class="line"> return prefix + word + suffix;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>创建一个配置类,提供 starter 引入方自定义属性</li></ul><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></pre></td><td class="code"><pre><span class="line">//prefix = "hello" 引入 starter 的项目配置文件的前置是 hello</span><br><span class="line">@ConfigurationProperties(prefix = "hello")</span><br><span class="line">public class HelloProperties {</span><br><span class="line"> private String prefix;</span><br><span class="line"> private String suffix;</span><br><span class="line"></span><br><span class="line"> public String getPrefix() {</span><br><span class="line"> return prefix;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void setPrefix(String prefix) {</span><br><span class="line"> this.prefix = prefix;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public String getSuffix() {</span><br><span class="line"> return suffix;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void setSuffix(String suffix) {</span><br><span class="line"> this.suffix = suffix;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>创建自动配置类</li></ul><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></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @Configuration 定义当前类是个配置类</span><br><span class="line"> * @ConditionalOnClass,当classpath下发现该类(HelloService)的情况下进行自动配置。</span><br><span class="line"> * @ConditionalOnMissingBean,当Spring Context中不存在该Bean时。</span><br><span class="line"> * @ConditionalOnProperty(prefix = "example.service",value = "enabled",havingValue = "true"),</span><br><span class="line"> * 当配置文件中example.service.enabled=true时将这个类创建为一个 Spring Bean</span><br><span class="line"> * @EnableConfigurationProperties(HelloProperties.class) 让使用 @ConfigurationProperties 注解的类生效</span><br><span class="line"> */</span><br><span class="line">@Configuration</span><br><span class="line">@ConditionalOnClass(HelloService.class)</span><br><span class="line">@EnableConfigurationProperties(HelloProperties.class)</span><br><span class="line">public class HelloAutoConfiguration {</span><br><span class="line"></span><br><span class="line"> @Autowired</span><br><span class="line"> private HelloProperties helloProperties;</span><br><span class="line"></span><br><span class="line"> @Bean</span><br><span class="line"> @ConditionalOnMissingBean</span><br><span class="line"> @ConditionalOnProperty(prefix = "hello", value = "enable", havingValue = "true")</span><br><span class="line"> HelloService helloService() {</span><br><span class="line"> return new HelloService(helloProperties.getPrefix(),helloProperties.getSuffix());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>在/resources/META-INF/spring.factories 创建这个文件<br>文件写入内容</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.yefan.hello.HelloAutoConfiguration</span><br></pre></td></tr></table></figure><ul><li>mvn install 将项目安装到本地仓库</li></ul><hr><ul><li>创建另外一个Spring Boot 项目,像引入其他 starter 方式引入上面创建的 maven 依赖</li></ul><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></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>com.yefan.study<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>hello-spring-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.0.1-SNAPSHOT<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><ul><li>在application.yml中配置</li></ul><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></pre></td><td class="code"><pre><span class="line">hello:</span><br><span class="line"> enable: true</span><br><span class="line"> prefix: xxx</span><br><span class="line"> suffix: xxx</span><br></pre></td></tr></table></figure><ul><li>注入 HelloService 即可使用</li></ul><figure class="highlight plain"><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">@Autowired</span><br><span class="line">HelloService helloService;</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Spring Boot已经为很多的开源项目提供了很多的 starter项目,你也可以开发你自定义的 starter。再开始之前让我们先理解下Spring Boot是如何自动配置的,如果你已经知道 Spring Boot自动配置的过程可以直接调到创建自定义starter步骤。</p>
</summary>
<category term="spring boot starter" scheme="https://yefan813.github.io/tags/spring-boot-starter/"/>
</entry>
<entry>
<title>Spring中Bean的生命周期</title>
<link href="https://yefan813.github.io/2020/03/05/Spring%E4%B8%ADBean%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/"/>
<id>https://yefan813.github.io/2020/03/05/Spring%E4%B8%ADBean%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/</id>
<published>2020-03-05T12:15:25.000Z</published>
<updated>2020-04-01T10:02:56.992Z</updated>
<content type="html"><![CDATA[<h1 id="Spring-中-Bean-的生命周期图"><a href="#Spring-中-Bean-的生命周期图" class="headerlink" title="Spring 中 Bean 的生命周期图"></a>Spring 中 Bean 的生命周期图</h1><a id="more"></a><p><img src="https://tva1.sinaimg.cn/large/00831rSTgy1gcjbq7e3q8j30n8123agj.jpg" alt=""></p><p><a href="https://github.com/yefan813/spring-bean-life-cycle" target="_blank" rel="noopener">源码地址</a><br>项目中每个类的作用在上图中都写得非常清楚,可以下载下载直接运行看下整体效果,最好能 Debug 一下看下详细的流程。</p><p>###参考文献:<br><a href="https://www.cnblogs.com/xrq730/p/5721366.html" target="_blank" rel="noopener">https://www.cnblogs.com/xrq730/p/5721366.html</a><br><a href="https://blog.csdn.net/caihaijiang/article/details/35552859" target="_blank" rel="noopener">https://blog.csdn.net/caihaijiang/article/details/35552859</a><br><a href="https://www.cnblogs.com/zrtqsk/p/3735273.html" target="_blank" rel="noopener">https://www.cnblogs.com/zrtqsk/p/3735273.html</a></p>]]></content>
<summary type="html">
<h1 id="Spring-中-Bean-的生命周期图"><a href="#Spring-中-Bean-的生命周期图" class="headerlink" title="Spring 中 Bean 的生命周期图"></a>Spring 中 Bean 的生命周期图</h1>
</summary>
</entry>
<entry>
<title>chrome模拟跨域访问</title>
<link href="https://yefan813.github.io/2020/02/11/chrome%E6%A8%A1%E6%8B%9F%E8%B7%A8%E5%9F%9F%E8%AE%BF%E9%97%AE/"/>
<id>https://yefan813.github.io/2020/02/11/chrome%E6%A8%A1%E6%8B%9F%E8%B7%A8%E5%9F%9F%E8%AE%BF%E9%97%AE/</id>
<published>2020-02-11T06:33:53.000Z</published>
<updated>2020-02-11T06:37:31.666Z</updated>
<content type="html"><![CDATA[<ol><li><p>首先随便打开一个网站</p></li><li><p>打开 console<br>输入</p></li></ol><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">var data = {</span><br><span class="line"> password: "xxx",</span><br><span class="line"> userName: "xxx"</span><br><span class="line">}; </span><br><span class="line">var xhr = new XMLHttpRequest();</span><br><span class="line">xhr.open('post', 'http://127.0.0.1:8080/console/login'); </span><br><span class="line">xhr.setRequestHeader("Content-type","application/json");</span><br><span class="line">xhr.setRequestHeader("SESSION_TOKEN","112233");</span><br><span class="line">xhr.setRequestHeader("CONSOLE_SESSION_TOKEN","112233");</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">xhr.send(JSON.stringify(data));</span><br><span class="line">xhr.onload = function(e) {</span><br><span class="line"> var xhr = e.target;</span><br><span class="line"> console.log(xhr.responseText);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="3"><li>修改域名、参数、header等信息即可</li></ol>]]></content>
<summary type="html">
<ol>
<li><p>首先随便打开一个网站</p>
</li>
<li><p>打开 console<br>输入</p>
</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre>
</summary>
</entry>
<entry>
<title>mybatis 常见面试问题及答案</title>
<link href="https://yefan813.github.io/2019/10/31/mybatis-%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E5%8F%8A%E7%AD%94%E6%A1%88/"/>
<id>https://yefan813.github.io/2019/10/31/mybatis-%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E5%8F%8A%E7%AD%94%E6%A1%88/</id>
<published>2019-10-31T12:56:23.000Z</published>
<updated>2019-12-13T09:23:25.266Z</updated>
<content type="html"><![CDATA[<h2 id="Mybatis面试连环炮"><a href="#Mybatis面试连环炮" class="headerlink" title="Mybatis面试连环炮"></a>Mybatis面试连环炮</h2><p>先抛出几个问题,然后带着问题一起看下 <a href="https://mybatis.org/mybatis-3/" target="_blank" rel="noopener">Mybatis官网</a>如何解释这个问题。</p><h3 id="什么是Mybatis"><a href="#什么是Mybatis" class="headerlink" title="什么是Mybatis?"></a><em>什么是Mybatis?</em></h3><blockquote><p>MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。</p></blockquote><a id="more"></a><hr><h3 id="Mybaits的优点"><a href="#Mybaits的优点" class="headerlink" title="Mybaits的优点"></a><em>Mybaits的优点</em></h3><blockquote></blockquote><ol><li>门槛低易上手</li><li>sql 可以统一写到 xml 统一管理</li><li>提供映射标签,支持对象与数据库的orm字段关系映射</li><li>提供对象关系映射标签,支持对象关系组建维护</li><li>提供xml标签,支持编写动态sql。</li></ol><hr><h3 id="Mybaits的缺点"><a href="#Mybaits的缺点" class="headerlink" title="Mybaits的缺点"></a><em>Mybaits的缺点</em></h3><blockquote></blockquote><ol><li>sql依赖于数据库,导致数据库移植性差。</li><li>缓存使用不当,容易产生脏数据。</li></ol><hr><h3 id="Mybatis全局配置文件中有哪些标签-分别代表什么意思"><a href="#Mybatis全局配置文件中有哪些标签-分别代表什么意思" class="headerlink" title="Mybatis全局配置文件中有哪些标签?分别代表什么意思?"></a><em>Mybatis全局配置文件中有哪些标签?分别代表什么意思?</em></h3><blockquote><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hg3nx33uj30fe0b4gnl.jpg" alt=""></p></blockquote><blockquote><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hg2p0aw2j30a607wmxx.jpg" alt=""></p></blockquote><hr><h3 id="和-的区别是什么?"><a href="#和-的区别是什么?" class="headerlink" title="#{}和${}的区别是什么?"></a><em>#{}和${}的区别是什么?</em></h3><blockquote><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hix8i6caj317t0ihgql.jpg" alt=""></p></blockquote><hr><h3 id="Mybatis-映射器引用资源方式有几种"><a href="#Mybatis-映射器引用资源方式有几种" class="headerlink" title="Mybatis 映射器引用资源方式有几种"></a>Mybatis 映射器引用资源方式有几种</h3><blockquote><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hhyiasmvj31810jzq7i.jpg" alt=""></p></blockquote><hr><h3 id="当实体类中的属性名和表中的字段名不一样-,怎么办-?"><a href="#当实体类中的属性名和表中的字段名不一样-,怎么办-?" class="headerlink" title="当实体类中的属性名和表中的字段名不一样 ,怎么办 ?"></a><em>当实体类中的属性名和表中的字段名不一样 ,怎么办 ?</em></h3><p>第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致</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></pre></td><td class="code"><pre><span class="line"> <select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”> </span><br><span class="line"> select order_id id, order_no orderno ,order_price price form orders where order_id=#{id}; </span><br><span class="line"></select></span><br></pre></td></tr></table></figure><p>第2种: 通过<resultmap>来映射字段名和实体类属性名的一一对应的关系</resultmap></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"><select id="getOrder" parameterType="int" resultMap="orderresultmap"></span><br><span class="line"> select * from orders where order_id=#{id}</span><br><span class="line"> </select></span><br><span class="line"> <resultMap type=”me.gacl.domain.order” id=”orderresultmap”> </span><br><span class="line"> <!–用id属性来映射主键字段–> </span><br><span class="line"> <id property=”id” column=”order_id”> </span><br><span class="line"> <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–> </span><br><span class="line"> <result property = “orderno” column =”order_no”/> </span><br><span class="line"> <result property=”price” column=”order_price” /> </span><br><span class="line"> </reslutMap></span><br></pre></td></tr></table></figure><hr><h3 id="如何获取自动生成的-主-键值"><a href="#如何获取自动生成的-主-键值" class="headerlink" title="如何获取自动生成的(主)键值?"></a><em>如何获取自动生成的(主)键值?</em></h3><blockquote><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hjvaw3isj31880jsjwn.jpg" alt=""></p></blockquote><hr><h3 id="Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理?"><a href="#Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理?" class="headerlink" title="Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理?"></a><em>Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理?</em></h3><blockquote><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hjxub3t9j318a0qiafh.jpg" alt=""></p></blockquote><blockquote><p>其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。</p></blockquote><p><a href="https://mybatis.org/mybatis-3/zh/dynamic-sql.html" target="_blank" rel="noopener">传送门</a></p><hr><h3 id="Mybatis-几个关键类对象的作用域-Scope-和生命周期"><a href="#Mybatis-几个关键类对象的作用域-Scope-和生命周期" class="headerlink" title="Mybatis 几个关键类对象的作用域(Scope)和生命周期"></a>Mybatis 几个关键类对象的作用域(Scope)和生命周期</h3><blockquote><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hg0t1yxvj317r0mqtg4.jpg" alt=""></p></blockquote><h3 id="Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?"><a href="#Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?" class="headerlink" title="Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?"></a><em>Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?</em></h3><blockquote><p>这是来自 Mybatis 官网一段描述<br><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hfm140fmj318903a3zi.jpg" alt=""><br>不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;毕竟namespace不是必须的,只是最佳实践而已。<br>如下:<br><code><mapper namespace="org.mybatis.example.BlogMapper"></code></p></blockquote><hr><h3 id="通常一个Xml映射文件,都会写一个Dao-Mapper-接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?"><a href="#通常一个Xml映射文件,都会写一个Dao-Mapper-接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?" class="headerlink" title="通常一个Xml映射文件,都会写一个Dao(Mapper)接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?"></a><em>通常一个Xml映射文件,都会写一个Dao(Mapper)接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?</em></h3><blockquote><p>Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。</p></blockquote><blockquote><p>Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement</p></blockquote><blockquote><p>Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。</p></blockquote><p><code>Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。</code></p><hr><h3 id="接口绑定有几种实现方式-分别是怎么实现的"><a href="#接口绑定有几种实现方式-分别是怎么实现的" class="headerlink" title="接口绑定有几种实现方式,分别是怎么实现的?"></a><em>接口绑定有几种实现方式,分别是怎么实现的?</em></h3><blockquote><ol><li>一种是通过注解绑定,就是在接口的方法上面加上@Select@Update等注解里面包含Sql语句来绑定</li><li>另外一种就是通过xml里面写SQL来绑定,在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名.</li></ol></blockquote><hr><h3 id="Mybatis是如何进行分页的?分页插件的原理是什么?"><a href="#Mybatis是如何进行分页的?分页插件的原理是什么?" class="headerlink" title="Mybatis是如何进行分页的?分页插件的原理是什么?"></a><em>Mybatis是如何进行分页的?分页插件的原理是什么?</em></h3><blockquote><p>Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。</p></blockquote><blockquote><p>分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。</p></blockquote><hr><h3 id="简述Mybatis的插件运行原理,以及如何编写一个插件"><a href="#简述Mybatis的插件运行原理,以及如何编写一个插件" class="headerlink" title="简述Mybatis的插件运行原理,以及如何编写一个插件"></a><em>简述Mybatis的插件运行原理,以及如何编写一个插件</em></h3><blockquote><p>Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。</p></blockquote><blockquote><p>实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。</p></blockquote><h2 id=""><a href="#" class="headerlink" title=""></a><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hnaapuy1j318q0ogn3c.jpg" alt=""></h2><h3 id="Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?"><a href="#Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?" class="headerlink" title="Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?"></a><em>Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?</em></h3><blockquote><p>Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。</p></blockquote><blockquote><p>它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。</p></blockquote><p><a href="https://www.cnblogs.com/ashleyboy/p/9286814.html" target="_blank" rel="noopener">延迟加载实例</a></p><hr><h3 id="Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?"><a href="#Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?" class="headerlink" title="Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?"></a><em>Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?</em></h3><blockquote><p>第一种是使用<resultmap>标签,逐一定义列名和对象属性名之间的映射关系。第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。</resultmap></p></blockquote><blockquote><p>有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。</p></blockquote><hr><h3 id="Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别"><a href="#Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别" class="headerlink" title="Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别"></a><em>Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别</em></h3><blockquote><p>能,Mybatis不仅可以执行一对一、一对多的关联查询,还可以执行多对一,多对多的关联查询,多对一查询,其实就是一对一查询,只需要把selectOne()修改为selectList()即可;多对多查询,其实就是一对多查询,只需要把selectOne()修改为selectList()即可。</p></blockquote><blockquote><p>关联对象查询,有两种实现方式,一种是单独发送一个sql去查询关联对象,赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用join查询,一部分列是A对象的属性值,另外一部分列是关联对象B的属性值,好处是只发一个sql查询,就可以把主对象和其关联对象查出来。</p></blockquote><blockquote><p>那么问题来了,join查询出来100条记录,如何确定主对象是5个,而不是100个?其去重复的原理是<resultmap>标签内的<id>子标签,指定了唯一确定一条记录的id列,Mybatis根据<id>列值来完成100条记录的去重复功能,<id>可以有多个,代表了联合主键的语意。</id></id></id></resultmap></p></blockquote><blockquote><p>同样主对象的关联对象,也是根据这个原理去重复的,尽管一般情况下,只有主对象会有重复记录,关联对象一般不会重复。</p></blockquote><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hnxze2t7j30p408naaw.jpg" alt=""></p><blockquote><p>官方 resultMap 文档:<br><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hnyzjlfaj318g0jidlt.jpg" alt=""></p></blockquote><hr><h3 id="Mybatis是否可以映射Enum枚举类?"><a href="#Mybatis是否可以映射Enum枚举类?" class="headerlink" title="Mybatis是否可以映射Enum枚举类?"></a><em>Mybatis是否可以映射Enum枚举类?</em></h3><p>可以<br><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g8hgj8d3icj318b0pltfz.jpg" alt=""></p><hr><h3 id="Mybatis都有哪些Executor执行器?它们之间的区别是什么?"><a href="#Mybatis都有哪些Executor执行器?它们之间的区别是什么?" class="headerlink" title="Mybatis都有哪些Executor执行器?它们之间的区别是什么?"></a><em>Mybatis都有哪些Executor执行器?它们之间的区别是什么?</em></h3><ol><li>SIMPLE 就是普通的执行器,每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象;</li><li>REUSE 执行器会重用预处理语句(prepared statements),执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象</li><li>BATCH 执行器将重用语句并执行批量更新,执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。</li></ol><hr><h3 id="Mybatis中如何指定使用哪一种Executor执行器?"><a href="#Mybatis中如何指定使用哪一种Executor执行器?" class="headerlink" title="Mybatis中如何指定使用哪一种Executor执行器?"></a><em>Mybatis中如何指定使用哪一种Executor执行器?</em></h3><p>在 Mybatis xml配置文件 <setting> 中设置defaultExecutorType<br><code><setting name="defaultExecutorType" value="SIMPLE"/></code></setting></p><hr><h3 id="如何执行批量插入"><a href="#如何执行批量插入" class="headerlink" title="如何执行批量插入?"></a><em>如何执行批量插入?</em></h3><blockquote><ol><li><p>在 mapper.xml文件中使用<foreach></foreach></p></li><li><p>SqlSessionFactory sqlSessionFactory=getSqlSessionFactory();</p><pre><code>//可以执行批量操作的sqlSessionSqlSession openSession=sqlSessionFactory.openSession(ExecutorType.BATCH);</code></pre></li></ol></blockquote><hr><h3 id="说一下resultMap和resultType的区别"><a href="#说一下resultMap和resultType的区别" class="headerlink" title="说一下resultMap和resultType的区别"></a><em>说一下resultMap和resultType的区别</em></h3><p><strong>resultType:</strong> 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。</p><p><strong>resultMap:</strong> 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同时使用。</p><hr><h3 id="什么是Mybatis的一级、二级缓存-如何开启-什么样的数据适合缓存"><a href="#什么是Mybatis的一级、二级缓存-如何开启-什么样的数据适合缓存" class="headerlink" title="什么是Mybatis的一级、二级缓存,如何开启?什么样的数据适合缓存?"></a><em>什么是Mybatis的一级、二级缓存,如何开启?什么样的数据适合缓存?</em></h3><blockquote><ol><li>一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。</li></ol></blockquote><blockquote><ol><li>二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache> ;</cache></li></ol></blockquote><blockquote><ol><li>对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。</li></ol></blockquote><hr><h3 id="Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?"><a href="#Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?" class="headerlink" title="Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?"></a><em>Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?</em></h3><blockquote><p>虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。</p></blockquote><blockquote><p>原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。</p></blockquote><hr>]]></content>
<summary type="html">
<h2 id="Mybatis面试连环炮"><a href="#Mybatis面试连环炮" class="headerlink" title="Mybatis面试连环炮"></a>Mybatis面试连环炮</h2><p>先抛出几个问题,然后带着问题一起看下 <a href="https://mybatis.org/mybatis-3/" target="_blank" rel="noopener">Mybatis官网</a>如何解释这个问题。</p>
<h3 id="什么是Mybatis"><a href="#什么是Mybatis" class="headerlink" title="什么是Mybatis?"></a><em>什么是Mybatis?</em></h3><blockquote>
<p>MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。</p>
</blockquote>
</summary>
<category term="mybatis" scheme="https://yefan813.github.io/tags/mybatis/"/>
</entry>
<entry>
<title>tcc-traction源码阅读笔记</title>
<link href="https://yefan813.github.io/2019/10/24/tcc-traction%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"/>
<id>https://yefan813.github.io/2019/10/24/tcc-traction%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/</id>
<published>2019-10-23T16:06:56.000Z</published>
<updated>2020-04-01T10:03:16.785Z</updated>
<content type="html"><![CDATA[<p><a href="https://github.com/xie-summer/tcc-transaction-v1.1.5.git" target="_blank" rel="noopener">tcc-trancation 分布式事务框架源码阅读</a></p><p><a href="https://blog.csdn.net/dm_vincent/article/details/92432059" target="_blank" rel="noopener">TCC的异常场景及应对机制</a></p><a id="more"></a><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><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></pre></td><td class="code"><pre><span class="line">1>com.roncoo.pay.service.trade.biz.impl.RpTradePaymentManagerBizImpl#completeSuccessOrder 银行返回订单支付成功,后调支付成功,还未执行方法,被拦截器拦截</span><br><span class="line"> 2>org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor#interceptCompensableMethod 进入可事务补偿拦截器</span><br><span class="line">3>org.mengyun.tcctransaction.utils.CompensableMethodUtils#calculateMethodType 判断当前执行者类型 MethodType.ROOT</span><br><span class="line"> 4>org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor#rootMethodProceed 主事务方法的处理</span><br><span class="line"> 5>org.mengyun.tcctransaction.TransactionManager#begin 事务开始(创建事务日志记录,并将该事务日志记录存入当前线程的事务局部变量中) TransactionType.ROOT</span><br><span class="line"> 6>org.aspectj.lang.ProceedingJoinPoint#proceed() Try (开始执行被拦截的方法,或进入下一个拦截器处理逻辑) ,执行完毕等待返回....</span><br><span class="line"> 7>org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor#interceptTransactionContextMethod 资源拦截器,拦截事务上下文方法</span><br><span class="line"> 8>org.mengyun.tcctransaction.TransactionManager#getCurrentTransaction 获取当前事务,没有则新建事务(trying状态)</span><br><span class="line"> 9>org.mengyun.tcctransaction.utils.CompensableMethodUtils#calculateMethodType 判断当前执行者类型(ROOT)</span><br><span class="line"> 10>org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor#generateAndEnlistRootParticipant </span><br><span class="line">生成和登记根参与者Participant(Participant保存确认方法和确认方法的实例) 加入到参与者列表,更新事务信息(加入了事务参与者,包含了触发confirm或cancel方法的参数信息)</span><br><span class="line"> 11>org.aspectj.lang.ProceedingJoinPoint#proceed(java.lang.Object[]) 开始执行被拦截的方法,或进入下一个拦截器处理逻辑</span><br><span class="line">12>com.roncoo.pay.service.trade.biz.impl.RpTradePaymentManagerBizImpl#completeSuccessOrder 真正进入到方法内部执行 修改支付记录状态, 修改支付订单状态</span><br><span class="line"> 13>com.roncoo.pay.service.account.api.RpAccountTransactionService#creditToAccountTcc 调用给商户资金帐户加款(平台收款) try 方法,调用参与者账户资金系统加款,调用之前被拦截器拦截</span><br><span class="line"> 14>org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor#interceptTransactionContextMethod 获取当前事务,从当前线程 threadLocal 中可以获取到事务</span><br><span class="line"> 15>org.mengyun.tcctransaction.utils.CompensableMethodUtils#calculateMethodType 判断当前执行者类型,MethodType.CONSUMER</span><br><span class="line"> 16>org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor#generateAndEnlistConsumerParticipant 生成并登记消费者的参与者Participant</span><br><span class="line"> 服务接口的 TransactionContext 参数设值(新的事务分支ID),状态为当前transaction状态TRYING ,构建事务confirm上下文和构建事务cancle上下文</span><br><span class="line"> 17>org.aspectj.lang.ProceedingJoinPoint#proceed(java.lang.Object[]) 继续执行 方法</span><br><span class="line"> 18> com.roncoo.pay.service.account.api.RpAccountTransactionService#creditToAccountTcc 回到方法执行 try 方法,发起远程调用</span><br><span class="line"> 19>com.roncoo.pay.service.account.aip.impl.RpAccountTransactionServiceImpl#creditToAccountTcc 到了资金账户系统 provider 方真正执行方法</span><br><span class="line"> 20>org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor#interceptCompensableMethod 被可补偿事务拦截器拦截</span><br><span class="line"> 21>org.mengyun.tcctransaction.utils.CompensableMethodUtils#calculateMethodType 判断当前执行方的类型,MethodType.PROVIDER</span><br><span class="line"> 22>org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor#providerMethodProceed 服务提供者事务方法处理. 当前分支事务状态为TRYING</span><br><span class="line"> 基于全局事务ID扩展创建新的分支事务,并存于当前线程的事务局部变量中.</span><br><span class="line"> 23>org.aspectj.lang.ProceedingJoinPoint#proceed()开始执行被拦截的方法,或进入下一个拦截器处理逻辑</span><br><span class="line"> 24> org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor#interceptTransactionContextMethod 进入到资源协调拦截器</span><br><span class="line"> 25>org.mengyun.tcctransaction.utils.CompensableMethodUtils#calculateMethodType 判断当前类型为PROVIDER</span><br><span class="line"> 26>org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor#generateAndEnlistProviderParticipant 生成并登记服务提供者的参与者 </span><br><span class="line"> 构建确认方法的提交上下文,构建取消方法的提交上下文 加入参与者列表更新本地缓存</span><br><span class="line"> 27>org.aspectj.lang.ProceedingJoinPoint#proceed(java.lang.Object[]) 开始执行被拦截的方法,或进入下一个拦截器处理逻辑</span><br><span class="line"> 28>com.roncoo.pay.service.account.aip.impl.RpAccountTransactionServiceImpl#creditToAccountTcc 开始真正执行账户资金系统的 try 方法</span><br><span class="line">29>com.roncoo.pay.service.trade.biz.impl.RpTradePaymentManagerBizImpl#completeSuccessOrder 账户资金加款成功,回到completeSuccessOrder方法继续执行</span><br><span class="line"> 30>com.roncoo.pay.service.point.api.RpPointAccountService#creditToPointAccountTcc client调用被拦截 过程和 13 ~ 29 过程一样</span><br><span class="line"> .</span><br><span class="line"> .</span><br><span class="line"> .</span><br><span class="line"> >com.roncoo.pay.service.point.api.RpPointAccountService#creditToPointAccountTcc rpc 执行完毕返回</span><br><span class="line"> > org.aspectj.lang.ProceedingJoinPoint#proceed() 执行完毕回到 步骤 6</span><br><span class="line"> >org.mengyun.tcctransaction.TransactionManager#commit Try检验正常后提交(try 方法过程中如果出现异常或调用org.mengyun.tcctransaction.TransactionManager#rollback 火锅就是遍历事务参与者这列表中的 cancle 方法)(事务管理器在控制提交):Confirm</span><br><span class="line"> >org.mengyun.tcctransaction.Transaction#commit 遍历调用事务参与者列表的 commit 方法</span><br><span class="line"> >org.mengyun.tcctransaction.Participant#commit</span><br><span class="line"> >org.mengyun.tcctransaction.Terminator#invoke confirmInvocationContext 调用之前写入的 confirm 上线文直接执行各个参与者的 confirm 方法</span><br><span class="line"> >org.mengyun.tcctransaction.TransactionRepository#delete confirm 完成后会调用事务删除方法删除日志,这里注意如果 confirm 失败了导致日志没有删除,会有个事务调度系统TransactionRecovery 定时的检索出为被删除的事务数据然后一直调用 confirm 方法去确认并记录重试次数(重试一定次数不在重试)。</span><br><span class="line"> 所以接口一定要保持幂等才行 这里特别注意</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p><a href="https://github.com/xie-summer/tcc-transaction-v1.1.5.git" target="_blank" rel="noopener">tcc-trancation 分布式事务框架源码阅读</a></p>
<p><a href="https://blog.csdn.net/dm_vincent/article/details/92432059" target="_blank" rel="noopener">TCC的异常场景及应对机制</a></p>
</summary>
</entry>
<entry>
<title>java远程调试</title>
<link href="https://yefan813.github.io/2019/10/11/java%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95/"/>
<id>https://yefan813.github.io/2019/10/11/java%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95/</id>
<published>2019-10-11T10:29:05.000Z</published>
<updated>2019-12-13T09:25:22.295Z</updated>
<content type="html"><![CDATA[<h2 id="java-远程调试"><a href="#java-远程调试" class="headerlink" title="java 远程调试"></a>java 远程调试</h2><p>调试对于排查 java 各种异常问题非常重要,相信本地调试大家都很熟悉,今天分享一下如何开启远程调试。</p><ol><li><p>如果需要编译.java文件执行命令javac,生成.class文件件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac SynchronizedStudy.java</span><br></pre></td></tr></table></figure></li><li><p>执行编译过后的.class文件</p><ul><li><p>如果当前类有包路径到包的根路径下执行</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java com.xxx.xx.SynchronizedStudy</span><br></pre></td></tr></table></figure></li><li><p>如果当前类没有包直接执行</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java SynchronizedStudy</span><br></pre></td></tr></table></figure></li></ul></li></ol><a id="more"></a><ol><li><p>如果要启动远程 debug 端口</p><ul><li><p>如果执行 java 文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=5005 com.yefan.study.SynchronizedStudy</span><br></pre></td></tr></table></figure></li><li><p>如果执行 jar 文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=5005 -jar SynchronizedStudy.jar</span><br></pre></td></tr></table></figure></li></ul></li></ol><p>启动成功会显示如下</p><blockquote><p>Listening for transport dt_socket at address: 5005</p></blockquote><pre><code>### 远程调试命令参数说明**-Xdebug:** 启用调试特性。**-Xrunjdwp:** 在目标 VM 中加载 JDWP 实现。它通过传输和 JDWP 协议与独立的调试器应用程序通信。**transport:** 这里通常使用套接字传输。**server:** 如果值为 y,目标应用程序监听将要连接的调试器应用程序。否则,它将连接到特定地址上的调试器应用程序。**address:** 这是连接的传输地址。如果服务器为 n,将尝试连接到该地址上的调试器应用程序。否则,将在这个端口监听连接。**suspend:**如果值为 y,目标 VM 将暂停,直到调试器应用程序进行连接。如果值为 n,没有调试器连接则继续执行</code></pre><h2 id="IDEA远程调试配置"><a href="#IDEA远程调试配置" class="headerlink" title="IDEA远程调试配置"></a>IDEA远程调试配置</h2><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g7unzpdz6hj30gu11o452.jpg" alt=""></p><p><img src="https://tva1.sinaimg.cn/large/006y8mN6gy1g7unxw0y3oj31bj0u0n35.jpg" alt=""></p>]]></content>
<summary type="html">
<h2 id="java-远程调试"><a href="#java-远程调试" class="headerlink" title="java 远程调试"></a>java 远程调试</h2><p>调试对于排查 java 各种异常问题非常重要,相信本地调试大家都很熟悉,今天分享一下如何开启远程调试。</p>
<ol>
<li><p>如果需要编译.java文件执行命令javac,生成.class文件件</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac SynchronizedStudy.java</span><br></pre></td></tr></table></figure>
</li>
<li><p>执行编译过后的.class文件</p>
<ul>
<li><p>如果当前类有包路径到包的根路径下执行</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java com.xxx.xx.SynchronizedStudy</span><br></pre></td></tr></table></figure>
</li>
<li><p>如果当前类没有包直接执行</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java SynchronizedStudy</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
</ol>
</summary>
<category term="java javac javap 远程调试" scheme="https://yefan813.github.io/tags/java-javac-javap-%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95/"/>
</entry>
<entry>
<title>Filter 和 Interceptor 比较</title>
<link href="https://yefan813.github.io/2018/07/31/filer-interceptor/"/>
<id>https://yefan813.github.io/2018/07/31/filer-interceptor/</id>
<published>2018-07-31T02:55:42.000Z</published>
<updated>2018-08-31T07:21:10.108Z</updated>
<content type="html"><![CDATA[<ol><li>过滤器(Filter)</li><li>拦截器(Interceptor</li></ol><h1 id="Filter"><a href="#Filter" class="headerlink" title="Filter"></a>Filter</h1><ul><li><p>Filter是servlet规范中定义的java web组件, 在所有支持java web的容器中都可以使用</p></li><li><p>Filter和Filter Chain是密不可分的, Filter可以实现依次调用正是因为有了Filter Chain</p></li></ul><p><img src="https://ws2.sinaimg.cn/large/006tKfTcgy1ftsv1u5fihj30hh089abw.jpg" alt=""></p><p>上图是Filter对请求进行拦截的原理图, 那么java web容器(以tomcat为例子)是如何实现这个功能的呢?</p><p>下面看下Filter和Filter Chain的源码</p><a id="more"></a><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></pre></td><td class="code"><pre><span class="line">// Filter</span><br><span class="line"> public interface Filter {</span><br><span class="line"></span><br><span class="line"> // 容器创建的时候调用, 即启动tomcat的时候调用</span><br><span class="line"> public void init(FilterConfig filterConfig) throws ServletException;</span><br><span class="line"></span><br><span class="line"> // 由FilterChain调用, 并且传入Filter Chain本身</span><br><span class="line"> public void doFilter(ServletRequest request, ServletResponse response,</span><br><span class="line"> FilterChain chain) throws IOException, ServletException;</span><br><span class="line"></span><br><span class="line"> // 容器销毁的时候调用, 即关闭tomcat的时候调用</span><br><span class="line"> public void destroy();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // FilterChain</span><br><span class="line"> public interface FilterChain {</span><br><span class="line"></span><br><span class="line"> // 由Filter.doFilter()中的chain.doFilter调用</span><br><span class="line"> public void doFilter(ServletRequest request, ServletResponse response)</span><br><span class="line"> throws IOException, ServletException;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><ul><li><p>正是因为Filter Chain在调用每一个Filter.doFilter()时将自身引用传递进去, 才实现了Filter的依次调用, 在Filter全部调用完之后再调用真正处理请求的servlet, 并且再次逆序回调Filter. 可能这么看还是不太明白是怎么实现Filter的顺序调用, 调用真正的servlet, 逆序调用Filter的, 一起看下Tomcat的源码就一目了然了.</p></li><li><p>在tomcat中Filter Chain的默认实现是ApplicationFilterChain, 在ApplicationFilterChain中最关键的方法就是internalDoFilter, 整个Filter流程的实现就是由这个方法完成.</p></li></ul><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><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></pre></td><td class="code"><pre><span class="line">// internalDoFilter(只保留关键代码)</span><br><span class="line"> private void internalDoFilter(ServletRequest request, ServletResponse response)</span><br><span class="line"> throws IOException, ServletException {</span><br><span class="line"></span><br><span class="line"> // Call the next filter if there is one</span><br><span class="line"> // pos: 当前的filter的索引, n: 调用链中所有的Filter的数量</span><br><span class="line"> // 如果调用链中还有没有调用的Filter就继续调用, 否则跳过if语句</span><br><span class="line"> if (pos < n) {</span><br><span class="line"> ApplicationFilterConfig filterConfig = filters[pos++];</span><br><span class="line"> try {</span><br><span class="line"> // 获取Filter</span><br><span class="line"> Filter filter = filterConfig.getFilter();</span><br><span class="line"> if( Globals.IS_SECURITY_ENABLED ) {</span><br><span class="line"> ...</span><br><span class="line"> 其他代码</span><br><span class="line"> ... </span><br><span class="line"> } else {</span><br><span class="line"> // 这句话是重点, 调用Filter的doFilter方法并把Filter Chain本身传进去(this)</span><br><span class="line"> filter.doFilter(request, response, this);</span><br><span class="line"> }</span><br><span class="line"> } catch (IOException | ServletException | RuntimeException e) {</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"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // We fell off the end of the chain -- call the servlet instance</span><br><span class="line"> try {</span><br><span class="line"> ...</span><br><span class="line"> 其他代码</span><br><span class="line"> ...</span><br><span class="line"> // Use potentially wrapped request from this point</span><br><span class="line"> if ((request instanceof HttpServletRequest) &&</span><br><span class="line"> (response instanceof HttpServletResponse) &&</span><br><span class="line"> Globals.IS_SECURITY_ENABLED ) {</span><br><span class="line"> ...</span><br><span class="line"> 其他代码</span><br><span class="line"> ...</span><br><span class="line"> } else {</span><br><span class="line"> // 调用真正的Filter</span><br><span class="line"> servlet.service(request, response);</span><br><span class="line"> }</span><br><span class="line"> } catch (IOException | ServletException | RuntimeException e) {</span><br><span class="line"> ...</span><br><span class="line"> 异常处理代码</span><br><span class="line"> ...</span><br><span class="line"> } finally {</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><br></pre></td></tr></table></figure><blockquote><p>Filter的正序调用的过程和调用真正的servlet的过程了, 但是Filter的逆序调用在哪里体现了呢?</p></blockquote><ul><li>假设下面的Filter就是调用链中的最后一个Filter</li></ul><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">public class LogFilter implements Filter {</span><br><span class="line"> public void doFilter(ServletRequest request, ServletResponse response,</span><br><span class="line"> FilterChain chain) throws IOException, ServletException {</span><br><span class="line"></span><br><span class="line"> Log.info("before");</span><br><span class="line"> chain.doFilter(request, response); </span><br><span class="line"> Log.info("after");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>那么在调用chain.doFilter之后就跳过了if语句从而调用了真正的servlet, 然后internalDoFilter方法就结束(出栈)了, 紧接着就是调用Log.info(“after”)了, 然后LogFilter的doFilter就结束了(也出栈了), 紧接着就是internalDoFilter中filter.doFilter(request, response, this)的结束然后return, 然后就是调用上一个filter的chain.doFilter()之后的代码, 以此类推.</p><p>因此Filter调用链的实现其实就是一个方法调用链的过程. 刚开始, Filter Chain每调用一个Filter.doFilter()方法就是向方法调用栈中进行压栈操作(代码上的体现就是执行Filter.doFilter之前的代码), 当Filter全部调用完成之后就调用真正处理请求的servlet, 然后由方法调用链自动进行出栈操作(代码上的体现就是执行Filter.doFilter之后的代码), 从而完成整个Filter的调用链. 因为Filter功能实现实际上就是利用了方法的压栈出栈, 所以可以在调用chain.doFilter之前将方法返回, 让容器不在调用servlet方法, 从而实现权限的控制, 关键词的过滤等功能.</p><h1 id="Interceptor"><a href="#Interceptor" class="headerlink" title="Interceptor"></a>Interceptor</h1><ul><li>Interceptor不是servlet规范中的java web组件, 而是Spring提供的组件, 功能上和Filter差不多. 但是实现上和Filter不一样.</li></ul><p>Interceptor功能的实现主要是在Spring Mvc的DispatcherServelt.doDispatch方法中, 让我们来看看源码</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></pre></td><td class="code"><pre><span class="line">// Interceptor的源码</span><br><span class="line">public interface HandlerInterceptor {</span><br><span class="line"></span><br><span class="line"> // 在调用真正的处理请求类之前调用</span><br><span class="line"> boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)</span><br><span class="line"> throws Exception;</span><br><span class="line"></span><br><span class="line"> // 在调用真正的处理请求类之后调用</span><br><span class="line"> void postHandle(</span><br><span class="line"> HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)</span><br><span class="line"> throws Exception;</span><br><span class="line"></span><br><span class="line"> // 在完成渲染或者出错之后调用</span><br><span class="line"> void afterCompletion(</span><br><span class="line"> HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</span><br><span class="line"> throws Exception;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>doDispatch</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><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"> doDispatch源码(只保留关键代码)</span><br><span class="line"></span><br><span class="line">protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {</span><br><span class="line"> try {</span><br><span class="line"> ModelAndView mv = null;</span><br><span class="line"> Exception dispatchException = null;</span><br><span class="line"></span><br><span class="line"> try {</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><br><span class="line"> if (!mappedHandler.applyPreHandle(processedRequest, response)) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // Actually invoke the handler.</span><br><span class="line"> // 调用真正的处理请求的方法</span><br><span class="line"> mv = ha.handle(processedRequest, response, mappedHandler.getHandler());</span><br><span class="line"></span><br><span class="line"> // 找到渲染模版</span><br><span class="line"> applyDefaultViewName(processedRequest, mv);</span><br><span class="line"></span><br><span class="line"> // 调用拦截器的后置处理方法</span><br><span class="line"> mappedHandler.applyPostHandle(processedRequest, response, mv);</span><br><span class="line"> }</span><br><span class="line"> catch (Exception ex) {</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"> finally {</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>其实看了doDispatch的关键代码, Spring Mvc对整个请求的处理流程已经很清楚了:</p><blockquote><p>调用拦截器的前置方法 -> 调用处理请求的方法 -> 渲染模版 -> 调用拦截器的后置处理方法 -> 调用拦截器的完成方法</p></blockquote><p>接下来看一看Spring Mvc是如何实现依次调用这么多拦截器的前置方法, 后置方法, 完成方法的。</p><p>进入到mapperHandler.applyPreHandle()方法中(调用拦截器的前置方法)</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></pre></td><td class="code"><pre><span class="line">boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {</span><br><span class="line"> HandlerInterceptor[] interceptors = getInterceptors();</span><br><span class="line"> // 如果拦截器数组不为空</span><br><span class="line"> if (!ObjectUtils.isEmpty(interceptors)) {</span><br><span class="line"> // 按顺序调用拦截器数组中的preHandle方法</span><br><span class="line"> for (int i = 0; i < interceptors.length; i++) {</span><br><span class="line"> HandlerInterceptor interceptor = interceptors[i];</span><br><span class="line"> // 如果拦截器的preHandle方法返回false, 则调用当前拦截器的triggerAfterCompletion方法, 然后返回, 并且不再调用后续的拦截器</span><br><span class="line"> if (!interceptor.preHandle(request, response, this.handler)) {</span><br><span class="line"> triggerAfterCompletion(request, response, null);</span><br><span class="line"> return false;</span><br><span class="line"> }</span><br><span class="line"> this.interceptorIndex = i;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return true;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>进入到mappedHandler.applyPostHandle()方法中(调用拦截器的后置方法)</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></pre></td><td class="code"><pre><span class="line">void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {</span><br><span class="line"> HandlerInterceptor[] interceptors = getInterceptors();</span><br><span class="line"> // 如果拦截器数组不为空</span><br><span class="line"> if (!ObjectUtils.isEmpty(interceptors)) {</span><br><span class="line"> // 倒序调用拦截器数组中拦截器的postHandle方法</span><br><span class="line"> for (int i = interceptors.length - 1; i >= 0; i--) {</span><br><span class="line"> HandlerInterceptor interceptor = interceptors[i];</span><br><span class="line"> interceptor.postHandle(request, response, this.handler, mv);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>不管是否出异常triggerAfterCompletion方法始终会被调用</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></pre></td><td class="code"><pre><span class="line">void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)</span><br><span class="line"> throws Exception {</span><br><span class="line"></span><br><span class="line"> HandlerInterceptor[] interceptors = getInterceptors();</span><br><span class="line"> // 拦截器数组不为空</span><br><span class="line"> if (!ObjectUtils.isEmpty(interceptors)) {</span><br><span class="line"> // 从成功执行的最后一个拦截器开始“逆序”调用afterCompletion方法</span><br><span class="line"> for (int i = this.interceptorIndex; i >= 0; i--) {</span><br><span class="line"> HandlerInterceptor interceptor = interceptors[i];</span><br><span class="line"> try {</span><br><span class="line"> interceptor.afterCompletion(request, response, this.handler, ex);</span><br><span class="line"> }</span><br><span class="line"> catch (Throwable ex2) {</span><br><span class="line"> logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);</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>注:triggerAfterCompletion会逆序调用afterCompletion方法</strong></p><p>看过以上三个方法之后, Spring Mvc如何处理拦截器的前置, 后置, 完成方法就一目了然了. 其实Spring Mvc就是将拦截器统一放到了拦截器数组中, 然后在调用真正的处理请求方法之前和之后正序或者倒序遍历拦截器, 同时调用拦截器的相应的方法. 最后不管是否正常结束这个流程还是出异常都会从成功的最后一个拦截器开始逆序调用afterCompletion方法</p><p>Filter 和 Inteceptor 调用流程<br><img src="https://ws4.sinaimg.cn/large/006tKfTcgy1ftsuzrys62j30mc0ct105.jpg" alt=""></p><p><img src="http://s2.51cto.com/wyfs02/M02/8B/3D/wKiom1hHhbmxseDtAACidU9Y84s787.png" alt=""></p><h1 id="总结:"><a href="#总结:" class="headerlink" title="总结:"></a>总结:</h1><ol><li>从以上分析可以看到过滤器和拦截器实现的方式的不同. Filter是利用了方法的调用(入栈出栈)完成整个流程, 而Interceptor是利用了for循环完成了整个流程.</li><li>Filter的实现比较占用栈空间, 在Filter多的情况下可能会有栈溢出的风险存在.</li><li>Interceptor的实现逻辑更加的清晰简单</li><li>Filter组件更加的通用, 只要支持java servlet的容器都可以使用, 而Interceptor必须依赖于Spring</li><li>Filter的优先级是高于Interceptor, 即请求是先到Filter再到Interceptor的, 因为Interceptor的实现主体还是一个servlet</li><li>Filter 不能使用 spring</li></ol>]]></content>
<summary type="html">
<ol>
<li>过滤器(Filter)</li>
<li>拦截器(Interceptor</li>
</ol>
<h1 id="Filter"><a href="#Filter" class="headerlink" title="Filter"></a>Filter</h1><ul>
<li><p>Filter是servlet规范中定义的java web组件, 在所有支持java web的容器中都可以使用</p>
</li>
<li><p>Filter和Filter Chain是密不可分的, Filter可以实现依次调用正是因为有了Filter Chain</p>
</li>
</ul>
<p><img src="https://ws2.sinaimg.cn/large/006tKfTcgy1ftsv1u5fihj30hh089abw.jpg" alt=""></p>
<p>上图是Filter对请求进行拦截的原理图, 那么java web容器(以tomcat为例子)是如何实现这个功能的呢?</p>
<p>下面看下Filter和Filter Chain的源码</p>
</summary>
<category term="Filter Interceptor 过滤器 拦截器" scheme="https://yefan813.github.io/tags/Filter-Interceptor-%E8%BF%87%E6%BB%A4%E5%99%A8-%E6%8B%A6%E6%88%AA%E5%99%A8/"/>
</entry>
<entry>
<title>JVM之 GC 调优步骤</title>
<link href="https://yefan813.github.io/2018/07/29/JVM4/"/>
<id>https://yefan813.github.io/2018/07/29/JVM4/</id>
<published>2018-07-29T13:34:22.000Z</published>
<updated>2018-08-31T07:24:37.747Z</updated>
<content type="html"><![CDATA[<p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftr25l9g3zj30om0e841i.jpg" alt=""></p><h2 id="初始化设置参数"><a href="#初始化设置参数" class="headerlink" title="初始化设置参数"></a>初始化设置参数</h2><p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftr26pfc0wj30y008g436.jpg" alt=""></p><h2 id="Parallel-GC-调优的指导原则"><a href="#Parallel-GC-调优的指导原则" class="headerlink" title="Parallel GC 调优的指导原则"></a>Parallel GC 调优的指导原则</h2><p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftr28i9o7cj30y10feq8j.jpg" alt=""></p><h2 id="G1-调优"><a href="#G1-调优" class="headerlink" title="G1 调优"></a>G1 调优</h2><p><img src="https://ws3.sinaimg.cn/large/006tNc79gy1ftr2el1ajwj30zd0degrk.jpg" alt=""><br><img src="https://ws2.sinaimg.cn/large/006tNc79gy1ftr2eug0huj31030ivgqx.jpg" alt=""></p><a id="more"></a><p>参考文档:<br><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations" target="_blank" rel="noopener">Oracle官网</a></p><p><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html" target="_blank" rel="noopener">GC 调优指南</a><br><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html" target="_blank" rel="noopener">如何选择垃圾收集器</a><br><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations" target="_blank" rel="noopener">G1最佳实践</a><br><a href="https://zhuanlan.zhihu.com/p/22591838" target="_blank" rel="noopener">G1 GC的一些关键技术</a></p>]]></content>
<summary type="html">
<p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftr25l9g3zj30om0e841i.jpg" alt=""></p>
<h2 id="初始化设置参数"><a href="#初始化设置参数" class="headerlink" title="初始化设置参数"></a>初始化设置参数</h2><p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftr26pfc0wj30y008g436.jpg" alt=""></p>
<h2 id="Parallel-GC-调优的指导原则"><a href="#Parallel-GC-调优的指导原则" class="headerlink" title="Parallel GC 调优的指导原则"></a>Parallel GC 调优的指导原则</h2><p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftr28i9o7cj30y10feq8j.jpg" alt=""></p>
<h2 id="G1-调优"><a href="#G1-调优" class="headerlink" title="G1 调优"></a>G1 调优</h2><p><img src="https://ws3.sinaimg.cn/large/006tNc79gy1ftr2el1ajwj30zd0degrk.jpg" alt=""><br><img src="https://ws2.sinaimg.cn/large/006tNc79gy1ftr2eug0huj31030ivgqx.jpg" alt=""></p>
</summary>
<category term="GC" scheme="https://yefan813.github.io/tags/GC/"/>
</entry>
<entry>
<title>GC日志分析工具</title>
<link href="https://yefan813.github.io/2018/07/29/JVM3/"/>
<id>https://yefan813.github.io/2018/07/29/JVM3/</id>
<published>2018-07-29T13:14:10.000Z</published>
<updated>2018-07-29T13:17:23.839Z</updated>
<content type="html"><![CDATA[<ol><li><p>在线工具:<a href="http://gceasy.io" target="_blank" rel="noopener">http://gceasy.io</a></p></li><li><p>GCViewer</p></li></ol><p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftr1n1v45qj30ww0ddaey.jpg" alt=""></p>]]></content>
<summary type="html">
<ol>
<li><p>在线工具:<a href="http://gceasy.io" target="_blank" rel="noopener">http://gceasy.io</a></p>
</li>
<li><p>GCViewer</p>
</li>
</ol>
<p
</summary>
<category term="GC" scheme="https://yefan813.github.io/tags/GC/"/>
</entry>
<entry>
<title>JVM之垃圾回收算法</title>
<link href="https://yefan813.github.io/2018/07/29/JVM2/"/>
<id>https://yefan813.github.io/2018/07/29/JVM2/</id>
<published>2018-07-29T09:59:14.000Z</published>
<updated>2018-08-31T07:24:30.084Z</updated>
<content type="html"><![CDATA[<p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftqvzd2erdj30zs0datd1.jpg" alt=""></p><p>##标记清楚算法(Mark-Sweep)<br><img src="https://ws3.sinaimg.cn/large/006tNc79gy1ftqw93v8ldj30y30ba789.jpg" alt=""><br><img src="https://ws4.sinaimg.cn/large/006tNc79ly1ftqwdlfu4cj30p40dq0us.jpg" alt=""></p><p>##复制算法(Copying)<br>为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如图:</p><p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftqwirry1nj30l00blac4.jpg" alt=""></p><p>这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying算法的效率会大大降低。</p><a id="more"></a><h2 id="标记-整理算法-Mark-Compact"><a href="#标记-整理算法-Mark-Compact" class="headerlink" title="标记-整理算法(Mark-Compact)"></a>标记-整理算法(Mark-Compact)</h2><p>结合了以上两个算法,为了避免缺陷而提出。标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:<br><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftqwkj0puvj30ld0cb75n.jpg" alt=""></p><p>缺点:没有内存碎片,但是整理内存比较耗时</p><h2 id="分代收集算法-Generational-Collection"><a href="#分代收集算法-Generational-Collection" class="headerlink" title="分代收集算法(Generational Collection)"></a>分代收集算法(Generational Collection)</h2><p>分代收集法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。</p><p>目前大部分JVM的GC对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1来划分新生代。一般将新生代划分为一块较大的Eden空间和两个较小的Survivor空间(From Space, To Space),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor空间中。</p><p><img src="https://ws2.sinaimg.cn/large/006tNc79gy1ftqwmjpuyvj30kg046wfh.jpg" alt=""></p><p>而老生代因为每次只回收少量对象,因而采用Mark-Compact算法。</p><p>另外,不要忘记在<a href="http://www.cnblogs.com/cielosun/p/6622983.html" target="_blank" rel="noopener">Java基础:Java虚拟机(JVM)</a>中提到过的处于方法区的永生代(Permanet Generation)。它用来存储class类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。</p><p>对象的内存分配主要在新生代的Eden Space和Survivor Space的From Space(Survivor目前存放对象的那一块),少数情况会直接分配到老生代。当新生代的Eden Space和From Space空间不足时就会发生一次GC,进行GC后,Eden Space和From Space区的存活对象会被挪到To Space,然后将Eden Space和From Space进行清理。如果To Space无法足够存储某个对象,则将这个对象存储到老生代。在进行GC后,使用的便是Eden Space和To Space了,如此反复循环。当对象在Survivor区躲过一次GC后,其年龄就会+1。默认情况下年龄到达15的对象会被移到老生代中。</p><h1 id="对象分配"><a href="#对象分配" class="headerlink" title="对象分配"></a>对象分配</h1><p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftqwuiheq8j30wl0agq70.jpg" alt=""></p><h1 id="典型的垃圾收集器"><a href="#典型的垃圾收集器" class="headerlink" title="典型的垃圾收集器"></a>典型的垃圾收集器</h1><p>垃圾收集算法是垃圾收集器的理论基础,而垃圾收集器就是其具体实现。下面介绍HotSpot虚拟机提供的几种垃圾收集器。</p><p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftqzmigfi8j30zu08o41k.jpg" alt=""></p><ol><li><p><strong>Serial/Serial Old</strong><br>最古老的收集器,是一个单线程收集器,用它进行垃圾回收时,必须暂停所有用户线程。Serial是针对新生代的收集器,采用Copying算法;而Serial Old是针对老生代的收集器,采用Mark-Compact算法。优点是简单高效,缺点是需要暂停用户线程。</p></li><li><p><strong>ParNew</strong><br>Seral/Serial Old的多线程版本,使用多个线程进行垃圾收集。</p></li><li><p><strong>Parallel Scavenge</strong><br>新生代的<strong>并行</strong>收集器,回收期间不需要暂停其他线程,采用Copying算法。该收集器与前两个收集器不同,主要为了达到一个可控的吞吐量。</p></li><li><p><strong>Parallel Old</strong><br>Parallel Scavenge的老生代版本,采用Mark-Compact算法和多线程。</p></li><li><p><strong>CMS</strong><br>Current Mark Sweep收集器是一种以最小回收时间停顿为目标的<strong>并发</strong>回收器,因而采用Mark-Sweep算法。</p></li><li><p><strong>G1</strong><br>G1(Garbage First)收集器技术的前沿成果,是面向服务端的收集器,能充分利用CPU和多核环境。是一款并行与并发收集器,它能够建立可预测的停顿时间模型。</p></li></ol><h2 id="这里要强调一下并行与并发的概念:"><a href="#这里要强调一下并行与并发的概念:" class="headerlink" title="这里要强调一下并行与并发的概念:"></a>这里要强调一下并行与并发的概念:</h2><p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftqzpiq83bj30z80clwlo.jpg" alt=""></p><h2 id="停顿时间-VS-吞吐量"><a href="#停顿时间-VS-吞吐量" class="headerlink" title="停顿时间 VS 吞吐量"></a>停顿时间 VS 吞吐量</h2><p> <img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftqzrdxmovj30w908rdjq.jpg" alt=""></p><h1 id="垃圾收集器搭配"><a href="#垃圾收集器搭配" class="headerlink" title="垃圾收集器搭配"></a>垃圾收集器搭配</h1><p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftqzwboe8qj30rc0jkdpn.jpg" alt=""></p><h1 id="垃圾收集器详解:"><a href="#垃圾收集器详解:" class="headerlink" title="垃圾收集器详解:"></a>垃圾收集器详解:</h1><h2 id="并行垃圾收集器(Parallel-Collector)"><a href="#并行垃圾收集器(Parallel-Collector)" class="headerlink" title="并行垃圾收集器(Parallel Collector)"></a>并行垃圾收集器(Parallel Collector)</h2><p><img src="https://ws3.sinaimg.cn/large/006tNc79gy1ftqzz6v7u4j30ri08jtb3.jpg" alt=""></p><h2 id="CMS-垃圾收集器"><a href="#CMS-垃圾收集器" class="headerlink" title="CMS 垃圾收集器"></a>CMS 垃圾收集器</h2><p><img src="https://ws2.sinaimg.cn/large/006tNc79gy1ftr01s112jj30hl09m3zp.jpg" alt=""></p><h3 id="CMS-垃圾收集过程"><a href="#CMS-垃圾收集过程" class="headerlink" title="CMS 垃圾收集过程"></a>CMS 垃圾收集过程</h3><ol><li>CMS initialmark: 初始化标记Root, stop the world</li><li>CMS concurrent mark:并发标记</li><li>CMS concurrent preclean:并发预清理</li><li>CMS remark :重新标记。 stop the world</li><li>CMS concurrent sweep:并发清除</li><li>CMS concurrent reset: 并发重置</li></ol><h3 id="CMS-缺点"><a href="#CMS-缺点" class="headerlink" title="CMS 缺点"></a>CMS 缺点</h3><p>CPU 敏感<br>浮动垃圾<br>空间碎片</p><h3 id="CMS-相关参数"><a href="#CMS-相关参数" class="headerlink" title="CMS 相关参数"></a>CMS 相关参数</h3><p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftr07yp4fbj30zm0awq6l.jpg" alt=""><br><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftr093dpxoj30yl0dgaep.jpg" alt=""></p><h2 id="G1-垃圾收集器"><a href="#G1-垃圾收集器" class="headerlink" title="G1 垃圾收集器"></a>G1 垃圾收集器</h2><p>新生代和老年代垃圾收集器</p><h3 id="概念:"><a href="#概念:" class="headerlink" title="概念:"></a>概念:</h3><p><img src="https://ws3.sinaimg.cn/large/006tNc79gy1ftr0dqxjbkj30yf0bd793.jpg" alt=""> </p><h3 id="Mixed-GC:"><a href="#Mixed-GC:" class="headerlink" title="Mixed GC:"></a>Mixed GC:</h3><p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftr0fw6h38j30pu05tmyk.jpg" alt=""><br><img src="https://ws2.sinaimg.cn/large/006tNc79gy1ftr0gtvi2xj30w80a4dj2.jpg" alt=""></p><h3 id="Mixed-GC-时机:"><a href="#Mixed-GC-时机:" class="headerlink" title="Mixed GC 时机:"></a>Mixed GC 时机:</h3><p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftr0hcs8tuj30zc0gjn4x.jpg" alt=""></p><h3 id="Mixed-GC-相关参数:"><a href="#Mixed-GC-相关参数:" class="headerlink" title="Mixed GC 相关参数:"></a>Mixed GC 相关参数:</h3><p><img src="https://ws2.sinaimg.cn/large/006tNc79gy1ftr0j7fupxj30xt0g479o.jpg" alt=""><br><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftr0k5ocwdj30uu04ljt9.jpg" alt=""></p><p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftr0lktqmpj30ya0dvwka.jpg" alt=""></p><p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftr0mm2vpej30uk0dcn1c.jpg" alt=""></p><p> 参考文章:</p><p> <a href="https://www.cnblogs.com/cielosun/p/6674431.html" target="_blank" rel="noopener">JVM垃圾回收算法</a></p><p> <a href="https://blog.csdn.net/aijiudu/article/details/72991993" target="_blank" rel="noopener">JVM架构和GC垃圾回收机制</a></p>]]></content>
<summary type="html">
<p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1ftqvzd2erdj30zs0datd1.jpg" alt=""></p>
<p>##标记清楚算法(Mark-Sweep)<br><img src="https://ws3.sinaimg.cn/large/006tNc79gy1ftqw93v8ldj30y30ba789.jpg" alt=""><br><img src="https://ws4.sinaimg.cn/large/006tNc79ly1ftqwdlfu4cj30p40dq0us.jpg" alt=""></p>
<p>##复制算法(Copying)<br>为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如图:</p>
<p><img src="https://ws4.sinaimg.cn/large/006tNc79gy1ftqwirry1nj30l00blac4.jpg" alt=""></p>
<p>这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying算法的效率会大大降低。</p>
</summary>
<category term="JVM 垃圾回收 算法" scheme="https://yefan813.github.io/tags/JVM-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6-%E7%AE%97%E6%B3%95/"/>
</entry>
</feed>