-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
359 lines (173 loc) · 447 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>【Minecraft】最佳附魔组合</title>
<link href="/2025/01/21/%E3%80%90Minecraft%E3%80%91%E6%9C%80%E4%BD%B3%E9%99%84%E9%AD%94%E7%BB%84%E5%90%88/"/>
<url>/2025/01/21/%E3%80%90Minecraft%E3%80%91%E6%9C%80%E4%BD%B3%E9%99%84%E9%AD%94%E7%BB%84%E5%90%88/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>原文:<a href="https://www.18183.com/minecraft/202111/3734044.html">我的世界最佳附魔组合</a></p></blockquote><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Minecraft中共有十一种武器有最佳附魔搭配,分别是钻石剑、钻石镐、钻石斧、钻石锹、弩、弓、三叉戟、钻石头盔、钻石胸甲、钻石护腿、钻石靴,其中钻石镐、弩、三叉戟、钻石靴子都有两种最佳附魔搭配,玩家可以根据自己的需求来搭配不同的最佳附魔,让装备物尽其用。</p><h2 id="武器"><a href="#武器" class="headerlink" title="武器"></a>武器</h2><h3 id="1、钻石剑"><a href="#1、钻石剑" class="headerlink" title="1、钻石剑"></a>1、钻石剑</h3><blockquote><p>锋利5,耐久3,横扫之刃3,击退2,火焰附加2,经验修补,抢夺3</p></blockquote><p>小锋利比截肢杀手,亡灵杀手更加的有性价比。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221313057.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h3 id="2、弩"><a href="#2、弩" class="headerlink" title="2、弩"></a>2、弩</h3><blockquote><p>耐久3,快速装填3,无限/经验修补,多重射击3/穿透4</p></blockquote><p>它的冲突附魔是无限和经验修补,多重射击和穿透,根据需求选择附魔。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221313501.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h3 id="3、弓"><a href="#3、弓" class="headerlink" title="3、弓"></a>3、弓</h3><blockquote><p>力量5,火矢,冲击2,耐久3,经验修补/无限</p></blockquote><p>它的冲突附魔是无限和经验修补,根据个人需求选择附魔。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221313643.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h3 id="4、三叉戟"><a href="#4、三叉戟" class="headerlink" title="4、三叉戟"></a>4、三叉戟</h3><blockquote><p>经验修补,耐久3,穿刺5,忠诚3,引雷(可将忠诚3、引雷替换成激流3)</p></blockquote><p>它的冲突附魔是激流和忠诚,引雷,所以三叉戟有两种玩法,引雷在雷雨天气召唤雷电去劈生物,激流可以在水中或者下雨的时候进行飞行位移。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221314422.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h2 id="防具"><a href="#防具" class="headerlink" title="防具"></a>防具</h2><h3 id="1、钻石头盔"><a href="#1、钻石头盔" class="headerlink" title="1、钻石头盔"></a>1、钻石头盔</h3><blockquote><p>保护4,荆棘3,耐久3,经验修补,水下呼吸3,水下速掘</p></blockquote><p>保护比其他的附魔好用更具性价比,因人而异。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221314247.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h3 id="2、钻石胸甲"><a href="#2、钻石胸甲" class="headerlink" title="2、钻石胸甲"></a>2、钻石胸甲</h3><blockquote><p>保护4,荆棘3,耐久3,经验修补</p></blockquote><p>保护比其他的附魔好用更具性价比,与钻石头盔一样因人而异。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221314357.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h3 id="3、钻石护腿"><a href="#3、钻石护腿" class="headerlink" title="3、钻石护腿"></a>3、钻石护腿</h3><blockquote><p>保护4,荆棘3,耐久3,经验修补</p></blockquote><p>穿在腿上时,让盔甲和盔甲任性有所增加,更具有保护性。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221314193.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h3 id="4、钻石靴子"><a href="#4、钻石靴子" class="headerlink" title="4、钻石靴子"></a>4、钻石靴子</h3><blockquote><p>保护4,荆棘3,耐久3,经验修补,摔落保护4,深海探索者或冰霜行者2</p></blockquote><p>钻石靴子有冲突附魔冰霜行者和深海探索者,所以最佳附魔有两个,两种附魔有两种玩法,深海探索者水下游的飞快,冰霜行者可以在水面上结冰行走。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221314287.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><h3 id="1、钻石镐"><a href="#1、钻石镐" class="headerlink" title="1、钻石镐"></a>1、钻石镐</h3><blockquote><p>耐久3,时运3,效率5,经验修补</p></blockquote><p>选择精准采集和时运,所以钻石镐有两种最佳附魔,另外一种搭配是耐久3,精准采集,效率5,经验修补。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221314075.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h3 id="2、钻石斧"><a href="#2、钻石斧" class="headerlink" title="2、钻石斧"></a>2、钻石斧</h3><blockquote><p>效率5,时运3,耐久3,经验修补</p></blockquote><p>斧子的主要作用是砍伐木材。在生存游戏中,附加效率可以提高砍伐木材的速度,附加耐久可以保证斧子的使用时间加长。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221314420.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p><h3 id="3、钻石锹"><a href="#3、钻石锹" class="headerlink" title="3、钻石锹"></a>3、钻石锹</h3><blockquote><p>时运3,经验修补,效率5,耐久3</p></blockquote><p>钻石锹耐久高,等级高,速度快,带一把在手能省去非常多的事。</p><p><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202501221314600.jpeg" alt="我的世界最佳附魔组合汇总 最佳附魔搭配图分享"></p>]]></content>
<categories>
<category> 游戏攻略 </category>
</categories>
<tags>
<tag> Minecraft </tag>
<tag> 游戏攻略 </tag>
</tags>
</entry>
<entry>
<title>【设计模式】SpringBoot优雅使用策略模式</title>
<link href="/2024/09/14/%E3%80%90%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%91SpringBoot%E4%BC%98%E9%9B%85%E4%BD%BF%E7%94%A8%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F/"/>
<url>/2024/09/14/%E3%80%90%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%91SpringBoot%E4%BC%98%E9%9B%85%E4%BD%BF%E7%94%A8%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><h1 id="一、简述"><a href="#一、简述" class="headerlink" title="一、简述"></a>一、简述</h1><blockquote><p>引用:<a href="https://blog.csdn.net/qq_38249409/article/details/131322100">【设计模式】SpringBoot优雅使用策略模式</a></p></blockquote><p>策略模式有3种角色,分别为:<strong>选择器</strong>、<strong>抽象策略</strong>、<strong>策略实例</strong>。<br>其中<strong>选择器</strong><code>selector</code>又被称为上下文<code>context</code>,其作用为通过不同的标识来获取对应的策略实例。<strong>策略实例</strong>就是封装不同算法的实例对象实例对象&spm=1001.2101.3001.7020),而<strong>抽象策略</strong>就是策略实例的顶层接口。</p><p>简单类图大概就是这个样子:</p><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://i-blog.csdnimg.cn/blog_migrate/8dd05e2bb8451d6c8cddfe81e62a010d.png"></div><h1 id="二、实现"><a href="#二、实现" class="headerlink" title="二、实现"></a>二、实现</h1><h2 id="1、定义对象"><a href="#1、定义对象" class="headerlink" title="1、定义对象"></a>1、定义对象</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 定义beanName需要注意属性顺序</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> {</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> <span class="keyword">private</span> Integer age;</span><br><span class="line"> <span class="keyword">private</span> Boolean isSp;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="2、抽象策略处理器"><a href="#2、抽象策略处理器" class="headerlink" title="2、抽象策略处理器"></a>2、抽象策略处理器</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">TestService</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//方法策略</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doMethod</span><span class="params">(User user)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//可以定义共同方法</span></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">spHandle</span><span class="params">(User user)</span>{</span><br><span class="line"> user.setAge(user.getAge()+<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="3、定义策略选择器"><a href="#3、定义策略选择器" class="headerlink" title="3、定义策略选择器"></a>3、定义策略选择器</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServiceFactory</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Resource</span></span><br><span class="line"> <span class="keyword">private</span> Map<String, TestService> services;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取service实例</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> user</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> TestService <span class="title function_">instance</span><span class="params">(User user)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//自定义beanName处理,此处为拼接对象属性,空值不参与拼接,{ _ }分隔</span></span><br><span class="line"> <span class="type">String</span> <span class="variable">beanName</span> <span class="operator">=</span> StringUtils.concatenateFields(user, <span class="string">"_"</span>);</span><br><span class="line"> <span class="keyword">if</span> (services.containsKey(beanName)) {</span><br><span class="line"> <span class="keyword">return</span> services.get(beanName);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//可以定义一个默认策略</span></span><br><span class="line"> <span class="keyword">return</span> services.get(<span class="string">"default"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (IllegalAccessException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</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><h3 id="对象字段拼接字符串工具类"><a href="#对象字段拼接字符串工具类" class="headerlink" title="对象字段拼接字符串工具类"></a>对象字段拼接字符串工具类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StringUtils</span> <span class="keyword">extends</span> <span class="title class_">StrUtil</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 将对象的所有非空字段拼接成一个字符串。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> obj 要转换的对象</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> separator 字段之间的分隔符</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 字符串表示</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> IllegalAccessException 如果无法访问字段抛出此异常</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">concatenateFields</span><span class="params">(Object obj, String separator)</span> <span class="keyword">throws</span> IllegalAccessException {</span><br><span class="line"> <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"> Class<?> clazz = obj.getClass();</span><br><span class="line"> Field[] fields = clazz.getDeclaredFields();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 遍历所有字段</span></span><br><span class="line"> <span class="keyword">for</span> (Field field : fields) {</span><br><span class="line"> <span class="comment">// 设置为可访问,以便能访问私有变量</span></span><br><span class="line"> field.setAccessible(<span class="literal">true</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> field.get(obj);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 检查是否为空或空白字符串</span></span><br><span class="line"> <span class="keyword">if</span> (value != <span class="literal">null</span> && !(value <span class="keyword">instanceof</span> String && ((String) value).trim().isEmpty())) {</span><br><span class="line"> <span class="keyword">if</span> (sb.length() > <span class="number">0</span>) {</span><br><span class="line"> sb.append(separator);</span><br><span class="line"> }</span><br><span class="line"> sb.append(value.toString());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> sb.toString();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="4、策略实现类"><a href="#4、策略实现类" class="headerlink" title="4、策略实现类"></a>4、策略实现类</h2><p><strong>注意:此处定义beanName时会按照对象属性的顺序进行拼接</strong></p><h3 id="默认策略实现"><a href="#默认策略实现" class="headerlink" title="默认策略实现"></a>默认策略实现</h3><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service("default")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultTestServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">TestService</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doMethod</span><span class="params">(User user)</span> {</span><br><span class="line"> spHandle(user);</span><br><span class="line"> System.out.println(<span class="string">"DefaultTestServiceImpl"</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="实现策略A"><a href="#实现策略A" class="headerlink" title="实现策略A"></a>实现策略A</h3><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service("sherlock_18_false")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ATestServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">TestService</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doMethod</span><span class="params">(User user)</span> {</span><br><span class="line"> spHandle(user);</span><br><span class="line"> System.out.println(<span class="string">"DefaultTestServiceImpl"</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="实现策略B"><a href="#实现策略B" class="headerlink" title="实现策略B"></a>实现策略B</h3><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service("sherlock_true")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BTestServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">TestService</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doMethod</span><span class="params">(User user)</span> {</span><br><span class="line"> spHandle(user);</span><br><span class="line"> System.out.println(<span class="string">"DefaultTestServiceImpl"</span>);</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">spHandle</span><span class="params">(User user)</span> {</span><br><span class="line"> <span class="comment">// 自定义重写方法</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 技术分享 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 技术分享 </tag>
<tag> SpringBoot </tag>
<tag> 设计模式 </tag>
</tags>
</entry>
<entry>
<title>【Pixelmon】准神宝可梦</title>
<link href="/2024/08/21/%E3%80%90Pixelmon%E3%80%91%E5%87%86%E7%A5%9E%E5%AE%9D%E5%8F%AF%E6%A2%A6/"/>
<url>/2024/08/21/%E3%80%90Pixelmon%E3%80%91%E5%87%86%E7%A5%9E%E5%AE%9D%E5%8F%AF%E6%A2%A6/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>“大器晚成”的宝可梦指的是一些在进化后展现出强大实力的宝可梦,它们的强度能够与“传说中的宝可梦”相媲美,通常玩家们将它们称为“准神”。</p><h2 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h2><p>每个宝可梦世代都会引入新的“大器晚成”宝可梦。这些宝可梦特点是通过两次进化达到最终形态,并且它们都属于经验值累积较慢的组别,因此被赋予了“大器晚成”的称号。这些宝可梦在最终进化形态下的种族值总和通常达到600。在宝可梦图鉴中,这些“大器晚成”宝可梦的编号常常紧随传说中的宝可梦之后,有时甚至穿插在传说宝可梦之间,通常情况下,“大器晚成”的称呼主要是指这些宝可梦的最终进化形态。</p><p>此外,单从种族值来看铝钢桥龙也可纳入“大器晚成”的宝可梦,但它与一般意义上“大器晚成”的宝可梦的进化规律与升级速度不相同。</p><h2 id="准神宝可梦"><a href="#准神宝可梦" class="headerlink" title="准神宝可梦"></a>准神宝可梦</h2><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211458511.png"></div><h3 id="快龙"><a href="#快龙" class="headerlink" title="快龙"></a><a href="https://wiki.52poke.com/wiki/%E5%BF%AB%E9%BE%99">快龙</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/8/8b/149Dragonite.png/300px-149Dragonite.png"></div><h4 id="种族值"><a href="#种族值" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="60%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211634974.png"></div>#### 属性相性<div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408221342273.png"></div><h4 id="培育推荐"><a href="#培育推荐" class="headerlink" title="培育推荐"></a>培育推荐</h4><blockquote><p><a href="https://abitingpokedex.com/dragonite/">《寶可夢朱紫》快龍配招推薦&努力值分配&太晶選擇</a></p></blockquote><ul><li><p><strong>性格:固执/爽朗</strong></p><p>提升攻击/速度,降低特攻</p><p>固执性格龙舞一次快不过速度线130的,但是推图足够</p></li><li><p><strong>特性:多重鳞片</strong></p><p>多重鳞片特性的宝可梦在HP全满时,受到的伤害减少1/2。</p></li><li><p><strong>努力值:攻击252,速度252,HP6</strong></p></li><li><p><strong>技能:神速,龙舞,冰/火/电拳,逆鳞/龙爪</strong></p></li><li><p><strong>道具:剩饭</strong></p></li><li><p><strong>捕捉地点:</strong></p></li></ul><h3 id="班基拉斯"><a href="#班基拉斯" class="headerlink" title="班基拉斯 "></a><a href="https://wiki.52poke.com/wiki/%E7%8F%AD%E5%9F%BA%E6%8B%89%E6%96%AF">班基拉斯 </a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/8/82/248Tyranitar.png/300px-248Tyranitar.png"></div><h4 id="种族值-1"><a href="#种族值-1" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="35%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211636931.png"></div>#### 属性相性<h3 id="暴飞龙"><a href="#暴飞龙" class="headerlink" title="暴飞龙"></a><a href="https://wiki.52poke.com/wiki/%E6%9A%B4%E9%A3%9E%E9%BE%99">暴飞龙</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/4/41/373Salamence.png/300px-373Salamence.png"></div><h4 id="种族值-2"><a href="#种族值-2" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="60%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211640547.png"></div><h3 id="巨金怪"><a href="#巨金怪" class="headerlink" title="巨金怪"></a><a href="https://wiki.52poke.com/wiki/%E5%B7%A8%E9%87%91%E6%80%AA">巨金怪</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/0/05/376Metagross.png/300px-376Metagross.png"></div><h4 id="种族值-3"><a href="#种族值-3" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="60%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211642784.png"></div><h3 id="烈咬陆鲨"><a href="#烈咬陆鲨" class="headerlink" title="烈咬陆鲨"></a><a href="https://wiki.52poke.com/wiki/%E7%83%88%E5%92%AC%E9%99%86%E9%B2%A8">烈咬陆鲨</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/f/fa/445Garchomp.png/300px-445Garchomp.png"></div><h4 id="种族值-4"><a href="#种族值-4" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="35%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211614208.png"></div><h4 id="属性相性"><a href="#属性相性" class="headerlink" title="属性相性"></a>属性相性</h4><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408221333271.png"></div><h4 id="培育推荐-1"><a href="#培育推荐-1" class="headerlink" title="培育推荐"></a>培育推荐</h4><ul><li><p><strong>性格:固执/爽朗</strong></p><p>提升攻击/速度,降低特攻</p></li><li><p><strong>特性:多重鳞片</strong></p><p>多重鳞片特性的宝可梦在HP全满时,受到的伤害减少1/2。</p></li><li><p><strong>努力值:攻击252,速度252,HP6</strong></p></li><li><p><strong>技能:剑舞+地震+龙爪+逆鳞/沙暴/咬碎/其他属性物攻招式</strong></p></li><li><p><strong>道具:剩饭</strong></p></li><li><p><strong>捕捉地点:</strong></p></li></ul><h3 id="三首恶龙"><a href="#三首恶龙" class="headerlink" title="三首恶龙"></a><a href="https://wiki.52poke.com/wiki/%E4%B8%89%E9%A6%96%E6%81%B6%E9%BE%99">三首恶龙</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/3/3e/635Hydreigon.png/300px-635Hydreigon.png"></div><h4 id="种族值-5"><a href="#种族值-5" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="60%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211646393.png"></div><h3 id="黏美龙"><a href="#黏美龙" class="headerlink" title="黏美龙"></a><a href="https://wiki.52poke.com/wiki/%E9%BB%8F%E7%BE%8E%E9%BE%99">黏美龙</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/d/df/706Goodra.png/300px-706Goodra.png"></div><h4 id="种族值-6"><a href="#种族值-6" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="60%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211648274.png"></div><h3 id="杖尾鳞甲龙"><a href="#杖尾鳞甲龙" class="headerlink" title="杖尾鳞甲龙"></a><a href="https://wiki.52poke.com/wiki/%E6%9D%96%E5%B0%BE%E9%B3%9E%E7%94%B2%E9%BE%99">杖尾鳞甲龙</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/8/84/784Kommo-o.png/250px-784Kommo-o.png"></div><h4 id="种族值-7"><a href="#种族值-7" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="60%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211649732.png"></div><h3 id="多龙巴鲁托"><a href="#多龙巴鲁托" class="headerlink" title="多龙巴鲁托"></a><a href="https://wiki.52poke.com/wiki/%E5%A4%9A%E9%BE%99%E5%B7%B4%E9%B2%81%E6%89%98">多龙巴鲁托</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/f/f7/887Dragapult.png/250px-887Dragapult.png"></div><h4 id="种族值-8"><a href="#种族值-8" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="60%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211649199.png"></div><h3 id="洗翠黏美龙"><a href="#洗翠黏美龙" class="headerlink" title="洗翠黏美龙"></a><a href="https://wiki.52poke.com/wiki/%E9%BB%8F%E7%BE%8E%E9%BE%99">洗翠黏美龙</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/2/27/706Goodra-Hisui.png/300px-706Goodra-Hisui.png"></div><h4 id="种族值-9"><a href="#种族值-9" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="60%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211648703.png"></div><h3 id="戟脊龙"><a href="#戟脊龙" class="headerlink" title="戟脊龙"></a><a href="https://wiki.52poke.com/wiki/%E6%88%9F%E8%84%8A%E9%BE%99">戟脊龙</a></h3><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://s1.52poke.com/wiki/thumb/1/17/998Baxcalibur.png/250px-998Baxcalibur.png"></div><h4 id="种族值-10"><a href="#种族值-10" class="headerlink" title="种族值"></a>种族值</h4><div style="display:inline-block;"> <img width="60%" style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408211650147.png"></div>]]></content>
<categories>
<category> 游戏攻略 </category>
</categories>
<tags>
<tag> Pixelmon </tag>
<tag> Minecraft </tag>
<tag> 宝可梦 </tag>
<tag> 游戏攻略 </tag>
</tags>
</entry>
<entry>
<title>【Pixelmon】抓宠工具宝可梦</title>
<link href="/2024/08/20/%E3%80%90Pixelmon%E3%80%91%E6%8A%93%E5%AE%A0%E5%B7%A5%E5%85%B7%E5%AE%9D%E5%8F%AF%E6%A2%A6/"/>
<url>/2024/08/20/%E3%80%90Pixelmon%E3%80%91%E6%8A%93%E5%AE%A0%E5%B7%A5%E5%85%B7%E5%AE%9D%E5%8F%AF%E6%A2%A6/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>引用:<a href="https://forums.pokemmo.com/index.php?/topic/114236-%E6%96%B0%E6%89%8B%E5%90%91-%E5%B7%A5%E5%85%B7%E5%AE%A0%E7%89%A9%E7%AF%87/">【新手向】 工具宠物篇 - 攻略馆 - PokeMMO</a></p></blockquote><p><strong>①图图犬</strong></p><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://forums.pokemmo.com/uploads/monthly_2020_04/240px-235Smeargle.png.fa08baac7e66920744a498646c471c6d.png"></div><ul><li><strong>技能:点到为止、蘑菇孢子、浸水、大蛇瞪眼</strong></li><li><strong>特性:我行我素/技术高手</strong></li><li><strong>道具:丝绸围巾</strong></li><li><strong>努力值:攻击252 速度252</strong></li><li><strong>推荐等级:80</strong></li><li><strong>捕捉地点:丰缘对战区大瀑布下面的工匠之穴</strong></li></ul><p>小提示:这里由于图图犬的技能学习机制比较特殊,在这里说明一下,图图犬每升10级可以获得写生技能,写生技能可以对战中复制学习一只宝可梦的技能,双人对战(双人草丛),一只宠物使用要学的技能(点到为止),然后图图犬使用(写生)即可学会点为止 </p><p><strong>②斗笠菇</strong></p><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://forums.pokemmo.com/uploads/monthly_2020_04/240px-286Breloom.png.3e4f41359289a32216899f8a8e41b01a.png"></div><ul><li><strong>技能推荐:点到为止 蘑菇孢子 替身 种子炸弹</strong></li><li><strong>特性:毒疗</strong> </li><li><strong>携带道具:毒珠</strong> </li><li><strong>努力值:攻击252 速度252</strong></li><li><strong>推荐等级:80</strong></li><li><strong>捕捉地点:无法直接捕捉 需要捕捉蘑蘑菇进化(橙华森林)</strong></li></ul><p>小提示:斗笠菇有毒珠的情况下会中毒,只能存在一种负面buff,所以可以增加抓宠时候的效率</p><p><strong>③图图犬(黑色眼神)</strong></p><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://forums.pokemmo.com/uploads/monthly_2020_04/240px-235Smeargle.png.fa08baac7e66920744a498646c471c6d.png"></div><ul><li><strong>技能:点到为止、蘑菇孢子、黑色眼神、暗影爪</strong></li><li><strong>特性:我行我素/技术高手</strong></li><li><strong>道具:丝绸围巾</strong></li><li><strong>努力值:攻击252 速度252</strong></li><li><strong>推荐等级:80</strong></li><li><strong>捕捉地点:丰缘对战区大瀑布下面的工匠之穴</strong></li></ul><p>小提示:黑色眼神主要用来抓有瞬间移动的宠物,如凯西,拉鲁拉丝等。暗影爪可以用来在拉鲁拉丝群怪中解决多余精灵。</p><p><strong>④图图犬(纹理)</strong></p><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://forums.pokemmo.com/uploads/monthly_2020_04/240px-235Smeargle.png.fa08baac7e66920744a498646c471c6d.png"></div><ul><li><strong>技能:暗影爪/黑夜魔影、蘑菇孢子、点到为止、纹理</strong></li><li><strong>特性:我行我素/技术高手</strong></li><li><strong>道具:丝绸围巾</strong></li><li><strong>努力值:攻击252 速度252</strong></li><li><strong>推荐等级:80</strong></li><li><strong>捕捉地点:丰缘对战区大瀑布下面的工匠之穴</strong></li></ul><p>小提示:纹理图图用来抓会自伤技能的精灵,如猛撞、舍身冲撞等技能。鬼系技能一定要放在第一位,图图出场后要第一时间使用纹理。</p><p><strong>⑤沼王(乌波)</strong></p><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://forums.pokemmo.com/uploads/monthly_2020_04/240px-195Quagsire.png.2525d762e3ec8593331f97ed461e6602.png"></div><ul><li><strong>技能推荐:哈欠 泥巴炸弹 水枪</strong></li><li><strong>特性:湿气</strong></li><li><strong>携带道具: 吃剩的饭</strong></li><li><strong>努力值:HP252 特攻252</strong></li><li><strong>推荐等级:60</strong></li><li><strong>捕捉地点:四之岛PC右边水面,狩猎地带 神奥212道路等</strong></li></ul><p>小提示:湿气特性可以放止有自爆技能的宠物使用自爆,防止惨案的发生</p>]]></content>
<categories>
<category> 游戏攻略 </category>
</categories>
<tags>
<tag> Pixelmon </tag>
<tag> Minecraft </tag>
<tag> 宝可梦 </tag>
<tag> 游戏攻略 </tag>
</tags>
</entry>
<entry>
<title>Hexo常用操作</title>
<link href="/2024/08/19/Hexo%E5%B8%B8%E7%94%A8%E6%93%8D%E4%BD%9C/"/>
<url>/2024/08/19/Hexo%E5%B8%B8%E7%94%A8%E6%93%8D%E4%BD%9C/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><h1 id="Hexo常用操作"><a href="#Hexo常用操作" class="headerlink" title="Hexo常用操作"></a>Hexo常用操作</h1><h2 id="一、更新指令"><a href="#一、更新指令" class="headerlink" title="一、更新指令"></a>一、更新指令</h2><h3 id="1、普通更新"><a href="#1、普通更新" class="headerlink" title="1、普通更新"></a>1、普通更新</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo cl&&hexo ge&&hexo d&&hexo s</span><br></pre></td></tr></table></figure><h3 id="2、完整指令"><a href="#2、完整指令" class="headerlink" title="2、完整指令"></a>2、完整指令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo cl&&hexo bangumi -u&&hexo game -u&&hexo ge&&hexo algolia&&hexo d&&hexo s</span><br></pre></td></tr></table></figure><h3 id="3、单条指令"><a href="#3、单条指令" class="headerlink" title="3、单条指令"></a>3、单条指令</h3><h4 id="1-清空"><a href="#1-清空" class="headerlink" title="1.清空"></a>1.清空</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo cl</span><br></pre></td></tr></table></figure><h4 id="2-更新番剧、游戏数据"><a href="#2-更新番剧、游戏数据" class="headerlink" title="2.更新番剧、游戏数据"></a>2.更新番剧、游戏数据</h4><blockquote><p><a href="https://bangumi.tv/">Bangumi 番组计划</a></p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo bangumi -u</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo game -u</span><br></pre></td></tr></table></figure><h4 id="3-构建hexo页面"><a href="#3-构建hexo页面" class="headerlink" title="3.构建hexo页面"></a>3.构建hexo页面</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo ge</span><br></pre></td></tr></table></figure><h4 id="4-更新algolia搜索数据"><a href="#4-更新algolia搜索数据" class="headerlink" title="4.更新algolia搜索数据"></a>4.更新algolia搜索数据</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo algolia</span><br></pre></td></tr></table></figure><h4 id="5-发布"><a href="#5-发布" class="headerlink" title="5.发布"></a>5.发布</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo d</span><br></pre></td></tr></table></figure><h4 id="6-本地部署"><a href="#6-本地部署" class="headerlink" title="6.本地部署"></a>6.本地部署</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo s</span><br></pre></td></tr></table></figure><h2 id="二、新建文章"><a href="#二、新建文章" class="headerlink" title="二、新建文章"></a>二、新建文章</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo new [title]</span><br></pre></td></tr></table></figure><h3 id="文章头部信息"><a href="#文章头部信息" class="headerlink" title="文章头部信息"></a>文章头部信息</h3><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: 标题</span><br><span class="line">date: 2024-08-19 15:17:40</span><br><span class="line">cover: 封面(750x349),需要上传到图床,网络图片会失效</span><br><span class="line">tags: </span><br><span class="line"><span class="bullet">-</span> 标签</span><br><span class="line">categories: </span><br><span class="line"><span class="bullet">-</span> 分类</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"referrer"</span> <span class="attr">content</span>=<span class="string">"no-referrer"</span>/></span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="三、图片显示"><a href="#三、图片显示" class="headerlink" title="三、图片显示"></a>三、图片显示</h2><h3 id="1、显示网络图片"><a href="#1、显示网络图片" class="headerlink" title="1、显示网络图片"></a>1、显示网络图片</h3><p>文章内显示网络图片需要在头部添加以下内容</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"referrer"</span> <span class="attr">content</span>=<span class="string">"no-referrer"</span>/></span></span><br></pre></td></tr></table></figure><h3 id="2、图片靠左对齐"><a href="#2、图片靠左对齐" class="headerlink" title="2、图片靠左对齐"></a>2、图片靠左对齐</h3><p><strong>Hexo中想要图片靠左显示需要使用HTML语法</strong></p><figure class="highlight html"><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"><span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"display:inline-block;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">style</span>=<span class="string">"float:left; margin-right: 10px;"</span> <span class="attr">src</span>=<span class="string">"$url"</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>实际效果:</p><div style="display:inline-block;"><img style="float:left; margin-right: 10px;" src="https://forums.pokemmo.com/uploads/monthly_2020_04/240px-235Smeargle.png.fa08baac7e66920744a498646c471c6d.png"></div><h3 id="3、图片文字同行"><a href="#3、图片文字同行" class="headerlink" title="3、图片文字同行"></a>3、图片文字同行</h3><p>在一行文字中间插入图片,其中 <code>transform: translateY(20%);</code> 设置图片具体高度,如不设置则默认居中。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515107.png"</span> <span class="attr">style</span>=<span class="string">"display: inline; vertical-align: middle; transform: translateY(20%); max-width: 50px; height: auto;"</span>></span></span><br></pre></td></tr></table></figure><p>实际效果:</p><p>有概率获得<img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515107.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;">薄荷种子</p>]]></content>
<categories>
<category> 技术分享 </category>
</categories>
<tags>
<tag> 技术分享 </tag>
<tag> Hexo </tag>
</tags>
</entry>
<entry>
<title>【Pixelmon】宝可梦培育攻略</title>
<link href="/2024/08/19/%E3%80%90Pixelmon%E3%80%91%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%9F%B9%E8%82%B2%E6%94%BB%E7%95%A5/"/>
<url>/2024/08/19/%E3%80%90Pixelmon%E3%80%91%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%9F%B9%E8%82%B2%E6%94%BB%E7%95%A5/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><h2 id="性格"><a href="#性格" class="headerlink" title="性格"></a>性格</h2><p>使用<a href="https://www.mcmod.cn/item/443324.html">对战外技能</a><strong>【材料】</strong>有概率获得<img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515107.png" style="display: inline; vertical-align: middle; transform: translateY(30%);max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386653.html">薄荷插穗</a>,种植耕地上,破坏成熟的<a href="https://www.mcmod.cn/item/386653.html">薄荷叶</a>随机获得1种薄荷</p><div style="display:inline-block;"> <img style="float:left; margin-right: 10px;" src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515201.png"></div><h2 id="努力值"><a href="#努力值" class="headerlink" title="努力值"></a>努力值</h2><h3 id="查看努力值指令"><a href="#查看努力值指令" class="headerlink" title="查看努力值指令"></a>查看努力值指令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/evs <1-6队伍位置></span><br></pre></td></tr></table></figure><h3 id="携带物"><a href="#携带物" class="headerlink" title="携带物"></a>携带物</h3><table><thead><tr><th>携带物</th><th>效果</th></tr></thead><tbody><tr><td><img src="https://i.mcmod.cn/item/icon/32x32/38/385824.png?v=3" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385824.html">力量护腕</a></td><td>携带后的宝可梦在战斗中击败宝可梦时会获得额外的 8 点攻击基本点数(努力值),<br>但宝可梦的速度会降低一半。</td></tr><tr><td><img src="https://i.mcmod.cn/item/icon/32x32/38/386039.png?v=4" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386039.html">力量腰带</a></td><td>携带后的宝可梦在战斗中击败宝可梦时会获得额外的 8 点基本防御点数(努力值),<br>但宝可梦的速度会降低一半。</td></tr><tr><td><img src="https://i.mcmod.cn/item/icon/32x32/38/386259.png?v=3" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386259.html">力量镜</a></td><td>携带后的宝可梦在战斗中击败宝可梦时会获得额外的 8 点特攻基本点数(努力值),<br>但宝可梦的速度会降低一半。</td></tr><tr><td><img src="https://i.mcmod.cn/item/icon/32x32/38/386305.png?v=3" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386305.html">力量负重</a></td><td>携带后的宝可梦在战斗中击败宝可梦时会获得额外的 8 点 HP 基本点数(努力值),<br>但宝可梦的速度会降低一半。</td></tr><tr><td><img src="https://i.mcmod.cn/item/icon/32x32/38/386397.png?v=3" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386397.html">力量束带</a></td><td>携带后的宝可梦在战斗中击败宝可梦时会获得额外的 8 点特防基本点数(努力值),<br>但宝可梦的速度会降低一半。</td></tr><tr><td><img src="https://i.mcmod.cn/item/icon/32x32/38/386816.png?v=3" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386816.html">力量护踝</a></td><td>携带后的宝可梦在战斗中击败宝可梦时会获得额外的 8 点速度基本点数(努力值),<br>但在战斗中宝可梦的速度会降低一半。</td></tr><tr><td><img src="https://i.mcmod.cn/item/icon/32x32/38/386686.png?v=3" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386686.html">强制锻炼器</a></td><td>携带后的宝可梦在打败宝可梦时获得的基础点数(努力值)会翻倍,但在战斗中速度会减半。<br>携带强制锻炼器的宝可梦在获得 <a href="https://www.mcmod.cn/item/386011.html">学习装置(六代后)</a> 和宝可病毒提供的额外的基础点数也会翻倍。</td></tr></tbody></table><h3 id="对战基础值"><a href="#对战基础值" class="headerlink" title="对战基础值"></a>对战基础值</h3><blockquote><p><a href="https://wiki.52poke.com/wiki/%E8%8E%B7%E5%BE%97%E5%9F%BA%E7%A1%80%E7%82%B9%E6%95%B0%E4%B8%80%E8%A7%88%E8%A1%A8%EF%BC%88%E7%AC%AC%E5%85%AB%E4%B8%96%E4%BB%A3%EF%BC%89">获得基础点数一览表</a></p></blockquote><h3 id="树果道具"><a href="#树果道具" class="headerlink" title="树果道具"></a>树果道具</h3><p>使用<a href="https://www.mcmod.cn/item/443324.html">对战外技能</a><strong>【头锤】</strong>有概率获得羽毛,或击败对应的宝可梦掉落</p><table><thead><tr><th>属性</th><th>洗点树果</th><th>增强剂</th><th>增强剂所需树果</th><th>增强剂所需羽毛</th><th>掉落羽毛宝可梦/概率/数量</th></tr></thead><tbody><tr><td>攻击</td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408201114031.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385817.html">藻根果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515185.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386018.html">攻击增强剂</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515111.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385713.html">枝荔果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515266.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386549.html">肌力之羽</a></td><td>葱游兵 / 30% / 1<br>大王燕 / 30% / 1<br/>勇士雄鹰 / 10% / 1<br/>大葱鸭 /10% / 1</td></tr><tr><td>特攻</td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515371.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386172.html">哈密果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515375.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385166.html">特攻增强剂</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515404.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386852.html">龙火果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515423.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385524.html">智力之羽</a></td><td>大嘴鸥 / 30% / 1<br/>猫头夜鹰 / 10% / 1<br/>聒噪鸟 / 10% / 1</td></tr><tr><td>防御</td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515509.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386516.html">比巴果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515534.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385859.html">防御增强剂</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515687.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385591.html">龙睛果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515613.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385316.html">抵抗之羽</a></td><td>钢铠鸦 / 30% / 1 ~ 2<br/>大比鸟 / 30% / 1 ~ 2<br/>高傲雉鸡 / 10% / 1<br/>秃鹰娜 / 10% / 1</td></tr><tr><td>特防</td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515727.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386143.html">萄葡果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515709.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386166.html">特防增强剂</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515736.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386231.html">杏仔果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515808.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385912.html">精神之羽</a></td><td>大嘴雀 / 10% / 1<br/>天然鸟 / 10% / 1<br/>古月鸟 / 10% / 1</td></tr><tr><td>速度</td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515884.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386041.html">茄番果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515928.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385588.html">速度增强剂</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515035.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386557.html">沙鳞果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515996.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386573.html">瞬发之羽</a></td><td>摔角鹰人 / 30% / 1<br/>烈箭鹰 / 10% / 1<br/>舞天鹅 / 10% / 1<br/>嘟嘟利 / 10% / 1</td></tr><tr><td>生命</td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515981.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386326.html">榴石果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515039.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385620.html">HP增强剂</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515125.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385258.html">文柚果</a></td><td><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515225.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385440.html">体力之羽</a></td><td>姆克鹰 / 50% / 1 ~ 2<br/>乌鸦头头 / 30% / 1<br/>铳嘴大鸟 / 10% / 1</td></tr></tbody></table><h2 id="个体值"><a href="#个体值" class="headerlink" title="个体值"></a>个体值</h2><h3 id="查看个体值指令"><a href="#查看个体值指令" class="headerlink" title="查看个体值指令"></a>查看个体值指令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/ivs <1-6队伍位置></span><br></pre></td></tr></table></figure><p>刷极<strong>巨巢穴BOSS</strong>获取,王冠只能对<strong>满级</strong>宝可梦使用,<strong>神兽至少有三项努力值是满的,所以不建议使用金色王冠。</strong></p><h3 id="银色王冠"><a href="#银色王冠" class="headerlink" title="银色王冠"></a><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515217.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/385508.html">银色王冠</a></h3><p>银色王冠只能对满级宝可梦使用,使用后可以选择HP、攻击、防御、特攻、特防、速度之中任意一项个体值并不为满(31)的数值,将其能力值变化为对应个体满的数值。</p><p><strong>获取:</strong>垂钓有几率获得;打开<a href="https://www.mcmod.cn/item/386077.html">大师球箱</a>也有概率获得;击败极巨巢穴的boss也有几率获得。</p><h3 id="金色王冠"><a href="#金色王冠" class="headerlink" title="金色王冠"></a><img src="https://sherlockersun-images.oss-cn-hangzhou.aliyuncs.com/picgo/202408191515198.png" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386559.html">金色王冠</a></h3><p>金色王冠只能对满级宝可梦使用,使用后所有能力值(HP、攻击、防御、特攻、特防、速度)都会变化为个体值为满的数值。</p><p><strong>获取:</strong>垂钓与打败极巨巢穴的boss可以获得。</p><h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><h3 id="特性胶囊"><a href="#特性胶囊" class="headerlink" title="特性胶囊"></a><img src="https://i.mcmod.cn/item/icon/32x32/38/386066.png?v=5" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/386066.html">特性胶囊</a></h3><p>特性胶囊是一种药剂。如果用于有着两种特性的宝可梦,就能令其现有特性变为另一种。如果宝可梦只有一种普通特性,则不能使用。若需要改变为隐藏特性,见<a href="https://www.mcmod.cn/item/490541.html">特性膏药</a>。</p><p><strong>获取:</strong>在第三层级的特殊掉落中获得,击败罕见级别的 Boss 宝可梦掉落。</p><p><strong>宝可梦掉落</strong></p><table><thead><tr><th>宝可梦</th><th>概率</th><th>数量</th></tr></thead><tbody><tr><td>梦幻</td><td>5%</td><td>1</td></tr><tr><td>超梦</td><td>5%</td><td>1</td></tr><tr><td>胡帕</td><td>5%</td><td>1</td></tr></tbody></table><h3 id="特性膏药"><a href="#特性膏药" class="headerlink" title="特性膏药"></a><img src="https://i.mcmod.cn/item/icon/32x32/49/490541.png?v=3" style="display: inline; vertical-align: middle; transform: translateY(30%); max-width: 50px; height: auto;"><a href="https://www.mcmod.cn/item/490541.html">特性膏药</a></h3><p><strong>特性膏药</strong>是一种将宝可梦的特性从其普通特性之一更改为隐藏特性的回复道具。它可以将宝可梦的特性从其普通特性更改为其隐藏特性。</p><p>更改为隐藏特性后无法通过<a href="https://www.mcmod.cn/item/386066.html">特性胶囊</a>或特性膏药将已改后的隐藏特性改回普通特性。</p><p><strong>获取:</strong>它可以作为第 3 层精灵球箱获取物获得,终极宝可梦Boss有几率掉落。</p>]]></content>
<categories>
<category> 游戏攻略 </category>
</categories>
<tags>
<tag> Pixelmon </tag>
<tag> Minecraft </tag>
<tag> 宝可梦 </tag>
<tag> 游戏攻略 </tag>
</tags>
</entry>
<entry>
<title>SpringBoot整合Quartz</title>
<link href="/2024/07/25/SpringBoot%E6%95%B4%E5%90%88Quartz/"/>
<url>/2024/07/25/SpringBoot%E6%95%B4%E5%90%88Quartz/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><h1 id="SpringBoot整合Quartz定时任务"><a href="#SpringBoot整合Quartz定时任务" class="headerlink" title="SpringBoot整合Quartz定时任务"></a>SpringBoot整合Quartz定时任务</h1><blockquote><p>原文:<a href="https://blog.csdn.net/weixin_38192427/article/details/121111677">SpringBoot整合Quartz</a></p></blockquote><h2 id="1、Quartz介绍"><a href="#1、Quartz介绍" class="headerlink" title="1、Quartz介绍"></a>1、Quartz介绍</h2><h3 id="1-1-简介"><a href="#1-1-简介" class="headerlink" title="1.1.简介"></a>1.1.简介</h3><blockquote><p>官方网站:<a href="http://quartz-scheduler.org/">http://quartz-scheduler.org/</a></p></blockquote><p><code>Quartz</code> 是 <code>OpenSymphony</code> 开源组织在 <code>Job Scheduling</code> 领域又一个开源项目,是完全由 <code>Java</code> 开发的一个开源任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。<code>Quartz</code> 是一个开源的作业调度框架,它完全由 <code>Java</code> 写成,并设计用于 <code>J2SE</code> 和 <code>J2EE</code> 应用中,它提供了巨大的灵活性而不牺牲简单性。</p><p>当定时任务愈加复杂时,使用 <code>Spring</code> 注解 <code>@Schedule</code> 已经不能满足业务需要。</p><p>在项目开发中,经常需要定时任务来帮助我们来做一些内容,如定时派息、跑批对账、将任务纳入日程或者从日程中取消,开始,停止,暂停日程进度等。<code>SpringBoot</code> 中现在有两种方案可以选择,第一种是 <code>SpringBoot</code> 内置的方式简单注解就可以使用,当然如果需要更复杂的应用场景还是得 <code>Quartz</code> 上场,<code>Quartz</code> 目前是 <code>Java</code> 体系中最完善的定时方案。</p><h3 id="1-2-优点"><a href="#1-2-优点" class="headerlink" title="1.2.优点"></a>1.2.优点</h3><ul><li>丰富的 <code>Job</code> 操作 <code>API</code></li><li>支持多种配置</li><li><code>SpringBoot</code> 无缝集成</li><li>支持持久化</li><li>支持集群</li><li><code>Quartz</code> 还支持开源,是一个功能丰富的开源作业调度库,可以集成到几乎任何 <code>Java</code> 应用程序中</li></ul><h3 id="1-3-核心概念"><a href="#1-3-核心概念" class="headerlink" title="1.3. 核心概念"></a>1.3. 核心概念</h3><ul><li><p><strong>Scheduler</strong></p><p><code>Quartz</code> 中的任务调度器,通过 <code>Trigger</code> 和 <code>JobDetail</code> 可以用来调度、暂停和删除任务。调度器就相当于一个容器,装载着任务和触发器,该类是一个接口,代表一个 <code>Quartz</code> 的独立运行容器,<code>Trigger</code> 和 <code>JobDetail</code> 可以注册到 <code>Scheduler</code> 中,两者在 <code>Scheduler</code> 中拥有各自的组及名称,组及名称是 <code>Scheduler</code> 查找定位容器中某一对象的依据,<code>Trigger</code> 的组及名称必须唯一,<code>JobDetail</code> 的组和名称也必须唯一(但可以和 <code>Trigger</code> 的组和名称相同,因为它们是不同类型的);</p></li><li><p><strong>Trigger</strong></p><p><code>Quartz</code> 中的触发器,是一个类,描述触发 <code>Job</code> 执行的时间触发规则,主要有 <code>SimpleTrigger</code> 和 <code>CronTrigger</code> 这两个子类。当且仅当需调度一次或者以固定时间间隔周期执行调度,<code>SimpleTrigger</code> 是最适合的选择;而 <code>CronTrigger</code> 则可以通过 <code>Cron</code> 表达式定义出各种复杂时间规则的调度方案:如工作日周一到周五的 <code>15:00 ~ 16:00</code> 执行调度等;</p></li><li><p><strong>JobDetail</strong></p><p><code>Quartz</code> 中需要执行的任务详情,包括了任务的唯一标识和具体要执行的任务,可以通过 <code>JobDataMap</code> 往任务中传递数据;</p></li><li><p><strong>Job</strong></p><p><code>Quartz</code> 中具体的任务,包含了执行任务的具体方法。是一个接口,只定义一个方法 <code>execute()</code> 方法,在实现接口的 <code>execute()</code> 方法中编写所需要定时执行的 <code>Job</code>;</p></li></ul><p>当然可以这样快速理解:</p><ul><li><strong>job:</strong> 任务 - 你要做什么事</li><li><strong>Trigger:</strong> 触发器 - 你什么时候去做</li><li><strong>Scheduler:</strong> 任务调度 - 你什么时候需要做什么事</li></ul><p>四者其关系如下图所示</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/66b7e47ea2b882ceb9430e04c5f6da93.png" alt="四者关系"></p><ul><li><strong>Job</strong> 为作业的接口,为任务调度的对象;</li><li><strong>JobDetail</strong> 用来描述 <code>Job</code> 的实现类及其他相关的静态信息;</li><li><strong>Trigger</strong> 做为作业的定时管理工具,一个 <code>Trigger</code> 只能对应一个作业实例,而一个作业实例可对应多个触发器;</li><li><strong>Scheduler</strong> 做为定时任务容器,是 <code>Quartz</code> 最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个 <code>Scheduler</code> 都存有 <code>JobDetail</code> 和 <code>Trigger</code> 的注册,一个 <code>Scheduler</code> 中可以注册多个 <code>JobDetail</code> 和多个 <code>Trigger</code>;</li></ul><h3 id="1-4-Quartz的作业存储类型"><a href="#1-4-Quartz的作业存储类型" class="headerlink" title="1.4.Quartz的作业存储类型"></a>1.4.Quartz的作业存储类型</h3><ul><li><p><strong>RAMJobStore</strong></p><p><code>RAM</code> 也就是内存,<strong>默认情况下 Quartz 会将任务调度存储在内存中</strong>,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失</p></li><li><p><strong>JDBC 作业存储</strong></p><p>存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理,随时停止、暂停、修改任务。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢</p></li></ul><h3 id="1-5-Cron表达式"><a href="#1-5-Cron表达式" class="headerlink" title="1.5.Cron表达式"></a>1.5.Cron表达式</h3><p><code>Cron</code> 表达式是一个字符串,包括<code>6~7</code>个时间元素,在 Quartz中可以<strong>用于指定任务的执行时间</strong></p><h4 id="1-5-1-Cron语法"><a href="#1-5-1-Cron语法" class="headerlink" title="1.5.1.Cron语法"></a>1.5.1.Cron语法</h4><table><thead><tr><th>Seconds</th><th>Minutes</th><th>Hours</th><th>DayofMonth</th><th>Month</th><th>DayofWeek</th></tr></thead><tbody><tr><td>秒</td><td>分钟</td><td>小时</td><td>日期天/日</td><td>日期月份</td><td>星期</td></tr></tbody></table><h4 id="1-5-2-Cron元素说明"><a href="#1-5-2-Cron元素说明" class="headerlink" title="1.5.2.Cron元素说明"></a>1.5.2.Cron元素说明</h4><table><thead><tr><th>时间元素</th><th>可出现的字符</th><th>有效数值范围</th></tr></thead><tbody><tr><td>Seconds</td><td>, - * /</td><td>0-59</td></tr><tr><td>Minutes</td><td>, - * /</td><td>0-59</td></tr><tr><td>Hours</td><td>, - * /</td><td>0-23</td></tr><tr><td>DayofMonth</td><td>, - * / ? L W</td><td>0-31</td></tr><tr><td>Month</td><td>, - * /</td><td>1-12</td></tr><tr><td>DayofWeek</td><td>, - * / ? L #</td><td>1-7或SUN-SAT</td></tr></tbody></table><h4 id="1-5-3-Cron字符说明"><a href="#1-5-3-Cron字符说明" class="headerlink" title="1.5.3. Cron字符说明"></a>1.5.3. Cron字符说明</h4><table><thead><tr><th>字符</th><th>作用</th><th>举例</th></tr></thead><tbody><tr><td>,</td><td>列出枚举值</td><td>在Minutes域使用5,10,表示在5分和10分各触发一次</td></tr><tr><td>-</td><td>表示触发范围</td><td>在Minutes域使用5-10,表示从5分到10分钟每分钟触发一次</td></tr><tr><td>*</td><td>匹配任意值</td><td>在Minutes域使用*, 表示每分钟都会触发一次</td></tr><tr><td>/</td><td>起始时间开始触发,每隔固定时间触发一次</td><td>在Minutes域使用5/10,表示5分时触发一次,每10分钟再触发一次</td></tr><tr><td>?</td><td>在DayofMonth和DayofWeek中,用于匹配任意值</td><td>在DayofMonth域使用?,表示每天都触发一次</td></tr><tr><td>#</td><td>在DayofMonth中,确定第几个星期几</td><td>1#3表示第三个星期日</td></tr><tr><td>L</td><td>表示最后</td><td>在DayofWeek中使用5L,表示在最后一个星期四触发</td></tr><tr><td>W</td><td>表示有效工作日(周一到周五)</td><td>在DayofMonth使用5W,如果5日是星期六,则将在最近的工作日4日触发一次</td></tr></tbody></table><h4 id="1-5-4-在线-Cron-表达式生成器"><a href="#1-5-4-在线-Cron-表达式生成器" class="headerlink" title="1.5.4.在线 Cron 表达式生成器"></a>1.5.4.在线 Cron 表达式生成器</h4><blockquote><p>地址1:<a href="https://cron.qqe2.com/">https://cron.qqe2.com/</a></p><p>地址2:<a href="https://www.pppet.net/">https://www.pppet.net/</a></p></blockquote><p>其实 <code>Cron</code> 表达式无需多记,需要使用的时候直接使用在线生成器就可以了</p><h2 id="2、SpringBoot整合Quartz"><a href="#2、SpringBoot整合Quartz" class="headerlink" title="2、SpringBoot整合Quartz"></a>2、SpringBoot整合Quartz</h2><blockquote><p>源码:<a href="https://gitee.com/chaojiangcj/springboot-quartz.git">https://gitee.com/chaojiangcj/springboot-quartz.git</a></p></blockquote><ul><li><code>SpringBoot</code> 版本:<code>2.0.9.RELEASE</code></li><li><code>MySQL</code> 版本:<code>5.7.35</code></li></ul><h3 id="2-1-数据库表准备"><a href="#2-1-数据库表准备" class="headerlink" title="2.1. 数据库表准备"></a>2.1. 数据库表准备</h3><p><code>Quartz</code> 存储任务信息有两种方式,使用内存或者使用数据库来存储,这里我们采用 <code>MySQL</code> 数据库存储的方式,首先需要新建 <code>Quartz</code> 的相关表,<code>sql</code> 脚本下载地址:<a href="http://www.quartz-scheduler.org/downloads/">http://www.quartz-scheduler.org/downloads/</a>,名称为 <code>tables_mysql.sql</code>,创建成功后数据库中多出 <code>11</code> 张表</p><h3 id="2-2-添加Maven依赖"><a href="#2-2-添加Maven依赖" class="headerlink" title="2.2.添加Maven依赖"></a>2.2.添加Maven依赖</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-quartz<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="comment"><!-- 5.1.* 版本适用于MySQL Server的5.6.*、5.7.*和8.0.* --></span></span><br><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>mysql<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mysql-connector-java<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>5.1.38<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><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.alibaba<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>druid-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>1.1.10<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><span class="line"><span class="comment"><!--mybatis--></span></span><br><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.mybatis.spring.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>mybatis-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>1.3.2<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><span class="line"><span class="comment"><!--pagehelper分页--></span></span><br><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.github.pagehelper<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>pagehelper-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>1.3.0<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><p>这里使用 <code>druid</code> 作为数据库连接池,<code>Quartz</code> 默认使用 <code>c3p0</code></p><h3 id="2-3-配置文件"><a href="#2-3-配置文件" class="headerlink" title="2.3. 配置文件"></a>2.3. 配置文件</h3><h4 id="2-3-1-quartz-properties"><a href="#2-3-1-quartz-properties" class="headerlink" title="2.3.1. quartz.properties"></a>2.3.1. quartz.properties</h4><p>默认情况下,<code>Quartz</code> 会加载 <code>classpath</code> 下的 <code>quartz.properties</code> 作为配置文件。如果找不到,则会使用 <code>quartz</code> 框架自己 <code>jar</code> 包下 <code>org/quartz</code> 底下的 <code>quartz.properties</code> 文件</p><figure class="highlight yml"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">#主要分为scheduler、threadPool、jobStore、dataSource等部分</span></span><br><span class="line"></span><br><span class="line"><span class="string">org.quartz.scheduler.instanceId=AUTO</span></span><br><span class="line"><span class="string">org.quartz.scheduler.instanceName=DefaultQuartzScheduler</span></span><br><span class="line"><span class="comment">#如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true</span></span><br><span class="line"><span class="comment">#在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略</span></span><br><span class="line"><span class="string">org.quartz.scheduler.rmi.export=false</span></span><br><span class="line"><span class="comment">#如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099</span></span><br><span class="line"><span class="string">org.quartz.scheduler.rmi.proxy=false</span></span><br><span class="line"><span class="string">org.quartz.scheduler.wrapJobExecutionInUserTransaction=false</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#实例化ThreadPool时,使用的线程类为SimpleThreadPool</span></span><br><span class="line"><span class="string">org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool</span></span><br><span class="line"><span class="comment">#threadCount和threadPriority将以setter的形式注入ThreadPool实例</span></span><br><span class="line"><span class="comment">#并发个数 如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.</span></span><br><span class="line"><span class="comment">#只有1到100之间的数字是非常实用的</span></span><br><span class="line"><span class="string">org.quartz.threadPool.threadCount=5</span></span><br><span class="line"><span class="comment">#优先级 默认值为5</span></span><br><span class="line"><span class="string">org.quartz.threadPool.threadPriority=5</span></span><br><span class="line"><span class="comment">#可以是“true”或“false”,默认为false</span></span><br><span class="line"><span class="string">org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)</span></span><br><span class="line"><span class="string">org.quartz.jobStore.misfireThreshold=5000</span></span><br><span class="line"><span class="comment"># 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失</span></span><br><span class="line"><span class="comment">#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#持久化方式,默认存储在内存中,此处使用数据库方式</span></span><br><span class="line"><span class="string">org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX</span></span><br><span class="line"><span class="comment">#您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作</span></span><br><span class="line"><span class="comment"># StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序</span></span><br><span class="line"><span class="string">org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate</span></span><br><span class="line"><span class="comment">#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,</span></span><br><span class="line"><span class="comment">#因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题</span></span><br><span class="line"><span class="string">org.quartz.jobStore.useProperties=true</span></span><br><span class="line"><span class="comment">#表前缀</span></span><br><span class="line"><span class="string">org.quartz.jobStore.tablePrefix=QRTZ_</span></span><br><span class="line"><span class="comment">#数据源别名,自定义</span></span><br><span class="line"><span class="string">org.quartz.jobStore.dataSource=qzDS</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#使用阿里的druid作为数据库连接池</span></span><br><span class="line"><span class="string">org.quartz.dataSource.qzDS.connectionProvider.class=org.example.config.DruidPoolingconnectionProvider</span></span><br><span class="line"><span class="string">org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC</span></span><br><span class="line"><span class="string">org.quartz.dataSource.qzDS.user=root</span></span><br><span class="line"><span class="string">org.quartz.dataSource.qzDS.password=123456</span></span><br><span class="line"><span class="string">org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver</span></span><br><span class="line"><span class="string">org.quartz.dataSource.qzDS.maxConnections=10</span></span><br><span class="line"><span class="comment">#设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏</span></span><br><span class="line"><span class="comment">#org.quartz.jobStore.isClustered=false</span></span><br></pre></td></tr></table></figure><p>关于配置详细解释:<a href="https://blog.csdn.net/zixiao217/article/details/53091812">https://blog.csdn.net/zixiao217/article/details/53091812</a></p><p>也可以查看官网:<a href="http://www.quartz-scheduler.org/documentation/2.3.1-SNAPSHOT/">http://www.quartz-scheduler.org/documentation/2.3.1-SNAPSHOT/</a></p><h4 id="2-3-2-application-yml"><a href="#2-3-2-application-yml" class="headerlink" title="2.3.2. application.yml"></a>2.3.2. application.yml</h4><figure class="highlight yml"><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></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line"></span><br><span class="line"><span class="attr">mybatis:</span></span><br><span class="line"> <span class="comment">#指定 mapper 文件路径</span></span><br><span class="line"> <span class="attr">mapper-locations:</span> <span class="string">classpath:org/example/mapper/*.xml</span></span><br><span class="line"> <span class="attr">configuration:</span></span><br><span class="line"> <span class="attr">cache-enabled:</span> <span class="literal">true</span></span><br><span class="line"> <span class="comment">#开启驼峰命名</span></span><br><span class="line"> <span class="attr">map-underscore-to-camel-case:</span> <span class="literal">true</span></span><br><span class="line"> <span class="comment">#打印 SQL 语句</span></span><br><span class="line"> <span class="attr">log-impl:</span> <span class="string">org.apache.ibatis.logging.stdout.StdOutImpl</span></span><br><span class="line"></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">datasource:</span></span><br><span class="line"> <span class="attr">type:</span> <span class="string">com.alibaba.druid.pool.DruidDataSource</span></span><br><span class="line"> <span class="attr">druid:</span></span><br><span class="line"> <span class="comment">#JDBC 配置:MySQL Server 版本为 5.7.35</span></span><br><span class="line"> <span class="attr">driver-class-name:</span> <span class="string">com.mysql.jdbc.Driver</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC</span></span><br><span class="line"> <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line"> <span class="attr">password:</span> <span class="number">123456</span></span><br><span class="line"> <span class="comment">#druid 连接池配置</span></span><br><span class="line"> <span class="attr">initial-size:</span> <span class="number">3</span></span><br><span class="line"> <span class="attr">max-active:</span> <span class="number">10</span></span><br><span class="line"> <span class="attr">max-wait:</span> <span class="number">60000</span></span><br><span class="line"> <span class="attr">min-idle:</span> <span class="number">3</span></span><br></pre></td></tr></table></figure><h3 id="2-4-配置类-QuartzConfig"><a href="#2-4-配置类-QuartzConfig" class="headerlink" title="2.4. 配置类 QuartzConfig"></a>2.4. 配置类 QuartzConfig</h3><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">QuartzConfig</span> <span class="keyword">implements</span> <span class="title class_">SchedulerFactoryBeanCustomizer</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="keyword">public</span> Properties <span class="title function_">properties</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="type">PropertiesFactoryBean</span> <span class="variable">propertiesFactoryBean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PropertiesFactoryBean</span>();</span><br><span class="line"> <span class="comment">// 对quartz.properties文件进行读取</span></span><br><span class="line"> propertiesFactoryBean.setLocation(<span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">"/quartz.properties"</span>));</span><br><span class="line"> <span class="comment">// 在quartz.properties中的属性被读取并注入后再初始化对象</span></span><br><span class="line"> propertiesFactoryBean.afterPropertiesSet();</span><br><span class="line"> <span class="keyword">return</span> propertiesFactoryBean.getObject();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="keyword">public</span> SchedulerFactoryBean <span class="title function_">schedulerFactoryBean</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="type">SchedulerFactoryBean</span> <span class="variable">schedulerFactoryBean</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SchedulerFactoryBean</span>();</span><br><span class="line"> schedulerFactoryBean.setQuartzProperties(properties());</span><br><span class="line"> <span class="keyword">return</span> schedulerFactoryBean;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * quartz初始化监听器</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="keyword">public</span> QuartzInitializerListener <span class="title function_">executorListener</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">QuartzInitializerListener</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 通过SchedulerFactoryBean获取Scheduler的实例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="keyword">public</span> Scheduler <span class="title function_">scheduler</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="keyword">return</span> schedulerFactoryBean().getScheduler();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 使用阿里的druid作为数据库连接池</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">customize</span><span class="params">(<span class="meta">@NotNull</span> SchedulerFactoryBean schedulerFactoryBean)</span> {</span><br><span class="line"> schedulerFactoryBean.setStartupDelay(<span class="number">2</span>);</span><br><span class="line"> schedulerFactoryBean.setAutoStartup(<span class="literal">true</span>);</span><br><span class="line"> schedulerFactoryBean.setOverwriteExistingJobs(<span class="literal">true</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="2-5-创建任务类-HelloJob"><a href="#2-5-创建任务类-HelloJob" class="headerlink" title="2.5. 创建任务类 HelloJob"></a>2.5. 创建任务类 HelloJob</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloJob</span> <span class="keyword">implements</span> <span class="title class_">Job</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(JobExecutionContext jobExecutionContext)</span> {</span><br><span class="line"> <span class="type">QuartzService</span> <span class="variable">quartzService</span> <span class="operator">=</span> (QuartzService) SpringUtil.getBean(<span class="string">"quartzServiceImpl"</span>);</span><br><span class="line"> PageInfo<JobAndTriggerDto> jobAndTriggerDetails = quartzService.getJobAndTriggerDetails(<span class="number">1</span>, <span class="number">10</span>);</span><br><span class="line"> log.info(<span class="string">"任务列表总数为:"</span> + jobAndTriggerDetails.getTotal());</span><br><span class="line"> log.info(<span class="string">"Hello Job执行时间: "</span> + DateUtil.now());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="2-6-业务-Service-层"><a href="#2-6-业务-Service-层" class="headerlink" title="2.6. 业务 Service 层"></a>2.6. 业务 Service 层</h3><p>具体的 <code>QuartzService</code> 接口这里不在赘述,可以查看后面的源码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">QuartzServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">QuartzService</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> JobDetailMapper jobDetailMapper;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> Scheduler scheduler;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> PageInfo<JobAndTriggerDto> <span class="title function_">getJobAndTriggerDetails</span><span class="params">(Integer pageNum, Integer pageSize)</span> {</span><br><span class="line"> PageHelper.startPage(pageNum, pageSize);</span><br><span class="line"> List<JobAndTriggerDto> list = jobDetailMapper.getJobAndTriggerDetails();</span><br><span class="line"> PageInfo<JobAndTriggerDto> pageInfo = <span class="keyword">new</span> <span class="title class_">PageInfo</span><>(list);</span><br><span class="line"> <span class="keyword">return</span> pageInfo;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 新增定时任务</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jName 任务名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jGroup 任务组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> tName 触发器名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> tGroup 触发器组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> cron cron表达式</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addjob</span><span class="params">(String jName, String jGroup, String tName, String tGroup, String cron)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 构建JobDetail</span></span><br><span class="line"> <span class="type">JobDetail</span> <span class="variable">jobDetail</span> <span class="operator">=</span> JobBuilder.newJob(HelloJob.class)</span><br><span class="line"> .withIdentity(jName, jGroup)</span><br><span class="line"> .build();</span><br><span class="line"> <span class="comment">// 按新的cronExpression表达式构建一个新的trigger</span></span><br><span class="line"> <span class="type">CronTrigger</span> <span class="variable">trigger</span> <span class="operator">=</span> TriggerBuilder.newTrigger()</span><br><span class="line"> .withIdentity(tName, tGroup)</span><br><span class="line"> .startNow()</span><br><span class="line"> .withSchedule(CronScheduleBuilder.cronSchedule(cron))</span><br><span class="line"> .build();</span><br><span class="line"> <span class="comment">// 启动调度器</span></span><br><span class="line"> scheduler.start();</span><br><span class="line"> scheduler.scheduleJob(jobDetail, trigger);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> log.info(<span class="string">"创建定时任务失败"</span> + e);</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">pausejob</span><span class="params">(String jName, String jGroup)</span> <span class="keyword">throws</span> SchedulerException {</span><br><span class="line"> scheduler.pauseJob(JobKey.jobKey(jName, jGroup));</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">resumejob</span><span class="params">(String jName, String jGroup)</span> <span class="keyword">throws</span> SchedulerException {</span><br><span class="line"> scheduler.resumeJob(JobKey.jobKey(jName, jGroup));</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">rescheduleJob</span><span class="params">(String jName, String jGroup, String cron)</span> <span class="keyword">throws</span> SchedulerException {</span><br><span class="line"> <span class="type">TriggerKey</span> <span class="variable">triggerKey</span> <span class="operator">=</span> TriggerKey.triggerKey(jName, jGroup);</span><br><span class="line"> <span class="comment">// 表达式调度构建器</span></span><br><span class="line"> <span class="type">CronScheduleBuilder</span> <span class="variable">scheduleBuilder</span> <span class="operator">=</span> CronScheduleBuilder.cronSchedule(cron);</span><br><span class="line"> <span class="type">CronTrigger</span> <span class="variable">trigger</span> <span class="operator">=</span> (CronTrigger) scheduler.getTrigger(triggerKey);</span><br><span class="line"> <span class="comment">// 按新的cronExpression表达式重新构建trigger</span></span><br><span class="line"> trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();</span><br><span class="line"> <span class="comment">// 按新的trigger重新设置job执行,重启触发器</span></span><br><span class="line"> scheduler.rescheduleJob(triggerKey, trigger);</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deletejob</span><span class="params">(String jName, String jGroup)</span> <span class="keyword">throws</span> SchedulerException {</span><br><span class="line"> scheduler.pauseTrigger(TriggerKey.triggerKey(jName, jGroup));</span><br><span class="line"> scheduler.unscheduleJob(TriggerKey.triggerKey(jName, jGroup));</span><br><span class="line"> scheduler.deleteJob(JobKey.jobKey(jName, jGroup));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="2-7-Controller-层"><a href="#2-7-Controller-层" class="headerlink" title="2.7. Controller 层"></a>2.7. Controller 层</h3><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><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping(path = "/quartz")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">QuartzController</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> QuartzService quartzService;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 新增定时任务</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jName 任务名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jGroup 任务组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> tName 触发器名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> tGroup 触发器组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> cron cron表达式</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> ResultMap</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@PostMapping(path = "/addjob")</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="keyword">public</span> ResultMap <span class="title function_">addjob</span><span class="params">(String jName, String jGroup, String tName, String tGroup, String cron)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> quartzService.addjob(jName, jGroup, tName, tGroup, cron);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().success().message(<span class="string">"添加任务成功"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().error().message(<span class="string">"添加任务失败"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 暂停任务</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jName 任务名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jGroup 任务组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> ResultMap</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@PostMapping(path = "/pausejob")</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="keyword">public</span> ResultMap <span class="title function_">pausejob</span><span class="params">(String jName, String jGroup)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> quartzService.pausejob(jName, jGroup);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().success().message(<span class="string">"暂停任务成功"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (SchedulerException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().error().message(<span class="string">"暂停任务失败"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 恢复任务</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jName 任务名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jGroup 任务组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> ResultMap</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@PostMapping(path = "/resumejob")</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="keyword">public</span> ResultMap <span class="title function_">resumejob</span><span class="params">(String jName, String jGroup)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> quartzService.resumejob(jName, jGroup);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().success().message(<span class="string">"恢复任务成功"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (SchedulerException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().error().message(<span class="string">"恢复任务失败"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 重启任务</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jName 任务名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jGroup 任务组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> cron cron表达式</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> ResultMap</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@PostMapping(path = "/reschedulejob")</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="keyword">public</span> ResultMap <span class="title function_">rescheduleJob</span><span class="params">(String jName, String jGroup, String cron)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> quartzService.rescheduleJob(jName, jGroup, cron);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().success().message(<span class="string">"重启任务成功"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (SchedulerException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().error().message(<span class="string">"重启任务失败"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 删除任务</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jName 任务名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jGroup 任务组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> ResultMap</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@PostMapping(path = "/deletejob")</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="keyword">public</span> ResultMap <span class="title function_">deletejob</span><span class="params">(String jName, String jGroup)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> quartzService.deletejob(jName, jGroup);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().success().message(<span class="string">"删除任务成功"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (SchedulerException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().error().message(<span class="string">"删除任务失败"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查询任务</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> pageNum 页码</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> pageSize 每页显示多少条数据</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> Map</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@GetMapping(path = "/queryjob")</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="keyword">public</span> ResultMap <span class="title function_">queryjob</span><span class="params">(Integer pageNum, Integer pageSize)</span> {</span><br><span class="line"> PageInfo<JobAndTriggerDto> pageInfo = quartzService.getJobAndTriggerDetails(pageNum, pageSize);</span><br><span class="line"> Map<String, Object> map = <span class="keyword">new</span> <span class="title class_">HashMap</span><>();</span><br><span class="line"> <span class="keyword">if</span> (!StringUtils.isEmpty(pageInfo.getTotal())) {</span><br><span class="line"> map.put(<span class="string">"JobAndTrigger"</span>, pageInfo);</span><br><span class="line"> map.put(<span class="string">"number"</span>, pageInfo.getTotal());</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().success().data(map).message(<span class="string">"查询任务成功"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResultMap</span>().fail().message(<span class="string">"查询任务成功失败,没有数据"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="2-8-接口测试"><a href="#2-8-接口测试" class="headerlink" title="2.8. 接口测试"></a>2.8. 接口测试</h3><h4 id="2-8-1-新增定时任务"><a href="#2-8-1-新增定时任务" class="headerlink" title="2.8.1. 新增定时任务"></a>2.8.1. 新增定时任务</h4><p><code>postman</code> 测试如下</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/f9926dda9aa480ff9a693d243660530c.png" alt="postman测试结果"></p><p>数据库数据展示如下</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/ecdd1abb0bce8a6ca6e0fcddc32c8969.png" alt="数据库数据展示1"></p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/eff54990064322113c08958bec32b854.png" alt="数据库数据展示2"></p><p>同样,我们的任务类 <code>HelloJob</code> 也开始执行了,控制台日志如下</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/32c276774f265777c16783a150d7956d.png" alt="控制台日志"></p><h4 id="2-8-2-停止项目,再启动运行"><a href="#2-8-2-停止项目,再启动运行" class="headerlink" title="2.8.2. 停止项目,再启动运行"></a>2.8.2. 停止项目,再启动运行</h4><p>可以看到项目中 <code>HelloJob</code> 的任务依然在运行,这就是 <code>quartz</code> 数据库持久化的好处</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/858b8416073ec88912048e473648b1dc.png" alt="控制台日志"></p>]]></content>
<categories>
<category> 技术分享 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 技术分享 </tag>
<tag> SpringBoot </tag>
</tags>
</entry>
<entry>
<title>SpringBoot整合EMQX(MQTT协议)</title>
<link href="/2024/07/25/SpringBoot%E6%95%B4%E5%90%88EMQX%EF%BC%88MQTT%E5%8D%8F%E8%AE%AE%EF%BC%89/"/>
<url>/2024/07/25/SpringBoot%E6%95%B4%E5%90%88EMQX%EF%BC%88MQTT%E5%8D%8F%E8%AE%AE%EF%BC%89/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><h1 id="SpringBoot整合EMQX(MQTT协议)"><a href="#SpringBoot整合EMQX(MQTT协议)" class="headerlink" title="SpringBoot整合EMQX(MQTT协议)"></a>SpringBoot整合EMQX(MQTT协议)</h1><blockquote><p>原文:<a href="https://blog.csdn.net/weixin_43888891/article/details/121880848">springboot当中使用EMQX(MQTT协议)</a></p></blockquote><h2 id="1、MQTT协议"><a href="#1、MQTT协议" class="headerlink" title="1、MQTT协议"></a>1、MQTT协议</h2><h3 id="1-1、MQTT简介"><a href="#1-1、MQTT简介" class="headerlink" title="1.1、MQTT简介"></a>1.1、MQTT简介</h3><p>MQTT 全称为 Message Queuing Telemetry Transport(消息队列遥测传输),是一种基于 发布/订阅 模式的 轻量级物联网消息传输协议。IBM 公司的<code>安迪·斯坦福-克拉克</code>及 Arcom 公司的<code>阿兰·尼普</code>于 <code>1999</code> 年撰写了该协议的第一个版本1,之后 MQTT 便以简单易实现、支持 QoS、轻量且省带宽等众多特性逐渐成为了 IoT 通讯的标准。</p><p>MQTT 协议每个消息最少仅需 2 个字节 (其中报头仅需 1 个字节,其余字节可以全部作为消息载荷)就可以完成通信,专为那些资源和空间有限、功耗敏感的硬件所打造。</p><h3 id="1-2、MQTT-协议基本特点"><a href="#1-2、MQTT-协议基本特点" class="headerlink" title="1.2、MQTT 协议基本特点"></a>1.2、MQTT 协议基本特点</h3><ol><li>使用发布/订阅消息模式,提供了一对多的消息分发和应用程序的解耦。</li><li>不关心负载内容的消息传输。</li><li>提供 3 种消息服务质量等级,满足不同投递需求。</li><li>很小的传输消耗和协议数据交换,最大限度减少网络流量。</li><li>提供连接异常断开时通知相关各方的机制。</li></ol><h3 id="1-3、MQTT-应用行业"><a href="#1-3、MQTT-应用行业" class="headerlink" title="1.3、MQTT 应用行业"></a>1.3、MQTT 应用行业</h3><p>MQTT 作为一种低开销,低带宽占用的即时通讯协议,可以用极少的代码和带宽为联网设备提供实时可靠的消息服务,它适用于硬件资源有限的设备及带宽有限的网络环境。因此,MQTT 协议广泛应用于物联网、移动互联网、智能硬件、车联网、电力能源等行业。</p><h3 id="1-4、MQTT-协议原理"><a href="#1-4、MQTT-协议原理" class="headerlink" title="1.4、MQTT 协议原理"></a>1.4、MQTT 协议原理</h3><p>基于发布/订阅模式的 MQTT 协议中有三种角色:<code>发布者(Publisher)</code>、<code>代理(Broker)</code>、<code>订阅者(Subscriber)</code>。发布者向代理发布消息,代理向订阅者转发这些消息。通常情况下,客户端的角色是发布者和订阅者,服务器的角色是代理,但实际上,服务器也可能主动发布消息或者订阅主题,客串一下客户端的角色。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/7627ec7a599f58f117924bfd291caeb9.png" alt="MQTT 协议原理"></p><p>为了方便理解,MQTT 传输的消息可以简化为:<code>主题(Topic)</code>和<code>载荷(Payload)</code>两部分:</p><ul><li><strong>Topic</strong>,消息主题,订阅者向代理订阅主题后,一旦代理收到相应主题的消息,就会向订阅者转发该消息。</li><li><strong>Payload</strong>,消息载荷(也可以理解为传输的数据),订阅者在消息中真正关心的部分,通常是业务相关的。</li></ul><h3 id="1-5、MQTT-协议基础概念"><a href="#1-5、MQTT-协议基础概念" class="headerlink" title="1.5、MQTT 协议基础概念"></a>1.5、MQTT 协议基础概念</h3><h4 id="1-5-1、会话(Session)"><a href="#1-5-1、会话(Session)" class="headerlink" title="1.5.1、会话(Session)"></a>1.5.1、会话(Session)</h4><p>每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话可以存在于一个网络连接之间,也可以跨越多个连续的网络连接存在。</p><h4 id="1-5-2、订阅(Subscription)"><a href="#1-5-2、订阅(Subscription)" class="headerlink" title="1.5.2、订阅(Subscription)"></a>1.5.2、订阅(Subscription)</h4><p>订阅包含一个主题过滤器(Topic Filter)和一个最大的服务质量(QoS)等级。订阅与单个会话(Session)关联。会话可以包含多于一个的订阅。</p><h4 id="1-5-3、主题名(Topic-Name)"><a href="#1-5-3、主题名(Topic-Name)" class="headerlink" title="1.5.3、主题名(Topic Name)"></a>1.5.3、主题名(Topic Name)</h4><p>附加在应用消息上的一个标签,被用于匹配服务端已存在的订阅。服务端会向所有匹配订阅的客户端发送此应用消息。</p><h4 id="1-5-4、主题过滤器(Topic-Filter)"><a href="#1-5-4、主题过滤器(Topic-Filter)" class="headerlink" title="1.5.4、主题过滤器(Topic Filter)"></a>1.5.4、主题过滤器(Topic Filter)</h4><p>仅在订阅时使用的主题表达式,可以包含通配符,以匹配多个主题名。就是可以通过通配符达到,发一条消息,多个主题能接受到消息的效果。</p><h4 id="1-5-5、载荷(Payload)"><a href="#1-5-5、载荷(Payload)" class="headerlink" title="1.5.5、载荷(Payload)"></a>1.5.5、载荷(Payload)</h4><p>对于 PUBLISH 报文来说载荷就是业务消息(就是指发送的消息内容),它可以是任意格式(二进制、十六进制、普通字符串、JSON 字符串、Base64)的数据。</p><h3 id="1-6、MQTT-协议进阶"><a href="#1-6、MQTT-协议进阶" class="headerlink" title="1.6、MQTT 协议进阶"></a>1.6、MQTT 协议进阶</h3><h4 id="1-6-1、消息服务质量(QoS)"><a href="#1-6-1、消息服务质量(QoS)" class="headerlink" title="1.6.1、消息服务质量(QoS)"></a>1.6.1、消息服务质量(QoS)</h4><p>MQTT 协议提供了 3 种消息服务质量等级(Quality of Service),它保证了在不同的网络环境下消息传递的可靠性。这里有一点要明白,<strong>必须先订阅,发布消息才会收到</strong>。假如没订阅,他发送消息了,我再订阅,这时候不管QoS设置几,都是收不到消息的。</p><ol><li><h5 id="QoS-0(最多分发一次)"><a href="#QoS-0(最多分发一次)" class="headerlink" title="QoS 0(最多分发一次)"></a>QoS 0(最多分发一次)</h5><p>当 QoS 为 0 时,消息的分发依赖于底层网络的能力。发布者只会发布一次消息,接收者不会应答消息,发布者也不会储存和重发消息。消息在这个等级下具有最高的传输效率,但可能送达一次也可能根本没送达。</p></li><li><p><strong>Qos 1(至少分发一次)</strong></p><p>当 QoS 为 1 时,可以保证消息至少送达一次。MQTT 通过简单的 ACK 机制来保证 QoS 1。</p><ul><li><strong>发送者:</strong> 发布消息,并等待接收者的 PUBACK 报文的应答,在规定的时间内没有收到 PUBACK 的应答,发布者会将消息的 DUP 置为1 并重发消息。</li><li><strong>接收者:</strong> 接收到 QoS 为 1 的消息时应该回应 PUBACK 报文,可能因为网络延迟等原因没有及时发出,这时接收者可能会多次接受同一个消息,无论 DUP标志如何,接收者都会将收到的消息当作一个新的消息并发送 PUBACK 报文应答。</li></ul><p>**核心:就是发送消息的时候,接受者需要确认一次,规定时间内没有确认就会重新发。如果使用这种方式,写业务的时候需要保证<code>幂等性</code>**。</p></li><li><p><strong>QoS 2(只分发一次)</strong></p><p>当 QoS 为 2 时,发布者和订阅者通过两次会话来保证消息只被传递一次,这是最高等级的服务质量,消息丢失和重复都是不可接受的。使用这个服务质量等级会有额外的开销。</p><ul><li><strong>发送者:</strong> 发布 QoS 为 2 的消息之后,消息储存起来并等待接收者回复 PUBREC 的消息。</li><li><strong>接收者:</strong> 收到一条 QoS 为 2 的消息时,他会处理此消息并返回一条 PUBREC 进行应答。</li><li><strong>发送者:</strong> 收到 PUBREC 消息后,丢弃掉之前的发布消息。保存 PUBREC 消息,并应答一个 PUBREL。等待接收者回复 PUBCOMP 消息</li><li><strong>接收者:</strong> 当接收者收到 PUBREL 消息之后,它会丢弃掉所有已保存的状态,并回复 PUBCOMP。</li><li><strong>发送者:</strong> 当发送者收到 PUBCOMP 消息之后会清空之前所保存的状态。</li></ul><p><strong>核心:发送消息的时候,接受者需要确认两次,来保证消息确实已经送到。</strong></p><p>无论在传输过程中何时出现丢包,发送端都负责重发上一条消息。不管发送端是 Publisher(发送端) 还是 Broker(服务器),都是如此。因此,接收端也需要对每一条命令消息都进行应答。</p></li></ol><h4 id="1-6-2、QoS-在发布与订阅中的区别"><a href="#1-6-2、QoS-在发布与订阅中的区别" class="headerlink" title="1.6.2、QoS 在发布与订阅中的区别"></a>1.6.2、QoS 在发布与订阅中的区别</h4><p>发布时的 QoS 表示消息发送到服务端时使用的 QoS<br>订阅时的 QoS 表示服务端向自己转发消息时可以使用的最大 QoS</p><ul><li>客户端 A 的发布 QoS 大于客户端 B 的订阅 QoS 时,服务端向客户端 B 转发消息时使用的 QoS 为客户端 B 的订阅QoS。</li><li>客户端 A 的发布 QoS 小于客户端 B 的订阅 QoS 时,服务端向客户端 B 转发消息时使用的 QoS 为客户端 A 的发布 QoS。</li></ul><p><strong>总结:接收端可以设置订阅Qos为2,这样就可以接所有qos等级消息。也就是发布消息qos为多少,那我这边接受消息就是多少。主要以发布消息的qos为准。</strong></p><h4 id="1-6-3、如何选择-MQTT-QoS-等级"><a href="#1-6-3、如何选择-MQTT-QoS-等级" class="headerlink" title="1.6.3、如何选择 MQTT QoS 等级"></a>1.6.3、如何选择 MQTT QoS 等级</h4><p><strong>QoS 级别越高,流程越复杂,系统资源消耗越大。</strong> 应用程序可以根据自己的网络场景和业务需求,选择合适的 QoS 级别。</p><ol><li><p>以下情况下可以选择 <strong>QoS 0</strong></p><ul><li><p>可以接受消息偶尔丢失。</p></li><li><p>在同一个子网内部的服务间的消息交互,或其他客户端与服务端 网络非常稳定的场景。</p></li></ul></li><li><p>以下情况下可以选择 <strong>QoS 1</strong></p><ul><li><p>对系统资源消耗较为关注,希望性能最优化。</p></li><li><p>消息不能丢失,但能接受并处理重复的消息。</p></li></ul></li><li><p>以下情况下可以选择 <strong>QoS 2</strong></p><ul><li><p>不能忍受消息丢失(消息的丢失会造成生命或财产的损失),且不希望收到重复的消息。</p></li><li><p>数据完整性与及时性要求较高的银行、消防、航空等行业。</p></li></ul></li></ol><h4 id="1-6-4、清除会话(Clean-Session)"><a href="#1-6-4、清除会话(Clean-Session)" class="headerlink" title="1.6.4、清除会话(Clean Session)"></a>1.6.4、清除会话(Clean Session)</h4><p>MQTT 客户端向服务器发起 CONNECT 请求时,可以通过 Clean Session 标志设置是否创建全新的会话。</p><ol><li>Clean Session 设置为 <strong>0</strong> 时:<ul><li>如果存在一个关联此客户标识符的会话,服务端必须基于此会话的状态恢复与客户端的通信。</li><li>如果不存在任何关联此客户标识符的会话,服务端必须创建一个新的会话。</li></ul></li><li>Clean Session 设置为 <strong>1</strong> 时:<ul><li>客户端和服务端必须丢弃任何已存在的会话,并开始一个新的会话。</li></ul></li></ol><p><strong>总结:监听端建议设置为0,一般监听端,我们都会配置单例,并且项目启动就开始创建连接监听,设置为0,这样可以保证连接的唯一性,和消息的安全性。</strong></p><h4 id="1-6-5、保活心跳(Keep-Alive)"><a href="#1-6-5、保活心跳(Keep-Alive)" class="headerlink" title="1.6.5、保活心跳(Keep Alive)"></a>1.6.5、保活心跳(Keep Alive)</h4><p>MQTT 客户端向服务器发起 CONNECT 请求时,通过 Keep Alive 参数设置保活周期。</p><p>客户端在无报文发送时,按 Keep Alive 周期定时发送 2 字节的 PINGREQ 心跳报文,服务端收到 PINGREQ 报文后,回复 2 字节的 PINGRESP 报文。</p><p>服务端在 1.5 个心跳周期内,既没有收到客户端发布订阅报文,也没有收到 PINGREQ 心跳报文时,将断开客户端连接。</p><h4 id="1-6-6、保留消息(Retained-Message)"><a href="#1-6-6、保留消息(Retained-Message)" class="headerlink" title="1.6.6、保留消息(Retained Message)"></a>1.6.6、保留消息(Retained Message)</h4><p>MQTT 客户端向服务器发布(PUBLISH)消息时,可以设置保留消息(Retained Message)标志。保留消息会驻留在消息服务器,后来的订阅者订阅主题时可以接收到最新<strong>一条(注意,是只有最近的一条)</strong>保留消息。</p><h4 id="1-6-7、遗嘱消息(Will-Message)"><a href="#1-6-7、遗嘱消息(Will-Message)" class="headerlink" title="1.6.7、遗嘱消息(Will Message)"></a>1.6.7、遗嘱消息(Will Message)</h4><p>MQTT 客户端向服务端发送 CONNECT 请求时,可以携带遗嘱消息。MQTT 客户端异常下线时(客户端断开前未向服务器发送 DISCONNECT 消息),MQTT 消息服务器会发布遗嘱消息。</p><p>在连接的时候通过调用 MqttConnectOptions 实例的 setWill 方法来设定。任何订阅了下面的主题的客户端都可以收到该遗嘱消息。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//方法1</span></span><br><span class="line">MqttConnectOptions.setWill(MqttTopic topic, <span class="type">byte</span>[] payload, <span class="type">int</span> qos, <span class="type">boolean</span> retained);</span><br><span class="line"><span class="comment">//方法2</span></span><br><span class="line">MqttConnectOptions.setWill(java.lang.String topic, <span class="type">byte</span>[] payload, <span class="type">int</span> qos, <span class="type">boolean</span> retained);</span><br></pre></td></tr></table></figure><p>以下情况下会发送 <code>Will Message</code>:</p><ul><li>服务端发生了I/O 错误或者网络失败;</li><li>客户端在定义的心跳时期失联;</li><li>客户端在发送下线包( DISCONNECT)之前关闭网络连接;</li><li>服务端在收到下线包之前关闭网络连接。</li></ul><p><strong>总结:发送遗嘱信息可以理解为,创建客户端连接的时候,告诉服务器(mqtt服务器)我挂了之后,给哪些主题发这些消息。当订阅到遗嘱消息之后,他就知道监听端挂了,我不能给他发消息了,遗嘱消息在客户端正常调用 disconnect 方法之后并不会被发送。</strong></p><p><strong>高级使用场景:</strong><br>这里介绍一下如何将 Retained(保留) 消息与Will (遗嘱)消息结合起来进行使用。</p><ol><li>客户端 A 遗嘱消息设定为”offline“,该遗嘱主题与一个普通发送状态的主题设定成同一个 A/status;</li><li>当客户端 A 连接时,向主题 A/status 发送 “online” 的 Retained 消息,其它客户端订阅主题 A/status的时候,获取 Retained 消息为 “online” ;</li><li>当客户端 A 异常断开时,系统自动向主题 A/status 发送”offline“的消息,其它订阅了此主题的客户端会马上收到”offline“消息;如果遗嘱消息被设定了 Retained 的话,这时有新的订阅A/status主题的客户端上线的时候,获取到的消息为“offline”。</li></ol><h2 id="2、EMQ-X-Cloud"><a href="#2、EMQ-X-Cloud" class="headerlink" title="2、EMQ X Cloud"></a>2、EMQ X Cloud</h2><blockquote><p>官网:<a href="https://www.emqx.com/zh/cloud">https://www.emqx.com/zh/cloud</a></p></blockquote><h3 id="2-1、EMQ-X-Cloud简介"><a href="#2-1、EMQ-X-Cloud简介" class="headerlink" title="2.1、EMQ X Cloud简介"></a>2.1、EMQ X Cloud简介</h3><p>通过开放标准的物联网协议 MQTT、MQTT over WebSocket、CoAP/LwM2M 将数以亿计的物联网设备可靠地连接到 EMQ X Cloud。通过 TLS/SSL 和基于 X.509 证书的认证确保安全的双向通信。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/4e0a6a5ae120ee86beac43be31f659d1.png" alt="EMQ X Cloud模型"></p><p>在该模型中,EMQ X Cloud 提供的 MQTT 服务不仅为设备与设备、设备与应用间架起桥梁,同时可将需要的数据进行持久化,以便非实时应用在后续对获取的数据加以利用。</p><h3 id="2-2、EMQ-X-Cloud优势"><a href="#2-2、EMQ-X-Cloud优势" class="headerlink" title="2.2、EMQ X Cloud优势"></a>2.2、EMQ X Cloud优势</h3><h4 id="2-2-1、协议支持完整"><a href="#2-2-1、协议支持完整" class="headerlink" title="2.2.1、协议支持完整"></a>2.2.1、协议支持完整</h4><p>支持 MQTT v3.1,v3.1.1 与 v5.0 协议版本,是全球首个支持 MQTT 5.0 的公有云服务,支持 MQTT WebSocket 服务,完整支持 QoS0, QoS1 与 QoS2 级别 MQTT 消息。</p><h4 id="2-2-2、多种协议接入"><a href="#2-2-2、多种协议接入" class="headerlink" title="2.2.2、多种协议接入"></a>2.2.2、多种协议接入</h4><p>支持包含 MQTT、MQTT-SN、CoAP、LwM2M、私有 TCP 协议在内的多种通信协议接入,覆盖各类行业应用;可根据您的特殊使用场景定制私有化功能,充分契合业务需求。</p><h4 id="2-2-3、容量预估与伸缩"><a href="#2-2-3、容量预估与伸缩" class="headerlink" title="2.2.3、容量预估与伸缩"></a>2.2.3、容量预估与伸缩</h4><p>通过连接数与消息吞吐量自动预估容量,通过紧密的监控来制定伸缩计划,集群大小可随业务规模平滑调整。</p><h3 id="2-3、EMQ-X-和-RabbitMQ对比"><a href="#2-3、EMQ-X-和-RabbitMQ对比" class="headerlink" title="2.3、EMQ X 和 RabbitMQ对比"></a>2.3、EMQ X 和 RabbitMQ对比</h3><p>EMQ X 是基于高并发的 Erlang/OTP 语言平台开发,支持百万级连接、分布式集群架构、发布订阅模式的开源 MQTT 消息服务器。开源至今,EMQ X 在全球物联网市场得到了广泛应用。在开源版基础上,还陆续发展了商业版和提供云版本(cloud-hosting)。EMQ X 支持很多插件,具有强大拓展能力,用户依靠插件可以实现更多的功能。</p><p>RabbitMQ 是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ 服务器也是基于 Erlang 语言开发的,现在可以通过插件配置的形式,使其支持 MQTT 协议。</p><h4 id="2-3-1、测试场景"><a href="#2-3-1、测试场景" class="headerlink" title="2.3.1、测试场景"></a>2.3.1、测试场景</h4><p>以下的测试均使用了 QoS 1 的消息。当发送 QoS 1 的消息时,这些消息每次都要作为可持久化的备份保存在硬盘上。所以队列空间的使用也尤为重要。</p><p>这次评测使用了一个云主机 M5 large 的实例,每个 MQTT 消息服务器集群由 3 个节点组成,每个节点的配置是双核,8GB 内存。需要强调的是,我们对于 EMQ X 和 RabbitMQ 的测试使用了完全一致的硬件资源以消除变量。</p><p>压力测试将会有两个场景,<strong>「多对一」</strong> 和 <strong>「一对多」</strong>。</p><h5 id="1-多对一"><a href="#1-多对一" class="headerlink" title="1.多对一"></a>1.多对一</h5><p>许多设备作为发布者,如温度传感器或者是压力传感器,发送数据给一个服务器。服务器再将这些数据发送给一个控制器(即订阅者)处理这些数据。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/63227dacea9dcc17179af7818b92c82c.png" alt="多对一"></p><h5 id="2-一对多"><a href="#2-一对多" class="headerlink" title="2.一对多"></a>2.一对多</h5><p>一个控制器作为发布者将消息传送给服务器,再由服务器将这些消息传送给多个作为订阅者的设备。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/7c48551d16a0a880bc1b88611fefc9ab.png" alt="一对多"></p><p>在每个场景里,「多」的那一方的数量将会从 2000 个逐渐上升到 10000 个。每个场景里,每一秒会发送一条载荷为 256 字节的消息。这样的发布并不会造成过大的吞吐量。仅仅使用 256 字节载荷是为了展示出这两个服务器的工作原理,以及他们的集群模式如何对这些场景作出反应的。</p><h4 id="2-3-2、测试结果"><a href="#2-3-2、测试结果" class="headerlink" title="2.3.2、测试结果"></a>2.3.2、测试结果</h4><p>左侧Y轴是指 CPU 占用,底部X轴是指「多」侧的客户端数量变化。</p><h5 id="1-多对一-1"><a href="#1-多对一-1" class="headerlink" title="1.多对一"></a>1.多对一</h5><p>从 「多对一」 的结果可以看出,EMQ X 和 RabbitMQ 相比并没有太大差别。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/2b19ffedf45b7d9eb09764916d921513.png" alt="多对一测试结果"></p><h5 id="2-一对多-1"><a href="#2-一对多-1" class="headerlink" title="2.一对多"></a>2.一对多</h5><p>但是从「一对多」的结果来看,RabbitMQ 相比于 EMQ X 确实有很明显的差距。</p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/04f1426e4ea489a79916ad8c2e348e45.png" alt="一对多测试结果"></p><h4 id="2-3-3、测试总结"><a href="#2-3-3、测试总结" class="headerlink" title="2.3.3、测试总结"></a>2.3.3、测试总结</h4><p>结果表明:在「多对一」 场景中,EMQ X 和 RabbitMQ 相比并没有太大差别;而在「一对多」场景中,RabbitMQ 则较 EMQ X 产生了较为明显的差距。相比较而言,RabbitMQ使用MQTT协议,和 EMQ X使用MQTT协议存在着一定的差距。</p><h4 id="2-3-4、注意"><a href="#2-3-4、注意" class="headerlink" title="2.3.4、注意"></a>2.3.4、注意</h4><p>使用MQTT的发布-订阅模型不能满足使用要求。可以选择使用AMQP。</p><h2 id="3、Eclipse-Paho-Java"><a href="#3、Eclipse-Paho-Java" class="headerlink" title="3、Eclipse Paho Java"></a>3、Eclipse Paho Java</h2><p>Paho Java客户端是用Java编写的MQTT客户端库,用于开发在JVM或其他Java兼容平台(例如Android)上运行的应用程序。<br>Paho不仅可以对接EMQ X Broker,还可以对接满足符合MQTT协议规范的消息代理服务端,目前Paho可以支持到MQTT5.0以下版本。MQTT3.3.1协议版本基本能满足百分之九十多的接入场景。</p><h2 id="4、SpringBoot整合Eclipse-Paho-Java"><a href="#4、SpringBoot整合Eclipse-Paho-Java" class="headerlink" title="4、SpringBoot整合Eclipse Paho Java"></a>4、SpringBoot整合Eclipse Paho Java</h2><p>EMQX是消息服务器,而我们java想要发送消息,和订阅消息都是和服务器打交道,想要和服务器打交道就需要想办法连上他,这时候就需要用到了Eclipse Paho Java客户端,用来在java当中连接EMQX消息服务器。</p><p>下面案例是按照我的应用场景来写的,监听单独用了一个客户端存入了内存,使用了static变量,启动项目的时候初始化,发送客户端并没有存入内存,而是发送一条,创建一个客户端。这里有一点需要注意,客户端id一定不要重复,就是对于MQTT服务器来说,clientId一定要保持唯一。</p><h3 id="4-1、导入Maven依赖"><a href="#4-1、导入Maven依赖" class="headerlink" title="4.1、导入Maven依赖"></a>4.1、导入Maven依赖</h3><p><code>SpringBoot版本:2.3.9.RELEASE</code></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- mqtt --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-integration<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><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.integration<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-integration-stream<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><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.integration<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-integration-mqtt<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="comment"><!--配置文件报错问题--></span></span><br><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-configuration-processor<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">optional</span>></span>true<span class="tag"></<span class="name">optional</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="comment"><!--lombok--></span></span><br><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.projectlombok<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>lombok<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>1.18.22<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"><<span class="name">scope</span>></span>provided<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><h3 id="4-2、配置文件"><a href="#4-2、配置文件" class="headerlink" title="4.2、配置文件"></a>4.2、配置文件</h3><h4 id="4-2-1、application-yml"><a href="#4-2-1、application-yml" class="headerlink" title="4.2.1、application.yml"></a>4.2.1、application.yml</h4><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">mqtt:</span></span><br><span class="line"> <span class="attr">hostUrl:</span> <span class="string">tcp://192.168.56.103:1883</span></span><br><span class="line"> <span class="attr">username:</span> <span class="string">dev</span></span><br><span class="line"> <span class="attr">password:</span> <span class="string">dev</span></span><br><span class="line"> <span class="attr">client-id:</span> <span class="string">MQTT-CLIENT-DEV</span></span><br><span class="line"> <span class="attr">cleanSession:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">reconnect:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">timeout:</span> <span class="number">100</span></span><br><span class="line"> <span class="attr">keepAlive:</span> <span class="number">100</span></span><br><span class="line"> <span class="attr">defaultTopic:</span> <span class="string">client/dev/report</span></span><br><span class="line"> <span class="attr">serverTopic:</span> <span class="string">server/dev/report</span></span><br><span class="line"> <span class="attr">isOpen:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">qos:</span> <span class="number">0</span></span><br></pre></td></tr></table></figure><h4 id="4-2-2、MqttProperties"><a href="#4-2-2、MqttProperties" class="headerlink" title="4.2.2、MqttProperties"></a>4.2.2、MqttProperties</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> net.iot.mqtt.client.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> lombok.Data;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.context.properties.ConfigurationProperties;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> : MQTT配置信息</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Author</span> : Sherlock</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> : 2023/8/1 16:25</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@ConfigurationProperties("mqtt")</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MqttProperties</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 用户名</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> String username;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 密码</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 连接地址</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> String hostUrl;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 客户端Id,同一台服务器下,不允许出现重复的客户端id</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> String clientId;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 默认连接主题,以/#结尾表示订阅所有以test开头的主题</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> String defaultTopic;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 默认服务器发送主题前缀,格式:server:${env}:report:${topic}</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> String serverTopic;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 超时时间</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> timeout;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端</span></span><br><span class="line"><span class="comment"> * 发送个消息判断客户端是否在线,但这个方法并没有重连的机制</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> keepAlive;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连</span></span><br><span class="line"><span class="comment"> * 接记录,这里设置为true表示每次连接到服务器都以新的身份连接</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> Boolean cleanSession;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 是否断线重连</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> Boolean reconnect;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 启动的时候是否关闭mqtt</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> Boolean isOpen;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 连接方式</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> Integer qos;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取默认主题,以/#结尾表示订阅所有以test开头的主题</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">getDefaultTopic</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> defaultTopic + <span class="string">"/#"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取服务器发送主题,格式:server/${env}/report/${topic}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> topic</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">getServerTopic</span><span class="params">(String topic)</span> {</span><br><span class="line"> <span class="keyword">return</span> serverTopic + <span class="string">"/"</span> + topic;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-3、添加MQTT接受服务的客户端"><a href="#4-3、添加MQTT接受服务的客户端" class="headerlink" title="4.3、添加MQTT接受服务的客户端"></a>4.3、添加MQTT接受服务的客户端</h3><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><span class="line">104</span><br><span class="line">105</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> net.iot.mqtt.client.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.MqttClient;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.MqttConnectOptions;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.MqttException;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> : MQTT接受服务的客户端</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Author</span> : Sherlock</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> : 2023/8/1 16:26</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MqttAcceptClient</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(MqttAcceptClient.class);</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MqttAcceptCallback mqttAcceptCallback;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MqttProperties mqttProperties;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> MqttClient client;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> MqttClient <span class="title function_">getClient</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> client;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">setClient</span><span class="params">(MqttClient client)</span> {</span><br><span class="line"> MqttAcceptClient.client = client;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 客户端连接</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">connect</span><span class="params">()</span> {</span><br><span class="line"> MqttClient client;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> client = <span class="keyword">new</span> <span class="title class_">MqttClient</span>(mqttProperties.getHostUrl(), mqttProperties.getClientId(),</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">MemoryPersistence</span>());</span><br><span class="line"> <span class="type">MqttConnectOptions</span> <span class="variable">options</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MqttConnectOptions</span>();</span><br><span class="line"> options.setUserName(mqttProperties.getUsername());</span><br><span class="line"> options.setPassword(mqttProperties.getPassword().toCharArray());</span><br><span class="line"> options.setConnectionTimeout(mqttProperties.getTimeout());</span><br><span class="line"> options.setKeepAliveInterval(mqttProperties.getKeepAlive());</span><br><span class="line"> options.setAutomaticReconnect(mqttProperties.getReconnect());</span><br><span class="line"> options.setCleanSession(mqttProperties.getCleanSession());</span><br><span class="line"> MqttAcceptClient.setClient(client);</span><br><span class="line"> <span class="comment">// 设置回调</span></span><br><span class="line"> client.setCallback(mqttAcceptCallback);</span><br><span class="line"> client.connect(options);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> logger.error(<span class="string">"MqttAcceptClient connect error,message:{}"</span>, e.getMessage());</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="comment">/**</span></span><br><span class="line"><span class="comment"> * 重新连接</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reconnection</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> client.connect();</span><br><span class="line"> } <span class="keyword">catch</span> (MqttException e) {</span><br><span class="line"> logger.error(<span class="string">"MqttAcceptClient reconnection error,message:{}"</span>, e.getMessage());</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="comment">/**</span></span><br><span class="line"><span class="comment"> * 订阅某个主题</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> topic 主题</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> qos 连接方式</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">subscribe</span><span class="params">(String topic, <span class="type">int</span> qos)</span> {</span><br><span class="line"> logger.info(<span class="string">"========================【开始订阅主题:"</span> + topic + <span class="string">"】========================"</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> client.subscribe(topic, qos);</span><br><span class="line"> } <span class="keyword">catch</span> (MqttException e) {</span><br><span class="line"> logger.error(<span class="string">"MqttAcceptClient subscribe error,message:{}"</span>, e.getMessage());</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="comment">/**</span></span><br><span class="line"><span class="comment"> * 取消订阅某个主题</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> topic</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unsubscribe</span><span class="params">(String topic)</span> {</span><br><span class="line"> logger.info(<span class="string">"========================【取消订阅主题:"</span> + topic + <span class="string">"】========================"</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> client.unsubscribe(topic);</span><br><span class="line"> } <span class="keyword">catch</span> (MqttException e) {</span><br><span class="line"> logger.error(<span class="string">"MqttAcceptClient unsubscribe error,message:{}"</span>, e.getMessage());</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-4、添加MQTT接受服务的回调类"><a href="#4-4、添加MQTT接受服务的回调类" class="headerlink" title="4.4、添加MQTT接受服务的回调类"></a>4.4、添加MQTT接受服务的回调类</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> net.iot.mqtt.client.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.MqttCallbackExtended;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.MqttException;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.MqttMessage;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.UnsupportedEncodingException;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> : MQTT接受服务的回调类</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Author</span> : Sherlock</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> : 2023/8/1 16:29</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MqttAcceptCallback</span> <span class="keyword">implements</span> <span class="title class_">MqttCallbackExtended</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(MqttAcceptCallback.class);</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MqttAcceptClient mqttAcceptClient;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MqttProperties mqttProperties;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 客户端断开后触发</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> throwable</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">connectionLost</span><span class="params">(Throwable throwable)</span> {</span><br><span class="line"> logger.info(<span class="string">"连接断开,可以重连"</span>);</span><br><span class="line"> <span class="keyword">if</span> (MqttAcceptClient.client == <span class="literal">null</span> || !MqttAcceptClient.client.isConnected()) {</span><br><span class="line"> logger.info(<span class="string">"【emqx重新连接】...................................................."</span>);</span><br><span class="line"> mqttAcceptClient.reconnection();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 客户端收到消息触发</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> topic 主题</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> mqttMessage 消息</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">messageArrived</span><span class="params">(String topic, MqttMessage mqttMessage)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> logger.info(<span class="string">"【接收消息主题】:"</span> + topic);</span><br><span class="line"> logger.info(<span class="string">"【接收消息Qos】:"</span> + mqttMessage.getQos());</span><br><span class="line"> logger.info(<span class="string">"【接收消息内容】:"</span> + <span class="keyword">new</span> <span class="title class_">String</span>(mqttMessage.getPayload()));</span><br><span class="line"> <span class="comment">// int i = 1/0;</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 发布消息成功</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> token token</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deliveryComplete</span><span class="params">(IMqttDeliveryToken token)</span> {</span><br><span class="line"> String[] topics = token.getTopics();</span><br><span class="line"> <span class="keyword">for</span> (String topic : topics) {</span><br><span class="line"> logger.info(<span class="string">"向主题【"</span> + topic + <span class="string">"】发送消息成功!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">MqttMessage</span> <span class="variable">message</span> <span class="operator">=</span> token.getMessage();</span><br><span class="line"> <span class="type">byte</span>[] payload = message.getPayload();</span><br><span class="line"> <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(payload, <span class="string">"UTF-8"</span>);</span><br><span class="line"> logger.info(<span class="string">"【消息内容】:"</span> + s);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> logger.error(<span class="string">"MqttAcceptCallback deliveryComplete error,message:{}"</span>, e.getMessage());</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="comment">/**</span></span><br><span class="line"><span class="comment"> * 连接emq服务器后触发</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> b</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> s</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">connectComplete</span><span class="params">(<span class="type">boolean</span> b, String s)</span> {</span><br><span class="line"> logger.info(<span class="string">"============================= 客户端【"</span> + MqttAcceptClient.client.getClientId() + <span class="string">"】连接成功!============================="</span>);</span><br><span class="line"> <span class="comment">// 以/#结尾表示订阅所有以test开头的主题</span></span><br><span class="line"> <span class="comment">// 订阅所有机构主题</span></span><br><span class="line"> mqttAcceptClient.subscribe(mqttProperties.getDefaultTopic(), <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-5、添加MQTT发送客户端"><a href="#4-5、添加MQTT发送客户端" class="headerlink" title="4.5、添加MQTT发送客户端"></a>4.5、添加MQTT发送客户端</h3><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><span class="line">104</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> net.iot.mqtt.client.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.*;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.UUID;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> : MQTT发送客户端</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Author</span> : Sherlock</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> : 2023/8/1 16:30</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MqttSendClient</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(MqttSendClient.class);</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MqttSendCallBack mqttSendCallBack;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MqttProperties mqttProperties;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> MqttClient <span class="title function_">connect</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">MqttClient</span> <span class="variable">client</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">String</span> <span class="variable">uuid</span> <span class="operator">=</span> UUID.randomUUID().toString().replaceAll(<span class="string">"-"</span>, <span class="string">""</span>);</span><br><span class="line"> client = <span class="keyword">new</span> <span class="title class_">MqttClient</span>(mqttProperties.getHostUrl(), uuid, <span class="keyword">new</span> <span class="title class_">MemoryPersistence</span>());</span><br><span class="line"> <span class="type">MqttConnectOptions</span> <span class="variable">options</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MqttConnectOptions</span>();</span><br><span class="line"> options.setUserName(mqttProperties.getUsername());</span><br><span class="line"> options.setPassword(mqttProperties.getPassword().toCharArray());</span><br><span class="line"> options.setConnectionTimeout(mqttProperties.getTimeout());</span><br><span class="line"> options.setKeepAliveInterval(mqttProperties.getKeepAlive());</span><br><span class="line"> options.setCleanSession(<span class="literal">true</span>);</span><br><span class="line"> options.setAutomaticReconnect(<span class="literal">false</span>);</span><br><span class="line"> <span class="comment">// 设置回调</span></span><br><span class="line"> client.setCallback(mqttSendCallBack);</span><br><span class="line"> client.connect(options);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> logger.error(<span class="string">"MqttSendClient connect error,message:{}"</span>, e.getMessage());</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> client;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 发布消息</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> retained 是否保留</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> topic 主题,格式: server:${env}:report:${topic}</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> content 消息内容</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">publish</span><span class="params">(<span class="type">boolean</span> retained, String topic, String content)</span> {</span><br><span class="line"> <span class="type">MqttMessage</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MqttMessage</span>();</span><br><span class="line"> message.setQos(mqttProperties.getQos());</span><br><span class="line"> message.setRetained(retained);</span><br><span class="line"> message.setPayload(content.getBytes());</span><br><span class="line"> MqttDeliveryToken token;</span><br><span class="line"> <span class="type">MqttClient</span> <span class="variable">mqttClient</span> <span class="operator">=</span> connect();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> mqttClient.publish(mqttProperties.getServerTopic(topic), message);</span><br><span class="line"> } <span class="keyword">catch</span> (MqttException e) {</span><br><span class="line"> logger.error(<span class="string">"MqttSendClient publish error,message:{}"</span>, e.getMessage());</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> disconnect(mqttClient);</span><br><span class="line"> close(mqttClient);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 关闭连接</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> mqttClient</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">disconnect</span><span class="params">(MqttClient mqttClient)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (mqttClient != <span class="literal">null</span>)</span><br><span class="line"> mqttClient.disconnect();</span><br><span class="line"> } <span class="keyword">catch</span> (MqttException e) {</span><br><span class="line"> logger.error(<span class="string">"MqttSendClient disconnect error,message:{}"</span>, e.getMessage());</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="comment">/**</span></span><br><span class="line"><span class="comment"> * 释放资源</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> mqttClient</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">close</span><span class="params">(MqttClient mqttClient)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (mqttClient != <span class="literal">null</span>)</span><br><span class="line"> mqttClient.close();</span><br><span class="line"> } <span class="keyword">catch</span> (MqttException e) {</span><br><span class="line"> logger.error(<span class="string">"MqttSendClient close error,message:{}"</span>, e.getMessage());</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-6、添加MQTT发送客户端的回调类"><a href="#4-6、添加MQTT发送客户端的回调类" class="headerlink" title="4.6、添加MQTT发送客户端的回调类"></a>4.6、添加MQTT发送客户端的回调类</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> net.iot.mqtt.client.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.MqttCallbackExtended;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.MqttException;</span><br><span class="line"><span class="keyword">import</span> org.eclipse.paho.client.mqttv3.MqttMessage;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.UnsupportedEncodingException;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> : MQTT发送客户端的回调类</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Author</span> : Sherlock</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> : 2023/8/1 16:31</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MqttSendCallBack</span> <span class="keyword">implements</span> <span class="title class_">MqttCallbackExtended</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(MqttSendCallBack.class);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 客户端断开后触发</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> throwable</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">connectionLost</span><span class="params">(Throwable throwable)</span> {</span><br><span class="line"> logger.info(<span class="string">"连接断开,可以重连"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 客户端收到消息触发</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> topic 主题</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> mqttMessage 消息</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">messageArrived</span><span class="params">(String topic, MqttMessage mqttMessage)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> logger.info(<span class="string">"【接收消息主题】: "</span> + topic);</span><br><span class="line"> logger.info(<span class="string">"【接收消息Qos】: "</span> + mqttMessage.getQos());</span><br><span class="line"> logger.info(<span class="string">"【接收消息内容】: "</span> + <span class="keyword">new</span> <span class="title class_">String</span>(mqttMessage.getPayload()));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 发布消息成功</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> token token</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deliveryComplete</span><span class="params">(IMqttDeliveryToken token)</span> {</span><br><span class="line"> String[] topics = token.getTopics();</span><br><span class="line"> <span class="keyword">for</span> (String topic : topics) {</span><br><span class="line"> logger.info(<span class="string">"向主题【"</span> + topic + <span class="string">"】发送消息成功!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">MqttMessage</span> <span class="variable">message</span> <span class="operator">=</span> token.getMessage();</span><br><span class="line"> <span class="type">byte</span>[] payload = message.getPayload();</span><br><span class="line"> <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(payload, <span class="string">"UTF-8"</span>);</span><br><span class="line"> logger.info(<span class="string">"【消息内容】:"</span> + s);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> logger.error(<span class="string">"MqttSendCallBack deliveryComplete error,message:{}"</span>, e.getMessage());</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="comment">/**</span></span><br><span class="line"><span class="comment"> * 连接emq服务器后触发</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> b</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> s</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">connectComplete</span><span class="params">(<span class="type">boolean</span> b, String s)</span> {</span><br><span class="line"> logger.info(<span class="string">"============================= 客户端【"</span> + MqttAcceptClient.client.getClientId() + <span class="string">"】连接成功!============================="</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-7、添加配置类"><a href="#4-7、添加配置类" class="headerlink" title="4.7、添加配置类"></a>4.7、添加配置类</h3><p>自定义配置,通过这个配置,来控制启动项目的时候是否启动mqtt</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> net.iot.mqtt.client.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.config.ConfigurableListableBeanFactory;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Condition;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.ConditionContext;</span><br><span class="line"><span class="keyword">import</span> org.springframework.core.env.Environment;</span><br><span class="line"><span class="keyword">import</span> org.springframework.core.type.AnnotatedTypeMetadata;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> : 自定义配置,通过这个配置,来控制启动项目的时候是否启动mqtt</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Author</span> : Sherlock</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> : 2023/8/1 16:32</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MqttCondition</span> <span class="keyword">implements</span> <span class="title class_">Condition</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">matches</span><span class="params">(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata)</span> {</span><br><span class="line"> <span class="comment">//1、能获取到ioc使用的beanfactory</span></span><br><span class="line"> <span class="type">ConfigurableListableBeanFactory</span> <span class="variable">beanFactory</span> <span class="operator">=</span> context.getBeanFactory();</span><br><span class="line"> <span class="comment">//2、获取类加载器</span></span><br><span class="line"> <span class="type">ClassLoader</span> <span class="variable">classLoader</span> <span class="operator">=</span> context.getClassLoader();</span><br><span class="line"> <span class="comment">//3、获取当前环境信息</span></span><br><span class="line"> <span class="type">Environment</span> <span class="variable">environment</span> <span class="operator">=</span> context.getEnvironment();</span><br><span class="line"> <span class="type">String</span> <span class="variable">isOpen</span> <span class="operator">=</span> environment.getProperty(<span class="string">"mqtt.isOpen"</span>);</span><br><span class="line"> <span class="keyword">return</span> Boolean.valueOf(isOpen);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="4-8、启动服务的时候开启监听客户端"><a href="#4-8、启动服务的时候开启监听客户端" class="headerlink" title="4.8、启动服务的时候开启监听客户端"></a>4.8、启动服务的时候开启监听客户端</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> net.iot.mqtt.client.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Bean;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Conditional;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> : 启动服务的时候开启监听客户端</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Author</span> : Sherlock</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> : 2023/8/1 16:35</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MqttConfig</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MqttAcceptClient mqttAcceptClient;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 订阅mqtt</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Conditional(MqttCondition.class)</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="keyword">public</span> MqttAcceptClient <span class="title function_">getMqttPushClient</span><span class="params">()</span> {</span><br><span class="line"> mqttAcceptClient.connect();</span><br><span class="line"> <span class="keyword">return</span> mqttAcceptClient;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-9、测试类"><a href="#4-9、测试类" class="headerlink" title="4.9、测试类"></a>4.9、测试类</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> net.dreamlu.iot.mqtt.client.controller;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> net.dreamlu.iot.mqtt.client.config.MqttSendClient;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.GetMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RestController;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> : 测试类</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Author</span> : Sherlock</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> : 2023/8/1 16:35</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping("/mqtt")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MqttController</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> MqttSendClient mqttSendClient;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping(value = "/publishTopic")</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">publishTopic</span><span class="params">(String topic, String sendMessage)</span> {</span><br><span class="line"> System.out.println(<span class="string">"topic:"</span> + topic);</span><br><span class="line"> System.out.println(<span class="string">"message:"</span> + sendMessage);</span><br><span class="line"> <span class="built_in">this</span>.mqttSendClient.publish(<span class="literal">false</span>, topic, sendMessage);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"topic:"</span> + topic + <span class="string">"\nmessage:"</span> + sendMessage;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-10、发送和接收消息测试"><a href="#4-10、发送和接收消息测试" class="headerlink" title="4.10、发送和接收消息测试"></a>4.10、发送和接收消息测试</h3><h4 id="4-10-1、发送消息"><a href="#4-10-1、发送消息" class="headerlink" title="4.10.1、发送消息"></a>4.10.1、发送消息</h4><p>访问:<code>http://localhost:8080/mqtt/publishTopic?sendMessage=222</code></p><p><img src="https://i-blog.csdnimg.cn/blog_migrate/91de48a566c83f5cf9b5eaf3021c11b1.png" alt="发送消息测试"></p><h4 id="4-10-2、接收消息"><a href="#4-10-2、接收消息" class="headerlink" title="4.10.2、接收消息"></a>4.10.2、接收消息</h4><p><img src="https://i-blog.csdnimg.cn/blog_migrate/2ba2c52fe47cf7834a43eef01f24bb28.png" alt="监听消息测试"></p><h3 id="4-11、可能出现的问题"><a href="#4-11、可能出现的问题" class="headerlink" title="4.11、可能出现的问题"></a>4.11、可能出现的问题</h3><p><strong>问题:循环引用</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">***************************</span><br><span class="line">APPLICATION FAILED TO START</span><br><span class="line">***************************</span><br><span class="line"></span><br><span class="line">Description:</span><br><span class="line"></span><br><span class="line">The dependencies of some of the beans in the application context form a cycle:</span><br><span class="line"></span><br><span class="line">┌─────┐</span><br><span class="line">| mqttAcceptCallback (field private net.iot.mqtt.client.config.MqttAcceptClient net.iot.mqtt.client.config.MqttAcceptCallback.mqttAcceptClient)</span><br><span class="line">↑ ↓</span><br><span class="line">| mqttAcceptClient (field private net.iot.mqtt.client.config.MqttAcceptCallback net.iot.mqtt.client.config.MqttAcceptClient.mqttAcceptCallback)</span><br><span class="line">└─────┘</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Action:</span><br><span class="line"></span><br><span class="line">Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.</span><br></pre></td></tr></table></figure><p><strong>解决方法:打开循环引用</strong></p> <figure class="highlight yml"><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"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">main:</span></span><br><span class="line"> <span class="attr">allow-circular-references:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 技术分享 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 技术分享 </tag>
<tag> Springboot </tag>
<tag> 网络协议 </tag>
</tags>
</entry>
<entry>
<title>Oracle调优之看懂Oracle执行计划</title>
<link href="/2024/07/25/Oracle%E8%B0%83%E4%BC%98%E4%B9%8B%E7%9C%8B%E6%87%82Oracle%E6%89%A7%E8%A1%8C%E8%AE%A1%E5%88%92/"/>
<url>/2024/07/25/Oracle%E8%B0%83%E4%BC%98%E4%B9%8B%E7%9C%8B%E6%87%82Oracle%E6%89%A7%E8%A1%8C%E8%AE%A1%E5%88%92/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>原文地址:<a href="https://www.cnblogs.com/mzq123/p/13156390.html">Oracle调优之看懂Oracle执行计划 </a></p></blockquote><h3 id="1、文章写作前言简介"><a href="#1、文章写作前言简介" class="headerlink" title="1、文章写作前言简介"></a>1、文章写作前言简介</h3><p>之前曾经拜读过《收获,不止 sql 调优》一书,此书是国内 DBA 写的一本很不错的调优类型的书,是一些很不错的调优经验的分享。虽然读了一遍,做了下<a href="https://blog.csdn.net/u014427391/article/details/89604262">读书笔记</a>,觉得很有所收获,但是到实际的实践中觉得还是很缺实践。刚好最近又有一次 sql 调优培训活动,去参加后,重新复习 Oracle 执行计划,所以整理资料,做成笔记分享出来</p><h3 id="2、什么是执行计划?"><a href="#2、什么是执行计划?" class="headerlink" title="2、什么是执行计划?"></a>2、什么是执行计划?</h3><p>执行计划是一条查询语句在 Oracle 中的执行过程或访问路径的描述。</p><p>执行计划描述了 SQL 引擎为执行 SQL 语句进行的操作;分析 SQL 语句相关的性能问题或仅仅质疑查询优化器的决定时,必须知道执行计划;所以执行计划常用于 sql 调优。</p><h3 id="3、怎么查看执行计划?"><a href="#3、怎么查看执行计划?" class="headerlink" title="3、怎么查看执行计划?"></a>3、怎么查看执行计划?</h3><p>查看 Oracle 执行计划有很多种,详情参考我之前的<a href="https://blog.csdn.net/u014427391/article/details/89604262">读书笔记</a>,本博客只介绍很常用的方法</p><p>oracle 要使用执行计划一般在 sqlplus 执行 sql:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain plan <span class="keyword">for</span> <span class="keyword">select</span> <span class="number">1</span> <span class="keyword">from</span> t</span><br></pre></td></tr></table></figure><p>不过如果是使用 PLSQL 的话,那就可以使用 PLSQL 提供的查询执行计划了, 也就是按 F5</p><p>打开 PLSQL 工具 -> 首选项 -> 窗口类型 -> 计划窗口 ,在这里加入执行计划需要的参数<br><img src="https://i-blog.csdnimg.cn/blog_migrate/53e88277599ab3dd42f03f3022b31a90.png"></p><p>找个 SQL,用 PLSQL 执行一下,这是 plsql 的简单使用<br><img src="https://i-blog.csdnimg.cn/blog_migrate/fe925077608442a0287ca42d8b830a71.png"></p><p>解释一下这些参数的意思:</p><ul><li>基数 (Rows):Oracle 估计的当前步骤的返回结果集行数</li><li>字节 (Bytes):执行 SQL 对应步骤返回的字节数</li><li>耗费 (COST)、CPU 耗费:Oracle 估计的该步骤的执行耗费和 CPU 耗费</li><li>时间 (Time):Oracle 估计的执行 sql 对于步骤需要的时间</li></ul><h3 id="4、查看真实执行计划"><a href="#4、查看真实执行计划" class="headerlink" title="4、查看真实执行计划"></a>4、查看真实执行计划</h3><p>之前查看执行计划也喜欢按 F5,不过最近去培训,听一名 dba 说,这种方法有时候不能获取真实的执行计划,收集的信息也不全面,然后怎么查看 sql 执行过程的真实信息?从培训中学到的经验做成笔记</p><p>sqlplus 窗口执行:</p><ul><li>step1:set statistics_level</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">alter</span> session <span class="keyword">set</span> statistics_level<span class="operator">=</span><span class="keyword">ALL</span>;</span><br></pre></td></tr></table></figure><ul><li>step2:执行业务 sql</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="comment">/*+ monitor */</span> <span class="operator">*</span> <span class="keyword">from</span> ... <span class="keyword">where</span> ....;</span><br></pre></td></tr></table></figure><ul><li>step3:为了样式,设置 linesize</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">set</span> linesize <span class="number">200</span> pagesize <span class="number">300</span>;</span><br></pre></td></tr></table></figure><ul><li>step4:查询真实执行计划</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">table</span>(dbms_xplan.display_cursor(<span class="keyword">null</span>, <span class="keyword">null</span>, <span class="string">'iostats last'</span>));</span><br></pre></td></tr></table></figure><p>sqlplus 一般要数据库管理员才可以使用,如果你不是 dba,只能使用 plsql developer 的话,只能用下面的方法,方法是从培训中学到的</p><p>使用存储过程,SQL:</p><figure class="highlight sql"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span></span><br><span class="line"> b1 <span class="type">date</span>;</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line"> <span class="keyword">execute</span> immediate <span class="string">'alter session set statistics_level=ALL'</span>;</span><br><span class="line"> b1 :<span class="operator">=</span> sysdate <span class="operator">-</span> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> test <span class="keyword">in</span> (</span><br><span class="line"> <span class="comment">/*业务SQL(sql后面不需要加";")*/</span></span><br><span class="line"> <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> t) loop</span><br><span class="line"> <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">end</span> loop;</span><br><span class="line"> <span class="keyword">for</span> x <span class="keyword">in</span> (<span class="keyword">select</span> p.plan_table_output</span><br><span class="line"> <span class="keyword">from</span> <span class="keyword">table</span>(dbms_xplan.display_cursor(<span class="keyword">null</span>,</span><br><span class="line"> <span class="keyword">null</span>,</span><br><span class="line"> <span class="string">'advanced -bytes -PROJECTION allstats last'</span>)) p) loop</span><br><span class="line"> dbms_output.put_line(x.plan_table_output);</span><br><span class="line"> <span class="keyword">end</span> loop;</span><br><span class="line"> <span class="keyword">rollback</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"><span class="operator">/</span></span><br></pre></td></tr></table></figure><p>两种窗口:</p><ul><li>1、SQL 窗口的,执行 SQL 后只能去 output 查看;</li><li>2、command window 的,需要先设置<code>set serveroutput on size unlimited</code>,然后再执行存储过程</li></ul><p>output 或者命令窗口查看的真实执行计划和统计信息:<br><img src="https://i-blog.csdnimg.cn/blog_migrate/5db0961923c196193f7b83f20ae2acd7.png"></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">SQL_ID abk3ghv9u1tvb, child number <span class="number">0</span></span><br><span class="line"><span class="comment">-------------------------------------</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="comment">/*+ monitor */</span> <span class="operator">*</span> <span class="keyword">FROM</span> APPR_HANDLE_INFO</span><br><span class="line"> </span><br><span class="line">Plan hash <span class="keyword">value</span>: <span class="number">885170757</span></span><br><span class="line"> </span><br><span class="line"><span class="comment">------------------------------------------------------------------------------------------------------------------------</span></span><br><span class="line"><span class="operator">|</span> Id <span class="operator">|</span> Operation <span class="operator">|</span> Name <span class="operator">|</span> Starts <span class="operator">|</span> E<span class="operator">-</span><span class="keyword">Rows</span> <span class="operator">|</span> Cost (<span class="operator">%</span>CPU)<span class="operator">|</span> E<span class="operator">-</span><span class="type">Time</span> <span class="operator">|</span> A<span class="operator">-</span><span class="keyword">Rows</span> <span class="operator">|</span> A<span class="operator">-</span><span class="type">Time</span> <span class="operator">|</span> Buffers <span class="operator">|</span></span><br><span class="line"><span class="comment">------------------------------------------------------------------------------------------------------------------------</span></span><br><span class="line"><span class="operator">|</span> <span class="number">0</span> <span class="operator">|</span> <span class="keyword">SELECT</span> STATEMENT <span class="operator">|</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="operator">|</span> <span class="number">210</span> (<span class="number">100</span>)<span class="operator">|</span> <span class="operator">|</span> <span class="number">72059</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.06</span> <span class="operator">|</span> <span class="number">2460</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="keyword">TABLE</span> ACCESS <span class="keyword">FULL</span><span class="operator">|</span> APPR_HANDLE_INFO <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">32752</span> <span class="operator">|</span> <span class="number">210</span> (<span class="number">1</span>)<span class="operator">|</span> <span class="number">00</span>:<span class="number">00</span>:<span class="number">03</span> <span class="operator">|</span> <span class="number">72059</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.06</span> <span class="operator">|</span> <span class="number">2460</span> <span class="operator">|</span></span><br><span class="line"><span class="comment">------------------------------------------------------------------------------------------------------------------------</span></span><br><span class="line"> </span><br><span class="line">Query Block Name <span class="operator">/</span> Object Alias (identified <span class="keyword">by</span> operation id):</span><br><span class="line"><span class="comment">-------------------------------------------------------------</span></span><br><span class="line"> </span><br><span class="line"> <span class="number">1</span> <span class="operator">-</span> SEL$<span class="number">1</span> <span class="operator">/</span> APPR_HANDLE_INFO<span class="variable">@SEL</span>$<span class="number">1</span></span><br><span class="line"> </span><br><span class="line">Outline Data</span><br><span class="line"><span class="comment">-------------</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">/*+</span></span><br><span class="line"><span class="comment"> BEGIN_OUTLINE_DATA</span></span><br><span class="line"><span class="comment"> IGNORE_OPTIM_EMBEDDED_HINTS</span></span><br><span class="line"><span class="comment"> OPTIMIZER_FEATURES_ENABLE('11.2.0.4')</span></span><br><span class="line"><span class="comment"> DB_VERSION('11.2.0.4')</span></span><br><span class="line"><span class="comment"> ALL_ROWS</span></span><br><span class="line"><span class="comment"> OUTLINE_LEAF(@"SEL$1")</span></span><br><span class="line"><span class="comment"> FULL(@"SEL$1" "APPR_HANDLE_INFO"@"SEL$1")</span></span><br><span class="line"><span class="comment"> END_OUTLINE_DATA</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>关键信息解释:</p><ul><li>Starts:该 SQL 执行的次数</li><li>E-Rows:为执行计划预计的行数</li><li>A-Rows:实际返回的行数,E-Rows 和 A-Rows 作比较,就可以看出具体那一步执行计划出问题了</li><li>A-Time:每一步实际执行的时间,可以看出耗时的 SQL</li><li>Buffers:每一步实际执行的逻辑读或一致性读</li></ul><h3 id="5、看懂-Oracle-执行计划"><a href="#5、看懂-Oracle-执行计划" class="headerlink" title="5、看懂 Oracle 执行计划"></a>5、看懂 Oracle 执行计划</h3><p>上面已经介绍了如何查看执行计划,现在简单介绍一下一些基本方法和相关理论知识</p><h4 id="5-1-查看-explain"><a href="#5-1-查看-explain" class="headerlink" title="5.1 查看 explain"></a>5.1 查看 explain</h4><p>找一条比较复杂的 SQL,执行:</p><p>F5 方式查看:<br><img src="https://i-blog.csdnimg.cn/blog_migrate/1b1f8ce4f0eba50d5aa9e4ddd0001d66.png"><br>set statistics_level=ALL 方式:<br><img src="https://i-blog.csdnimg.cn/blog_migrate/3a6c4fbd637dcbd700c712fe33603f49.png"></p><figure class="highlight sql"><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></pre></td><td class="code"><pre><span class="line">SQL_ID <span class="number">4</span>qfq3t2ukm0y1, child number <span class="number">0</span></span><br><span class="line"><span class="comment">-------------------------------------</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="comment">/*+ monitor*/</span> A.USER_CODE, A.FULL_NAME, A.USER_PWD, C.UNIT_CODE, </span><br><span class="line">C.UNIT_NAME <span class="keyword">FROM</span> BASE_USER A <span class="keyword">LEFT</span> <span class="keyword">JOIN</span> (<span class="keyword">SELECT</span> UR.USER_CODE, </span><br><span class="line">UR.UNIT_CODE <span class="keyword">FROM</span> APPR_USER_ROLE UR <span class="keyword">WHERE</span> UR.USER_ROLE <span class="operator"><</span> <span class="number">10</span>) B <span class="keyword">ON</span> </span><br><span class="line">A.USER_CODE <span class="operator">=</span> B.USER_CODE <span class="keyword">LEFT</span> <span class="keyword">JOIN</span> LZCITY_APPROVE_UNIT_INFO C <span class="keyword">ON</span> </span><br><span class="line">B.UNIT_CODE <span class="operator">=</span> C.UNIT_CODE <span class="keyword">WHERE</span> C.UNIT_CODE <span class="operator">=</span><span class="string">'15803'</span></span><br><span class="line"> </span><br><span class="line">Plan hash <span class="keyword">value</span>: <span class="number">3288287052</span></span><br><span class="line"> </span><br><span class="line"><span class="comment">------------------------------------------------------------------------------------------------------------------------------------------------</span></span><br><span class="line"><span class="operator">|</span> Id <span class="operator">|</span> Operation <span class="operator">|</span> Name <span class="operator">|</span> Starts <span class="operator">|</span> E<span class="operator">-</span><span class="keyword">Rows</span> <span class="operator">|</span> Cost (<span class="operator">%</span>CPU)<span class="operator">|</span> E<span class="operator">-</span><span class="type">Time</span> <span class="operator">|</span> A<span class="operator">-</span><span class="keyword">Rows</span> <span class="operator">|</span> A<span class="operator">-</span><span class="type">Time</span> <span class="operator">|</span> Buffers <span class="operator">|</span></span><br><span class="line"><span class="comment">------------------------------------------------------------------------------------------------------------------------------------------------</span></span><br><span class="line"><span class="operator">|</span> <span class="number">0</span> <span class="operator">|</span> <span class="keyword">SELECT</span> STATEMENT <span class="operator">|</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="operator">|</span> <span class="number">3</span> (<span class="number">100</span>)<span class="operator">|</span> <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.01</span> <span class="operator">|</span> <span class="number">38</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> NESTED LOOPS <span class="operator">|</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">3</span> (<span class="number">0</span>)<span class="operator">|</span> <span class="number">00</span>:<span class="number">00</span>:<span class="number">01</span> <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.01</span> <span class="operator">|</span> <span class="number">38</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">2</span> <span class="operator">|</span> NESTED LOOPS <span class="operator">|</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">3</span> (<span class="number">0</span>)<span class="operator">|</span> <span class="number">00</span>:<span class="number">00</span>:<span class="number">01</span> <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.01</span> <span class="operator">|</span> <span class="number">22</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">3</span> <span class="operator">|</span> NESTED LOOPS <span class="operator">|</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">2</span> (<span class="number">0</span>)<span class="operator">|</span> <span class="number">00</span>:<span class="number">00</span>:<span class="number">01</span> <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.01</span> <span class="operator">|</span> <span class="number">5</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">4</span> <span class="operator">|</span> <span class="keyword">TABLE</span> ACCESS <span class="keyword">BY</span> INDEX ROWID<span class="operator">|</span> LZCITY_APPROVE_UNIT_INFO <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> (<span class="number">0</span>)<span class="operator">|</span> <span class="number">00</span>:<span class="number">00</span>:<span class="number">01</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.01</span> <span class="operator">|</span> <span class="number">3</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span><span class="operator">*</span> <span class="number">5</span> <span class="operator">|</span> INDEX <span class="keyword">UNIQUE</span> SCAN <span class="operator">|</span> PK_LZCITY_APPROVE_UNIT_INFO <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">0</span> (<span class="number">0</span>)<span class="operator">|</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.01</span> <span class="operator">|</span> <span class="number">2</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span><span class="operator">*</span> <span class="number">6</span> <span class="operator">|</span> INDEX <span class="keyword">RANGE</span> SCAN <span class="operator">|</span> PK_APPR_USER_ROLE <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> (<span class="number">0</span>)<span class="operator">|</span> <span class="number">00</span>:<span class="number">00</span>:<span class="number">01</span> <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.01</span> <span class="operator">|</span> <span class="number">2</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span><span class="operator">*</span> <span class="number">7</span> <span class="operator">|</span> INDEX <span class="keyword">UNIQUE</span> SCAN <span class="operator">|</span> PK_BASE_USER <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">0</span> (<span class="number">0</span>)<span class="operator">|</span> <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.01</span> <span class="operator">|</span> <span class="number">17</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="number">8</span> <span class="operator">|</span> <span class="keyword">TABLE</span> ACCESS <span class="keyword">BY</span> INDEX ROWID <span class="operator">|</span> BASE_USER <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span> <span class="number">1</span> <span class="operator">|</span> <span class="number">1</span> (<span class="number">0</span>)<span class="operator">|</span> <span class="number">00</span>:<span class="number">00</span>:<span class="number">01</span> <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span><span class="number">00</span>:<span class="number">00</span>:<span class="number">00.01</span> <span class="operator">|</span> <span class="number">16</span> <span class="operator">|</span></span><br><span class="line"><span class="comment">------------------------------------------------------------------------------------------------------------------------------------------------</span></span><br><span class="line"> </span><br><span class="line">Query Block Name <span class="operator">/</span> Object Alias (identified <span class="keyword">by</span> operation id):</span><br><span class="line"><span class="comment">-------------------------------------------------------------</span></span><br><span class="line"> </span><br><span class="line"> <span class="number">1</span> <span class="operator">-</span> SEL$E3445A69</span><br><span class="line"> <span class="number">4</span> <span class="operator">-</span> SEL$E3445A69 <span class="operator">/</span> C<span class="variable">@SEL</span>$<span class="number">4</span></span><br><span class="line"> <span class="number">5</span> <span class="operator">-</span> SEL$E3445A69 <span class="operator">/</span> C<span class="variable">@SEL</span>$<span class="number">4</span></span><br><span class="line"> <span class="number">6</span> <span class="operator">-</span> SEL$E3445A69 <span class="operator">/</span> UR<span class="variable">@SEL</span>$<span class="number">2</span></span><br><span class="line"> <span class="number">7</span> <span class="operator">-</span> SEL$E3445A69 <span class="operator">/</span> A<span class="variable">@SEL</span>$<span class="number">3</span></span><br><span class="line"> <span class="number">8</span> <span class="operator">-</span> SEL$E3445A69 <span class="operator">/</span> A<span class="variable">@SEL</span>$<span class="number">3</span></span><br><span class="line"> </span><br><span class="line">Outline Data</span><br><span class="line"><span class="comment">-------------</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">/*+</span></span><br><span class="line"><span class="comment"> BEGIN_OUTLINE_DATA</span></span><br><span class="line"><span class="comment"> IGNORE_OPTIM_EMBEDDED_HINTS</span></span><br><span class="line"><span class="comment"> OPTIMIZER_FEATURES_ENABLE('11.2.0.4')</span></span><br><span class="line"><span class="comment"> DB_VERSION('11.2.0.4')</span></span><br><span class="line"><span class="comment"> ALL_ROWS</span></span><br><span class="line"><span class="comment"> OUTLINE_LEAF(@"SEL$E3445A69")</span></span><br><span class="line"><span class="comment"> MERGE(@"SEL$2")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$A2E96217")</span></span><br><span class="line"><span class="comment"> OUTER_JOIN_TO_INNER(@"SEL$E9F4A6F9" "B"@"SEL$1")</span></span><br><span class="line"><span class="comment"> OUTER_JOIN_TO_INNER(@"SEL$E9F4A6F9" "C"@"SEL$4")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$2")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$E9F4A6F9")</span></span><br><span class="line"><span class="comment"> MERGE(@"SEL$80808B20")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$6")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$80808B20")</span></span><br><span class="line"><span class="comment"> MERGE(@"SEL$4")</span></span><br><span class="line"><span class="comment"> MERGE(@"SEL$F1D6E378")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$5")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$4")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$F1D6E378")</span></span><br><span class="line"><span class="comment"> MERGE(@"SEL$1")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$3")</span></span><br><span class="line"><span class="comment"> OUTLINE(@"SEL$1")</span></span><br><span class="line"><span class="comment"> INDEX_RS_ASC(@"SEL$E3445A69" "C"@"SEL$4" ("LZCITY_APPROVE_UNIT_INFO"."UNIT_CODE"))</span></span><br><span class="line"><span class="comment"> INDEX(@"SEL$E3445A69" "UR"@"SEL$2" ("APPR_USER_ROLE"."UNIT_CODE" "APPR_USER_ROLE"."USER_CODE" "APPR_USER_ROLE"."AREA_SEQ" </span></span><br><span class="line"><span class="comment"> "APPR_USER_ROLE"."USER_ROLE"))</span></span><br><span class="line"><span class="comment"> INDEX(@"SEL$E3445A69" "A"@"SEL$3" ("BASE_USER"."USER_CODE"))</span></span><br><span class="line"><span class="comment"> LEADING(@"SEL$E3445A69" "C"@"SEL$4" "UR"@"SEL$2" "A"@"SEL$3")</span></span><br><span class="line"><span class="comment"> USE_NL(@"SEL$E3445A69" "UR"@"SEL$2")</span></span><br><span class="line"><span class="comment"> USE_NL(@"SEL$E3445A69" "A"@"SEL$3")</span></span><br><span class="line"><span class="comment"> NLJ_BATCHING(@"SEL$E3445A69" "A"@"SEL$3")</span></span><br><span class="line"><span class="comment"> END_OUTLINE_DATA</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> </span><br><span class="line">Predicate Information (identified <span class="keyword">by</span> operation id):</span><br><span class="line"><span class="comment">---------------------------------------------------</span></span><br><span class="line"> </span><br><span class="line"> <span class="number">5</span> <span class="operator">-</span> access("C"."UNIT_CODE"<span class="operator">=</span><span class="string">'15803'</span>)</span><br><span class="line"> <span class="number">6</span> <span class="operator">-</span> access("UR"."UNIT_CODE"<span class="operator">=</span><span class="string">'15803'</span> <span class="keyword">AND</span> "UR"."USER_ROLE"<span class="operator"><</span><span class="number">10</span>)</span><br><span class="line"> <span class="keyword">filter</span>("UR"."USER_ROLE"<span class="operator"><</span><span class="number">10</span>)</span><br><span class="line"> <span class="number">7</span> <span class="operator">-</span> access("A"."USER_CODE"<span class="operator">=</span>"UR"."USER_CODE")</span><br></pre></td></tr></table></figure><h4 id="5-2-explain-执行顺序"><a href="#5-2-explain-执行顺序" class="headerlink" title="5.2 explain 执行顺序"></a>5.2 explain 执行顺序</h4><p>所以不管是用 F5 方式还是 set statistics_level=ALL 方式,都有 Operation 参数,Operation 表示 sql 执行过程,查看怎么执行的,有两个规则:</p><ul><li>根据 Operation 缩进判断,缩进最多的最先执行;</li><li>Operation 缩进相同时,最上面的是最先执行的;</li></ul><p>如图执行计划,根据规则,可以得出执行顺序:INDEX UNIQUE SCAN->TABLE ACCESS BY INDEX ROWID->INDEX RANGE SCAN ->NESTED LOOPS ->INDEX UNIQUE SCAN->NESTED LOOPS ->TABLE ACCESS BY INDEX ROWID->NESTED LOOPS-> SELECT STATEMENT<br><img src="https://i-blog.csdnimg.cn/blog_migrate/fe1988bd861883181c54e4083a885736.png"></p><h4 id="5-3-访问数据的方法"><a href="#5-3-访问数据的方法" class="headerlink" title="5.3 访问数据的方法"></a>5.3 访问数据的方法</h4><p>Oracle 访问表中数据的方法有两种,一种是直接表中访问数据,另外一种是先访问索引,如果索引数据不符合目标 SQL,就回表,符合就不回表,直接访问索引就可以。</p><p>Oracle 直接访问表中数据的方法又分为两种:一种是全表扫描;另一种是 ROWID 扫描</p><h5 id="5-3-1-全表扫描(TABLE-ACCESS-FULL)"><a href="#5-3-1-全表扫描(TABLE-ACCESS-FULL)" class="headerlink" title="5.3.1 全表扫描(TABLE ACCESS FULL)"></a>5.3.1 全表扫描(TABLE ACCESS FULL)</h5><ul><li>全表扫描;(TABLE ACCESS FULL)</li></ul><p>全表扫描是 Oracle 直接访问数据的一种方法,全表扫描时从第一个区 (EXTENT) 的第一个块 (BLOCK) 开始扫描,一直扫描的到表的高水位线(High Water Mark),这个范围内的数据块都会扫描到</p><p>全表扫描是采用多数据块一起扫的,并不是一个个数据库扫的,然后我们经常说全表扫描慢是针对数据量很多的情况,数据量少的话,全表扫描并不慢的,不过随着数据量越多,高水位线也就越高,也就是说需要扫描的数据库越多,自然扫描所需要的 IO 越多,时间也越多</p><p>注意:数据量越多,全表扫描所需要的时间就越多,然后直接删了表数据呢?查询速度会变快?其实并不会的,因为即使我们删了数据,高位水线并不会改变,也就是同样需要扫描那么多数据块</p><h5 id="5-3-2-ROWID-扫描(TABLE-ACCESS-BY-ROWID)"><a href="#5-3-2-ROWID-扫描(TABLE-ACCESS-BY-ROWID)" class="headerlink" title="5.3.2 ROWID 扫描(TABLE ACCESS BY ROWID)"></a>5.3.2 ROWID 扫描(TABLE ACCESS BY ROWID)</h5><ul><li>ROWID 扫描(TABLE ACCESS BY ROWID)</li></ul><p>ROWID 也就是表数据行所在的物理存储地址,所谓的 ROWID 扫描是通过 ROWID 所在的数据行记录去定位。ROWID 是一个伪列,数据库里并没有这个列,它是数据库查询过程中获取的一个物理地址,用于表示数据对应的行数。<br>用 sql 查询:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> t.<span class="operator">*</span> , rowid <span class="keyword">from</span> 表格</span><br></pre></td></tr></table></figure><p>随意获取一个 ROWID 序列:AAAWSJAAFAAAWwUAAA,前 6 位表示对象编号 (Data Object number),其后 3 位文件编号 (Relative file number),接着其后 6 位表示块编号 (Block number), 再其后 3 位表示行编号 (Row number)<br><img src="https://i-blog.csdnimg.cn/blog_migrate/3301d5b4e9ee8e09de0226b38286d417.jpeg"></p><p><strong>ROWID 编码方法是:A ~ Z 表示 0 到 25;a ~ z 表示 26 到 51;0~9 表示 52 到 61;+ 表示 62;/ 表示 63;刚好 64 个字符。</strong></p><p>这里随意找张表查一下文件编号、区编号、行编号,查询后会返回 rowid 的一系列物理地址和文件编号 (rowid_relative_fno(rowid))、块编号 (rowid_block_number(rowid))、行编号 (rowid_row_number(rowid))</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> t.seq,</span><br><span class="line"> rowid,</span><br><span class="line"> dbms_rowid.rowid_relative_fno(rowid),</span><br><span class="line"> dbms_rowid.rowid_block_number(rowid),</span><br><span class="line"> dbms_rowid.rowid_row_number(rowid)</span><br><span class="line"> <span class="keyword">from</span> t_info t</span><br></pre></td></tr></table></figure><p>SQL 查询一下表格名称为 TABLE 的对象编码</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> owner,object_id,data_object_id,status <span class="keyword">from</span> dba_objects <span class="keyword">where</span> object_name<span class="operator">=</span><span class="string">'TABLE'</span>;</span><br></pre></td></tr></table></figure><p><strong>相对文件 id 和绝对文件编码</strong><br>相对文件 id 是指相对于表空间,在表空间唯一; 绝对文件编码是指相当于全局数据库而言的,全局唯一;下面 SQL 查询一下相对文件 id 和绝对文件编码</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> file_name,file_id,relative_fno <span class="keyword">from</span> dba_data_files;</span><br></pre></td></tr></table></figure><p>访问索引(TABLE ACCESS BY INDEX SCAN)的情况就比较多了,可以分为:</p><ul><li>索引唯一扫描(INDEX UNIQUE SCAN)</li><li>索引全扫描(INDEX FULL SCAN)</li><li>索引范围扫描(INDEX RANGE SCAN)</li><li>索引快速全扫描(INDEX FAST FULL SCAN)</li><li>索引跳跃式扫描(INDEX SKIP SCAN)</li></ul><h5 id="5-3-3-索引唯一扫描(INDEX-UNIQUE-SCAN)"><a href="#5-3-3-索引唯一扫描(INDEX-UNIQUE-SCAN)" class="headerlink" title="5.3.3 索引唯一扫描(INDEX UNIQUE SCAN)"></a>5.3.3 索引唯一扫描(INDEX UNIQUE SCAN)</h5><ul><li>索引唯一扫描(INDEX UNIQUE SCAN)</li></ul><p>索引唯一性扫描 (INDEX UNIQUE SCAN) 是针对唯一性索引 (UNIQUE INDEX) 来说的,也就是建立唯一性索引才能索引唯一性扫描,唯一性扫描,其结果集只会返回一条记录。</p><ul><li>索引范围扫描(INDEX RANGE SCAN)</li></ul><h5 id="5-3-4-索引范围扫描(INDEX-RANGE-SCAN)"><a href="#5-3-4-索引范围扫描(INDEX-RANGE-SCAN)" class="headerlink" title="5.3.4 索引范围扫描(INDEX RANGE SCAN)"></a>5.3.4 索引范围扫描(INDEX RANGE SCAN)</h5><ul><li>索引范围扫描(INDEX RANGE SCAN)索引范围扫描 (INDEX RANGE SCAN) 适用于所有类型的 B 树索引,一般不包括唯一性索引,因为唯一性索引走索引唯一性扫描。 当扫描的对象是非唯一性索引的情况,where 谓词条件为 Between、=、<、>等等的情况就是索引范围扫描,注意,可以是等值查询,也可以是范围查询。<strong>如果 where 条件里有一个索引键值列没限定为非空的,那就可以走索引范围扫描,如果改索引列是非空的,<em>那就走索引全扫描</em></strong></li></ul><p>前面说了,同样的 SQL 建的索引不同,就可能是走索引唯一性扫描,也有可能走索引范围扫描。在同等的条件下,索引范围扫描所需要的逻辑读和索引唯一性扫描对比,逻辑读如何?索引范围扫描可能返回多条记录,所以优化器为了确认,肯定会多扫描,_<strong>所以在同等条件,索引范围扫描所需要的逻辑读至少会比相应的唯一性扫描的逻辑读多 1</strong>_</p><h5 id="5-3-5-索引全扫描(INDEX-FULL-SCAN)"><a href="#5-3-5-索引全扫描(INDEX-FULL-SCAN)" class="headerlink" title="5.3.5 索引全扫描(INDEX FULL SCAN)"></a>5.3.5 索引全扫描(INDEX FULL SCAN)</h5><ul><li>索引全扫描(INDEX FULL SCAN)</li></ul><p>索引全扫描 (INDEX FULL SCAN) 适用于所有类型的 B 树索引(包括唯一性索引和非唯一性索引)。</p><p>索引全扫描过程简述:索引全扫描是指扫描目标索引所有叶子块的索引行,但不意思着需要扫描所有的分支块,索引全扫描时只需要访问必要的分支块,然后定位到位于改索引最左边的叶子块的第一行索引行,就可以利用改索引叶子块之间的双向指针链表,从左往右依次顺序扫描所有的叶子块的索引行</p><h5 id="5-3-6-索引快速全扫描(INDEX-FAST-FULL-SCAN)"><a href="#5-3-6-索引快速全扫描(INDEX-FAST-FULL-SCAN)" class="headerlink" title="5.3.6 索引快速全扫描(INDEX FAST FULL SCAN)"></a>5.3.6 索引快速全扫描(INDEX FAST FULL SCAN)</h5><ul><li>索引快速全扫描(INDEX FAST FULL SCAN)</li></ul><p>索引快速全扫描和索引全扫描很类似,也适用于所有类型的 B 树索引 (包括唯一性索引和非唯一性索引)。和索引全扫描类似,也是扫描所有叶子块的索引行,这些都是索引快速全扫描和索引全扫描的相同点</p><p>索引快速全扫描和索引全扫描区别:</p><ul><li>索引快速全扫描只适应于 CBO(基于成本的优化器)</li><li>索引快速全扫描可以使用多块读,也可以并行执行</li><li>索引全扫描会按照叶子块排序返回,而索引快速全扫描则是按照索引段内存储块顺序返回</li><li>索引快速全扫描的执行结果不一定是有序的,而索引全扫描的执行结果是有序的,因为索引快速全扫描是根据索引行在磁盘的物理存储顺序来扫描的,不是根据索引行的逻辑顺序来扫描的</li></ul><h5 id="5-3-7-索引跳跃式扫描(INDEX-SKIP-SCAN)"><a href="#5-3-7-索引跳跃式扫描(INDEX-SKIP-SCAN)" class="headerlink" title="5.3.7 索引跳跃式扫描(INDEX SKIP SCAN)"></a>5.3.7 索引跳跃式扫描(INDEX SKIP SCAN)</h5><ul><li>索引跳跃式扫描(INDEX SKIP SCAN)</li></ul><p>索引跳跃式扫描 (INDEX SKIP SCAN) 适用于所有类型的_<strong>复合 B 树索引</strong>_ (包括唯一性索引和非唯一性索引),索引跳跃式扫描可以使那些在 where 条件中没有目标索引的前导列指定查询条件但是有索引的非前导列指定查询条件的目标 SQL 依然可以使用跳跃索引</p><p>如图执行计划就有 INDEX RANGE SCAN、 INDEX UNIQUE SCAN 等等<br><img src="https://i-blog.csdnimg.cn/blog_migrate/7db938c74d0e5d47a5cfd6da4a01056b.png"></p><h4 id="5-4-表连接方法"><a href="#5-4-表连接方法" class="headerlink" title="5.4 表连接方法"></a>5.4 表连接方法</h4><p>如图,执行计划中有如下 NESTED LOOPS 等等这些,是什么?这种其实就是 Oracle 中表连接的方法<br><img src="https://i-blog.csdnimg.cn/blog_migrate/60cd361a40e43973417a64421703bbde.png"></p><p>两个表之间的表连接方法有排序合并连接、嵌套循环连接、哈希连接、笛卡尔连接</p><ul><li><p>排序合并连接(merge sort join)<br>merge sort join 是先将关联表的关联列各自做排序,然后从各自的排序表中抽取数据,到另一个排序表中做匹配</p></li><li><p>嵌套循环连接(Nested loop join)<br>Nested loops 工作方式是循环从一张表中读取数据 (驱动表 outer table),然后访问另一张表(被查找表 inner table, 通常有索引)。驱动表中的每一行与 inner 表中的相应记录 JOIN。类似一个嵌套的循环。<strong>对于被连接的数据子集较小的情况,nested loop 连接是个较好的选择</strong></p></li><li><p>哈希连接(Hash join)<br>散列连接是 CBO 做大数据集连接时的常用方式,优化器使用两个表中较小的表(或数据源)利用连接键在内存中建立散列表,然后扫描较大的表并探测散列表,找出与散列表匹配的行。</p></li><li><p>笛卡尔连接(Cross join)<br>如果两个表做表连接而没有连接条件,而会产生笛卡尔积,在实际工作中应该尽可能避免笛卡尔积</p></li></ul><p>对于这些连接的详细介绍可以查看《收获,不止 sql 调优》一书,或者查看我做的<a href="https://blog.csdn.net/u014427391/article/details/94862797">读书笔记</a></p><h4 id="5-5-explain-参数信息"><a href="#5-5-explain-参数信息" class="headerlink" title="5.5 explain 参数信息"></a>5.5 explain 参数信息</h4><p>前面的学习,我们已经知道了执行计划执行的顺序、sql 是做索引,还是全表扫描,或者是 rowid 扫描,但是如图执行计划还有很多参数,如图,比如 Starts,E-Rows,Cost (%CPU) 等等,这些参数表示什么含义?<br><img src="https://i-blog.csdnimg.cn/blog_migrate/1223ef8a85b499e494cc84a2405a1f9c.png"><br>执行计划关键信息介绍:</p><ul><li>Starts:该 SQL 执行的次数</li><li>E-Rows:为执行计划预计的行数</li><li><a href="http://www.itpub.net/forum.php?mod=viewthread&tid=1264620&highlight=">Cost (%CPU)</a>:CPU cost 在整个 cost 中占的百分比</li><li>A-Rows:实际返回的行数,E-Rows 和 A-Rows 作比较,就可以看出具体那一步执行计划出问题了</li><li>A-Time:每一步实际执行的时间,可以看出耗时的 SQL</li><li>Buffers:每一步实际执行的逻辑读或一致性读</li></ul>]]></content>
<categories>
<category> 技术分享 </category>
</categories>
<tags>
<tag> 技术分享 </tag>
<tag> 数据库 </tag>
<tag> Oracle </tag>
<tag> SQL </tag>
</tags>
</entry>
<entry>
<title>截图工具</title>
<link href="/2024/07/24/%E6%88%AA%E5%9B%BE%E5%B7%A5%E5%85%B7/"/>
<url>/2024/07/24/%E6%88%AA%E5%9B%BE%E5%B7%A5%E5%85%B7/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><p>PinPix</p><p><a href="https://pixpinapp.com/">官网地址</a></p>]]></content>
<categories>
<category> 资源中心 </category>
</categories>
<tags>
<tag> 软件 </tag>
<tag> 工具 </tag>
</tags>
</entry>
<entry>
<title>JVM调优</title>
<link href="/2024/07/24/JVM%E8%B0%83%E4%BC%98/"/>
<url>/2024/07/24/JVM%E8%B0%83%E4%BC%98/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>原文:<a href="https://www.cnblogs.com/three-fighter/p/14644152.html">【JVM进阶之路】十:JVM调优总结</a></p><p>合集:<a href="https://www.cnblogs.com/three-fighter/tag/JVM/">【JVM进阶之路】</a></p><p>合集:<a href="https://www.cnblogs.com/andy-zhou/p/5327288.html">【JVM调优总结】</a></p></blockquote><h1 id="1、调优原则"><a href="#1、调优原则" class="headerlink" title="1、调优原则"></a>1、调优原则</h1><p>JVM调优听起来很高大上,但是要认识到,JVM调优应该是Java性能优化的最后一颗子弹。</p><p><img src="https://img-blog.csdnimg.cn/img_convert/c9acc15e9b0c2fffc9b3e2865726fd62.png" alt="Java项目需要调优吗"></p><p>比较认可廖雪峰老师的观点,要认识到JVM调优不是常规手段,性能问题一般第一选择是优化程序,最后的选择才是进行JVM调优。</p><p><img src="https://img-blog.csdnimg.cn/img_convert/c4034a5794ccb3b71eaddab18a448529.png" alt="调优层级"></p><p>JVM的自动内存管理本来就是为了将开发人员从内存管理的泥潭里拉出来。即使不得不进行JVM调优,也绝对不能拍脑门就去调整参数,一定要全面监控,详细分析性能数据。</p><h1 id="2、JVM调优的时机"><a href="#2、JVM调优的时机" class="headerlink" title="2、JVM调优的时机"></a>2、JVM调优的时机</h1><p>不得不考虑进行JVM调优的是那些情况呢?</p><ul><li>Heap内存(老年代)持续上涨达到设置的最大内存值;</li><li>Full GC 次数频繁;</li><li>GC 停顿时间过长(超过1秒);</li><li>应用出现OutOfMemory 等内存异常;</li><li>应用中有使用本地缓存且占用大量内存空间;</li><li>系统吞吐量与响应性能不高或下降。</li></ul><h1 id="3、JVM调优的目标"><a href="#3、JVM调优的目标" class="headerlink" title="3、JVM调优的目标"></a>3、JVM调优的目标</h1><p>吞吐量、延迟、内存占用三者类似CAP,构成了一个不可能三角,只能选择其中两个进行调优,不可三者兼得。</p><ul><li>延迟:GC低停顿和GC低频率;</li><li>低内存占用;</li><li>高吞吐量;</li></ul><p>选择了其中两个,必然会会以牺牲另一个为代价。</p><p>下面展示了一些JVM调优的量化目标参考实例:</p><ul><li>Heap 内存使用率 <= 70%;</li><li>Old generation内存使用率<= 70%;</li><li>avgpause <= 1秒;</li><li>Full gc 次数0 或 avg pause interval >= 24小时 ;</li></ul><p>注意:不同应用的JVM调优量化目标是不一样的。</p><h1 id="4、JVM调优的步骤"><a href="#4、JVM调优的步骤" class="headerlink" title="4、JVM调优的步骤"></a>4、JVM调优的步骤</h1><p>一般情况下,JVM调优可通过以下步骤进行:</p><ul><li>分析系统系统运行情况:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;</li><li>确定JVM调优量化目标;</li><li>确定JVM调优参数(根据历史JVM参数来调整);</li><li>依次确定调优内存、延迟、吞吐量等指标;</li><li>对比观察调优前后的差异;</li><li>不断的分析和调整,直到找到合适的JVM参数配置;</li><li>找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。</li></ul><p>以上操作步骤中,某些步骤是需要多次不断迭代完成的。一般是从满足程序的内存使用需求开始的,之后是时间延迟的要求,最后才是吞吐量的要求,要基于这个步骤来不断优化,每一个步骤都是进行下一步的基础,不可逆行。</p><p><img src="https://img-blog.csdnimg.cn/img_convert/ff1b03819c6d6eea7fa0fc45ce1fa774.png" alt="JVM调优步骤"></p><h1 id="5、JVM参数"><a href="#5、JVM参数" class="headerlink" title="5、JVM参数"></a>5、JVM参数</h1><p>下面来看一下JDK的JVM参数。</p><h2 id="5-1、基本参数"><a href="#5-1、基本参数" class="headerlink" title="5.1、基本参数"></a>5.1、基本参数</h2><table><thead><tr><th><strong>参数名称</strong></th><th><strong>含义</strong></th><th><strong>默认值</strong></th><th></th></tr></thead><tbody><tr><td>-Xms</td><td>初始堆大小</td><td>内存的1/64</td><td>默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.</td></tr><tr><td>-Xmx</td><td>最大堆大小</td><td>内存的1/4</td><td>默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制</td></tr><tr><td>-Xmn</td><td>年轻代大小</td><td></td><td><strong>注意</strong>:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8</td></tr><tr><td>-XX:NewSize</td><td>设置年轻代大小</td><td></td><td></td></tr><tr><td>-XX:MaxNewSize</td><td>年轻代最大值</td><td></td><td></td></tr><tr><td>-XX:PermSize</td><td>设置持久代(perm gen)初始值</td><td>内存的1/64</td><td>JDK1.8以前</td></tr><tr><td>-XX:MaxPermSize</td><td>设置持久代最大值</td><td>内存的1/4</td><td>JDK1.8以前</td></tr><tr><td>-Xss</td><td>每个线程的堆栈大小</td><td></td><td>JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右 一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长) 和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:”” -Xss is translated in a VM flag named ThreadStackSize” 一般设置这个值就可以了。</td></tr><tr><td>-<em>XX:ThreadStackSize</em></td><td>Thread Stack Size</td><td></td><td>(0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.]</td></tr><tr><td>-XX:NewRatio</td><td>年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)</td><td></td><td>-XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。</td></tr><tr><td>-XX:SurvivorRatio</td><td>Eden区与Survivor区的大小比值</td><td></td><td>设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10</td></tr><tr><td>-XX:LargePageSizeInBytes</td><td>内存页的大小不可设置过大, 会影响Perm的大小</td><td></td><td>=128m</td></tr><tr><td>-XX:+UseFastAccessorMethods</td><td>原始类型的快速优化</td><td></td><td></td></tr><tr><td>-XX:+DisableExplicitGC</td><td>关闭System.gc()</td><td></td><td>这个参数需要严格的测试</td></tr><tr><td>-XX:+ExplicitGCInvokesConcurrent</td><td>关闭System.gc()</td><td>disabled</td><td>Enables invoking of concurrent GC by using the System.gc() request. This option is disabled by default and can be enabled only together with the -XX:+UseConcMarkSweepGC option.</td></tr><tr><td>-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses</td><td>关闭System.gc()</td><td>disabled</td><td>Enables invoking of concurrent GC by using the System.gc() request and unloading of classes during the concurrent GC cycle. This option is disabled by default and can be enabled only together with the -XX:+UseConcMarkSweepGC option.</td></tr><tr><td>-XX:MaxTenuringThreshold</td><td>垃圾最大年龄</td><td></td><td>如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率 该参数只有在串行GC时才有效.</td></tr><tr><td>-XX:+AggressiveOpts</td><td>加快编译</td><td></td><td></td></tr><tr><td>-XX:+UseBiasedLocking</td><td>锁机制的性能改善</td><td></td><td></td></tr><tr><td>-Xnoclassgc</td><td>禁用垃圾回收</td><td></td><td></td></tr><tr><td>-XX:SoftRefLRUPolicyMSPerMB</td><td>每兆堆空闲空间中SoftReference的存活时间</td><td>1s</td><td>softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap</td></tr><tr><td>-XX:PretenureSizeThreshold</td><td>对象超过多大是直接在旧生代分配</td><td>0</td><td>单位字节 新生代采用Parallel Scavenge GC时无效 另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象.</td></tr><tr><td>-XX:TLABWasteTargetPercent</td><td>TLAB占eden区的百分比</td><td>1%</td><td></td></tr><tr><td>-XX:+<em>CollectGen0First</em></td><td>FullGC时是否先YGC</td><td>false</td><td></td></tr></tbody></table><p><strong>Jdk7版本的主要参数</strong></p><table><thead><tr><th><strong>参数名称</strong></th><th><strong>含义</strong></th><th><strong>默认值</strong></th><th></th></tr></thead><tbody><tr><td>-XX:PermSize</td><td>设置持久代</td><td></td><td>Jdk7版本及以前版本</td></tr><tr><td>-XX:MaxPermSize</td><td>设置最大持久代</td><td></td><td>Jdk7版本及以前版本</td></tr></tbody></table><p><strong>Jdk8版本的重要特有参数</strong></p><table><thead><tr><th><strong>参数名称</strong></th><th><strong>含义</strong></th><th><strong>默认值</strong></th><th></th></tr></thead><tbody><tr><td>-XX:MetaspaceSize</td><td>元空间大小</td><td></td><td>Jdk8版本</td></tr><tr><td>-XX:MaxMetaspaceSize</td><td>最大元空间</td><td></td><td>Jdk8版本</td></tr></tbody></table><h2 id="5-2、并行收集器相关参数"><a href="#5-2、并行收集器相关参数" class="headerlink" title="5.2、并行收集器相关参数"></a>5.2、并行收集器相关参数</h2><table><thead><tr><th><strong>参数名称</strong></th><th><strong>含义</strong></th><th><strong>默认值</strong></th><th></th></tr></thead><tbody><tr><td>-XX:+UseParallelGC</td><td>Full GC采用parallel MSC (此项待验证)</td><td></td><td>选择垃圾收集器为并行收集器.此配置仅对年轻代有效.即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集.(此项待验证)</td></tr><tr><td>-XX:+UseParNewGC</td><td>设置年轻代为并行收集</td><td></td><td>可与CMS收集同时使用 JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值</td></tr><tr><td>-XX:ParallelGCThreads</td><td>并行收集器的线程数</td><td></td><td>此值最好配置与处理器数目相等 同样适用于CMS</td></tr><tr><td>-XX:+UseParallelOldGC</td><td>年老代垃圾收集方式为并行收集(Parallel Compacting)</td><td></td><td>这个是JAVA 6出现的参数选项</td></tr><tr><td>-XX:MaxGCPauseMillis</td><td>每次年轻代垃圾回收的最长时间(最大暂停时间)</td><td></td><td>如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值.</td></tr><tr><td>-XX:+UseAdaptiveSizePolicy</td><td>自动选择年轻代区大小和相应的Survivor区比例</td><td></td><td>设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开.</td></tr><tr><td>-XX:GCTimeRatio</td><td>设置垃圾回收时间占程序运行时间的百分比</td><td></td><td>公式为1/(1+n)</td></tr><tr><td>-XX:+<em>ScavengeBeforeFullGC</em></td><td>Full GC前调用YGC</td><td>true</td><td>Do young generation GC prior to a full GC. (Introduced in 1.4.1.)</td></tr></tbody></table><h2 id="5-3、CMS相关参数"><a href="#5-3、CMS相关参数" class="headerlink" title="5.3、CMS相关参数"></a>5.3、CMS相关参数</h2><table><thead><tr><th><strong>参数名称</strong></th><th><strong>含义</strong></th><th><strong>默认值</strong></th><th></th></tr></thead><tbody><tr><td>-XX:+UseConcMarkSweepGC</td><td>使用CMS内存收集</td><td></td><td>测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明.所以,此时年轻代大小最好用-Xmn设置.???</td></tr><tr><td>-XX:+AggressiveHeap</td><td></td><td></td><td>试图是使用大量的物理内存 长时间大内存使用的优化,能检查计算资源(内存, 处理器数量) 至少需要256MB内存 大量的CPU/内存, (在1.4.1在4CPU的机器上已经显示有提升)</td></tr><tr><td>-XX:CMSFullGCsBeforeCompaction</td><td>多少次后进行内存压缩</td><td></td><td>由于并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生”碎片”,使得运行效率降低.此值设置运行多少次GC以后对内存空间进行压缩,整理.</td></tr><tr><td>-XX:+CMSParallelRemarkEnabled</td><td>降低标记停顿</td><td></td><td></td></tr><tr><td>-XX+UseCMSCompactAtFullCollection</td><td>在FULL GC的时候, 对年老代的压缩</td><td></td><td>CMS是不会移动内存的, 因此, 这个非常容易产生碎片, 导致内存不够用, 因此, 内存的压缩这个时候就会被启用。 增加这个参数是个好习惯。 可能会影响性能,但是可以消除碎片</td></tr><tr><td>-XX:+UseCMSInitiatingOccupancyOnly</td><td>使用手动定义初始化定义开始CMS收集</td><td></td><td>禁止hostspot自行触发CMS GC</td></tr><tr><td>-XX:CMSInitiatingOccupancyFraction=70</td><td>使用cms作为垃圾回收 使用70%后开始CMS收集</td><td>92</td><td>为了保证不出现promotion failed(见下面介绍)错误,该值的设置需要满足以下公式<strong>CMSInitiatingOccupancyFraction计算公式</strong></td></tr><tr><td>-XX:CMSInitiatingPermOccupancyFraction</td><td>设置Perm Gen使用到达多少比率时触发</td><td>92</td><td></td></tr><tr><td>-XX:+CMSIncrementalMode</td><td>设置为增量模式</td><td></td><td>用于单CPU情况</td></tr><tr><td>-XX:+CMSClassUnloadingEnabled</td><td></td><td></td><td></td></tr></tbody></table><h2 id="5-4、辅助信息"><a href="#5-4、辅助信息" class="headerlink" title="5.4、辅助信息"></a>5.4、辅助信息</h2><table><thead><tr><th><strong>参数名称</strong></th><th><strong>含义</strong></th><th><strong>默认值</strong></th><th></th></tr></thead><tbody><tr><td>-XX:+PrintGC</td><td></td><td></td><td>输出形式: [GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]</td></tr><tr><td>-XX:+PrintGCDetails</td><td></td><td></td><td>输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]</td></tr><tr><td>-XX:+PrintGCTimeStamps</td><td></td><td></td><td></td></tr><tr><td>-XX:+PrintGC:PrintGCTimeStamps</td><td></td><td></td><td>可与-XX:+PrintGC -XX:+PrintGCDetails混合使用 输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]</td></tr><tr><td>-XX:+PrintGCApplicationStoppedTime</td><td>打印垃圾回收期间程序暂停的时间.可与上面混合使用</td><td></td><td>输出形式:Total time for which application threads were stopped: 0.0468229 seconds</td></tr><tr><td>-XX:+PrintGCApplicationConcurrentTime</td><td>打印每次垃圾回收前,程序未中断的执行时间.可与上面混合使用</td><td></td><td>输出形式:Application time: 0.5291524 seconds</td></tr><tr><td>-XX:+PrintHeapAtGC</td><td>打印GC前后的详细堆栈信息</td><td></td><td></td></tr><tr><td>-Xloggc:filename</td><td>把相关日志信息记录到文件以便分析. 与上面几个配合使用</td><td></td><td></td></tr><tr><td>-XX:+PrintClassHistogram</td><td>garbage collects before printing the histogram.</td><td></td><td></td></tr><tr><td>-XX:+PrintTLAB</td><td>查看TLAB空间的使用情况</td><td></td><td></td></tr><tr><td>XX:+PrintTenuringDistribution</td><td>查看每次minor GC后新的存活周期的阈值</td><td></td><td>Desired survivor size 1048576 bytes, new threshold 7 (max 15) new threshold 7即标识新的存活周期的阈值为7。</td></tr></tbody></table><h1 id="6、主要工具"><a href="#6、主要工具" class="headerlink" title="6、主要工具"></a>6、主要工具</h1><h2 id="6-1、JDK工具"><a href="#6-1、JDK工具" class="headerlink" title="6.1、JDK工具"></a>6.1、JDK工具</h2><p>JDK自带了很多性能监控工具,我们可以用这些工具来监测系统和排查内存性能问题。</p><p><img src="https://img-blog.csdnimg.cn/img_convert/3ae1b6aaef7356b1cf4a8a5562af172c.png" alt="JDK自带工具"></p><h2 id="6-2、Linux-命令行工具"><a href="#6-2、Linux-命令行工具" class="headerlink" title="6.2、Linux 命令行工具"></a>6.2、Linux 命令行工具</h2><p>进行性能监控和问题排查的时候,常常是结合操作系统本身的命令行工具来进行。</p><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td>top</td><td>实时显示正在执行进程的 CPU 使用率、内存使用率以及系统负载等信息</td></tr><tr><td>vmstat</td><td>对操作系统的虚拟内存、进程、CPU活动进行监控</td></tr><tr><td>pidstat</td><td>监控指定进程的上下文切换</td></tr><tr><td>iostat</td><td>监控磁盘IO</td></tr></tbody></table><p>其它还有一些第三方的监控工具,同样是性能分析和故障排查的利器,如<strong>MAT</strong>、<strong>GChisto</strong>、<strong>JProfiler</strong>、<strong>arthas</strong>。</p><h1 id="7、常用调优策略"><a href="#7、常用调优策略" class="headerlink" title="7、常用调优策略"></a>7、常用调优策略</h1><p>这里还是要提一下,及时确定要进行JVM调优,也不要陷入“知见障”,进行分析之后,发现可以通过优化程序提升性能,仍然首选优化程序。</p><h2 id="7-1、选择合适的垃圾回收器"><a href="#7-1、选择合适的垃圾回收器" class="headerlink" title="7.1、选择合适的垃圾回收器"></a>7.1、选择合适的垃圾回收器</h2><p>CPU单核,那么毫无疑问Serial 垃圾收集器是你唯一的选择。</p><p>CPU多核,关注吞吐量 ,那么选择PS+PO组合。</p><p>CPU多核,关注用户停顿时间,JDK版本1.6或者1.7,那么选择CMS。</p><p>CPU多核,关注用户停顿时间,JDK1.8及以上,JVM可用内存6G以上,那么选择G1。</p><p>参数配置:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//设置Serial垃圾收集器(新生代)</span></span><br><span class="line">开启:-XX:+UseSerialGC</span><br><span class="line"></span><br><span class="line"><span class="comment">//设置PS+PO,新生代使用功能Parallel Scavenge 老年代将会使用Parallel Old收集器</span></span><br><span class="line">开启 -XX:+UseParallelOldGC</span><br><span class="line"></span><br><span class="line"><span class="comment">//CMS垃圾收集器(老年代)</span></span><br><span class="line">开启 -XX:+UseConcMarkSweepGC</span><br><span class="line"></span><br><span class="line"><span class="comment">//设置G1垃圾收集器</span></span><br><span class="line">开启 -XX:+UseG1GC</span><br></pre></td></tr></table></figure><h2 id="7-2、调整内存大小"><a href="#7-2、调整内存大小" class="headerlink" title="7.2、调整内存大小"></a>7.2、调整内存大小</h2><p>现象:垃圾收集频率非常频繁。</p><p>原因:如果内存太小,就会导致频繁的需要进行垃圾收集才能释放出足够的空间来创建新的对象,所以增加堆内存大小的效果是非常显而易见的。</p><p>注意:如果垃圾收集次数非常频繁,但是每次能回收的对象非常少,那么这个时候并非内存太小,而可能是内存泄露导致对象无法回收,从而造成频繁GC。</p><p>参数配置:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//设置堆初始值</span></span><br><span class="line">指令<span class="number">1</span>:-Xms2g</span><br><span class="line">指令<span class="number">2</span>:-XX:InitialHeapSize=2048m</span><br><span class="line"></span><br><span class="line"><span class="comment">//设置堆区最大值</span></span><br><span class="line">指令<span class="number">1</span>:`-Xmx2g` </span><br><span class="line">指令<span class="number">2</span>: -XX:MaxHeapSize=2048m</span><br><span class="line"></span><br><span class="line"><span class="comment">//新生代内存配置</span></span><br><span class="line">指令<span class="number">1</span>:-Xmn512m</span><br><span class="line">指令<span class="number">2</span>:-XX:MaxNewSize=512m</span><br></pre></td></tr></table></figure><h2 id="7-3、设置符合预期的停顿时间"><a href="#7-3、设置符合预期的停顿时间" class="headerlink" title="7.3、设置符合预期的停顿时间"></a>7.3、设置符合预期的停顿时间</h2><p>现象:程序间接性的卡顿</p><p>原因:如果没有确切的停顿时间设定,垃圾收集器以吞吐量为主,那么垃圾收集时间就会不稳定。</p><p>注意:不要设置不切实际的停顿时间,单次时间越短也意味着需要更多的GC次数才能回收完原有数量的垃圾.</p><p>参数配置:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//GC停顿时间,垃圾收集器会尝试用各种手段达到这个时间</span></span><br><span class="line">-XX:MaxGCPauseMillis </span><br></pre></td></tr></table></figure><h2 id="7-4、调整内存区域大小比率"><a href="#7-4、调整内存区域大小比率" class="headerlink" title="7.4、调整内存区域大小比率"></a>7.4、调整内存区域大小比率</h2><p>现象:某一个区域的GC频繁,其他都正常。</p><p>原因:如果对应区域空间不足,导致需要频繁GC来释放空间,在JVM堆内存无法增加的情况下,可以调整对应区域的大小比率。</p><p>注意:也许并非空间不足,而是因为内存泄造成内存无法回收。从而导致GC频繁。</p><p>参数配置:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//survivor区和Eden区大小比率</span></span><br><span class="line">指令:-XX:SurvivorRatio=<span class="number">6</span> <span class="comment">//S区和Eden区占新生代比率为1:6,两个S区2:6</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//新生代和老年代的占比</span></span><br><span class="line">-XX:NewRatio=<span class="number">4</span> <span class="comment">//表示新生代:老年代 = 1:4 即老年代占整个堆的4/5;默认值=2</span></span><br></pre></td></tr></table></figure><h2 id="7-5、调整对象升老年代的年龄"><a href="#7-5、调整对象升老年代的年龄" class="headerlink" title="7.5、调整对象升老年代的年龄"></a>7.5、调整对象升老年代的年龄</h2><p>现象:老年代频繁GC,每次回收的对象很多。</p><p>原因:如果升代年龄小,新生代的对象很快就进入老年代了,导致老年代对象变多,而这些对象其实在随后的很短时间内就可以回收,这时候可以调整对象的升级代年龄,让对象不那么容易进入老年代解决老年代空间不足频繁GC问题。</p><p>注意:增加了年龄之后,这些对象在新生代的时间会变长可能导致新生代的GC频率增加,并且频繁复制这些对象新生的GC时间也可能变长。</p><p>配置参数:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//进入老年代最小的GC年龄,年轻代对象转换为老年代对象最小年龄值,默认值7</span></span><br><span class="line"> -XX:InitialTenuringThreshol=<span class="number">7</span> </span><br></pre></td></tr></table></figure><h2 id="7-6、调整大对象的标准"><a href="#7-6、调整大对象的标准" class="headerlink" title="7.6、调整大对象的标准"></a>7.6、调整大对象的标准</h2><p>现象:老年代频繁GC,每次回收的对象很多,而且单个对象的体积都比较大。</p><p>原因:如果大量的大对象直接分配到老年代,导致老年代容易被填满而造成频繁GC,可设置对象直接进入老年代的标准。</p><p>注意:这些大对象进入新生代后可能会使新生代的GC频率和时间增加。</p><p>配置参数:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//新生代可容纳的最大对象,大于则直接会分配到老年代,0代表没有限制。</span></span><br><span class="line"> -XX:PretenureSizeThreshold=<span class="number">1000000</span> </span><br></pre></td></tr></table></figure><h2 id="7-7、调整GC的触发时机"><a href="#7-7、调整GC的触发时机" class="headerlink" title="7.7、调整GC的触发时机"></a>7.7、调整GC的触发时机</h2><p>现象:CMS,G1 经常 Full GC,程序卡顿严重。</p><p>原因:G1和CMS 部分GC阶段是并发进行的,业务线程和垃圾收集线程一起工作,也就说明垃圾收集的过程中业务线程会生成新的对象,所以在GC的时候需要预留一部分内存空间来容纳新产生的对象,如果这个时候内存空间不足以容纳新产生的对象,那么JVM就会停止并发收集暂停所有业务线程(STW)来保证垃圾收集的正常运行。这个时候可以调整GC触发的时机(比如在老年代占用60%就触发GC),这样就可以预留足够的空间来让业务线程创建的对象有足够的空间分配。</p><p>注意:提早触发GC会增加老年代GC的频率。</p><p>配置参数:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//使用多少比例的老年代后开始CMS收集,默认是68%,如果频繁发生SerialOld卡顿,应该调小</span></span><br><span class="line">-XX:CMSInitiatingOccupancyFraction</span><br><span class="line"></span><br><span class="line"><span class="comment">//G1混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%</span></span><br><span class="line">-XX:G1MixedGCLiveThresholdPercent=<span class="number">65</span> </span><br></pre></td></tr></table></figure><h2 id="7-8、调整-JVM本地内存大小"><a href="#7-8、调整-JVM本地内存大小" class="headerlink" title="7.8、调整 JVM本地内存大小"></a>7.8、调整 JVM本地内存大小</h2><p>现象:GC的次数、时间和回收的对象都正常,堆内存空间充足,但是报OOM</p><p>原因: JVM除了堆内存之外还有一块堆外内存,这片内存也叫本地内存,可是这块内存区域不足了并不会主动触发GC,只有在堆内存区域触发的时候顺带会把本地内存回收了,而一旦本地内存分配不足就会直接报OOM异常。</p><p>注意: 本地内存异常的时候除了上面的现象之外,异常信息可能是OutOfMemoryError:Direct buffer memory。 解决方式除了调整本地内存大小之外,也可以在出现此异常时进行捕获,手动触发GC(System.gc())。</p><p>配置参数:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">XX:MaxDirectMemorySize</span><br></pre></td></tr></table></figure><h1 id="8、JVM调优实例"><a href="#8、JVM调优实例" class="headerlink" title="8、JVM调优实例"></a>8、JVM调优实例</h1><p>以下是整理自网络的一些JVM调优实例:</p><h2 id="8-1、网站流量浏览量暴增后,网站反应页面响很慢"><a href="#8-1、网站流量浏览量暴增后,网站反应页面响很慢" class="headerlink" title="8.1、网站流量浏览量暴增后,网站反应页面响很慢"></a>8.1、网站流量浏览量暴增后,网站反应页面响很慢</h2><ol><li>问题推测:在测试环境测速度比较快,但是一到生产就变慢,所以推测可能是因为垃圾收集导致的业务线程停顿。</li><li>定位:为了确认推测的正确性,在线上通过jstat -gc 指令 看到JVM进行GC 次数频率非常高,GC所占用的时间非常长,所以基本推断就是因为GC频率非常高,所以导致业务线程经常停顿,从而造成网页反应很慢。</li><li>解决方案:因为网页访问量很高,所以对象创建速度非常快,导致堆内存容易填满从而频繁GC,所以这里问题在于新生代内存太小,所以这里可以增加JVM内存就行了,所以初步从原来的2G内存增加到16G内存。</li><li>第二个问题:增加内存后的确平常的请求比较快了,但是又出现了另外一个问题,就是不定期的会间断性的卡顿,而且单次卡顿的时间要比之前要长很多。</li><li>问题推测:练习到是之前的优化加大了内存,所以推测可能是因为内存加大了,从而导致单次GC的时间变长从而导致间接性的卡顿。</li><li>定位:还是通过jstat -gc 指令 查看到 的确FGC次数并不是很高,但是花费在FGC上的时间是非常高的,根据GC日志 查看到单次FGC的时间有达到几十秒的。</li><li>解决方案: 因为JVM默认使用的是PS+PO的组合,PS+PO垃圾标记和收集阶段都是STW,所以内存加大了之后,需要进行垃圾回收的时间就变长了,所以这里要想避免单次GC时间过长,所以需要更换并发类的收集器,因为当前的JDK版本为1.7,所以最后选择CMS垃圾收集器,根据之前垃圾收集情况设置了一个预期的停顿的时间,上线后网站再也没有了卡顿问题。</li></ol><h2 id="8-2、后台导出数据引发的OOM"><a href="#8-2、后台导出数据引发的OOM" class="headerlink" title="8.2、后台导出数据引发的OOM"></a>8.2、后台导出数据引发的OOM</h2><p><strong>问题描述:</strong> 公司的后台系统,偶发性的引发OOM异常,堆内存溢出。</p><ol><li>因为是偶发性的,所以第一次简单的认为就是堆内存不足导致,所以单方面的加大了堆内存从4G调整到8G。</li><li>但是问题依然没有解决,只能从堆内存信息下手,通过开启了-XX:+HeapDumpOnOutOfMemoryError参数 获得堆内存的dump文件。</li><li>VisualVM 对 堆dump文件进行分析,通过VisualVM查看到占用内存最大的对象是String对象,本来想跟踪着String对象找到其引用的地方,但dump文件太大,跟踪进去的时候总是卡死,而String对象占用比较多也比较正常,最开始也没有认定就是这里的问题,于是就从线程信息里面找突破点。</li><li>通过线程进行分析,先找到了几个正在运行的业务线程,然后逐一跟进业务线程看了下代码,发现有个引起我注意的方法,导出订单信息。</li><li>因为订单信息导出这个方法可能会有几万的数据量,首先要从数据库里面查询出来订单信息,然后把订单信息生成excel,这个过程会产生大量的String对象。</li><li>为了验证自己的猜想,于是准备登录后台去测试下,结果在测试的过程中发现到处订单的按钮前端居然没有做点击后按钮置灰交互事件,结果按钮可以一直点,因为导出订单数据本来就非常慢,使用的人员可能发现点击后很久后页面都没反应,结果就一直点,结果就大量的请求进入到后台,堆内存产生了大量的订单对象和EXCEL对象,而且方法执行非常慢,导致这一段时间内这些对象都无法被回收,所以最终导致内存溢出。</li><li>知道了问题就容易解决了,最终没有调整任何JVM参数,只是在前端的导出订单按钮上加上了置灰状态,等后端响应之后按钮才可以进行点击,然后减少了查询订单信息的非必要字段来减少生成对象的体积,然后问题就解决了。</li></ol><h2 id="8-3、单个缓存数据过大导致的系统CPU飚高"><a href="#8-3、单个缓存数据过大导致的系统CPU飚高" class="headerlink" title="8.3、单个缓存数据过大导致的系统CPU飚高"></a>8.3、单个缓存数据过大导致的系统CPU飚高</h2><ol><li>系统发布后发现CPU一直飚高到600%,发现这个问题后首先要做的是定位到是哪个应用占用CPU高,通过top 找到了对应的一个java应用占用CPU资源600%。</li><li>如果是应用的CPU飚高,那么基本上可以定位可能是锁资源竞争,或者是频繁GC造成的。</li><li>所以准备首先从GC的情况排查,如果GC正常的话再从线程的角度排查,首先使用jstat -gc PID 指令打印出GC的信息,结果得到得到的GC 统计信息有明显的异常,应用在运行了才几分钟的情况下GC的时间就占用了482秒,那么问这很明显就是频繁GC导致的CPU飚高。</li><li>定位到了是GC的问题,那么下一步就是找到频繁GC的原因了,所以可以从两方面定位了,可能是哪个地方频繁创建对象,或者就是有内存泄露导致内存回收不掉。</li><li>根据这个思路决定把堆内存信息dump下来看一下,使用jmap -dump 指令把堆内存信息dump下来(堆内存空间大的慎用这个指令否则容易导致会影响应用,因为我们的堆内存空间才2G所以也就没考虑这个问题了)。</li><li>把堆内存信息dump下来后,就使用visualVM进行离线分析了,首先从占用内存最多的对象中查找,结果排名第三看到一个业务VO占用堆内存约10%的空间,很明显这个对象是有问题的。</li><li>通过业务对象找到了对应的业务代码,通过代码的分析找到了一个可疑之处,这个业务对象是查看新闻资讯信息生成的对象,由于想提升查询的效率,所以把新闻资讯保存到了redis缓存里面,每次调用资讯接口都是从缓存里面获取。</li><li>把新闻保存到redis缓存里面这个方式是没有问题的,有问题的是新闻的50000多条数据都是保存在一个key里面,这样就导致每次调用查询新闻接口都会从redis里面把50000多条数据都拿出来,再做筛选分页拿出10条返回给前端。50000多条数据也就意味着会产生50000多个对象,每个对象280个字节左右,50000个对象就有13.3M,这就意味着只要查看一次新闻信息就会产生至少13.3M的对象,那么并发请求量只要到10,那么每秒钟都会产生133M的对象,而这种大对象会被直接分配到老年代,这样的话一个2G大小的老年代内存,只需要几秒就会塞满,从而触发GC。</li><li>知道了问题所在后那么就容易解决了,问题是因为单个缓存过大造成的,那么只需要把缓存减小就行了,这里只需要把缓存以页的粒度进行缓存就行了,每个key缓存10条作为返回给前端1页的数据,这样的话每次查询新闻信息只会从缓存拿出10条数据,就避免了此问题的 产生。</li></ol><h2 id="8-4、CPU经常100-问题定位"><a href="#8-4、CPU经常100-问题定位" class="headerlink" title="8.4、CPU经常100% 问题定位"></a>8.4、CPU经常100% 问题定位</h2><p>问题分析:CPU高一定是某个程序长期占用了CPU资源。</p><ol><li><p>所以先需要找出那个进行占用CPU高。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">top 列出系统各个进程的资源占用情况。</span><br></pre></td></tr></table></figure></li><li><p>然后根据找到对应进行里哪个线程占用CPU高。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">top -Hp 进程ID 列出对应进程里面的线程占用资源情况</span><br></pre></td></tr></table></figure></li><li><p>找到对应线程ID后,再打印出对应线程的堆栈信息</p><figure class="highlight shell"><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">printf "%x\n" PID 把线程ID转换为16进制。</span><br><span class="line">jstack PID 打印出进程的所有线程信息,从打印出来的线程信息中找到上一步转换为16进制的线程ID对应的线程信息。</span><br></pre></td></tr></table></figure></li><li><p>最后根据线程的堆栈信息定位到具体业务方法,从代码逻辑中找到问题所在。</p><figure class="highlight shell"><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">查看是否有线程长时间的watting 或blocked</span><br><span class="line">如果线程长期处于watting状态下, 关注watting on xxxxxx,说明线程在等待这把锁,然后根据锁的地址找到持有锁的线程。</span><br></pre></td></tr></table></figure></li></ol><h2 id="8-5、内存飚高问题定位"><a href="#8-5、内存飚高问题定位" class="headerlink" title="8.5、内存飚高问题定位"></a>8.5、内存飚高问题定位</h2><p>分析: 内存飚高如果是发生在java进程上,一般是因为创建了大量对象所导致,持续飚高说明垃圾回收跟不上对象创建的速度,或者内存泄露导致对象无法回收。</p><ol><li><p>先观察垃圾回收的情况</p><figure class="highlight shell"><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">jstat -gc PID 1000 查看GC次数,时间等信息,每隔一秒打印一次。</span><br><span class="line"> </span><br><span class="line">jmap -histo PID | head -20 查看堆内存占用空间最大的前20个对象类型,可初步查看是哪个对象占用了内存。</span><br></pre></td></tr></table></figure><p>如果每次GC次数频繁,而且每次回收的内存空间也正常,那说明是因为对象创建速度快导致内存一直占用很高;如果每次回收的内存非常少,那么很可能是因为内存泄露导致内存一直无法被回收。</p></li><li><p>导出堆内存文件快照</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmap -dump:live,format=b,file=/home/myheapdump.hprof PID dump堆内存信息到文件。</span><br></pre></td></tr></table></figure></li><li><p>使用visualVM对dump文件进行离线分析,找到占用内存高的对象,再找到创建该对象的业务代码位置,从代码和业务场景中定位具体问题。</p></li></ol><h2 id="8-6、数据分析平台系统频繁-Full-GC"><a href="#8-6、数据分析平台系统频繁-Full-GC" class="headerlink" title="8.6、数据分析平台系统频繁 Full GC"></a>8.6、数据分析平台系统频繁 Full GC</h2><p>平台主要对用户在 App 中行为进行定时分析统计,并支持报表导出,使用 CMS GC 算法。</p><p>数据分析师在使用中发现系统页面打开经常卡顿,通过 jstat 命令发现系统每次 Young GC 后大约有 10% 的存活对象进入老年代。</p><p>原来是因为 Survivor 区空间设置过小,每次 Young GC 后存活对象在 Survivor 区域放不下,提前进入老年代。</p><p>通过调大 Survivor 区,使得 Survivor 区可以容纳 Young GC 后存活对象,对象在 Survivor 区经历多次 Young GC 达到年龄阈值才进入老年代。</p><p>调整之后每次 Young GC 后进入老年代的存活对象稳定运行时仅几百 Kb,Full GC 频率大大降低。</p><h2 id="8-7、业务对接网关-OOM"><a href="#8-7、业务对接网关-OOM" class="headerlink" title="8.7、业务对接网关 OOM"></a>8.7、业务对接网关 OOM</h2><p>网关主要消费 Kafka 数据,进行数据处理计算然后转发到另外的 Kafka 队列,系统运行几个小时候出现 OOM,重启系统几个小时之后又 OOM。</p><p>通过 jmap 导出堆内存,在 eclipse MAT 工具分析才找出原因:代码中将某个业务 Kafka 的 topic 数据进行日志异步打印,该业务数据量较大,大量对象堆积在内存中等待被打印,导致 OOM。</p><h2 id="8-8、鉴权系统频繁长时间-Full-GC"><a href="#8-8、鉴权系统频繁长时间-Full-GC" class="headerlink" title="8.8、鉴权系统频繁长时间 Full GC"></a>8.8、鉴权系统频繁长时间 Full GC</h2><p>系统对外提供各种账号鉴权服务,使用时发现系统经常服务不可用,通过 Zabbix 的监控平台监控发现系统频繁发生长时间 Full GC,且触发时老年代的堆内存通常并没有占满,发现原来是业务代码中调用了 System.gc()。</p><hr><p><strong>参考:</strong></p><p>【1】:周志明编著《深入理解Java虚拟机:JVM高级特性与最佳实践》</p><p>【2】:《实战JAVA虚拟机 JVM故障诊断与性能优化》</p><p>【3】:<a href="https://www.choupangxia.com/2019/11/11/interview-jvm-gc-08/">JVM性能调优详解</a></p><p>【4】:<a href="https://juejin.cn/post/6844903506093015053">如何合理的规划一次jvm性能调优</a></p><p>【5】:<a href="https://juejin.cn/post/6844903953415536654">关于GC原理和性能调优实践,看这一篇就够了</a></p><p>【6】:<a href="https://zhuanlan.zhihu.com/p/90766088">Java 应用性能调优实践</a></p><p>【7】:<a href="https://zhuanlan.zhihu.com/p/269597178">JVM实战:JVM调优策略</a></p><p>【8】:<a href="https://www.zhihu.com/question/362201242">一般的Java项目需要JVM调优吗?</a></p><p>【9】:<a href="https://www.jianshu.com/p/4ae227a86d68">Java8 JVM参数解读</a></p><p>【10】:<a href="https://www.cnblogs.com/halberts/p/11918326.html">JVM参数设置-jdk8参数设置</a></p><p>【11】:<a href="https://juejin.cn/post/6844903802378665997">JVM面试问题系列:JVM 配置常用参数和常用 GC 调优策略</a></p><p>【12】:<a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html">Java1.8的jvm参数官方网站地址</a></p>]]></content>
<categories>
<category> 技术分享 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> JVM </tag>
<tag> 技术分享 </tag>
</tags>
</entry>
<entry>
<title>面试总结</title>
<link href="/2024/07/15/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/"/>
<url>/2024/07/15/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<meta name="referrer" content="no-referrer"/><h1 id="Java-基础"><a href="#Java-基础" class="headerlink" title="Java 基础"></a>Java 基础</h1><h2 id="1、Java的基本数据类型有哪些,占几个字节"><a href="#1、Java的基本数据类型有哪些,占几个字节" class="headerlink" title="1、Java的基本数据类型有哪些,占几个字节"></a>1、Java的基本数据类型有哪些,占几个字节</h2><p>八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。</p><ol><li><strong>整型:byte、short、int、long</strong></li><li><strong>浮点型:float、double</strong></li><li><strong>字符型:char</strong></li><li><strong>布尔型:boolean</strong></li></ol><table><thead><tr><th>序号</th><th>数据类型</th><th>位数</th><th>字节</th><th>默认值</th><th>取值范围</th><th>举例说明</th></tr></thead><tbody><tr><td>1</td><td>byte(位)</td><td>8</td><td>1</td><td>0</td><td>-2^7 - 2^7-1</td><td>byte e = 10;</td></tr><tr><td>2</td><td>short(短整数)</td><td>16</td><td>2</td><td>0</td><td>-2^15 - 2^15-1</td><td>short s = 10;</td></tr><tr><td>3</td><td>int(整数)</td><td>32</td><td>4</td><td>0</td><td>-2^31 - 2^31-1</td><td>int i = 10;</td></tr><tr><td>4</td><td>long(长整数)</td><td>64</td><td>8</td><td>0</td><td>-2^63 - 2^63-1</td><td>long l = 10l;</td></tr><tr><td>5</td><td>float(单精度)</td><td>32</td><td>4</td><td>0.0</td><td>-2^31 - 2^31-1</td><td>float f = 10.0f;</td></tr><tr><td>6</td><td>double(双精度)</td><td>64</td><td>8</td><td>0.0</td><td>-2^63 - 2^63-1</td><td>double d = 10.0d;</td></tr><tr><td>7</td><td>char(字符)</td><td>16</td><td>2</td><td>null</td><td>0 - 2^16-1</td><td>char e = ‘c’;</td></tr><tr><td>8</td><td>boolean(布尔值)</td><td>8</td><td>1</td><td>flase</td><td>true、false</td><td>boolean b = true;</td></tr></tbody></table><h2 id="2、String类型比较"><a href="#2、String类型比较" class="headerlink" title="2、String类型比较"></a>2、String类型比较</h2><blockquote><p><strong>string a=“string”</strong></p><p><strong>string b=“string</strong></p><p><strong>string c=new string(“string”);</strong></p><p><strong>a == b,a == c,结果分别是什么,为什么</strong></p></blockquote><p>true,false</p><p>ab 为 true 是因为直接 String a= “string”;这样创建 String 类型的字符串时,它是在字符串常量池中先查找是否有某个地址空间存在”string”这个 String 类型的对象,如果没有就创建”string”这样的对象,然后将其所在的地址复制一份交给 String 类型的变量空间 a 中,在申请创建 String 类型的变量 b 时,会优先再次去字符串常量池去查看是否存在某个地址空间存在”string”这个 String 类型的对象,因为之前已经创建过了,所以直接将对象”string”的地址直接复制一份交给 b。 </p><p>ac为false是因为 String c= new String(“string”); 这里采用new关键字创建了String类型的对象内value属性数组中存储的是s,t,r,i,n,g这6个字符。new关键字一出,说明这里是在堆内存中申请创建的对象空间。所以地址空间肯定不一样.</p><h2 id="3、AOP是什么,有用过哪里"><a href="#3、AOP是什么,有用过哪里" class="headerlink" title="3、AOP是什么,有用过哪里"></a>3、AOP是什么,有用过哪里</h2><p>AOP 是面向切面编程,可以在原有方法的基础上增加增强方法,比较常用的日志记录,在接口或者方法上面用注解的形式增加日志记录功能,记录调用方法的时间、操作内容、操作人信息、ip 等信息。<br>还有动态切换数据源,有些数据有地区之分,需要动态的去切换地区,使用对应的数据源。</p><h2 id="4、HashMap-原理,线程安全吗"><a href="#4、HashMap-原理,线程安全吗" class="headerlink" title="4、HashMap 原理,线程安全吗"></a>4、HashMap 原理,线程安全吗</h2><h3 id="1-HashMap原理"><a href="#1-HashMap原理" class="headerlink" title="1.HashMap原理"></a>1.HashMap原理</h3><p>HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的。</p><p><code>HashMap</code> 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个</p><p><strong>JDK1.8 之前</strong> HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。</p><p> <strong>JDK1.8 以后</strong>的 <code>HashMap</code> 在解决哈希冲突时有了较大的变化,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树,当链表长度大于等于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。</p><p><code>HashMap</code> 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, <code>HashMap</code> 总是使用 2 的幂作为哈希表的大小。</p><p><strong>哈希冲突:哈希表是有限的,而键的数量可能是无限的,因此不同的键经过哈希函数计算后可能会映射到相同的哈希桶(数组的位置)。</strong></p><p><strong>HashMap如果数组小于长度小于64则会扩容,HashMap初始大小为16,扩容为2的幂次方,在链表长度大于8,且数组长度大于64时将链表转换为红黑树。</strong></p><h3 id="2-HashMap扩容机制"><a href="#2-HashMap扩容机制" class="headerlink" title="2.HashMap扩容机制"></a>2.HashMap扩容机制</h3><ol><li>当哈希表的负载因子(load factor)超过阈值时,即元素数量超过容量的乘积与负载因子的乘积,HashMap 将触发扩容操作。默认情况下,负载因子的阈值为 0.75。</li><li>扩容时,HashMap 会创建一个新的数组,其容量为原数组的两倍。</li><li>遍历原数组中的每个非空桶,将其中的键值对重新计算哈希值,并根据新的数组长度定位到新的位置,然后存储到新的数组中。</li><li>扩容操作完成后,新数组中的桶链(如果存在冲突)的顺序可能发生改变,但是哈希表的结构保持不变。</li></ol><p><strong>HashMap的put方法:</strong></p><ol><li>首先判断数组是否为空,如果是,则进行初始化。</li><li>其次,根据**(n - 1) & hash**求出要添加对象所在的索引位置,判断此索引的内容是否为空,如果是,则直接存储,</li><li>如果不是,则判断索引位置的对象和要存储的对象是否相同,首先判断hash值知否相等,在判断key是否相等。(1.两个对象的hash值不同,一定不是同一个对象。2.hash值相同,两个对象也不一定相等)。如果是同一个对象,则直接进行覆盖,返回原值。</li><li>如果不是,则判断是否为树节点对象,如果是,直接添加</li><li>当既不是相同对象,又不是树节点,直接将其插入到链表的尾部。在进行判断是否需要进行树化。</li><li>最后,判断hashmap的size是否达到阈值,进行扩容resize()处理。</li></ol><h3 id="3-ConcurrentHashMap底层原理"><a href="#3-ConcurrentHashMap底层原理" class="headerlink" title="3.ConcurrentHashMap底层原理"></a>3.ConcurrentHashMap底层原理</h3><blockquote><p>使用synchronized锁加CAS机制,Node数组+链表/红黑树,node是一个类似于一个hashentry的结构。它的冲突在达到一定大小时会转化成红黑树,在冲突小于一定数量时又会退回链表。</p></blockquote><p>put方法</p><ol><li>如果没有初始化就先调用initTable()方法来进行初始化过程</li><li>如果没有哈希冲突就直接CAS插入</li><li>如果还在进行扩容操作就先进行扩容</li><li>如果存在哈希冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,</li><li>最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环</li><li>如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容</li></ol><h3 id="4-ConcurrentHashMap和HashMap"><a href="#4-ConcurrentHashMap和HashMap" class="headerlink" title="4.ConcurrentHashMap和HashMap"></a>4.ConcurrentHashMap和HashMap</h3><ol><li><p>安全性</p><p>HashMap是线程不安全的,ConcurrentHashMap是线程安全的,ConcurrentHashMap通过synchronized和CAS实现的线程安全。</p></li><li><p>数据结构</p><p>数组+链表/红黑树</p></li></ol><h3 id="5-HashMap和HashTable的区别"><a href="#5-HashMap和HashTable的区别" class="headerlink" title="5.HashMap和HashTable的区别"></a>5.HashMap和HashTable的区别</h3><ol><li><p>对Null key 和Null value的支持</p><p>HashMap支持null的键(key)和值(value),HashTable不支持null,会报空指针</p></li><li><p>线程安全</p><p>HashMap线程不安全,HashTable因为有synchronized修饰所以线程安全</p></li><li><p>效率</p><p>因为线程安全性,单线程下HashMap比HashTable效率高</p></li><li><p>初始容量</p><p>HashMap初始大小为16,扩容为2的幂次方。</p><p>HashTable初始为11,扩容为2n+1。</p></li><li><p>底层结构</p><p>HashMap会将链表长度大于阈值是转化为红黑树(会先判断当前数组的长度是否小于 64,是则扩容,而不转化),将链表转化为红黑树,以减少搜索时间。</p><p>Hashtable 没有这样的机制。</p></li></ol><h3 id="6-hashtable和concurrenthashmap"><a href="#6-hashtable和concurrenthashmap" class="headerlink" title="6.hashtable和concurrenthashmap"></a>6.hashtable和concurrenthashmap</h3><p>hashtable的线程安全是因为使用单锁,这极大的影响了性能</p><p>concurrenthashtable放弃了单锁,用的锁分离,synchronized+CAS</p><h3 id="7-CountDownLatch"><a href="#7-CountDownLatch" class="headerlink" title="7.CountDownLatch"></a>7.CountDownLatch</h3><p>运行count个线程阻塞在一个地方,直到线程的任务都执行完</p><p>使用线程池处理读取任务,每一个线程处理完之后就将 count-1,调用<code>CountDownLatch</code>对象的 <code>await()</code>方法,直到所有文件读取完之后,才会接着执行后面的逻辑</p><h2 id="5、ArrayList和LinkedList区别"><a href="#5、ArrayList和LinkedList区别" class="headerlink" title="5、ArrayList和LinkedList区别"></a>5、ArrayList和LinkedList区别</h2><ol><li><p>数据结构不同</p><p>ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。</p></li><li><p>效率不同</p><p>当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高,因为ArrayList可以根据下标进行查找,LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。</p><p>当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为ArrayList在进行增删操作时,会对操作数据的下标索引造成影响,需要对操作数据附近的数据进行移动。</p></li><li><p>容量性质不同</p><p>ArrayList有一个初始容量,刚创建ArrayList对象时不会定义底层数组长度,第一次调用add方法时会初始化长度为10,之后调用add方法会先调用ensure方法判断够不够,不够就会调用grow方法扩容,长度变为原来的1.5倍。</p><p>LinkedList能够动态的随数据量的变化而变化。</p></li><li><p>主要控件开销不同</p><p>ArrayList主要控件开销在于需要在List列表预留一定空间;</p><p>LinkedList主要控件开销在于需要存储结点信息以及结点指针信息。</p></li></ol><h2 id="6、-Transactional(rollback-Exception-class),有没有可能会失效,不加-rollback-会怎么样"><a href="#6、-Transactional(rollback-Exception-class),有没有可能会失效,不加-rollback-会怎么样" class="headerlink" title="6、@Transactional(rollback=Exception. class),有没有可能会失效,不加 rollback 会怎么样"></a>6、@Transactional(rollback=Exception. class),有没有可能会失效,不加 rollback 会怎么样</h2><p>@Transactional 失效的 4 种情况:</p><ol><li><p><strong>@Transaction 应用在非 public 修饰的方法上</strong></p><p>因为 <code>@Transactional</code> 是基于动态代理实现的</p></li><li><p><strong>@Transactional 注解属性 propagation 设置错误</strong></p><p>这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。</p><ol><li><code>TransactionDefinition.PROPAGATION_SUPPORTS</code>:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。</li><li><code>TransactionDefinition.PROPAGATION_NOT_SUPPORTED</code>:以非事务方式运行,如果当前存在事务,则把当前事务挂起。</li><li><code>TransactionDefinition.PROPAGATION_NEVER</code>:以非事务方式运行,如果当前存在事务,则抛出异常。</li></ol></li><li><p><strong>@Transactional 注解属性 rollbackFor 设置错误</strong></p><p>rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor 属性。</p></li><li><p><strong>同一个类中方法调用,导致 @Transactional 失效</strong></p><p>开发中避免不了会对同一个类里面的方法调用,比如有一个类 Test,它的一个方法 A,A 再调用本类的方法 B(不论方法 B 是用 public 还是 private 修饰),但方法 A 没有声明注解事务,而 B 方法有。则外部调用方法 A 之后,方法 B 的事务是不会起作用的。这也是经常犯错误的一个地方。</p></li></ol><h2 id="7、SpringMVC-流程"><a href="#7、SpringMVC-流程" class="headerlink" title="7、SpringMVC 流程"></a>7、SpringMVC 流程</h2><p><img src="https://img-blog.csdnimg.cn/75ec98a325394e748c10d1903f4ee419.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6bqm55Sw6YeM55qE5a6I5pyb6ICF5ZGA,size_17,color_FFFFFF,t_70,g_se,x_16" alt="img"></p><ol><li>用户通过浏览器发起 request 请求到前端控制器(DispatcherServlet)</li><li>前端控制器(DispatcherServlet)将请求发送到处理器映射器(HandlerMapping)</li><li>处理器映射器(HandlerMapping)根据请求找到对应的处理器(Controller),封装返回前端控制器(DispatcherServlet)</li><li>前端控制器(DispatcherServlet)会根据返回的处理器找到对应的处理器适配器(HandlerAdaptor)</li><li>处理器适配器(HandlerAdaptor)会调用对应的 Controller</li><li>Controller 将处理结果和跳转视图封装到 ModelAndView 返回到处理器适配器(HandlerAdaptor)</li><li>处理器适配器(HandlerAdaptor)将 ModelAndView 返回到前端控制器(DispatcherServlet)</li><li>前端控制器(DispatcherServlet)调用视图解析器(ViewResolver)对 ModelAndView 解析</li><li>视图解析器(ViewResolver)将解析出来的视图(View)封装成视图对象返回前端控制器(DispatcherServlet)</li><li>前端控制器(DispatcherServlet)调用视图对象进行视图渲染(将数据模型< model >填充到视图< view >中), 形成 response</li><li>前端控制器(DispatcherServlet)返回 response 到浏览器,展示在页面上</li></ol><h2 id="8、BIO、NIO、AIO"><a href="#8、BIO、NIO、AIO" class="headerlink" title="8、BIO、NIO、AIO"></a>8、BIO、NIO、AIO</h2><p><a href="https://blog.csdn.net/qq_40378034/article/details/119710529">https://blog.csdn.net/qq_40378034/article/details/119710529</a></p><ul><li>BIO:同步并阻塞</li><li>NIO:同步非阻塞</li><li>AIO:异步非阻塞</li></ul><h1 id="高并发"><a href="#高并发" class="headerlink" title="高并发"></a>高并发</h1><h2 id="1、线程的生命周期和状态"><a href="#1、线程的生命周期和状态" class="headerlink" title="1、线程的生命周期和状态"></a>1、线程的生命周期和状态</h2><blockquote><p><a href="https://www.jianshu.com/p/dca1f3f4588d">https://www.jianshu.com/p/dca1f3f4588d</a></p></blockquote><h3 id="1-线程的生命周期包含-5-个阶段:新建、就绪、运行、阻塞、销毁"><a href="#1-线程的生命周期包含-5-个阶段:新建、就绪、运行、阻塞、销毁" class="headerlink" title="1. 线程的生命周期包含 5 个阶段:新建、就绪、运行、阻塞、销毁"></a>1. 线程的生命周期包含 5 个阶段:<strong>新建</strong>、<strong>就绪</strong>、<strong>运行</strong>、<strong>阻塞</strong>、<strong>销毁</strong></h3><ol><li><p><strong>新建:</strong> 刚使用 new 方法创建出来的线程;</p></li><li><p><strong>就绪:</strong> 调用线程的 start ()方法后,线程处于等待 CPU 分配资源阶段,当线程获取到 CPU 资源后开始执行;</p></li><li><p><strong>运行:</strong> 当就绪的线程被调度并获得 CPU 资源时,便会进入运行状态,run ()方法定义了线程的操作和功能;</p></li><li><p><strong>阻塞:</strong> 在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,如 sleep ()、wait ()之后,线程就会处于阻塞状态。这个时候需要其他机制将处于阻塞状态的线程唤醒,如 notify ()、notifyAll ()方法。被唤醒的线程不会立即执行 run 方法,会回到就绪阶段,再次等待 CPU 分配资源进入运行状态。</p></li><li><p><strong>销毁:</strong> 如果线程正常执行完毕或被提前强制终止或出现异常导致结束,那么线程就会被销毁并释放资源。</p></li></ol><h3 id="2-线程的-6-个状态:"><a href="#2-线程的-6-个状态:" class="headerlink" title="2. 线程的 6 个状态:"></a>2. 线程的 6 个状态:</h3><ul><li>NEW: 初始状态,线程被创建出来但没有被调用 <code>start()</code> 。</li><li>RUNNABLE: 运行状态,线程被调用了 <code>start()</code> 等待运行的状态。</li><li>BLOCKED :阻塞状态,需要等待锁释放。</li><li>WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。</li><li>TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。</li><li>TERMINATED:终止状态,表示该线程已经运行完毕。</li></ul><h3 id="3-阻塞的三种情况:等待阻塞、同步阻塞、其他阻塞"><a href="#3-阻塞的三种情况:等待阻塞、同步阻塞、其他阻塞" class="headerlink" title="3. 阻塞的三种情况:等待阻塞、同步阻塞、其他阻塞"></a>3. 阻塞的三种情况:<strong>等待阻塞</strong>、<strong>同步阻塞</strong>、<strong>其他阻塞</strong></h3><ul><li><strong>等待阻塞:</strong> 正在运行中的线程执行 wait ()方法时,JVM 会把该线程放入等待队列中。</li><li><strong>同步阻塞:</strong> 运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用,则 JVM 会把该线程放入锁池中。</li><li><strong>其他阻塞:</strong> 运行的线程执行 sleep ()、join ()方法,或者发出了 IO 请求时,JVM 会把该线程置为阻塞状态。</li></ul><h4 id="sleep-和-wait-的区别:"><a href="#sleep-和-wait-的区别:" class="headerlink" title="sleep 和 wait 的区别:"></a>sleep 和 wait 的区别:</h4><p>sleep 和 wait 的区别在于这两个方法来自不同的类分别是 Thread 和 Object,<strong>sleep 方法没有释放锁,而 wait 方法释放了锁</strong>,使得其他线程可以使用同步控制块或者方法。</p><p>sleep 是线程被调用时,占着 cpu 去睡觉,其他线程不能占用 cpu,os 认为该线程正在工作,不会让出系统资源,wait 是进入等待池等待,让出系统资源,其他线程可以占用 cpu,一般 wait 不会加时间限制。</p><h3 id="4-线程死亡的三种情况:"><a href="#4-线程死亡的三种情况:" class="headerlink" title="4. 线程死亡的三种情况:"></a>4. 线程死亡的三种情况:</h3><ul><li><p><strong>正常结束:</strong> run ()或 call ()方法执行完成,线程正常结束;</p></li><li><p><strong>异常结束:</strong> 线程执行过程中抛出一个未捕获的异常导致结束;</p></li><li><p><strong>强制结束:</strong> 调用线程终止方法强制结束线程:</p><blockquote><p><a href="https://blog.csdn.net/k_young1997/article/details/106970529">https://blog.csdn.net/k_young1997/article/details/106970529</a></p></blockquote><ul><li><p><strong>使用退出标志</strong></p><p>定义一个<strong>volatile</strong>修饰的 boolean 型的标志位,在线程的 run 方法中根据这个标志位是为 true 还是为 false 来判断是否终止,这种情况多用于 while 循环中。</p><p><em>(使用 volatile 目的是保证可见性,一处修改了标志,处处都要去主存读取新的值,而不是使用缓存)</em></p></li><li><p><strong>Interrupt 方法</strong></p><p>使用 interrupt 方法中断线程有两种情况</p><ul><li><p>线程处于阻塞状态</p><p>如使用了 sleep,同步锁的 wait, socket 中的 receiver, accept 等方法时,会使线程处于阻塞状态。当调用线程的 <code>interrupt()</code> 方法时,会抛出 <code>InterruptException</code> 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> {</span><br><span class="line">threadDemo.start();</span><br><span class="line"><span class="comment">//阻塞线程</span></span><br><span class="line">threadDemo.wait();</span><br><span class="line"><span class="comment">//中断线程</span></span><br><span class="line">threadDemo.interrupt();</span><br><span class="line">}<span class="keyword">catch</span> (InterruptedException e){</span><br><span class="line"><span class="comment">//抛出异常,强制跳出,线程中断</span></span><br><span class="line">e.printStackTrace();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>线程未处于阻塞状态</p><p>使用 <code>isInterrupted()</code> 判断线程的中断标志来退出循环。当使用 interrupt ()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ThreadDemo</span> <span class="variable">threadDemo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadDemo</span>();</span><br><span class="line">threadDemo.start();</span><br><span class="line"><span class="comment">//中断线程</span></span><br><span class="line">threadDemo.interrupt();</span><br><span class="line"><span class="comment">//通过while循环不断确认线程是否已经终止</span></span><br><span class="line"><span class="keyword">while</span> (threadDemo.isInterrupted()) {</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line">System.out.println(<span class="string">"线程中断"</span>);</span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>stop 方法</strong></p><p>调用 stop ()方法,该方法不安全,容易导致死锁</p><ul><li>调用 stop 方法会立刻终止 run ()方法中剩余的全部任务,包括 catch 或 finally 中的任务,并且抛出 ThreadDeath 异常,因此可能会导致任务执行失败。</li><li>调用 stop 方法会立刻释放改线程所持有的所有锁,导致数据无法完成同步,出现数据不一致的问题。</li></ul></li></ul></li></ul><h2 id="2、创建线程的三种方式,线程池的好处"><a href="#2、创建线程的三种方式,线程池的好处" class="headerlink" title="2、创建线程的三种方式,线程池的好处"></a>2、创建线程的三种方式,线程池的好处</h2><h3 id="1-创建线程的三种方式:1-个继承,两个实现"><a href="#1-创建线程的三种方式:1-个继承,两个实现" class="headerlink" title="1. 创建线程的三种方式:1 个继承,两个实现"></a>1. 创建线程的三种方式:1 个继承,两个实现</h3><ul><li><p>继承 Thread 类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainDemo</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span>{</span><br><span class="line"> <span class="type">ThreadDemo</span> <span class="variable">threadDemo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadDemo</span>();</span><br><span class="line"> threadDemo.start();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ThreadDemo</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line"> System.out.println(<span class="string">"ThreadDemo"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>实现 Runnable 接口</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainDemo</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span>{</span><br><span class="line"> <span class="type">RunnableDemo</span> <span class="variable">runnableDemo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RunnableDemo</span>();</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(runnableDemo).start();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//不用担心单继承,没有返回值</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">RunnableDemo</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line"> System.out.println(<span class="string">"RunnableDemo"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>实现 Callable 接口</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainDemo</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException {</span><br><span class="line"> FutureTask<String> futureTask = <span class="keyword">new</span> <span class="title class_">FutureTask</span><>(<span class="keyword">new</span> <span class="title class_">CallableDemo</span>());</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(futureTask).start();</span><br><span class="line"> <span class="comment">//get()方法获取返回值</span></span><br><span class="line"> System.out.println(futureTask.get());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//有返回值也可以抛出异常</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">CallableDemo</span> <span class="keyword">implements</span> <span class="title class_">Callable</span><String> {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">call</span><span class="params">()</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> System.out.println(<span class="string">"CallableDemo"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"return CallableDemo"</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p><strong>区别:</strong></p><ul><li>Thread 只能单继承,Runnable 和 Callable 可以多实现</li><li>Thread 和 Runnable 不能得到返回值,Callable 可以获取返回值及捕获异常</li></ul><h3 id="2-使用线程池的好处"><a href="#2-使用线程池的好处" class="headerlink" title="2. 使用线程池的好处"></a>2. 使用线程池的好处</h3><ul><li><strong>降低资源消耗</strong>:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。</li><li><strong>提高响应速度</strong>:当任务到达时,任务可以不需要等到线程创建就能立即执行。</li><li><strong>提高线程的可管理性</strong>:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。</li></ul><h2 id="3、如何创建线程池"><a href="#3、如何创建线程池" class="headerlink" title="3、如何创建线程池"></a>3、如何创建线程池</h2><h3 id="1-通过构造方法实现"><a href="#1-通过构造方法实现" class="headerlink" title="1. 通过构造方法实现"></a>1. 通过构造方法实现</h3><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ThreadPoolExecutor%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95.png" alt="ThreadPoolExecutor构造方法"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">ThreadPoolExecutor</span><span class="params">(<span class="type">int</span> corePoolSize,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> maximumPoolSize,</span></span><br><span class="line"><span class="params"> <span class="type">long</span> keepAliveTime,</span></span><br><span class="line"><span class="params"> TimeUnit unit,</span></span><br><span class="line"><span class="params"> BlockingQueue<Runnable> workQueue,</span></span><br><span class="line"><span class="params"> ThreadFactory threadFactory,</span></span><br><span class="line"><span class="params"> RejectedExecutionHandler handler)</span>;</span><br><span class="line"><span class="comment">//核心线程:5;最大线程200;空闲线程存活时间:10;单位:秒;阻塞队列:类型linked,容量10000;线程工程:默认;饱和策略:抛出 RejectedExecutionException来拒绝新任务的处理。</span></span><br><span class="line"><span class="type">ThreadPoolExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">5</span>,</span><br><span class="line"> <span class="number">200</span>,</span><br><span class="line"> <span class="number">10</span>,</span><br><span class="line"> TimeUnit.SECONDS,</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span><>(<span class="number">10000</span>),</span><br><span class="line"> Executors.defaultThreadFactory(),</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.AbortPolicy());</span><br></pre></td></tr></table></figure><p><strong>ThreadPoolExecutor 7 个参数:</strong></p><ol><li><p><strong>核心线程数 <code>corePoolSize</code>:</strong> 核心线程数定义了最小可以同时运行的线程数量。</p></li><li><p><strong>最大线程数 <code>maximumPoolSize</code>:</strong> 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。</p></li><li><p><strong>阻塞队列 <code>workQueue</code>:</strong> 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。</p></li><li><p><strong>存活时间 <code>keepAliveTime</code>:</strong> 当线程池中的线程数量大于 <code>corePoolSize</code> 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 <code>keepAliveTime</code> 才会被回收销毁;</p></li><li><p><strong>存活时间单位 <code>unit</code>:</strong> <code>keepAliveTime</code> 参数的时间单位。</p></li><li><p><strong>线程工厂 <code>threadFactory</code>:</strong> executor 创建新线程的时候会用到。</p></li><li><p><strong>饱和策略 <code>handler</code>:</strong> 如果当前同时运行的线程数量达到最大线程数量并且阻塞队列也已经被放满了任务时,会根据饱和策略来处理多余的任务。</p><p>常见饱和策略:</p><ul><li><strong><code>ThreadPoolExecutor.AbortPolicy</code>:</strong> 抛出 <code>RejectedExecutionException</code> 异常来拒绝新任务的处理。</li></ul></li></ol><ul><li><strong><code>ThreadPoolExecutor.CallerRunsPolicy</code>:</strong> 调用执行自己的线程运行任务,也就是直接在调用 <code>execute</code> 方法的线程中运行 (<code>run</code>)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。<ul><li><strong><code>ThreadPoolExecutor.DiscardPolicy</code>:</strong> 不处理新任务,直接丢弃掉。</li><li><strong><code>ThreadPoolExecutor.DiscardOldestPolicy</code>:</strong> 此策略将丢弃最早的未处理的任务请求。</li></ul></li></ul><p><strong>线程池运行流程:</strong></p><ol><li>线程池创建,准备好 core 数量的核心线程,准备接受任务</li><li>新的任务进来,用 core 准备好的空闲线程执行。</li><li>核心线程 core 满了,就将再进来的任务放入阻塞队列中。空闲的 core 就会自己去阻塞队列获取任务执行</li><li>阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量</li><li>如果线程数开到了 max 的数量,还有新任务进来,就会使用 RejectedExecutionHandler 指定的拒绝策略拒绝任务</li><li>max 都执行完成,有很多空闲。在指定 keepAliveTime 后,会释放 Max-core 数量空闲的线程。最终保持到 core 大小。new LinkedBlockingQueue<>()默认是 integer 的最大值,内存不够</li><li>所有的线程创建都是由指定的 factory 创建的</li></ol><p><strong>总结:核心线程 -> 阻塞队列 -> 新线程 -> 拒绝策略 -> 自动释放空闲核心线程</strong></p><h3 id="2-通过-Executor-框架的工具类-Executors-来实现"><a href="#2-通过-Executor-框架的工具类-Executors-来实现" class="headerlink" title="2. 通过 Executor 框架的工具类 Executors 来实现"></a>2. 通过 Executor 框架的工具类 Executors 来实现</h3><p><strong>4 种常见线程池:</strong></p><ul><li><p><strong>CachedThreadPool</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,</span></span><br><span class="line"><span class="comment"> * 若无可回收,则新建线程。</span></span><br><span class="line"><span class="comment"> * 没有核心线程,所有线程都可以回收。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">Executors.newCachedThreadPool();</span><br></pre></td></tr></table></figure></li><li><p><strong>FixedThreadPool</strong> </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 创建一个定长线程池,可控制线程最大并发数,</span></span><br><span class="line"><span class="comment"> * 超出的线程会在队列中等待。</span></span><br><span class="line"><span class="comment"> * 核心线程数和最大线程数相同,固定线程数大小,所有线程都不可以回收。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">Executors.newFixedThreadPool(<span class="number">10</span>);</span><br></pre></td></tr></table></figure></li><li><p><strong>ScheduledThreadPool</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 创建一个定长线程池,支持定时及周期性任务执行。</span></span><br><span class="line"><span class="comment"> * 可以指定多长时间以后执行任务。定时任务线程池。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">Executors.newScheduledThreadPool(<span class="number">10</span>);</span><br></pre></td></tr></table></figure></li><li><p><strong>SingleThreadExecutor</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,</span></span><br><span class="line"><span class="comment"> * 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。</span></span><br><span class="line"><span class="comment"> * 单线程的线程池,核心和最大线程数都为 1。</span></span><br><span class="line"><span class="comment"> * 后台从队列中取一个执行一个,相当于后台用单线程执行任务。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">Executors.newSingleThreadExecutor();</span><br></pre></td></tr></table></figure></li></ul><h2 id="4、submit-和-execute-的区别"><a href="#4、submit-和-execute-的区别" class="headerlink" title="4、submit 和 execute 的区别"></a>4、submit 和 execute 的区别</h2><ol><li><strong><code>execute()</code> 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;</strong></li><li><strong><code>submit()</code> 方法用于提交需要返回值的任务。线程池会返回一个 <code>Future</code> 类型的对象,通过这个 <code>Future</code> 对象可以判断任务是否执行成功</strong>,并且可以通过 <code>Future</code> 的 <code>get()</code> 方法来获取返回值,<code>get()</code> 方法会阻塞当前线程直到任务完成,而使用 <code>get(long timeout,TimeUnit unit)</code> 方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。</li></ol><h2 id="5、Java-并发包下常用的类库"><a href="#5、Java-并发包下常用的类库" class="headerlink" title="5、Java 并发包下常用的类库"></a>5、Java 并发包下常用的类库</h2><ul><li><p>CountDownLatch</p></li><li><p>LockSupport</p></li><li><p>BlockingQueue</p></li><li><p>Executors</p></li><li><p>ArrayBlockingQueue</p></li><li><p>FutureTask</p></li><li><p>CompletableFuture</p></li></ul><h2 id="6、Nacos、ZooKeeper、Eureka-的选择,各种优缺点"><a href="#6、Nacos、ZooKeeper、Eureka-的选择,各种优缺点" class="headerlink" title="6、Nacos、ZooKeeper、Eureka 的选择,各种优缺点"></a>6、Nacos、ZooKeeper、Eureka 的选择,各种优缺点</h2><p>ZooKeeper 实现了 CP</p><p>Eureka 实现了 AP</p><p>Nacos 是根据配置实现 CP 和 AP</p><h2 id="7、AQS"><a href="#7、AQS" class="headerlink" title="7、AQS"></a>7、AQS</h2><h3 id="1-AQS-核心思想"><a href="#1-AQS-核心思想" class="headerlink" title="1. AQS 核心思想"></a>1. AQS 核心思想</h3><p><strong>如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是基于 CLH 锁 (Craig, Landin, and Hagersten locks) 实现的。</strong></p><h3 id="2-CLH"><a href="#2-CLH" class="headerlink" title="2. CLH"></a>2. CLH</h3><p>CLH 是指 Craig, Landin, and Hagersten,是一种经典的自旋锁算法,用于解决并发环境下的互斥访问问题。CLH 自旋锁是一种基于链表的软件锁算法,采用了无忙等待的自旋方式,避免了传统自旋锁的缺点。</p><p>CLH 自旋锁的核心思想是<strong>使用一个链表来表示所有等待获取锁的线程,链表中的每个节点代表一个竞争线程。每个节点都维护一个标志位,用来表示前一个节点是否已经释放了锁。</strong></p><p>CLH 自旋锁的获取和释放过程如下:</p><ol><li>获取锁:当线程需要获取锁时,它会创建一个新的节点,并将自己加入到链表的末尾。然后它会自旋等待,不断检查前一个节点的标志位,直到前一个节点释放了锁。</li><li>释放锁:当线程释放锁时,它会将自己所对应的节点的标志位置为已释放,并将其从链表中移除。这样后续等待的线程就可以通过自旋获取锁。</li></ol><p>CLH 自旋锁的特点包括:</p><ol><li>无忙等待:CLH 自旋锁通过自旋等待的方式来获取锁,避免了传统自旋锁在竞争激烈情况下的忙等待问题,减少了对 CPU 的占用,提高了性能。</li><li>FIFO 队列:CLH 自旋锁的链表是按照线程请求锁的顺序排队的,保证了公平性,避免了饥饿现象的发生。</li><li>缓存友好性:CLH 自旋锁的节点通过自旋等待获取锁,而不是忙等待,这样可以减少对共享变量的频繁访问,提高了缓存的命中率。</li></ol><p>需要注意的是,CLH 自旋锁在多处理器系统上的性能通常比较好,但在单处理器系统上可能存在性能问题,因为它会导致线程频繁地进行上下文切换。在实际使用时,需要根据具体的环境和需求来选择合适的锁算法。</p><p>CLH 锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系),暂时获取不到锁的线程将被加入到该队列中。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。</p><h3 id="3-什么是钩子方法"><a href="#3-什么是钩子方法" class="headerlink" title="3. 什么是钩子方法"></a>3. 什么是钩子方法</h3><p>钩子方法是一种被声明在抽象类中的方法,一般使用 <code>protected</code> 关键字修饰,它可以是空方法(由子类实现),也可以是默认实现的方法。模板设计模式通过钩子方法控制固定步骤的实现。</p><h2 id="8、CAS"><a href="#8、CAS" class="headerlink" title="8、CAS"></a>8、CAS</h2><p>CAS 的全称是 <strong>Compare And Swap(比较与交换)</strong> ,用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。</p><p>CAS 操作是原子性的,即在执行期间不会被其他线程中断,从而确保了线程安全。它实际上是利用了现代处理器提供的原子指令(比如 x 86 的 CMPXCHG 指令)来实现的。</p><p>CAS 操作由三个参数组成:</p><ul><li><strong>V</strong>:要更新的变量值 (Var)</li><li><strong>E</strong>:预期值 (Expected)</li><li><strong>N</strong>:拟写入的新值 (New)</li></ul><p>它的执行过程如下:</p><ol><li>检查变量值是否等于预期值。</li><li>如果相等,则将该地址上的值更新为新值。</li><li>如果不相等,则表示其他线程已经修改了该值,操作失败。</li></ol><p><strong>举一个简单的例子</strong>:线程 A 要修改变量 i 的值为 6,i 原值为 1(V = 1,E=1,N=6,假设不存在 ABA 问题)。</p><ol><li>i 与 1 进行比较,如果相等,则说明没被其他线程修改,可以被设置为 6 。</li><li>i 与 1 进行比较,如果不相等,则说明被其他线程修改,当前线程放弃更新,CAS 操作失败。</li></ol><p>CAS 的优点在于它避免了使用锁带来的线程切换和上下文切换的开销,从而减少了系统开销和提高了并发性能。然而,CAS 也存在一些限制和问题,主要包括:</p><p><strong>ABA 问题:如果变量的值在操作过程中被其他线程从预期值变为又变回预期值,CAS 操作可能会误判,无法感知到变量的变化。</strong></p><p>ABA 问题的解决思路是在变量前面追加上<strong>版本号或者时间戳</strong>。JDK 1.5 以后的 <code>AtomicStampedReference</code> 类就是用来解决 ABA 问题的,其中的 <code>compareAndSet()</code> 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。</p><p>为了解决 CAS 的限制,Java 提供了 Atomic 包下的一系列原子类,如 AtomicInteger、AtomicLong、AtomicReference 等,它们封装了 CAS 操作,提供了更便捷和安全的方式来进行原子操作。</p><h2 id="9、BlockingQueue"><a href="#9、BlockingQueue" class="headerlink" title="9、BlockingQueue"></a>9、BlockingQueue</h2><p>阻塞队列</p><p>ArrayBlockingQueue 是 Java 中的一个线程安全(线程同步)的固定大小的阻塞队列,它是基于数组实现的。它是在 java. util. concurrent 包下的一个类。</p><p>ArrayBlockingQueue 的主要特点有:</p><ol><li>有界性:ArrayBlockingQueue 的容量是固定的,即在创建队列时必须指定队列的容量大小。</li><li>先进先出(FIFO):ArrayBlockingQueue 严格按照元素的插入顺序进行操作,保证先进来的元素先被获取。</li><li>线程安全:ArrayBlockingQueue 使用内部锁(ReentrantLock)实现了线程安全。它对插入和删除操作进行了同步,以确保多线程环境下的并发操作不会出现问题。</li><li>阻塞操作:ArrayBlockingQueue 支持阻塞操作,当队列为空时,从队列中获取元素的操作会被阻塞,直到有元素可用;当队列已满时,插入元素的操作会被阻塞,直到队列有空闲位置可用。</li><li>支持可选的公平性:可以通过构造函数设置公平性参数,如果设置为 true,则等待时间更长的线程会优先获得锁。</li></ol><p>使用 ArrayBlockingQueue 可以方便地实现生产者-消费者模式,生产者线程将元素插入队列,消费者线程从队列中获取元素。当队列为空时,消费者线程会被阻塞,直到队列中有元素可用;当队列已满时,生产者线程会被阻塞,直到队列有空闲位置可用。</p><p>需要注意的是,在 ArrayBlockingQueue 中,当队列已满时尝试插入元素会导致线程被阻塞,当队列为空时尝试获取元素也会导致线程被阻塞。因此,在使用 ArrayBlockingQueue 时要合理处理阻塞操作可能引发的线程安全和性能问题。</p><h2 id="10、lock"><a href="#10、lock" class="headerlink" title="10、lock"></a>10、lock</h2><h3 id="1-锁(Lock)"><a href="#1-锁(Lock)" class="headerlink" title="1. 锁(Lock)"></a>1. 锁(Lock)</h3><p>在并发编程中,锁是用于控制对共享资源的访问的一种同步机制。它用于确保在同一时间内只有一个线程可以访问被锁定的资源,从而保证线程安全。</p><ol><li><strong>显示锁</strong>(Explicit Lock):显式锁是由代码明确调用的锁机制。在 Java 中,ReentrantLock 是一个常见的显式锁实现,它提供了显式的 lock () 和 unlock () 方法来控制对临界区的访问。</li><li><strong>隐式锁</strong>(Implicit Lock):隐式锁是由语言或运行时环境自动管理的锁机制。在 Java 中,synchronized 关键字就是一种隐式锁机制,它可以应用于方法或代码块,隐式地在进入和退出临界区时获取和释放锁。</li></ol><h3 id="2-互斥锁(Mutex-Lock)"><a href="#2-互斥锁(Mutex-Lock)" class="headerlink" title="2. 互斥锁(Mutex Lock)"></a>2. 互斥锁(Mutex Lock)</h3><p>互斥锁是一种特殊类型的锁,用于保护共享资源的互斥访问。当一个线程获取了互斥锁后,其他线程必须等待锁的释放才能访问共享资源。互斥锁提供了排他性,保证同一时间只有一个线程能够持有锁。</p><h3 id="3-自旋锁(Spin-Lock)"><a href="#3-自旋锁(Spin-Lock)" class="headerlink" title="3. 自旋锁(Spin Lock)"></a>3. 自旋锁(Spin Lock)</h3><p>自旋锁是一种特殊类型的锁,采用忙等待的方式来实现线程的同步。当一个线程尝试获取自旋锁时,如果锁已经被其他线程占用,该线程会进入忙等待状态,不断循环检查锁是否被释放。自旋锁一般适用于锁的持有时间较短的情况,避免线程切换带来的开销。</p><p>总而言之,锁是一种用于实现并发编程中线程同步和对共享资源进行保护的机制。它可以防止多个线程同时修改共享资源,从而确保数据的一致性和线程的安全性。不同类型的锁有不同的特点和适用场景,开发者需要根据具体需求选择合适的锁机制。</p><h2 id="11、volatile"><a href="#11、volatile" class="headerlink" title="11、volatile"></a>11、volatile</h2><p>volatile 是 Java 中的关键字,用于修饰变量。它具有以下两个主要的作用:</p><ol><li><strong>可见性</strong>(Visibility):使用 volatile 修饰的变量对所有线程可见。当一个线程修改了 volatile 变量的值,该变量的新值会立即被写入主内存,并且其他线程可以立即看到最新的值。这解决了多线程间的可见性问题,避免了使用普通变量时的数据不一致性问题。</li><li><strong>禁止指令重排序</strong>(Ordering):使用 volatile 修饰的变量的读写操作会被插入到内存屏障(Memory Barrier)之前和之后,防止指令重排序优化。这保证了 volatile 变量的读写操作具有所谓的 happens-before 关系,即写操作发生在后续的读操作之前,确保了操作的有序性。</li></ol><p>需要注意的是,volatile 提供的可见性和禁止指令重排序的保证是有限的,它并不能保证原子性。对于复合操作,如 i++ 的原子性操作,volatile 无法保证线程安全,需要使用其他同步机制(如锁)或者使用原子类(如 AtomicIntegerFieldUpdater)来实现。</p><p>使用 volatile 的常见场景包括:</p><ol><li><strong>标志位</strong>:用于控制并发任务的启动、暂停或停止。</li><li><strong>双重检查锁定</strong>(Double-Checked Locking):用于在延迟初始化对象的情况下,确保多个线程能够正确地获取初始化后的对象。</li></ol><p>总结来说,volatile 是一种用于保证变量可见性和禁止指令重排序的关键字。它提供了一种简单而轻量的线程同步机制,适用于某些特定的并发场景。然而,对于更复杂的线程同步需求,需要使用更强大的同步机制(如锁)来确保线程安全性。</p><h2 id="12、synchronized"><a href="#12、synchronized" class="headerlink" title="12、synchronized"></a>12、synchronized</h2><p>synchronized 是 Java 中的关键字,用于实现线程之间的同步和互斥访问。它可以应用于方法或代码块,用于控制对共享资源的访问,并保证多个线程对共享资源的安全性。</p><p>使用 synchronized 的主要作用包括:</p><ol><li><strong>互斥访问</strong>:当多个线程同时访问同一个被 synchronized 修饰的方法或代码块时,只会有一个线程能够进入临界区,其他线程需要等待。这样可以确保在同一时间内只有一个线程对共享资源进行操作,避免了数据的竞争和不一致性。</li><li><strong>可见性和有序性</strong>:除了提供互斥访问之外,synchronized 还提供了对变量的可见性和有序性保证。当一个线程释放 synchronized 的锁时,它会将对共享变量的更新刷新到主内存中,使得其他线程能够立即看到最新的值,并且保证了操作的有序性。</li></ol><p>使用 synchronized 的方式有两种:</p><ol><li><strong>同步方法</strong>(Synchronized Method):使用 synchronized 修饰的方法称为同步方法。当线程调用同步方法时,它会自动获取该方法所属对象(或类)的锁,并在方法执行过程中保持独占。其他线程需要等待锁的释放才能执行相同对象(或类)的同步方法。</li><li><strong>同步代码块</strong>(Synchronized Block):使用 synchronized 修饰的代码块称为同步代码块。它需要指定一个对象作为锁,也称为监视器对象。同一时间只有一个线程可以持有该对象的锁,并执行进入锁定的代码块。其他线程需要等待该锁的释放才能执行相同对象的同步代码块。</li></ol><p>需要注意的是,synchronized 是一种重量级的锁机制,涉及到线程的上下文切换和内核态的操作。在使用 synchronized 时,应尽量减小同步范围,避免持有锁的时间过长,以提高程序的性能。</p><p>总结来说,synchronized 是一种用于实现线程同步和互斥访问的关键字。它能够保证对共享资源的原子性操作、可见性和有序性,并防止多个线程同时修改共享资源。在多线程编程中,使用 synchronized 是一种常见且有效的同步机制。</p><h1 id="微服务"><a href="#微服务" class="headerlink" title="微服务"></a>微服务</h1><h2 id="1、微服务事务解决方案-2-PC、ttc-等"><a href="#1、微服务事务解决方案-2-PC、ttc-等" class="headerlink" title="1、微服务事务解决方案 2 PC、ttc 等"></a>1、微服务事务解决方案 2 PC、ttc 等</h2><h3 id="1-2-PC"><a href="#1-2-PC" class="headerlink" title="1. 2 PC:"></a>1. 2 PC:</h3><p>2 PC(Two-Phase Commit)是一种常见的分布式事务协议,用于实现多个参与者(Participants)之间的一致性。它通过协调器(Coordinator)和参与者之间的协作来确保在分布式环境中的事务的一致性。<br>2 PC 协议的执行过程分为两个阶段:</p><ol><li><p><strong>准备阶段</strong>(Prepare Phase):</p><p>在该阶段,协调器向所有参与者发出准备请求。参与者执行事务操作并将准备状态(Prepare)通知协调器。如果参与者成功执行并准备好提交事务,则返回准备就绪状态(Ready to Commit)。如果有任何一个参与者无法准备好提交事务,它将返回无法准备状态(Unable to Prepare)。</p></li><li><p><strong>提交阶段</strong>(Commit Phase):</p><p>如果所有参与者都返回了准备就绪状态,协调器将发送提交请求给所有参与者。参与者接收到提交请求后,执行事务的最终提交操作,并将提交确认(Commit Acknowledgment)通知协调器。一旦协调器接收到所有参与者的提交确认,它将发出全局提交(Global Commit)的通知。</p></li></ol><p>通过这个两阶段的协作,2 PC 协议实现了分布式事务的提交一致性。但是,2 PC 协议也存在一些缺点:</p><ol start="3"><li><p>阻塞问题:在 2 PC 的执行过程中,参与者在等待协调器的请求时会阻塞,这可能导致整个事务的执行时间较长,并且会增加协调器故障的风险。</p></li><li><p>单点故障:协调器作为中心化的组件,一旦发生故障,将导致整个协议无法继续执行,从而影响整个分布式事务的一致性。</p></li><li><p>数据不一致问题:2 PC 协议在网络分区、参与者故障或通信故障等情况下可能导致数据不一致的问题。例如,在准备阶段失败时,已准备好的参与者可能无法回滚之前的操作,导致数据不一致。</p></li></ol><p>鉴于 2 PC 的缺点,一些替代的分布式事务协议也被提出,如 3 PC(Three-Phase Commit)、Paxos、Raft 等,它们针对 2 PC 的一些问题进行了改进和优化。此外,一些基于补偿的事务处理模式(如 Saga 模式)也被广泛应用于大规模微服务架构中。</p><h3 id="2-TTC:"><a href="#2-TTC:" class="headerlink" title="2. TTC:"></a>2. TTC:</h3><p>在微服务架构中,处理分布式事务是一个挑战性的任务。其中,TTC(Transactional Two-Phase Commit)是一种用于解决微服务事务一致性的解决方案之一。<br>TTC 是一种变种的 2 PC(Two-Phase Commit)协议,专门用于处理微服务架构中的分布式事务。它在传统的 2 PC 协议基础上进行了改进,以提高性能和可靠性。<br>TTC 的主要思想是将事务划分为一组子事务(Subtransactions),每个子事务对应于一个微服务。协调者(Coordinator)负责协调所有子事务的一致性。<br>TTC 的执行过程如下:</p><ol><li><p><strong>准备阶段</strong>(Prepare Phase):</p><p>协调者将准备请求发送给所有子事务。每个子事务执行自身的事务操作,并将准备状态(Prepare)返回给协调者。如果所有子事务都准备好提交事务,则进入下一阶段。否则,如果有任何一个子事务无法准备好,则中止整个事务。</p></li><li><p><strong>提交阶段</strong>(Commit Phase):</p><p>协调者将提交请求发送给所有准备好的子事务。每个子事务执行自身的事务提交操作,并将确认状态(Commit Acknowledgment)返回给协调者。协调者等待所有子事务的确认,如果所有子事务都成功确认,则整个事务提交成功。如果有任何一个子事务无法确认,则中止整个事务。</p></li></ol><p>通过 TTC 协议,微服务架构中的分布式事务可以实现一致性。相较于传统的 2 PC 协议,TTC 减少了等待时间,并且在部分子事务失败的情况下能够快速中止整个事务,减少了不必要的等待和资源占用。<br>需要注意的是,尽管 TTC 在解决微服务事务一致性方面具有一定的优势,但它仍然可能遭遇网络分区、故障恢复等问题。因此,针对具体的应用场景,可能还需要结合其他技术和策略来提高事务处理的可靠性和性能。</p><h2 id="2、CAP、BASE-理论,在项目中的取舍"><a href="#2、CAP、BASE-理论,在项目中的取舍" class="headerlink" title="2、CAP、BASE 理论,在项目中的取舍"></a>2、CAP、BASE 理论,在项目中的取舍</h2><h3 id="1-CAP-定理"><a href="#1-CAP-定理" class="headerlink" title="1. CAP 定理"></a>1. CAP 定理</h3><ul><li>一致性</li><li>可用性</li><li>分区容错性</li></ul><p>这三个指标不可能同时做到,这个结论就叫做 CAP 定理。</p><ol><li><strong>分区容错性</strong>(Partition tolerance) :比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。介于分区状态下,G 1 和 G 2 是两台跨区的服务器,G 1 向 G 2 发送一条消息,G 2 可能无法收到,因为网络总是不可靠的,因此可以认为 P 总是成立的。</li><li><strong>可用性</strong>(Availability) :只要收到用户的请求,服务器就必须给出回应,但此时由于网络问题会导致服务器之间的数据无法做到实时同步,牺牲了一致性,此时满足 AP。</li><li><strong>一致性</strong>(Consistency) :用户 A 访问系统 A,用户 B 访问系统 B,系统 A 和系统 B 保持同步,当用户 A 对系统 A 的 data 做出更改后,用户 B 查询系统 B 的 data 时,需要查询出用户 A 操作后的数据,而同步是要通过网络,网络却又总是不可靠的,所以为确保用户 B 能查询出用户 A 修改的数据,必须在用户 B 查询前将系统 A 的 data 同步到系统 B 上,这样就牺牲了可用性,此时满足 CP。</li></ol><h3 id="2-BASE-理论"><a href="#2-BASE-理论" class="headerlink" title="2. BASE 理论"></a>2. BASE 理论</h3><p>由于 CAP 中一致性 C 和可用性 A 无法兼得,eBay 的架构师,提出了 BASE 理论,它是通过牺牲数据的强一致性,来获得可用性。它有如下3种特征:</p><ol><li>基本可用(Basically Available):分布式系统在出现不可预知故障的时候,允许损失部分可用性,保证核心功能的可用。</li><li>软状态(Soft State):软状态也称为弱状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。</li><li>最终一致性(Eventually consistent):最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。</li></ol><p> BASE 理论并没有要求数据的强一致性,而是允许数据在一定的时间段内是不一致的,但在最终某个状态会达到一致。在生产环境中,很多公司,会采用 BASE 理论来实现数据的一致,因为产品的可用性相比强一致性来说,更加重要。比如在电商平台中,当用户对一个订单发起支付时,往往会调用第三方支付平台,比如支付宝支付或者微信支付,调用第三方成功后,第三方并不能及时通知我方系统,在第三方没有通知我方系统的这段时间内,我们给用户的订单状态显示支付中,等到第三方回调之后,我们再将状态改成已支付。虽然订单状态在短期内存在不一致,但是用户却获得了更好的产品体验。</p><h1 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h1><h2 id="1、MySQL存储引擎有哪些,默认的存储引擎是什么,为什么用这个"><a href="#1、MySQL存储引擎有哪些,默认的存储引擎是什么,为什么用这个" class="headerlink" title="1、MySQL存储引擎有哪些,默认的存储引擎是什么,为什么用这个"></a>1、MySQL存储引擎有哪些,默认的存储引擎是什么,为什么用这个</h2><blockquote><p><a href="https://blog.csdn.net/qq_48826058/article/details/123690955">https://blog.csdn.net/qq_48826058/article/details/123690955</a></p></blockquote><h3 id="1-InnoDB存储引擎"><a href="#1-InnoDB存储引擎" class="headerlink" title="1.InnoDB存储引擎"></a>1.InnoDB存储引擎</h3><ul><li>事务安全</li><li>支持外键</li><li>支持全文索引</li></ul><p><strong>适用场景:需要事务支持、行级锁定对高并发有很好地适应能力,但需要确保查询是通过索引完成、数据更新较为频繁。</strong></p><h3 id="2-MyISAM存储引擎"><a href="#2-MyISAM存储引擎" class="headerlink" title="2.MyISAM存储引擎"></a>2.MyISAM存储引擎</h3><ul><li>不是事务安全的</li><li>不支持外键</li><li>表格可以被压缩,且支持全文索引</li><li>不支持缓存数据文件</li></ul><p><strong>适用场景</strong>:<strong>不需要事务支持</strong>、<strong>并发相对较低</strong>、<strong>数据修改相对较少</strong>、<strong>以读为主</strong>、<strong>数据一致性要求不是特别高</strong>。</p><h3 id="3-MEMORY存储引擎"><a href="#3-MEMORY存储引擎" class="headerlink" title="3.MEMORY存储引擎"></a>3.MEMORY存储引擎</h3><ul><li>把表临时性存放在内存中,数据库重启或崩溃数据就会丢失</li><li>默认使用哈希索引</li><li>只支持表锁</li><li>并发性能较差</li><li>不支持text和blob列类型</li><li>浪费内存,比如:存储变长字段(varchar)时是按照定长字段(char)的方式进行的。</li></ul><h3 id="4-MERGE存储引擎"><a href="#4-MERGE存储引擎" class="headerlink" title="4.MERGE存储引擎"></a>4.MERGE存储引擎</h3><ul><li>是一组MyISAM表的组合</li><li>对MERGE表进行drop操作,这个操作只删除MERGE的定义,对内部的表没有任何影响。</li><li>对表的大小有要求,不能是太大的表。</li></ul><p><strong>适用场景:需要很快的读/写速度,对数据安全性要求较低。</strong></p><h3 id="5-默认的存储引擎"><a href="#5-默认的存储引擎" class="headerlink" title="5.默认的存储引擎"></a>5.默认的存储引擎</h3><p><strong>mysql-5.1版本之前默认引擎是MyISAM,之后是innoDB</strong></p><h3 id="6-MyISAM和InnoDB的区别"><a href="#6-MyISAM和InnoDB的区别" class="headerlink" title="6.MyISAM和InnoDB的区别"></a>6.MyISAM和InnoDB的区别</h3><ol><li><p><strong>InnoDB支持事务</strong>,而<strong>MyISAM不支持事务</strong>。</p></li><li><p><strong>InnoDB支持外键</strong>,而<strong>MyISAM不支持外键</strong>。</p></li><li><p><strong>InnoDB是行锁</strong>,而<strong>MyISAM是表锁</strong>(每次更新增加删除都会锁住表)。</p></li><li><p>和MyISAM的索引都是基于b+树,但他们具体实现不一样,<strong>InnoDB的b+树的叶子节点是存放数据的,MyISAM的b+树的叶子节点是存放指针的。</strong></p></li><li><p><strong>InnoDB是聚簇索引</strong>,必须要有主键,一定会基于主键查询,但是辅助索引就会查询两次。<strong>MyISAM是非聚簇索引</strong>,索引和数据是分离的,索引里保存的是数据地址的指针,主键索引和辅助索引是分开的。</p></li><li><p>InnoDB不存储表的行数,所以select count( * )的时候会全表查询。而MyISAM会存放表的行数,select count(*)的时候会查的很快。</p></li></ol><h2 id="2、数据库事务特性,事务的隔离级别"><a href="#2、数据库事务特性,事务的隔离级别" class="headerlink" title="2、数据库事务特性,事务的隔离级别"></a>2、数据库事务特性,事务的隔离级别</h2><p>事务就是对数据的一系列操作</p><ol><li><p><strong>事务的4个特性</strong></p><ol><li><strong>原子性</strong>(<code>Atomicity</code>) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;</li><li><strong>一致性</strong>(<code>Consistency</code>): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;</li><li><strong>隔离性</strong>(<code>Isolation</code>): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;</li><li><strong>持久性</strong>(<code>Durabilily</code>): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。</li></ol><p><strong>只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。</strong></p></li><li><p><strong>并发事务会产生哪些问题?</strong></p><ul><li><strong>脏读(Dirty read):</strong> 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。</li><li><strong>丢失修改(Lost to modify):</strong> 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。</li><li><strong>不可重复读(Unrepeatable read):</strong> 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。</li><li><strong>幻读(Phantom read):</strong> 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。</li></ul><p><strong>不可重复读和幻读有什么区别?</strong></p><ul><li>不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改;</li><li>幻读的重点在于记录新增比如多次执行同一条查询语句(DQL)时,发现查到的记录增加了。</li></ul></li><li><p><strong>事务的4个隔离级别</strong></p><ul><li><strong>READ-UNCOMMITTED(读未提交)</strong> : 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。</li><li><strong>READ-COMMITTED(读已提交)</strong> : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。</li><li><strong>REPEATABLE-READ(可重复读)</strong> : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。</li><li><strong>SERIALIZABLE(可串行化)</strong> : 最高的隔离级别,所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。</li></ul><table><thead><tr><th align="center">隔离级别</th><th align="center">脏读</th><th align="center">不可重复读</th><th align="center">幻读</th></tr></thead><tbody><tr><td align="center">READ-UNCOMMITTED</td><td align="center">√</td><td align="center">√</td><td align="center">√</td></tr><tr><td align="center">READ-COMMITTED</td><td align="center">×</td><td align="center">√</td><td align="center">√</td></tr><tr><td align="center">REPEATABLE-READ</td><td align="center">×</td><td align="center">×</td><td align="center">√</td></tr><tr><td align="center">SERIALIZABLE</td><td align="center">×</td><td align="center">×</td><td align="center">×</td></tr></tbody></table></li></ol><h2 id="3、索引的优缺点,组合索引特性"><a href="#3、索引的优缺点,组合索引特性" class="headerlink" title="3、索引的优缺点,组合索引特性"></a>3、索引的优缺点,组合索引特性</h2><p><strong>优点</strong> :</p><ul><li>使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。</li><li>通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。</li></ul><p><strong>缺点</strong> :</p><ul><li>创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。</li><li>索引需要使用物理文件存储,也会耗费一定空间。</li></ul><h2 id="4、外联接和内联接的区别"><a href="#4、外联接和内联接的区别" class="headerlink" title="4、外联接和内联接的区别"></a>4、外联接和内联接的区别</h2><ul><li>内连接:指连接结果仅包含符合连接条件的行,参与连接的两个表都应该符合连接条件。</li><li>外连接:连接结果不仅包含符合连接条件的行同时也包含自身不符合条件的行。包括左外连接、右外连接和全外连接。<ul><li>左外连接:左边表数据行全部保留,右边表保留符合连接条件的行。</li><li>右外连接:右边表数据行全部保留,左边表保留符合连接条件的行。</li><li>全外连接:左外连接 union 右外连接。</li></ul></li></ul><h2 id="5、索引失效场景及原理"><a href="#5、索引失效场景及原理" class="headerlink" title="5、索引失效场景及原理"></a>5、索引失效场景及原理</h2><blockquote><p><a href="https://juejin.cn/post/7044793268780924935">索引失效场景及原理</a></p></blockquote><ol><li><p>使用函数或表达式:</p><p>在 WHERE 子句中对索引列使用函数或表达式会导致索引失效。因为 MySQL 无法预先计算表达式的结果,所以无法使用索引进行查找。<br>例:SELECT * FROM users WHERE YEAR(birthday) = 1990;<br>原理:此查询中,YEAR函数作用在索引列birthday上,导致索引失效。</p></li><li><p>隐式类型转换:</p><p>如果查询条件与索引列类型不匹配,MySQL会进行隐式类型转换,可能导致索引失效。<br>例:SELECT * FROM users WHERE age = ‘30’;<br>原理:此查询中,假设age字段是整数类型,而查询条件使用了字符串类型,导致类型不匹配。MySQL会尝试将age字段转换为字符串,从而导致索引失效。</p></li><li><p>不等于(!= 或 <>)操作符:</p><p>使用不等于操作符会导致索引失效,因为MySQL无法利用索引进行范围查找。<br>例:SELECT * FROM users WHERE age <> 30;<br>原理:此查询中,由于使用了不等于操作符,MySQL无法利用索引进行查找,因此索引失效。</p></li><li><p>范围查询的多列索引:</p><p>对于多列联合索引,如果查询条件中包含范围查询(如BETWEEN、>、< 等),那么在范围查询之后的索引列将失效。<br>例:SELECT * FROM orders WHERE user_id = 1 AND order_date > ‘2022-01-01’;<br>原理:假设存在一个多列联合索引(user_id, order_date),此查询中对order_date进行了范围查询,使得索引列order_date之后的索引失效。</p></li><li><p>OR 连接的条件:</p><p>使用OR连接的条件可能导致索引失效,尤其是在OR条件中涉及多个索引列时。<br>例:SELECT * FROM users WHERE age = 30 OR name = ‘Alice’;<br>原理:此查询中,由于使用了OR连接,MySQL可能无法同时利用age和name两个索引列进行查找,导致索引失效。</p></li><li><p>LIKE 查询:</p><p>如果在LIKE查询中,通配符(%或_)在字符串的<strong>开头</strong>,将导致索引失效。<br>例:SELECT * FROM users WHERE name LIKE ‘%Alice%’;<br>原理:此查询中,由于通配符在字符串的开头,MySQL无法使用索引进行查找,因此索引失效。</p></li></ol><p>需要注意的是,实际查询优化器会根据数据量、数据分布等因素决定是否使用索引。</p><h2 id="6、MySQL-全文索引"><a href="#6、MySQL-全文索引" class="headerlink" title="6、MySQL 全文索引"></a>6、MySQL 全文索引</h2><p><a href="https://juejin.cn/post/7101116456489713700">MySql全文索引</a></p><p>全文索引的创建和使用</p><figure class="highlight sql"><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"><span class="operator">/</span><span class="operator">/</span>创建全文索引 </span><br><span class="line"><span class="keyword">CREATE</span> FULLTEXT INDEX <span class="operator"><</span>index_name<span class="operator">></span> <span class="keyword">on</span> tableName(字段名) <span class="keyword">ALTER</span> <span class="keyword">TABLE</span> tableName <span class="keyword">ADD</span> FULLTEXT[index_name](字段名); <span class="keyword">CREATE</span> <span class="keyword">TABLE</span> tableName([....],FULLTEXT KEY[index_name](字段名))`</span><br></pre></td></tr></table></figure><p>和常用的<strong>like</strong>不同,全文索引有自己的格式,使用<strong>match</strong>和<strong>against</strong>关键字,如下:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> <span class="keyword">match</span>(name) against(<span class="string">'aaa'</span>);</span><br></pre></td></tr></table></figure><h2 id="7、SQL-关键字实际执行顺序"><a href="#7、SQL-关键字实际执行顺序" class="headerlink" title="7、SQL 关键字实际执行顺序"></a>7、SQL 关键字实际执行顺序</h2><p><strong>书写顺序</strong>:SELECT -> FROM -> JOIN -> ON -> WHERE -> GROUP BY -> HAVING -> UNION -> ORDER BY ->LIMIT</p><p><strong>执行顺序</strong>:FROM -> ON -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> UNION -> ORDER BY ->LIMIT</p><blockquote><p><a href="https://blog.csdn.net/dz77dz/article/details/115111559">SQL的执行顺序</a></p></blockquote><ol><li><p><strong>FROM</strong>:</p><p>确定查询的数据来源,执行笛卡尔积生成基础数据集。</p><p>选择from后面跟的表,产生虚拟表1。</p></li><li><p><strong>ON</strong>(对于JOIN操作):</p><p>应用JOIN条件,从笛卡尔积生成的结果中筛选出匹配的行,形成新的结果集。</p><p>ON是JOIN的连接条件,符合连接条件的行会被记录在虚拟表2中。</p></li><li><p><strong>JOIN</strong>:</p><p>根据JOIN类型合并表,如果有多个JOIN链接,会重复执行步骤1~3,直到处理完所有表。</p></li><li><p><strong>WHERE</strong>:</p><p>对上一步产生的结果集应用行级别的筛选条件,进一步减少行数,对虚拟表3进行WHERE条件过滤,符合条件的记录会被插入到虚拟表4中。</p></li><li><p><strong>GROUP BY</strong>:</p><p>根据GROUP BY子句中的列,对虚拟表2中的记录进行分组操作,产生虚拟表5。</p></li><li><p><strong>HAVING</strong>:</p><p>对分组后的数据集应用条件过滤,只保留满足条件的组,对虚拟表5进行HAVING过滤,符合条件的记录会被插入到虚拟表6中。</p></li><li><p><strong>SELECT</strong>:</p><p>SELECT到一步才执行,选择指定的列,插入到虚拟表7中</p><p>从处理过的数据集中选择指定的列,执行投影操作。这可能包括对列进行计算、使用别名等。</p></li><li><p><strong>UNION</strong>:</p><p>UNION连接的两个SELECT查询语句,会重复执行步骤1~7,产生两个虚拟表7,UNION会将这些记录合并到虚拟表8中。</p></li><li><p><strong>ORDER BY</strong>:</p><p>将结果集按照指定的列排序。</p><p>将虚拟表8中的记录进行排序,虚拟表9。</p></li><li><p><strong>LIMIT</strong>:</p><p>限制返回结果的数量或跳过指定数量的行后开始返回结果。</p><p>取出指定行的记录,返回结果集。</p></li></ol><h1 id="JVM"><a href="#JVM" class="headerlink" title="JVM"></a>JVM</h1><h2 id="1、JVM-的内存结构"><a href="#1、JVM-的内存结构" class="headerlink" title="1、JVM 的内存结构"></a>1、JVM 的内存结构</h2><p>按线程来讲可以分成两部分,一个是线程独占的,一个是线程共享的</p><p>线程共享的有方法区和堆</p><ul><li><p>堆:包括年轻代与老年代+字符串常量池,年轻代由一个Eden与两个Survivor区。</p></li><li><p>方法区:方法区是Java虚拟机的模型规范,具体实现是元空间和永久代,永久代是1.7的,1.8以后永久代就被移除了,就变成元空间了,元空间是分布在计算机内存中的,是脱离了Java虚拟机内存的,是独立存在的。</p></li></ul><p>线程独占的是虚拟机栈、本地方法栈、程序计数器</p><h2 id="2、JVM常用参数"><a href="#2、JVM常用参数" class="headerlink" title="2、JVM常用参数"></a>2、JVM常用参数</h2><ul><li><strong>-Xms</strong>:初始堆内存大小,设定程序启动时占用内存大小,默认物理内存1/64 -Xms = -XX:InitialHeapSiz</li><li><strong>-Xmx</strong>:最大堆内存,设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常,默认物理内存1/4,-Xmx = -XX:MaxHeapSize。上图中的-Xms与-Xmx设置的大小一样 6000M</li><li><strong>-Xmn</strong>:设置年轻代大小。<strong>整个堆大小=年轻代大小 + 年老代大小 + 常量池</strong>。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8</li><li><strong>-Xss:</strong> 设置单个线程栈大小,一般默认512~1024kb。单个线程栈大小跟操作系统和 JDK 版本都有关系,-Xss = -XX:ThreadStackSize</li><li><strong>-XX:MetaspaceSize</strong> :元空间大小,元空间本质跟永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间并不在虚拟机中,而是使用本地内存,由操作系统支配。因此,元空间大小仅受本地内存限制。</li><li><strong>-XX:+PrintGCDetails</strong> :打印GC详细日志信息</li><li><strong>-XX:SurvivorRatio</strong>:幸存者比例设置,设置年轻代中Eden区与Survivor区的大小比值。设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10</li><li><strong>-XX:NewRatio</strong>:新生代比例设置(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为1,则年轻代与年老代所占比值为1:1,年轻代占整个堆栈的1/2。</li><li><strong>-XX:MaxTenuringThreshold</strong>:进入老年代阈值设置</li></ul><h2 id="3、JVM常用工具"><a href="#3、JVM常用工具" class="headerlink" title="3、JVM常用工具"></a>3、JVM常用工具</h2><ul><li><p><strong>jsp</strong></p><p>查看java进程及其相关的信息。</p></li><li><p><strong>jinfo</strong></p><p>主要作用为实时查看和调整虚拟机各项参数。</p></li><li><p><strong>jmap</strong></p><p>查看堆内对象示例的统计信息、ClassLoader 的信息以及finalizer 队列。</p><p>也可以生成 java 程序的 dump 文件。</p></li><li><p><strong>jhat</strong></p><p>用来分析jmap生成dump文件的命令。</p></li><li><p><strong>jstat</strong></p><p>查看JVM运行时的状态信息,包括内存状态、垃圾回收等。</p></li><li><p><strong>jstack</strong></p><p>查看JVM线程快照的命令,线程快照是当前JVM线程正在执行的方法堆栈集合。</p><p>使用jstack命令可以定位线程出现长时间卡顿的原因,例如死锁,死循环等。</p><p>jstack还可以查看程序崩溃时生成的core文件中的stack信息。</p></li></ul><h2 id="4、JVM类加载机制"><a href="#4、JVM类加载机制" class="headerlink" title="4、JVM类加载机制"></a>4、JVM类加载机制</h2><blockquote><p><a href="https://blog.csdn.net/qq_48508278/article/details/122929631">https://blog.csdn.net/qq_48508278/article/details/122929631</a></p></blockquote><p><strong>加载-验证-准备-解析-初始化</strong></p><ul><li><p>加载</p><p>根据类的完整路径查找二进制文件,根据二进制文件创建类对象,存储在堆中。</p></li><li><p>验证</p><p>验证加载内容是否安全,是否会对虚拟机造成异常,验证文件格式,元数据和字节码。</p></li><li><p>准备</p><p>准备给类变量在方法区中进行内存分配,初始化赋零值,给初始值占坑。</p></li><li><p>解析</p><p>把常量池的符号引用转变成直接引用,在内存中通过这个引用找到目标。</p></li><li><p>初始化</p><p>执行Java代码,进行初始化,执行静态代码块,给静态变量赋值。</p></li></ul><h2 id="5、类加载器有哪些"><a href="#5、类加载器有哪些" class="headerlink" title="5、类加载器有哪些"></a>5、类加载器有哪些</h2><ul><li>启动类加载器</li><li>扩展类加载器</li><li>系统类加载器</li><li>自定义类加载器</li></ul><h2 id="6、双亲委派是什么"><a href="#6、双亲委派是什么" class="headerlink" title="6、双亲委派是什么"></a>6、双亲委派是什么</h2><p>当一个类要使用类加载器进行类加载时,会先请求委派给父类加载,当父类还有父类时,会继续往上委派,一直到顶,当父类加载器无法完成这个请求的时候,子类才会尝试去加载</p><p><strong>为什么要有双亲委派:认定两个对象同属于一个类型</strong></p><h2 id="7、垃圾回收算法常用的有哪些"><a href="#7、垃圾回收算法常用的有哪些" class="headerlink" title="7、垃圾回收算法常用的有哪些"></a>7、垃圾回收算法常用的有哪些</h2><ol><li>标记清除</li><li>标记复制</li><li>标记整理</li><li>分代</li></ol><h2 id="8、Java常见的几种垃圾收集器,Java8默认的垃圾收集器是什么"><a href="#8、Java常见的几种垃圾收集器,Java8默认的垃圾收集器是什么" class="headerlink" title="8、Java常见的几种垃圾收集器,Java8默认的垃圾收集器是什么"></a>8、Java常见的几种垃圾收集器,Java8默认的垃圾收集器是什么</h2><h3 id="1-常见的几种"><a href="#1-常见的几种" class="headerlink" title="1.常见的几种"></a>1.常见的几种</h3><p>7种经典垃圾回收器:Serial、Serial old、ParNew、Parallel Scavenge、Parallel old、CMS、G1</p><p>串行回收器:Serial、Serial old</p><p>并行回收器:ParNew、Parallel Scavenge、Parallel old</p><p>并发回收器:CMS、G1</p><h4 id="1-Serial收集器:"><a href="#1-Serial收集器:" class="headerlink" title="1.Serial收集器:"></a>1.Serial收集器:</h4><p>Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。</p><h4 id="2-Serial-Old收集器:"><a href="#2-Serial-Old收集器:" class="headerlink" title="2.Serial Old收集器:"></a>2.Serial Old收集器:</h4><p>是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。它主要有两大用途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。</p><p>启用命令: -XX:+UseSerialGC -XX:+UseSerialOldGC</p><h4 id="3-Parale-Scavenge收集器:"><a href="#3-Parale-Scavenge收集器:" class="headerlink" title="3.Parale Scavenge收集器:"></a>3.Parale Scavenge收集器:</h4><p>Parallel收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器类似。默认的收集线程数跟cpu核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。</p><p>Parallel Scavenge收集器关注点是达到一个可控的吞叶量,所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,香叶量就是99%。</p><p>新生代采用复制算法,老年代采用标记-整理算法。</p><h4 id="4-Parallel-Old收集器:"><a href="#4-Parallel-Old收集器:" class="headerlink" title="4.Parallel Old收集器:"></a>4.Parallel Old收集器:</h4><p>Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。</p><p>启用命令 -XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)</p><h4 id="5-ParNew收集器:"><a href="#5-ParNew收集器:" class="headerlink" title="5.ParNew收集器:"></a>5.ParNew收集器:</h4><p>ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。新生代采用复制算法,老年代采用标记-整理算法。</p><h4 id="6-CMS-Concurrent-Mark-Sweep-收集器"><a href="#6-CMS-Concurrent-Mark-Sweep-收集器" class="headerlink" title="6.CMS(Concurrent Mark Sweep) 收集器:"></a>6.CMS(Concurrent Mark Sweep) 收集器:</h4><p>收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。整个过程分为5个步骤:初始标记->并发标记->重新标记->并发清理->并发重置。</p><h4 id="7-G1收集器:"><a href="#7-G1收集器:" class="headerlink" title="7.G1收集器:"></a>7.G1收集器:</h4><p>标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选回收,不会产生空间碎片,可以精确地控制停顿。</p><h3 id="2-默认:ParallelGC"><a href="#2-默认:ParallelGC" class="headerlink" title="2.默认:ParallelGC"></a>2.默认:ParallelGC</h3><p><strong>1.新生代使用 Parallel Scavenge收集器</strong></p><p>使用的算法是基于标记-复制算法实现。收集器的目标是达到一个可控制的吞吐量,如何计算:吞吐量=用户代码运行时间/(代码运行时间+垃圾收集时间),重点关注一个参数吧 -XX:UserAdaptiveSizePolicy 这个参数激活后,不需要人工的指定新生代大小(-Xmn)、Eden与Surivivor区的比例,晋升老年代对象大小等参数了,虚拟机会根据当前系统运行情况收集性能监控信息动态调整这些参数以提供最合适的停顿时间合或最大的吞吐量。</p><p><strong>2.老年带使用的是Parallel Old收集器</strong></p><p>是Parallel Scavenge的老年代版本,基于标记-整理算法实现,支持多线程并行收集。他的出现缓解了Parallel Scavenge的尴尬处境,因为Parallel Scavenge和别的优秀的老年代收集器不搭。出现后他俩搭配,才让吞吐量优先的收集器名副其实。</p><h2 id="9、JVM-怎么调优"><a href="#9、JVM-怎么调优" class="headerlink" title="9、JVM 怎么调优"></a>9、JVM 怎么调优</h2><blockquote><p><a href="https://www.cnblogs.com/three-fighter/p/14644152.html">JVM调优总结</a></p></blockquote><h1 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h1><h2 id="1、Redis-数据类型"><a href="#1、Redis-数据类型" class="headerlink" title="1、Redis 数据类型"></a>1、Redis 数据类型</h2><ul><li>string</li><li>hash</li><li>list</li><li>set</li><li>zset</li></ul><h2 id="2、Redis-分布式锁用在哪里,哨兵机制"><a href="#2、Redis-分布式锁用在哪里,哨兵机制" class="headerlink" title="2、Redis 分布式锁用在哪里,哨兵机制"></a>2、Redis 分布式锁用在哪里,哨兵机制</h2><p>当多个线程想要去操作同一个缓存数据时,通过 redis 分布式锁将其锁住,并设置一个到期时间,防止业务异常导致无法解锁。</p><p>在查询一个数据的时候,先用 redis 分布式锁将其锁住,然后继续查询,先从 redis 缓存里面查,查不到再从数据库查,并且把查询结果保存到缓存中,然后返回结果,然后删除分布式锁。当下一个操作线程进入时,同样锁住,然后执行业务,可以从缓存中取到值。就是防止缓存穿透。</p><h3 id="Redis-哨兵机制"><a href="#Redis-哨兵机制" class="headerlink" title="Redis 哨兵机制"></a>Redis 哨兵机制</h3><blockquote><p><a href="https://www.cnblogs.com/zhonglongbo/p/13128955.html">Redis的四种模式,单机、主从、哨兵、集群</a></p></blockquote><h2 id="3、Redis-的-LUA-脚本有用过吗"><a href="#3、Redis-的-LUA-脚本有用过吗" class="headerlink" title="3、Redis 的 LUA 脚本有用过吗"></a>3、Redis 的 LUA 脚本有用过吗</h2><p>在 Redis 分布式锁用过,在代码中调用 execute 方法,script 参数用执行官方文档上的 LUA 脚本删除分布式锁</p><h2 id="4、缓存雪崩、穿透、击穿、数据库一致"><a href="#4、缓存雪崩、穿透、击穿、数据库一致" class="headerlink" title="4、缓存雪崩、穿透、击穿、数据库一致"></a>4、缓存雪崩、穿透、击穿、数据库一致</h2><p>三者出现的根本原因是:Redis 缓存命中率下降,请求直接打到 DB 上了</p><p>正常情况下,大量的资源请求都会被 redis 响应,在 redis 得不到响应的小部分请求才会去请求 DB,这样 DB 的压力是非常小的,是可以正常工作的</p><p>如果大量的请求在 redis 上得不到响应,那么就会导致这些请求会直接去访问 DB,导致 DB 的压力瞬间变大而卡死或者宕机。</p><p>大量的高并发的请求打在 redis 上</p><p>这些请求发现 redis 上并没有需要请求的资源,redis 命中率降低</p><p>因此这些大量的高并发请求转向 DB(数据库服务器)请求对应的资源</p><p>DB 压力瞬间增大,直接将 DB 打垮,进而引发一系列“灾害”</p><h3 id="1、缓存穿透:"><a href="#1、缓存穿透:" class="headerlink" title="1、缓存穿透:"></a>1、缓存穿透:</h3><p>是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。</p><p><strong>解决:</strong></p><p><strong>根本原因(结合上文)就是:请求根本不存在的资源</strong></p><ul><li><p><strong>对空值进行缓存</strong></p><p>类似于上面的例子,虽然数据库中没有 id=-9527 的用户的数据,但是在 redis 中对他进行缓存(key=-9527,value=null),这样当请求到达 redis 的时候就会直接返回一个 null 的值给客户端,避免了大量无法访问的数据直接打在 DB 上。</p></li><li><p>实时监控</p><p>对 redis 进行实时监控,当发现 redis 中的命中率下降的时候进行原因的排查,配合运维人员对访问对象和访问数据进行分析查询,从而进行黑名单的设置限制服务。</p></li><li><p>使用布隆过滤器</p><p>使用 BitMap 作为布隆过滤器,将目前所有可以访问到的资源通过简单的映射关系放入到布隆过滤器中(哈希计算),当一个请求来临的时候先进行布隆过滤器的判断,如果有那么才进行放行,否则就直接拦截。</p></li><li><p>接口校验</p><p>类似于用户权限的拦截,对于 id=-3872 这些无效访问就直接拦截,不允许这些请求到达 Redis、DB 上。</p></li></ul><h3 id="2、缓存雪崩:"><a href="#2、缓存雪崩:" class="headerlink" title="2、缓存雪崩:"></a>2、缓存雪崩:</h3><p>我们可以简单的理解为:由于原有缓存失效,新缓存未到时间 (例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。</p><p><strong>解决:</strong></p><p><strong>产生的原因:redis 中大量的 key 集体过期</strong></p><ul><li><p>将失效时间分散开</p><p>通过使用自动生成随机数使得 key 的过期时间是随机的,防止集体过期</p></li><li><p>使用多级架构</p><p>使用 nginx 缓存+redis 缓存+其他缓存,不同层使用不同的缓存,可靠性更强</p></li><li><p>设置缓存标记</p><p>记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际的 key</p></li><li><p>使用锁或者队列的方式</p><p>如果查不到就加上排它锁,其他请求只能进行等待</p></li></ul><h3 id="3、缓存击穿:"><a href="#3、缓存击穿:" class="headerlink" title="3、缓存击穿:"></a>3、缓存击穿:</h3><p>某个 key 非常非常热,访问非常的频繁,高并发访问的情况下,当这个 key 在失效(可能 expire 过期了,也可能 LRU 淘汰了)的瞬间,大量的请求进来,这时候就击穿了缓存,直接请求到了数据库,一下子来这么多,数据库肯定受不了,这就叫缓存击穿。某个 key 突然失效,然后这时候高并发来访问这个 key,结果缓存里没有,都跑到 db 了。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。</p><p><strong>解决:</strong></p><p><strong>产生的原因:redis 中的某个热点 key 过期,但是此时有大量的用户访问该过期 key。</strong></p><ul><li><p>提前对热点数据进行设置</p><p>类似于新闻、某博等软件都需要对热点数据进行预先设置在 redis 中</p></li><li><p>监控数据,适时调整</p><p>监控哪些数据是热门数据,实时的调整 key 的过期时长</p></li><li><p>使用锁机制</p><p>只有一个请求可以获取到互斥锁,然后到 DB 中将数据查询并返回到 Redis,之后所有请求就可以从 Redis 中得到响应</p></li></ul><h1 id="MQ"><a href="#MQ" class="headerlink" title="MQ"></a>MQ</h1><h2 id="1-消息确认"><a href="#1-消息确认" class="headerlink" title="1. 消息确认"></a>1. 消息确认</h2><p>消费者收到的每一条消息都必须进行确认(自动确认和消费者自行确认)。</p><p>消费者在声明队列时,可以指定 autoAck 参数,当 autoAck=false 时,RabbitMQ 会等待消费者显式发回 ack 信号后才从内存 (和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ 会在队列中消息被消费后立即删除它。</p><p>采用消息确认机制后,只要令 autoAck=false,消费者就有足够的时间处理消息 (任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为 RabbitMQ 会一直持有消息直到消费者显式调用 basicAck 为止。</p><p>当 autoAck=false 时,对于 RabbitMQ 服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者 ack 信号的消息。如果服务器端一直没有收到消费者的 ack 信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列。</p><p>RabbitMQ 不会为未 ack 的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是 RabbitMQ 允许消费者消费一条消息的时间可以很久很久。</p><h2 id="2-消息堆积"><a href="#2-消息堆积" class="headerlink" title="2. 消息堆积"></a>2. 消息堆积</h2><ol><li><p>消息堆积的后果</p><p>新消息无法进入队列、旧消息无法丢失、消息等待消费时间过长以至于超出了业务容许的范围。</p></li><li><p>消息堆积的原因</p><p>生产者突然大量发布消息、消费者来不及消费或消费失败、消费者出现性能瓶颈、消费者直接挂掉了。</p></li><li><p>如何解决消息堆积</p><p>(1)排查生产者,是否突然大量发布消息,限制下</p><p>(2)排查消费者,消费性能瓶颈,增加消费者的多线程处理(缩短线程休眠时间等)、部署多个消费者</p><p>(3)排查消息队列,可以想办法把消息按顺序的转移到另外一个新的队列,让消费者消费新队列中的消息。</p><p>(4)可以通过修改 RabbitMQ 的两个参数来增大消费消息的并发数:</p><ol><li><p><strong>concurrentConsumers</strong>:对每个 listener 在初始化的时候设置的并发消费者的个数。</p></li><li><p><strong>prefetchCount</strong>:</p><p>每次一次性从 broker 里面取的待消费的消息的个数,prefetchCount 是 BlockingQueueConsumer 内部维护的一个阻塞队列 LinkedBlockingQueue 的大小,其作用就是如果某个消费者队列阻塞,就无法接收新的消息,该消息会发送到其它未阻塞的消费者。</p></li></ol></li></ol><h2 id="3-消息丢失"><a href="#3-消息丢失" class="headerlink" title="3. 消息丢失"></a>3. 消息丢失</h2><p>消息分别在生产者、消息队列、消费者中丢失:</p><h4 id="1-消息在生产者丢失"><a href="#1-消息在生产者丢失" class="headerlink" title="1. 消息在生产者丢失"></a>1. 消息在生产者丢失</h4><p><strong>原因:</strong> 生产者发送消息成功,但 MQ 没收到该消息,一般由网络不稳定造成。</p><p>解决方案:发送方采用消息确认机制,当消息成功被 MQ 接收到后,会给生产者发送一个确认消息,表示接收成功。RabbitMQ 发送方确认模式有三种,普通确认、批量确认、异步确认。Spring 整合 RabbitMQ 后只使用了异步监听确认模式。</p><h4 id="2-消息在队列中丢失"><a href="#2-消息在队列中丢失" class="headerlink" title="2. 消息在队列中丢失"></a>2. 消息在队列中丢失</h4><p><strong>原因:</strong> 消息发送到 MQ 后,消息还没被消费却在 MQ 中丢失了。比如 MQ 服务器宕机或者未进行持久化就进行了重启。</p><p>解决方案:持久化交换机(Exchange)、队列、消息。确保 MQ 服务器异常重启时仍然能从磁盘恢复对应的交换机,队列和消息。然后我们把 MQ 做多台分布式集群,防止出现某一 MQ 服务器挂掉~</p><h4 id="3-消息在消费者丢失"><a href="#3-消息在消费者丢失" class="headerlink" title="3. 消息在消费者丢失"></a>3. 消息在消费者丢失</h4><p><strong>原因:</strong> 默认消费者消费消息时,设置的是自动回复 MQ 收到了消息。MQ 会立刻删除自身保存的这条消息,如果消息已经在 MQ 中被删除,但消费者的业务处理出现异常或消费者服务宕机,那么就会导致该消息没有处理成功从而导致消息丢失。</p><p><strong>解决方案:</strong> 消费者向 MQ 的回复我们设置成手动回复(配置成手动 ACK)。当消费者出现异常或者服务宕机时,MQ 服务器不会删除该消息,而是会把消息重发给绑定该队列的消费者,如果该队列只绑定了一个消费者,则该消息会一直保持在 MQ 服务器,直到消费者能正常消费为止。</p><p>正常业务逻辑应该是本地业务执行成功,手动 ack 这条消息。如果业务执行完毕,手动 ack 的时候恰好服务宕机了,重启……这不是会造成重复消费吗?没错,这就牵扯 mq 的另一个问题了,mq 消息重复消费~</p><h2 id="4-重复消费"><a href="#4-重复消费" class="headerlink" title="4. 重复消费"></a>4. 重复消费</h2><ol><li><p>场景<br>因消息重发机制会出现消息重复消费的情况</p></li><li><p>解决方案</p></li></ol><p>(1)幂等操作,同一个操作执行 N 次,结果不变。</p><p>(2)若实际业务中用不了幂等,则保存消息 id 到数据库(Redis)中,每次消费前查看消息是否已经被消费过。</p><h2 id="5-有序消费"><a href="#5-有序消费" class="headerlink" title="5. 有序消费"></a>5. 有序消费</h2><ol><li><p>场景<br>在 work queue 模式下,只有一个队列,但存在多个消费者。多个消费者线程的竞争会导致数据乱序。<br>在简单队列模式下,同样的多个消费者线程也会导致数据乱序。</p></li><li><p>解决方案<br>使用多个队列,对消息的 id 值做 hash。再对队列数取模(hash 值%队列数),将结果相同的消息压入同一个队列中去,这就保证了一个队列中有且仅有一个消费者。<br>在 MQ 队列后的 Java 代码中(消费方),再为每一个线程加一个内存队列,根据消息的 id 求 hash 值,然后把相同的结果压入同一个内存队列……</p></li></ol><h1 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h1><h2 id="1、线程安全的单例模式"><a href="#1、线程安全的单例模式" class="headerlink" title="1、线程安全的单例模式"></a>1、线程安全的单例模式</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> Singleton singleton;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">Singleton</span><span class="params">()</span> {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Singleton <span class="title function_">getSingleton</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (singleton == <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">synchronized</span> (Singleton.class) {</span><br><span class="line"> <span class="keyword">if</span> (singleton == <span class="literal">null</span>) {</span><br><span class="line"> singleton = <span class="keyword">new</span> <span class="title class_">Singleton</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> singleton;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 面试总结 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 面试总结 </tag>
</tags>
</entry>
</search>