-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
457 lines (252 loc) · 279 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>秋过冬漫长</title>
<subtitle>没有比脚更长的路,走过去,前面是个天!</subtitle>
<link href="https://alexhuihui.github.io/atom.xml" rel="self"/>
<link href="https://alexhuihui.github.io/"/>
<updated>2025-01-25T12:29:09.828Z</updated>
<id>https://alexhuihui.github.io/</id>
<author>
<name>汪永晖</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>【DNS】DNS的架构</title>
<link href="https://alexhuihui.github.io/article/20250125.html"/>
<id>https://alexhuihui.github.io/article/20250125.html</id>
<published>2025-01-25T12:28:38.000Z</published>
<updated>2025-01-25T12:29:09.828Z</updated>
<category term="DNS" scheme="https://alexhuihui.github.io/categories/DNS/"/>
<category term="DNS" scheme="https://alexhuihui.github.io/tags/DNS/"/>
</entry>
<entry>
<title>使用 cloudflare tunnel</title>
<link href="https://alexhuihui.github.io/article/20250113.html"/>
<id>https://alexhuihui.github.io/article/20250113.html</id>
<published>2025-01-13T11:54:06.000Z</published>
<updated>2025-01-25T13:27:36.360Z</updated>
<content type="html"><![CDATA[<p>使用 cloudflare tunnel</p><span id="more"></span> <h4 id="1-创建多个-Tunnel"><a href="#1-创建多个-Tunnel" class="headerlink" title="1. 创建多个 Tunnel"></a>1. <strong>创建多个 Tunnel</strong></h4><p>每个 Tunnel 都需要独立创建。以 SSH 和 HTTPS 为例:</p><ol><li><p><strong>创建 SSH Tunnel</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cloudflared tunnel create ssh-tunnel</span><br></pre></td></tr></table></figure></li><li><p><strong>创建 HTTPS Tunnel</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cloudflared tunnel create https-tunnel</span><br></pre></td></tr></table></figure></li></ol><p>每个 Tunnel 都会生成一个唯一的 <code>credentials-file</code>,通常位于 <code>~/.cloudflared/</code> 目录下。</p><hr><h4 id="2-配置多个-Tunnel-的服务"><a href="#2-配置多个-Tunnel-的服务" class="headerlink" title="2. 配置多个 Tunnel 的服务"></a>2. <strong>配置多个 Tunnel 的服务</strong></h4><p>为每个 Tunnel 创建独立的配置文件。例如:</p><h5 id="SSH-Tunnel-配置"><a href="#SSH-Tunnel-配置" class="headerlink" title="SSH Tunnel 配置"></a><strong>SSH Tunnel 配置</strong></h5><p>创建或编辑 <code>~/.cloudflared/ssh-config.yml</code>:</p><figure class="highlight yaml"><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="attr">tunnel:</span> <span class="string">ssh-tunnel</span></span><br><span class="line"><span class="attr">credentials-file:</span> <span class="string">/home/your-user/.cloudflared/ssh-tunnel.json</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ingress:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">hostname:</span> <span class="string">ssh.example.com</span></span><br><span class="line"> <span class="attr">service:</span> <span class="string">ssh://localhost:22</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">http_status:404</span></span><br></pre></td></tr></table></figure><h5 id="HTTPS-Tunnel-配置"><a href="#HTTPS-Tunnel-配置" class="headerlink" title="HTTPS Tunnel 配置"></a><strong>HTTPS Tunnel 配置</strong></h5><p>创建或编辑 <code>~/.cloudflared/https-config.yml</code>:</p><figure class="highlight yaml"><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="attr">tunnel:</span> <span class="string">https-tunnel</span></span><br><span class="line"><span class="attr">credentials-file:</span> <span class="string">/home/your-user/.cloudflared/https-tunnel.json</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ingress:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">hostname:</span> <span class="string">www.example.com</span></span><br><span class="line"> <span class="attr">service:</span> <span class="string">http://localhost:80</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">http_status:404</span></span><br></pre></td></tr></table></figure><hr><h4 id="3-创建DNS记录"><a href="#3-创建DNS记录" class="headerlink" title="3. 创建DNS记录"></a>3. <strong>创建DNS记录</strong></h4><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></pre></td><td class="code"><pre><span class="line">sudo cloudflared tunnel route dns ssh-tunnel ssh.example.com</span><br><span class="line"></span><br><span class="line">sudo cloudflared tunnel route dns https-tunnel www.example.com</span><br></pre></td></tr></table></figure><h4 id="4-启动多个-Tunnel"><a href="#4-启动多个-Tunnel" class="headerlink" title="4. 启动多个 Tunnel"></a>4. <strong>启动多个 Tunnel</strong></h4><p>可以使用以下命令分别启动每个 Tunnel:</p><ul><li><p>启动 SSH Tunnel:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cloudflared tunnel --config ~/.cloudflared/ssh-config.yml run ssh-tunnel</span><br></pre></td></tr></table></figure></li><li><p>启动 HTTPS Tunnel:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cloudflared tunnel --config ~/.cloudflared/https-config.yml run https-tunnel</span><br></pre></td></tr></table></figure></li></ul><hr><h4 id="5-使用-Systemd-管理多个-Tunnel"><a href="#5-使用-Systemd-管理多个-Tunnel" class="headerlink" title="5. 使用 Systemd 管理多个 Tunnel"></a>5. <strong>使用 Systemd 管理多个 Tunnel</strong></h4><p>为每个 Tunnel 配置一个 Systemd 服务,以便在系统启动时自动运行。</p><ol><li><p><strong>创建 Systemd 服务文件</strong></p><ul><li><p>为 SSH Tunnel 创建服务文件 <code>/etc/systemd/system/ssh-tunnel.service</code>:</p><figure class="highlight ini"><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="section">[Unit]</span></span><br><span class="line"><span class="attr">Description</span>=Cloudflare Tunnel for SSH</span><br><span class="line"><span class="attr">After</span>=network.target</span><br><span class="line"></span><br><span class="line"><span class="section">[Service]</span></span><br><span class="line"><span class="attr">ExecStart</span>=/usr/local/bin/cloudflared tunnel --config /home/your-user/.cloudflared/ssh-config.yml run ssh-tunnel</span><br><span class="line"><span class="attr">Restart</span>=always</span><br><span class="line"><span class="attr">User</span>=your-user</span><br><span class="line"></span><br><span class="line"><span class="section">[Install]</span></span><br><span class="line"><span class="attr">WantedBy</span>=multi-user.target</span><br></pre></td></tr></table></figure></li><li><p>为 HTTPS Tunnel 创建服务文件 <code>/etc/systemd/system/https-tunnel.service</code>:</p><figure class="highlight ini"><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="section">[Unit]</span></span><br><span class="line"><span class="attr">Description</span>=Cloudflare Tunnel for HTTPS</span><br><span class="line"><span class="attr">After</span>=network.target</span><br><span class="line"></span><br><span class="line"><span class="section">[Service]</span></span><br><span class="line"><span class="attr">ExecStart</span>=/usr/local/bin/cloudflared tunnel --config /home/your-user/.cloudflared/https-config.yml run https-tunnel</span><br><span class="line"><span class="attr">Restart</span>=always</span><br><span class="line"><span class="attr">User</span>=your-user</span><br><span class="line"></span><br><span class="line"><span class="section">[Install]</span></span><br><span class="line"><span class="attr">WantedBy</span>=multi-user.target</span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>启动和启用服务</strong></p><ul><li><p>启动 SSH Tunnel 服务:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl start ssh-tunnel</span><br><span class="line">sudo systemctl <span class="built_in">enable</span> ssh-tunnel</span><br></pre></td></tr></table></figure></li><li><p>启动 HTTPS Tunnel 服务:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl start https-tunnel</span><br><span class="line">sudo systemctl <span class="built_in">enable</span> https-tunnel</span><br></pre></td></tr></table></figure></li></ul></li></ol><hr><h4 id="6-配置本地的-ssh"><a href="#6-配置本地的-ssh" class="headerlink" title="6. **配置本地的 ssh **"></a>6. **配置本地的 ssh **</h4><p>需要在本地ssh配置代理</p><figure class="highlight plaintext"><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">Host ssh.example.com</span><br><span class="line">ProxyCommand C:\\Users\\29308\\.ssh\\cloudflared-windows-amd64.exe access ssh --hostname %h</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>使用 cloudflare tunnel</p></summary>
<category term="cloudflare" scheme="https://alexhuihui.github.io/categories/cloudflare/"/>
<category term="tunnel" scheme="https://alexhuihui.github.io/tags/tunnel/"/>
<category term="cloudflare" scheme="https://alexhuihui.github.io/tags/cloudflare/"/>
</entry>
<entry>
<title>【Spring】缓存注解@Cacheable、@CacheEvict、@CachePut使用</title>
<link href="https://alexhuihui.github.io/article/20240525.html"/>
<id>https://alexhuihui.github.io/article/20240525.html</id>
<published>2024-05-25T11:54:06.000Z</published>
<updated>2025-01-25T12:03:12.057Z</updated>
<content type="html"><![CDATA[<p>Spring Cache 通过封装统一的缓存抽象层,支持多种缓存实现(如Ehcache、Redis、Caffeine等)。可以使用注解@Cacheable、@CacheEvict、@CachePut等优雅的进行使用,无需修改业务代码。</p><span id="more"></span> <h3 id="Cacheable"><a href="#Cacheable" class="headerlink" title="@Cacheable"></a>@Cacheable</h3><p><code>@Cacheable</code>注解用于在方法执行前检查缓存,如果缓存中有数据则返回缓存中的数据,否则执行方法并将结果缓存。</p><p><strong>工作原理</strong>:</p><ol><li><strong>方法调用前</strong>:<ul><li>AOP代理拦截方法调用。</li><li>从指定的缓存区(<code>value</code>属性指定)中根据缓存键(<code>key</code>属性指定)查找缓存条目。</li><li>如果找到缓存条目,则返回缓存的值,不执行目标方法。</li></ul></li><li><strong>方法调用后</strong>:<ul><li>如果缓存中没有找到条目,则执行目标方法。</li><li>将方法返回值存储到缓存中,使用指定的缓存区和缓存键。</li></ul></li></ol><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></pre></td><td class="code"><pre><span class="line">@Cacheable(value = "users", key = "#userId")</span><br><span class="line">public User getUserById(Long userId) {</span><br><span class="line"> return userRepository.findById(userId).orElse(null);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="CacheEvict"><a href="#CacheEvict" class="headerlink" title="@CacheEvict"></a>@CacheEvict</h3><p><code>@CacheEvict</code>注解用于从缓存中移除一个或多个条目,通常在数据修改或删除操作时使用。</p><p><strong>工作原理</strong>:</p><ol><li><p>方法调用后</p><p>:</p><ul><li>AOP代理拦截方法调用。</li><li>执行目标方法。</li><li>从指定的缓存区(<code>value</code>属性指定)中根据缓存键(<code>key</code>属性指定)移除缓存条目。</li><li>如果设置了<code>allEntries = true</code>,则清空整个缓存区。</li></ul></li></ol><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></pre></td><td class="code"><pre><span class="line">@CacheEvict(value = "users", key = "#userId")</span><br><span class="line">public void deleteUserById(Long userId) {</span><br><span class="line"> userRepository.deleteById(userId);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="CachePut"><a href="#CachePut" class="headerlink" title="@CachePut"></a>@CachePut</h3><p><code>@CachePut</code>注解用于在方法执行后将结果更新到缓存中。与<code>@Cacheable</code>不同,它总是会执行目标方法。</p><p><strong>工作原理</strong>:</p><ol><li><p>方法调用后</p><p>:</p><ul><li>AOP代理拦截方法调用。</li><li>执行目标方法。</li><li>将方法返回值存储到缓存中,使用指定的缓存区(<code>value</code>属性指定)和缓存键(<code>key</code>属性指定)。</li></ul></li></ol><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></pre></td><td class="code"><pre><span class="line">@CachePut(value = "users", key = "#user.id")</span><br><span class="line">public User updateUser(User user) {</span><br><span class="line"> return userRepository.save(user);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>@CacheConfig</code>注解用于在类级别配置缓存的公共设置,简化方法级别的缓存配置。如果类中的多个方法共享相同的缓存配置(例如相同的缓存名称),使用<code>@CacheConfig</code>可以避免在每个方法上重复配置。</p><h3 id="CacheConfig的作用"><a href="#CacheConfig的作用" class="headerlink" title="@CacheConfig的作用"></a>@CacheConfig的作用</h3><ul><li><strong>缓存名称</strong>:设置默认的缓存名称,应用于该类中的所有缓存操作。</li><li><strong>缓存管理器</strong>:指定默认的缓存管理器,应用于该类中的所有缓存操作。</li></ul><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>假设我们有一个<code>UserService</code>类,其中多个方法需要使用相同的缓存配置。</p><h4 id="不使用-CacheConfig"><a href="#不使用-CacheConfig" class="headerlink" title="不使用@CacheConfig"></a>不使用@CacheConfig</h4><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">import org.springframework.cache.annotation.Cacheable;</span><br><span class="line">import org.springframework.cache.annotation.CacheEvict;</span><br><span class="line">import org.springframework.cache.annotation.CachePut;</span><br><span class="line">import org.springframework.stereotype.Service;</span><br><span class="line"></span><br><span class="line">@Service</span><br><span class="line">public class UserService {</span><br><span class="line"></span><br><span class="line"> @Cacheable(value = "users", key = "#userId")</span><br><span class="line"> public User getUserById(Long userId) {</span><br><span class="line"> return userRepository.findById(userId).orElse(null);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @CacheEvict(value = "users", key = "#userId")</span><br><span class="line"> public void deleteUserById(Long userId) {</span><br><span class="line"> userRepository.deleteById(userId);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @CachePut(value = "users", key = "#user.id")</span><br><span class="line"> public User updateUser(User user) {</span><br><span class="line"> return userRepository.save(user);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这个例子中,每个缓存注解都需要指定<code>value = "users"</code>,如果有多个方法共享相同的缓存名称,这样会显得冗余。</p><h4 id="使用-CacheConfig"><a href="#使用-CacheConfig" class="headerlink" title="使用@CacheConfig"></a>使用@CacheConfig</h4><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><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></pre></td><td class="code"><pre><span class="line">import org.springframework.cache.annotation.CacheConfig;</span><br><span class="line">import org.springframework.cache.annotation.Cacheable;</span><br><span class="line">import org.springframework.cache.annotation.CacheEvict;</span><br><span class="line">import org.springframework.cache.annotation.CachePut;</span><br><span class="line">import org.springframework.stereotype.Service;</span><br><span class="line"></span><br><span class="line">@Service</span><br><span class="line">@CacheConfig(cacheNames = "users")</span><br><span class="line">public class UserService {</span><br><span class="line"></span><br><span class="line"> @Cacheable(key = "#userId")</span><br><span class="line"> public User getUserById(Long userId) {</span><br><span class="line"> return userRepository.findById(userId).orElse(null);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @CacheEvict(key = "#userId")</span><br><span class="line"> public void deleteUserById(Long userId) {</span><br><span class="line"> userRepository.deleteById(userId);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @CachePut(key = "#user.id")</span><br><span class="line"> public User updateUser(User user) {</span><br><span class="line"> return userRepository.save(user);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这个例子中:</p><ul><li><code>@CacheConfig(cacheNames = "users")</code>:在类级别配置默认的缓存名称为“users”。</li><li>各个方法上不再需要重复指定缓存名称,只需要配置<code>key</code>属性。</li></ul><h3 id="CacheConfig-的使用场景"><a href="#CacheConfig-的使用场景" class="headerlink" title="@CacheConfig 的使用场景"></a>@CacheConfig 的使用场景</h3><ul><li><strong>统一缓存配置</strong>:当一个类中多个方法使用相同的缓存名称或缓存管理器时,可以通过<code>@CacheConfig</code>统一配置,减少代码冗余。</li><li><strong>简化配置</strong>:避免在每个方法的缓存注解中重复指定相同的缓存名称或其他配置。</li></ul><h3 id="进一步示例"><a href="#进一步示例" class="headerlink" title="进一步示例"></a>进一步示例</h3><p>假设我们在同一个类中使用多个缓存名称,可以通过<code>@CacheConfig</code>配置默认的缓存名称,同时在特定方法上覆盖默认配置。</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><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">import org.springframework.cache.annotation.CacheConfig;</span><br><span class="line">import org.springframework.cache.annotation.Cacheable;</span><br><span class="line">import org.springframework.cache.annotation.CacheEvict;</span><br><span class="line">import org.springframework.cache.annotation.CachePut;</span><br><span class="line">import org.springframework.stereotype.Service;</span><br><span class="line"></span><br><span class="line">@Service</span><br><span class="line">@CacheConfig(cacheNames = "users")</span><br><span class="line">public class UserService {</span><br><span class="line"></span><br><span class="line"> @Cacheable(key = "#userId")</span><br><span class="line"> public User getUserById(Long userId) {</span><br><span class="line"> return userRepository.findById(userId).orElse(null);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @CacheEvict(key = "#userId")</span><br><span class="line"> public void deleteUserById(Long userId) {</span><br><span class="line"> userRepository.deleteById(userId);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @CachePut(key = "#user.id")</span><br><span class="line"> public User updateUser(User user) {</span><br><span class="line"> return userRepository.save(user);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Cacheable(value = "admins", key = "#adminId")</span><br><span class="line"> public Admin getAdminById(Long adminId) {</span><br><span class="line"> return adminRepository.findById(adminId).orElse(null);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这个例子中:</p><ul><li><code>@CacheConfig(cacheNames = "users")</code>:在类级别设置默认缓存名称为“users”。</li><li><code>getAdminById</code>方法通过<code>@Cacheable(value = "admins", key = "#adminId")</code>覆盖了默认的缓存名称,使用“admins”缓存。</li></ul><p>通过使用<code>@CacheConfig</code>,可以使代码更清晰,避免重复配置,提高可维护性。</p><h3 id="实现细节"><a href="#实现细节" class="headerlink" title="实现细节"></a>实现细节</h3><ol><li><strong>Spring AOP代理</strong>:<ul><li>Spring AOP通过JDK动态代理或CGLIB创建代理对象。</li><li>代理对象拦截方法调用,在调用目标方法之前或之后执行缓存逻辑。</li></ul></li><li><strong>CacheManager和Cache接口</strong>:<ul><li><code>CacheManager</code>:Spring缓存抽象中的核心接口,用于管理不同的缓存实现。</li><li><code>Cache</code>:表示具体的缓存,提供基本的缓存操作方法,如<code>get</code>、<code>put</code>、<code>evict</code>等。</li></ul></li><li><strong>缓存配置</strong>:<ul><li>在Spring Boot项目中,通过<code>@EnableCaching</code>注解启用缓存功能。</li><li>在配置文件中指定具体的缓存实现(如Ehcache、Redis等)的配置。</li></ul></li></ol><h3 id="示例项目配置"><a href="#示例项目配置" class="headerlink" title="示例项目配置"></a>示例项目配置</h3><p><strong>引入依赖</strong>:</p><p>在<code>pom.xml</code>中添加依赖:</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></pre></td><td class="code"><pre><span class="line"><dependency></span><br><span class="line"> <groupId>org.springframework.boot</groupId></span><br><span class="line"> <artifactId>spring-boot-starter-cache</artifactId></span><br><span class="line"> </dependency></span><br><span class="line"></span><br><span class="line"> <dependency></span><br><span class="line"> <groupId>org.springframework.boot</groupId></span><br><span class="line"> <artifactId>spring-boot-starter-data-redis</artifactId></span><br><span class="line"> </dependency></span><br></pre></td></tr></table></figure><p><strong>配置文件Yaml</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></pre></td><td class="code"><pre><span class="line">spring:</span><br><span class="line"> application:</span><br><span class="line"> name: buzz-chat</span><br><span class="line"></span><br><span class="line"> data:</span><br><span class="line"> redis:</span><br><span class="line"> host: 127.0.0.1</span><br><span class="line"> port: 6379</span><br><span class="line"> database: 0</span><br><span class="line"></span><br><span class="line"> cache:</span><br><span class="line"> type: REDIS</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">import org.springframework.boot.SpringApplication;</span><br><span class="line">import org.springframework.boot.autoconfigure.SpringBootApplication;</span><br><span class="line">import org.springframework.cache.annotation.EnableCaching;</span><br><span class="line"></span><br><span class="line">@SpringBootApplication</span><br><span class="line">@EnableCaching</span><br><span class="line">public class CacheApplication {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> SpringApplication.run(CacheApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过上述配置和代码,Spring Cache和缓存注解可以在Spring应用中无缝工作。AOP和缓存抽象机制使得这些缓存操作透明地集成到业务逻辑中,大大简化了开发和维护工作。</p>]]></content>
<summary type="html"><p>Spring Cache 通过封装统一的缓存抽象层,支持多种缓存实现(如Ehcache、Redis、Caffeine等)。可以使用注解@Cacheable、@CacheEvict、@CachePut等优雅的进行使用,无需修改业务代码。</p></summary>
<category term="Spring" scheme="https://alexhuihui.github.io/categories/Spring/"/>
<category term="Spring" scheme="https://alexhuihui.github.io/tags/Spring/"/>
</entry>
<entry>
<title>【MySQL】索引失效的场景</title>
<link href="https://alexhuihui.github.io/article/20240522.html"/>
<id>https://alexhuihui.github.io/article/20240522.html</id>
<published>2024-05-22T11:54:06.000Z</published>
<updated>2025-01-25T12:34:39.694Z</updated>
<content type="html"><![CDATA[<p>今天我们讨论一下数据库索引在什么情况下会失效,总的来说有以下几种场景</p><ol><li>在索引列上加函数运算</li><li>组合索引中,不符合最左匹配原则</li><li>当索引列存在隐式转化的时候</li><li>使用like通配符匹配后缀%xxx的时候</li><li>使用or连接查询的时候,or语句前后没有同时使用索引</li></ol><span id="more"></span> <h3 id="设计-SQL-表"><a href="#设计-SQL-表" class="headerlink" title="设计 SQL 表"></a>设计 SQL 表</h3><p>假设我们设计一个员工表 <code>employees</code>,包含以下字段:</p><ul><li><code>employee_id</code>:员工ID(字符串类型,作为索引列)</li><li><code>name</code>:员工姓名</li><li><code>department_id</code>:部门ID(字符串类型,作为索引列)</li><li><code>age</code>:员工年龄</li></ul><h3 id="创建表的-SQL-语句"><a href="#创建表的-SQL-语句" class="headerlink" title="创建表的 SQL 语句"></a>创建表的 SQL 语句</h3><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></pre></td><td class="code"><pre><span class="line">CREATE TABLE employees (</span><br><span class="line"> employee_id VARCHAR(20) NOT NULL,</span><br><span class="line"> name VARCHAR(100),</span><br><span class="line"> department_id VARCHAR(20),</span><br><span class="line"> age INT,</span><br><span class="line"> PRIMARY KEY (employee_id),</span><br><span class="line"> KEY idx_department_id (department_id)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="插入示例数据"><a href="#插入示例数据" class="headerlink" title="插入示例数据"></a>插入示例数据</h3><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></pre></td><td class="code"><pre><span class="line">INSERT INTO employees (employee_id, name, department_id, age) VALUES</span><br><span class="line">('E001', 'Alice', 'D001', 30),</span><br><span class="line">('E002', 'Bob', 'D002', 25),</span><br><span class="line">('E003', 'Charlie', 'D001', 28),</span><br><span class="line">('E004', 'David', 'D003', 35),</span><br><span class="line">('E005', 'Eve', 'D002', 22);</span><br></pre></td></tr></table></figure><h3 id="查询语句及其-EXPLAIN-分析"><a href="#查询语句及其-EXPLAIN-分析" class="headerlink" title="查询语句及其 EXPLAIN 分析"></a>查询语句及其 EXPLAIN 分析</h3><h4 id="1-隐式类型转换导致索引失效"><a href="#1-隐式类型转换导致索引失效" class="headerlink" title="1. 隐式类型转换导致索引失效"></a>1. 隐式类型转换导致索引失效</h4><p>查询员工ID为123的员工信息:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM employees WHERE employee_id = 123;</span><br></pre></td></tr></table></figure><p>分析:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">EXPLAIN SELECT * FROM employees WHERE employee_id = 123;</span><br></pre></td></tr></table></figure><h4 id="2-LIKE-通配符匹配后缀导致索引失效"><a href="#2-LIKE-通配符匹配后缀导致索引失效" class="headerlink" title="2. LIKE 通配符匹配后缀导致索引失效"></a>2. LIKE 通配符匹配后缀导致索引失效</h4><p>查询名字以 ‘lice’ 结尾的员工:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM employees WHERE name LIKE '%lice';</span><br></pre></td></tr></table></figure><p>分析:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">EXPLAIN SELECT * FROM employees WHERE name LIKE '%lice';</span><br></pre></td></tr></table></figure><h4 id="3-OR-连接查询索引失效"><a href="#3-OR-连接查询索引失效" class="headerlink" title="3. OR 连接查询索引失效"></a>3. OR 连接查询索引失效</h4><p>查询员工ID为’E001’或部门ID为’D003’的员工信息:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM employees WHERE employee_id = 'E001' OR department_id = 'D003';</span><br></pre></td></tr></table></figure><p>分析:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">EXPLAIN SELECT * FROM employees WHERE employee_id = 'E001' OR department_id = 'D003';</span><br></pre></td></tr></table></figure><h4 id="4-正确使用索引的查询"><a href="#4-正确使用索引的查询" class="headerlink" title="4. 正确使用索引的查询"></a>4. 正确使用索引的查询</h4><p>查询名字以 ‘A’ 开头的员工:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM employees WHERE name LIKE 'A%';</span><br></pre></td></tr></table></figure><p>分析:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">EXPLAIN SELECT * FROM employees WHERE name LIKE 'A%';</span><br></pre></td></tr></table></figure><h3 id="EXPLAIN-结果分析"><a href="#EXPLAIN-结果分析" class="headerlink" title="EXPLAIN 结果分析"></a>EXPLAIN 结果分析</h3><p>通过 <code>EXPLAIN</code> 语句,可以分析每个查询的执行计划。以下是可能的解释:</p><h4 id="1-隐式类型转换导致索引失效-1"><a href="#1-隐式类型转换导致索引失效-1" class="headerlink" title="1. 隐式类型转换导致索引失效"></a>1. 隐式类型转换导致索引失效</h4><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></pre></td><td class="code"><pre><span class="line">+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+</span><br><span class="line">| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |</span><br><span class="line">+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+</span><br><span class="line">| 1 | SIMPLE | employees | ALL | PRIMARY | NULL | NULL | NULL | 5 | Using where |</span><br><span class="line">+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+</span><br></pre></td></tr></table></figure><p>解释:由于隐式类型转换,MySQL 进行了全表扫描(<code>type=ALL</code>),未能使用索引。</p><h4 id="2-LIKE-通配符匹配后缀导致索引失效-1"><a href="#2-LIKE-通配符匹配后缀导致索引失效-1" class="headerlink" title="2. LIKE 通配符匹配后缀导致索引失效"></a>2. LIKE 通配符匹配后缀导致索引失效</h4><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></pre></td><td class="code"><pre><span class="line">+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+</span><br><span class="line">| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |</span><br><span class="line">+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+</span><br><span class="line">| 1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 5 | Using where |</span><br><span class="line">+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+</span><br></pre></td></tr></table></figure><p>解释:由于通配符在前,MySQL 进行了全表扫描(<code>type=ALL</code>),未能使用索引。</p><h4 id="3-OR-连接查询索引失效-1"><a href="#3-OR-连接查询索引失效-1" class="headerlink" title="3. OR 连接查询索引失效"></a>3. OR 连接查询索引失效</h4><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></pre></td><td class="code"><pre><span class="line">+----+-------------+-----------+------+--------------------+------+---------+------+------+-------------+</span><br><span class="line">| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |</span><br><span class="line">+----+-------------+-----------+------+--------------------+------+---------+------+------+-------------+</span><br><span class="line">| 1 | SIMPLE | employees | ALL | PRIMARY,idx_department_id | NULL | NULL | NULL | 5 | Using where |</span><br><span class="line">+----+-------------+-----------+------+--------------------+------+---------+------+------+-------------+</span><br></pre></td></tr></table></figure><p>解释:由于 <code>OR</code> 语句使得查询需要考虑多个条件,MySQL 进行了全表扫描(<code>type=ALL</code>),未能使用索引。</p><h4 id="4-正确使用索引的查询-1"><a href="#4-正确使用索引的查询-1" class="headerlink" title="4. 正确使用索引的查询"></a>4. 正确使用索引的查询</h4><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></pre></td><td class="code"><pre><span class="line">+----+-------------+-----------+-------+---------------+---------------+---------+------+------+-------------+</span><br><span class="line">| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |</span><br><span class="line">+----+-------------+-----------+-------+---------------+---------------+---------+------+------+-------------+</span><br><span class="line">| 1 | SIMPLE | employees | range | NULL | idx_name | 1024 | NULL | 2 | Using where |</span><br><span class="line">+----+-------------+-----------+-------+---------------+---------------+---------+------+------+-------------+</span><br></pre></td></tr></table></figure><p>解释:由于通配符在后,符合最左匹配原则,MySQL 使用了索引(<code>type=range</code>)。</p><p>通过以上查询和分析,可以更好地理解 B+树索引在不同情况下的使用和失效原因,从而优化数据库查询性能。</p>]]></content>
<summary type="html"><p>今天我们讨论一下数据库索引在什么情况下会失效,总的来说有以下几种场景</p>
<ol>
<li>在索引列上加函数运算</li>
<li>组合索引中,不符合最左匹配原则</li>
<li>当索引列存在隐式转化的时候</li>
<li>使用like通配符匹配后缀%xxx的时候</li>
<li>使用or连接查询的时候,or语句前后没有同时使用索引</li>
</ol></summary>
<category term="MySQL" scheme="https://alexhuihui.github.io/categories/MySQL/"/>
<category term="MySQL" scheme="https://alexhuihui.github.io/tags/MySQL/"/>
</entry>
<entry>
<title>【Docker】容器的本质</title>
<link href="https://alexhuihui.github.io/article/20240430.html"/>
<id>https://alexhuihui.github.io/article/20240430.html</id>
<published>2024-04-30T11:54:06.000Z</published>
<updated>2025-01-25T12:11:04.378Z</updated>
<content type="html"><![CDATA[<p>一个正在运行的 Docker 容器,其实就是一个启用了多个 Linux Namespace 的应用进程,而这个进程能够使用的资源量,则受 Cgroups 配置的限制。</p><p>下面通过一个小实验证明这句话</p><span id="more"></span> <h3 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h3><p>在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mount -t cgroup </span><br></pre></td></tr></table></figure><p>进入到 /sys/fs/cgroup/cpu 目录下,并创建一个目录,操作系统会在新创建的 container 目录下,自动生成该子系统对应的资源限制文件。</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></pre></td><td class="code"><pre><span class="line">root@ubuntu:/sys/fs/cgroup/cpu$ mkdir container</span><br><span class="line">root@ubuntu:/sys/fs/cgroup/cpu$ ls container/</span><br><span class="line">cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release</span><br><span class="line">cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks</span><br></pre></td></tr></table></figure><p>执行一个脚本,打满CPU</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">while : ; do : ; done &</span><br></pre></td></tr></table></figure><p>修改container目录中的CPU资源限制并把刚刚创建的进程加入到任务组中</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></pre></td><td class="code"><pre><span class="line">echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us</span><br><span class="line"></span><br><span class="line">echo PID > /sys/fs/cgroup/cpu/container/tasks </span><br></pre></td></tr></table></figure><p>这时候再通过TOP去查看CPU使用就会发现它的使用率从100%降到了20%</p><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/20240430101156.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/20240430101156.png" alt="img"></a></p><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><p>docker使用namespace进行隔离但是隔离的不彻底,它本质上还是一个跑在宿主机上的进程。</p><p>docker使用cgroups进行使用资源的限制,但是Cgroups 对资源的限制能力也有很多不完善的地方。</p>]]></content>
<summary type="html"><p>一个正在运行的 Docker 容器,其实就是一个启用了多个 Linux Namespace 的应用进程,而这个进程能够使用的资源量,则受 Cgroups 配置的限制。</p>
<p>下面通过一个小实验证明这句话</p></summary>
<category term="Docker" scheme="https://alexhuihui.github.io/categories/Docker/"/>
<category term="Docker" scheme="https://alexhuihui.github.io/tags/Docker/"/>
</entry>
<entry>
<title>【事务处理】分布式事务</title>
<link href="https://alexhuihui.github.io/article/20240127.html"/>
<id>https://alexhuihui.github.io/article/20240127.html</id>
<published>2024-01-27T11:54:06.000Z</published>
<updated>2025-01-25T12:15:24.511Z</updated>
<content type="html"><![CDATA[<p>可以按照服务的数量以及对应的数据源的使用数量来划分分布式事务,例如单个服务使用多数据源称作全局事务可以使用2、3段提交,多个服务使用单数据源则称作共享事务可以使用共享数据库连接,还有一种就是多服务多数据源,这种一般称作在分布式服务环境下的事务处理机制。</p><span id="more"></span> <h3 id="全局事务"><a href="#全局事务" class="headerlink" title="全局事务"></a>全局事务</h3><blockquote><p>准备阶段:又叫作投票阶段,在这一阶段,协调者询问事务的所有参与者是否准备好提交,参与者如果已经准备好提交则回复 Prepared,否则回复 Non-Prepared。这里所说的准备操作跟人类语言中通常理解的准备并不相同,对于数据库来说,准备操作是在重做日志中记录全部事务提交操作所要做的内容,它与本地事务中真正提交的区别只是暂不写入最后一条 Commit Record 而已,这意味着在做完数据持久化后并不立即释放隔离性,即仍继续持有锁,维持数据对其他非事务内观察者的隔离状态。</p><p>提交阶段:又叫作执行阶段,协调者如果在上一阶段收到所有事务参与者回复的 Prepared 消息,则先自己在本地持久化事务状态为 Commit,在此操作完成后向所有参与者发送 Commit 指令,所有参与者立即执行提交操作;否则,任意一个参与者回复了 Non-Prepared 消息,或任意一个参与者超时未回复,协调者将自己的事务状态持久化为 Abort 之后,向所有参与者发送 Abort 指令,参与者立即执行回滚操作。对于数据库来说,这个阶段的提交操作应是很轻量的,仅仅是持久化一条 Commit Record 而已,通常能够快速完成,只有收到 Abort 指令时,才需要根据回滚日志清理已提交的数据,这可能是相对重负载的操作。</p></blockquote><p>缺点很明显,容易单点、性能问题、一致性风险</p><h3 id="共享事务"><a href="#共享事务" class="headerlink" title="共享事务"></a>共享事务</h3><blockquote><p>理论可行,因为该方案是与实际生产系统中的压力方向相悖的,一个服务集群里数据库才是压力最大而又最不容易伸缩拓展的重灾区。</p></blockquote><h3 id="分布式事务"><a href="#分布式事务" class="headerlink" title="分布式事务"></a>分布式事务</h3>]]></content>
<summary type="html"><p>可以按照服务的数量以及对应的数据源的使用数量来划分分布式事务,例如单个服务使用多数据源称作全局事务可以使用2、3段提交,多个服务使用单数据源则称作共享事务可以使用共享数据库连接,还有一种就是多服务多数据源,这种一般称作在分布式服务环境下的事务处理机制。</p></summary>
<category term="事务处理" scheme="https://alexhuihui.github.io/categories/%E4%BA%8B%E5%8A%A1%E5%A4%84%E7%90%86/"/>
<category term="事务处理" scheme="https://alexhuihui.github.io/tags/%E4%BA%8B%E5%8A%A1%E5%A4%84%E7%90%86/"/>
</entry>
<entry>
<title>【技术分享】Spring声明式事务的正确使用</title>
<link href="https://alexhuihui.github.io/article/20240118.html"/>
<id>https://alexhuihui.github.io/article/20240118.html</id>
<published>2024-01-18T11:54:06.000Z</published>
<updated>2025-01-25T12:40:04.556Z</updated>
<content type="html"><![CDATA[<p>大家肯定对@Transactional这个注解很熟悉,也对事务有着详细的了解,也知道多个数据库操作需要通过事务来保证一致性和原子性。但是很少会关注事务是否生效、有没有出错。这类问题也比较难在测试阶段发现,当出现线上问题的时候不可避免的会产生大量脏数据。所以这次我分享的内容就是帮助大家理清楚使用@Transactional的思路,避免使用不当产生bug。</p><span id="more"></span> <h3 id="事务为什么不生效"><a href="#事务为什么不生效" class="headerlink" title="事务为什么不生效"></a>事务为什么不生效</h3><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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserService</span> </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> UserRepository userRepository;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//一个公共方法供Controller调用,内部调用事务性的私有方法</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">createUserWrong1</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">this</span>.createUserPrivate(<span class="keyword">new</span> UserEntity(name));</span><br><span class="line"> } <span class="keyword">catch</span> (Exception ex) {</span><br><span class="line"> log.error(<span class="string">"create user failed because {}"</span>, ex.getMessage());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> userRepository.findByName(name).size();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//标记了@Transactional的private方法</span></span><br><span class="line"> <span class="meta">@Transactional</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">createUserPrivate</span><span class="params">(UserEntity entity)</span> </span>{</span><br><span class="line"> userRepository.save(entity);</span><br><span class="line"> <span class="keyword">if</span> (entity.getName().contains(<span class="string">"test"</span>)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"invalid username!"</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="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getUserCount</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> userRepository.findByName(name).size();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li>只有定义在public方法上的 @Transactional 才能生效</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">createUserWrong2</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">this</span>.createUserPublic(<span class="keyword">new</span> UserEntity(name));</span><br><span class="line"> } <span class="keyword">catch</span> (Exception ex) {</span><br><span class="line"> log.error(<span class="string">"create user failed because {}"</span>, ex.getMessage());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> userRepository.findByName(name).size();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//标记了@Transactional的public方法</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">createUserPublic</span><span class="params">(UserEntity entity)</span> </span>{</span><br><span class="line"> userRepository.save(entity);</span><br><span class="line"> <span class="keyword">if</span> (entity.getName().contains(<span class="string">"test"</span>)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"invalid username!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li>必须通过代理过的类从外部调用目标方法才能生效</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping("right2")</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">right2</span><span class="params">(<span class="meta">@RequestParam("name")</span> String name)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> userService.createUserPublic(<span class="keyword">new</span> UserEntity(name));</span><br><span class="line"> } <span class="keyword">catch</span> (Exception ex) {</span><br><span class="line"> log.error(<span class="string">"create user failed because {}"</span>, ex.getMessage());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> userService.getUserCount(name);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// This is an around advice: Invoke the next interceptor in the chain.</span></span><br><span class="line"> <span class="comment">// This will normally result in a target object being invoked.</span></span><br><span class="line"> retVal = invocation.proceedWithInvocation();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">catch</span> (Throwable ex) {</span><br><span class="line"> <span class="comment">// target invocation exception</span></span><br><span class="line"> completeTransactionAfterThrowing(txInfo, ex);</span><br><span class="line"> <span class="keyword">throw</span> ex;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">finally</span> {</span><br><span class="line"> cleanupTransactionInfo(txInfo);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li>只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚</li></ol><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="comment">/**</span></span><br><span class="line"><span class="comment"> * The default behavior is as with EJB: rollback on unchecked exception</span></span><br><span class="line"><span class="comment"> * ({<span class="doctag">@link</span> RuntimeException}), assuming an unexpected outcome outside of any</span></span><br><span class="line"><span class="comment"> * business rules. Additionally, we also attempt to rollback on {<span class="doctag">@link</span> Error} which</span></span><br><span class="line"><span class="comment"> * is clearly an unexpected outcome as well. By contrast, a checked exception is</span></span><br><span class="line"><span class="comment"> * considered a business exception and therefore a regular expected outcome of the</span></span><br><span class="line"><span class="comment"> * transactional business method, i.e. a kind of alternative return value which</span></span><br><span class="line"><span class="comment"> * still allows for regular completion of resource operations.</span></span><br><span class="line"><span class="comment"> * <p>This is largely consistent with TransactionTemplate's default behavior,</span></span><br><span class="line"><span class="comment"> * except that TransactionTemplate also rolls back on undeclared checked exceptions</span></span><br><span class="line"><span class="comment"> * (a corner case). For declarative transactions, we expect checked exceptions to be</span></span><br><span class="line"><span class="comment"> * intentionally declared as business exceptions, leading to a commit by default.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> org.springframework.transaction.support.TransactionTemplate#execute</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="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">rollbackOn</span><span class="params">(Throwable ex)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (ex <span class="keyword">instanceof</span> RuntimeException || ex <span class="keyword">instanceof</span> Error);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol><li>默认出现 RuntimeException(非受检异常)或 Error 的时候,Spring 才会回滚事务(可以指定回滚异常)</li></ol><h3 id="事务传播"><a href="#事务传播" class="headerlink" title="事务传播"></a>事务传播</h3><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><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">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> SubUserService subUserService;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">createUserWrong</span><span class="params">(UserEntity entity)</span> </span>{</span><br><span class="line"> createMainUser(entity);</span><br><span class="line"> subUserService.createSubUserWithExceptionWrong(entity);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">createMainUser</span><span class="params">(UserEntity entity)</span> </span>{</span><br><span class="line"> userRepository.save(entity);</span><br><span class="line"> log.info(<span class="string">"createMainUser finish"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上述代码异常跑出了@Transactional 注解标记的 createUserWrong 方法,Spring 会回滚事务</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">createUserWrong2</span><span class="params">(UserEntity entity)</span> </span>{</span><br><span class="line"> createMainUser(entity);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> subUserService.createSubUserWithExceptionWrong(entity);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception ex) {</span><br><span class="line"> log.error(<span class="string">"create sub user error:{}"</span>, ex.getMessage());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚。</p><p>看到这里就清楚了,只能让处理子用户的逻辑运行在单独的事务中,这就用到了SPRING的事务传播机制</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional(propagation = Propagation.REQUIRES_NEW)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">createSubUserWithExceptionRight</span><span class="params">(UserEntity entity)</span> </span>{</span><br><span class="line"> log.info(<span class="string">"createSubUserWithExceptionRight start"</span>);</span><br><span class="line"> userRepository.save(entity);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"invalid status"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">createUserRight</span><span class="params">(UserEntity entity)</span> </span>{</span><br><span class="line"> createMainUser(entity);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> subUserService.createSubUserWithExceptionRight(entity);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception ex) {</span><br><span class="line"> <span class="comment">// 捕获异常,防止主方法回滚</span></span><br><span class="line"> log.error(<span class="string">"create sub user error:{}"</span>, ex.getMessage());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>希望这次分享能帮助大家正确处理业务代码中的事务。</p><h3 id="实际使用踩的坑"><a href="#实际使用踩的坑" class="headerlink" title="实际使用踩的坑"></a>实际使用踩的坑</h3><h4 id="多数据源中不能使用事务"><a href="#多数据源中不能使用事务" class="headerlink" title="多数据源中不能使用事务"></a>多数据源中不能使用事务</h4><h4 id="事务和分布式锁使用的坑"><a href="#事务和分布式锁使用的坑" class="headerlink" title="事务和分布式锁使用的坑"></a>事务和分布式锁使用的坑</h4><p>现象:业务中加了分布式锁,也做了幂等处理如果业务ID存在相同的数据则直接返回。但还是会生成2条一样的数据。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Around("@annotation(redisLock)")</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">around</span><span class="params">(ProceedingJoinPoint joinPoint, RedisLock redisLock)</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> String spel = redisLock.key();</span><br><span class="line"> String lockName = redisLock.lockName();</span><br><span class="line"> String redisLockKey = getRedisKey(joinPoint, lockName, spel);</span><br><span class="line"> log.info(<span class="string">"生成的 redisKey 是 -> {}"</span>, redisLockKey);</span><br><span class="line"> RLock rLock = redissonClient.getLock(redisLockKey);</span><br><span class="line"> Object result;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> rLock.lock(redisLock.expire(), redisLock.timeUnit());</span><br><span class="line"> <span class="comment">//执行方法</span></span><br><span class="line"> result = joinPoint.proceed();</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException interruptedException) {</span><br><span class="line"> log.error(<span class="string">"获取分布式锁失败, "</span>, interruptedException);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"获取分布式锁失败"</span>);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="keyword">if</span> (rLock.isLocked()) {</span><br><span class="line"> rLock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br><span class="line"><span class="meta">@Transactional(rollbackFor = RuntimeException.class)</span></span><br><span class="line"><span class="meta">@RedisLock(lockName = "test", key = "test")</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 根据手机号查询用户</span></span><br><span class="line"> User user = bizService.queryByPhone(<span class="string">"17770848782"</span>);</span><br><span class="line"> log.info(<span class="string">"user==={}"</span>, user);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Transactional(rollbackFor = RuntimeException.class)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test2</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 根据手机号查询用户</span></span><br><span class="line"> User user = bizService.queryByPhone(<span class="string">"17770848782"</span>);</span><br><span class="line"> log.info(<span class="string">"user2==={}"</span>, user);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/c87dacaa25d90940960381b89cbcc36.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/c87dacaa25d90940960381b89cbcc36.png" alt="img"></a></p><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/2c863c0d9bede38ff6d4494ffd1e913.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/2c863c0d9bede38ff6d4494ffd1e913.png" alt="img"></a></p><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/31a1043cd83c4866d202ba4c86efd5a.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/31a1043cd83c4866d202ba4c86efd5a.png" alt="img"></a></p><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/152cfd85f8f8c853ed32f308155f461.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/152cfd85f8f8c853ed32f308155f461.png" alt="img"></a></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="meta">@GetMapping</span></span><br><span class="line"><span class="meta">@Operation(summary = "test2")</span></span><br><span class="line"><span class="meta">@RedisLock(lockName = "test2", key = "test2")</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ObjectResponse<String> <span class="title">test2</span><span class="params">()</span> </span>{</span><br><span class="line"> userAppService.test2();</span><br><span class="line"> <span class="keyword">return</span> ObjectResponse.success();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@PostMapping</span></span><br><span class="line"><span class="meta">@Operation(summary = "test")</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ObjectResponse<String> <span class="title">test</span><span class="params">()</span> </span>{</span><br><span class="line"> userAppService.test();</span><br><span class="line"> <span class="keyword">return</span> ObjectResponse.success();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>结论:当使用分布式锁和事务时,因为切面处理是用的先进后出的栈,又因为事务处理的优先级是默认最低的,所以如果没有知道分布式锁的优先级就会导致锁先于事务释放,导致可能出现事务的隔离性被破坏,产生脏数据。</p>]]></content>
<summary type="html"><p>大家肯定对@Transactional这个注解很熟悉,也对事务有着详细的了解,也知道多个数据库操作需要通过事务来保证一致性和原子性。但是很少会关注事务是否生效、有没有出错。这类问题也比较难在测试阶段发现,当出现线上问题的时候不可避免的会产生大量脏数据。所以这次我分享的内容就是帮助大家理清楚使用@Transactional的思路,避免使用不当产生bug。</p></summary>
<category term="技术分享" scheme="https://alexhuihui.github.io/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
<category term="技术分享" scheme="https://alexhuihui.github.io/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
</entry>
<entry>
<title>【ELK】springboot结合elk搭建日志平台</title>
<link href="https://alexhuihui.github.io/article/20231206.html"/>
<id>https://alexhuihui.github.io/article/20231206.html</id>
<published>2023-12-06T11:54:06.000Z</published>
<updated>2025-01-25T12:23:00.378Z</updated>
<content type="html"><![CDATA[<p>本文介绍如果通过搭建ELK收集springboot的日志。</p><span id="more"></span> <h3 id="部署ELK"><a href="#部署ELK" class="headerlink" title="部署ELK"></a>部署ELK</h3><p>通过docker compose安装</p><ol><li><p>安装docker compose</p><figure class="highlight plaintext"><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">yum install -y yum-utils device-mapper-persistent-data lvm2</span><br><span class="line">yum install docker-compose</span><br></pre></td></tr></table></figure></li><li><p>创建以下目录以及对应的配置文件</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></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── docker-compose.yml</span><br><span class="line">├── elasticsearch</span><br><span class="line">│ ├── config</span><br><span class="line">│ │ └── elasticsearch.yml</span><br><span class="line">│ ├── data</span><br><span class="line">│ └── logs</span><br><span class="line">├── kibana</span><br><span class="line">│ └── config</span><br><span class="line">│ └── kibana.yml</span><br><span class="line">└── logstash</span><br><span class="line"> ├── config</span><br><span class="line"> │ ├── logstash.yml</span><br><span class="line"> │ └── small-tools</span><br><span class="line"> │ └── dev-enlightrn-hub.config</span><br><span class="line"> └── data</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"># elasticsearch.yml</span><br><span class="line">cluster.name: "docker-cluster"</span><br><span class="line">network.host: 0.0.0.0</span><br><span class="line">http.port: 9200</span><br><span class="line"># 开启es跨域</span><br><span class="line">http.cors.enabled: true</span><br><span class="line">http.cors.allow-origin: "*"</span><br><span class="line">http.cors.allow-headers: Authorization</span><br><span class="line"># # 开启安全控制</span><br><span class="line">xpack.security.enabled: true</span><br><span class="line">xpack.security.transport.ssl.enabled: true</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"># kibana.yml</span><br><span class="line">server.name: kibana</span><br><span class="line">server.host: "0.0.0.0"</span><br><span class="line">server.publicBaseUrl: "http://kibana:5601"</span><br><span class="line">elasticsearch.hosts: [ "http://elasticsearch:9200" ] </span><br><span class="line">xpack.monitoring.ui.container.elasticsearch.enabled: true</span><br><span class="line">elasticsearch.username: "elastic"</span><br><span class="line">elasticsearch.password: "123456"</span><br><span class="line">i18n.locale: zh-CN</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"># logstash.yml</span><br><span class="line">http.host: "0.0.0.0"</span><br><span class="line">xpack.monitoring.enabled: true</span><br><span class="line">xpack.monitoring.elasticsearch.hosts: [ "http://elasticsearch:9200" ]</span><br><span class="line">xpack.monitoring.elasticsearch.username: "elastic"</span><br><span class="line">xpack.monitoring.elasticsearch.password: "123456"</span><br></pre></td></tr></table></figure><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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"># dev-enlightrn-hub.config</span><br><span class="line">input { #输入</span><br><span class="line"> </span><br><span class="line"> tcp {</span><br><span class="line"> mode => "server"</span><br><span class="line"> host => "0.0.0.0" # 允许任意主机发送日志</span><br><span class="line"> type => "dev-enlighten-hub" # 设定type以区分每个输入源</span><br><span class="line"> port => 9999</span><br><span class="line"> codec => json_lines # 数据格式</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">filter {</span><br><span class="line"> mutate {</span><br><span class="line"> # 导入之过滤字段</span><br><span class="line"> remove_field => ["LOG_MAX_HISTORY_DAY", "LOG_HOME", "APP_NAME"]</span><br><span class="line"> remove_field => ["@version", "_score", "port", "level_value", "tags", "_type", "host"]</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">output { #输出-控制台</span><br><span class="line"> stdout{</span><br><span class="line"> codec => rubydebug</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">output { #输出-es</span><br><span class="line"> </span><br><span class="line"> if [type] == "dev-enlighten-hub" {</span><br><span class="line"> elasticsearch {</span><br><span class="line"> action => "index" # 输出时创建映射</span><br><span class="line"> hosts => "http://elasticsearch:9200" # ES地址和端口</span><br><span class="line"> user => "elastic" # ES用户名</span><br><span class="line"> password => "123456" # ES密码</span><br><span class="line"> index => "dev-enlighten-hub-%{+YYYY.MM.dd}" # 指定索引名-按天</span><br><span class="line"> codec => "json"</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></li><li><p>编写docker-compose.yml</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><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></pre></td><td class="code"><pre><span class="line">version: '3.3'</span><br><span class="line">networks:</span><br><span class="line"> elk:</span><br><span class="line"> driver: bridge</span><br><span class="line">services:</span><br><span class="line"> elasticsearch:</span><br><span class="line"> image: registry.cn-hangzhou.aliyuncs.com/zhengqing/elasticsearch:7.14.1</span><br><span class="line"> container_name: elk_elasticsearch</span><br><span class="line"> restart: unless-stopped</span><br><span class="line"> volumes:</span><br><span class="line"> - "./elasticsearch/data:/usr/share/elasticsearch/data"</span><br><span class="line"> - "./elasticsearch/logs:/usr/share/elasticsearch/logs"</span><br><span class="line"> - "./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml"</span><br><span class="line"> environment:</span><br><span class="line"> TZ: Asia/Shanghai</span><br><span class="line"> LANG: en_US.UTF-8</span><br><span class="line"> TAKE_FILE_OWNERSHIP: "true" # 权限</span><br><span class="line"> discovery.type: single-node</span><br><span class="line"> ES_JAVA_OPTS: "-Xmx1g -Xms1g"</span><br><span class="line"> ELASTIC_PASSWORD: "111111" # elastic账号密码</span><br><span class="line"> ports:</span><br><span class="line"> - "9200:9200"</span><br><span class="line"> - "9300:9300"</span><br><span class="line"> networks:</span><br><span class="line"> - elk</span><br><span class="line"> </span><br><span class="line"> kibana:</span><br><span class="line"> image: registry.cn-hangzhou.aliyuncs.com/zhengqing/kibana:7.14.1</span><br><span class="line"> container_name: elk_kibana</span><br><span class="line"> restart: unless-stopped</span><br><span class="line"> volumes:</span><br><span class="line"> - "./kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml"</span><br><span class="line"> ports:</span><br><span class="line"> - "5601:5601"</span><br><span class="line"> depends_on:</span><br><span class="line"> - elasticsearch</span><br><span class="line"> links:</span><br><span class="line"> - elasticsearch</span><br><span class="line"> networks:</span><br><span class="line"> - elk</span><br><span class="line"> </span><br><span class="line"> logstash:</span><br><span class="line"> image: registry.cn-hangzhou.aliyuncs.com/zhengqing/logstash:7.14.1</span><br><span class="line"> container_name: elk_logstash</span><br><span class="line"> restart: unless-stopped</span><br><span class="line"> environment:</span><br><span class="line"> LS_JAVA_OPTS: "-Xmx1g -Xms1g"</span><br><span class="line"> volumes:</span><br><span class="line"> - "./logstash/data:/usr/share/logstash/data"</span><br><span class="line"> - "./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml"</span><br><span class="line"> - "./logstash/config/small-tools:/usr/share/logstash/config/small-tools"</span><br><span class="line"> command: logstash -f /usr/share/logstash/config/small-tools</span><br><span class="line"> ports:</span><br><span class="line"> - "9600:9600"</span><br><span class="line"> - "9999:9999"</span><br><span class="line"> depends_on:</span><br><span class="line"> - elasticsearch</span><br><span class="line"> networks:</span><br><span class="line"> - elk</span><br></pre></td></tr></table></figure></li><li><p>执行启动命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose up -d</span><br></pre></td></tr></table></figure></li><li><p>如果启动失败可以通过下述命令进行检查配置项</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose run --rm logstash logstash -t -f /usr/share/logstash/config/small-tools</span><br></pre></td></tr></table></figure></li></ol><h3 id="修改spring-boot项目"><a href="#修改spring-boot项目" class="headerlink" title="修改spring boot项目"></a>修改spring boot项目</h3><ol><li><p>添加maven依赖</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></pre></td><td class="code"><pre><span class="line"><dependency></span><br><span class="line"> <groupId>net.logstash.logback</groupId></span><br><span class="line"> <artifactId>logstash-logback-encoder</artifactId></span><br><span class="line"> <version>7.3</version></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure></li><li><p>添加logback-spring.xml</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><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></pre></td><td class="code"><pre><span class="line"><?xml version="1.0" encoding="UTF-8"?></span><br><span class="line"><configuration scan="true" scanPeriod="60 seconds" debug="false"></span><br><span class="line"> <!-- 日志存放路径 --></span><br><span class="line"> <property name="log.path" value="${user.dir}/logs"/></span><br><span class="line"> <!-- 日志输出格式 --></span><br><span class="line"> <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n"/></span><br><span class="line"></span><br><span class="line"> <!-- 控制台输出 --></span><br><span class="line"> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"></span><br><span class="line"> <encoder></span><br><span class="line"> <pattern>${log.pattern}</pattern></span><br><span class="line"> </encoder></span><br><span class="line"> </appender></span><br><span class="line"></span><br><span class="line"> <!-- 系统日志输出 --></span><br><span class="line"> <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender"></span><br><span class="line"> <file>${log.path}/info.log</file></span><br><span class="line"> <!-- 循环政策:基于时间创建日志文件 --></span><br><span class="line"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"></span><br><span class="line"> <!-- 日志文件名格式 --></span><br><span class="line"> <fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern></span><br><span class="line"> <!-- 日志最大的历史 7天 --></span><br><span class="line"> <maxHistory>7</maxHistory></span><br><span class="line"> </rollingPolicy></span><br><span class="line"> <encoder></span><br><span class="line"> <pattern>${log.pattern}</pattern></span><br><span class="line"> </encoder></span><br><span class="line"> <filter class="ch.qos.logback.classic.filter.LevelFilter"></span><br><span class="line"> <!-- 过滤的级别 --></span><br><span class="line"> <level>INFO</level></span><br><span class="line"> <!-- 匹配时的操作:接收(记录) --></span><br><span class="line"> <onMatch>ACCEPT</onMatch></span><br><span class="line"> <!-- 不匹配时的操作:拒绝(不记录) --></span><br><span class="line"> <onMismatch>DENY</onMismatch></span><br><span class="line"> </filter></span><br><span class="line"> </appender></span><br><span class="line"></span><br><span class="line"> <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender"></span><br><span class="line"> <file>${log.path}/error.log</file></span><br><span class="line"> <!-- 循环政策:基于时间创建日志文件 --></span><br><span class="line"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"></span><br><span class="line"> <!-- 日志文件名格式 --></span><br><span class="line"> <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern></span><br><span class="line"> <!-- 日志最大的历史 60天 --></span><br><span class="line"> <maxHistory>60</maxHistory></span><br><span class="line"> </rollingPolicy></span><br><span class="line"> <encoder></span><br><span class="line"> <pattern>${log.pattern}</pattern></span><br><span class="line"> </encoder></span><br><span class="line"> <filter class="ch.qos.logback.classic.filter.LevelFilter"></span><br><span class="line"> <!-- 过滤的级别 --></span><br><span class="line"> <level>ERROR</level></span><br><span class="line"> <!-- 匹配时的操作:接收(记录) --></span><br><span class="line"> <onMatch>ACCEPT</onMatch></span><br><span class="line"> <!-- 不匹配时的操作:拒绝(不记录) --></span><br><span class="line"> <onMismatch>DENY</onMismatch></span><br><span class="line"> </filter></span><br><span class="line"> </appender></span><br><span class="line"></span><br><span class="line"> <!-- 将日志文件输出到Logstash --></span><br><span class="line"> <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender"></span><br><span class="line"> <!-- 获取logstash地址作为输出的目的地 --></span><br><span class="line"> <destination>172.22.0.4:9999</destination></span><br><span class="line"> <encoder chatset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"/></span><br><span class="line"> </appender></span><br><span class="line"></span><br><span class="line"> <!-- 系统模块日志级别控制 --></span><br><span class="line"> <logger name="com.gyhappylife" level="info"/></span><br><span class="line"> <!-- Spring日志级别控制 --></span><br><span class="line"> <logger name="org.springframework" level="warn"/></span><br><span class="line"></span><br><span class="line"> <root level="info"></span><br><span class="line"> <appender-ref ref="console"/></span><br><span class="line"> </root></span><br><span class="line"></span><br><span class="line"> <!--系统操作日志--></span><br><span class="line"> <root level="info"></span><br><span class="line"> <appender-ref ref="file_info"/></span><br><span class="line"> <appender-ref ref="file_error"/></span><br><span class="line"> <appender-ref ref="logstash"/></span><br><span class="line"> </root></span><br><span class="line"></configuration></span><br></pre></td></tr></table></figure></li></ol><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><p>如果spring应用也是跑在docker中的话 那么它的网段可能会和ELK不一样 。这样就必须在iptables中添加路由规则例如:</p><figure class="highlight plaintext"><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">$ iptables -I DOCKER-USER -i br-3bed419583c5 -o br-787e52a81bb4 -j ACCEPT</span><br><span class="line">$ iptables -I DOCKER-USER -i br-787e52a81bb4 -o br-3bed419583c5 -j ACCEPT</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>本文介绍如果通过搭建ELK收集springboot的日志。</p></summary>
<category term="ELK" scheme="https://alexhuihui.github.io/categories/ELK/"/>
<category term="ELK" scheme="https://alexhuihui.github.io/tags/ELK/"/>
</entry>
<entry>
<title>【技术分享】优化延迟队列任务消费时间的技术分享</title>
<link href="https://alexhuihui.github.io/article/20231019.html"/>
<id>https://alexhuihui.github.io/article/20231019.html</id>
<published>2023-10-19T11:54:06.000Z</published>
<updated>2025-01-25T12:25:43.387Z</updated>
<content type="html"><![CDATA[<p>延迟队列在实际应用中是一项重要的技术,可以用于任务调度、定时提醒、消息重试等多种场景。然而,延迟队列的任务有时会在过期时间之后才得以执行,这可能导致应用程序的性能问题和不确定性。在本技术分享中,我将介绍一个优化延迟队列任务消费时间的解决方案,以及如何实现这一解决方案。</p><span id="more"></span> <p><strong>延迟队列实现原理</strong></p><p>在我们开始讨论如何优化延迟队列的任务消费时间之前,让我们首先了解一下延迟队列的实现原理。这个延迟队列基于Redis和Redisson库实现,它分为三个主要阶段:启动、任务添加和任务消费。</p><p><strong>第一阶段:启动</strong></p><ol><li>订阅队列:在这个阶段,我们订阅了一个名为”redisson_delay_queue_channel:{dest_queue1}”的固定队列,用于接收任务到期的通知。</li><li>获取过期任务:使用<code>zrangebyscore</code>命令,我们获取了过了当前时间的100条任务数据,以确保没有漏掉上次宕机后未执行的任务。</li><li>判断下一页:通过<code>zrange</code>命令,我们检查是否还有下一页的任务需要处理。</li><li>移除任务:使用<code>BLPOP</code>命令,我们移除并获取了队列”dest_queue1”中的第一个任务,如果队列为空,该操作会一直阻塞。</li></ol><p><strong>第二阶段:添加延时任务</strong></p><ol><li>添加任务:使用<code>zadd</code>命令,我们将任务添加到”redisson_delay_queue_timeout:{dest_queue1}”。</li><li>同步数据:将任务数据同步到”redisson_delay_queue:{dest_queue1}”。</li><li>获取即将触发的任务:通过<code>zrange</code>命令,我们获取了即将触发的任务。</li><li>发布通知:使用<code>publish</code>命令,我们向”redisson_delay_queue_channel:{dest_queue1}”发布了任务到期的消息,客户端收到通知后会在自己的进程中执行延时任务。</li></ol><p><strong>第三阶段:任务消费</strong></p><ol><li>获取到期任务:使用<code>zrangebyscore</code>命令,我们获取了”redisson_delay_queue_timeout:{dest_queue1}”中已到期的任务。</li><li>将任务放入队列:通过<code>rpush</code>命令,我们将这些任务放入第一阶段监听的阻塞队列”dest_queue1”。</li><li>客户端消费任务:客户端获取这些数据并进行处理。</li><li>删除任务:使用<code>lrem</code>和<code>zrem</code>命令,我们删除已经处理过的任务。</li><li>检查下一个任务:通过<code>zrange</code>命令,我们检查是否有下一个任务需要处理。</li><li>继续监听:使用<code>BLPOP</code>命令,我们继续监听”dest_queue1”这个阻塞队列。</li></ol><p><strong>问题分析</strong></p><p>上述实现中,客户端订阅了一个关键字(key),并且会在类初始化中执行该订阅操作。然而,在测试环境中,服务频繁升级和重启,只有在调用延迟队列客户端的静态方法时才会执行类初始化。因此,当服务重启后未再次订阅相应的key,导致无法从Redis获取任务数据,延迟队列的任务消费时间被推迟。</p><p><strong>优化延迟队列任务消费时间的解决方案</strong></p><p>为了优化延迟队列任务的消费时间,我们采用了Redisson库来实现延迟队列。下面是解决方案的主要步骤:</p><p><strong>1. 定义接口</strong></p><p>首先,我们定义了一个名为Queue的接口,用于表示延迟队列。</p><p><strong>2. 实现抽象类</strong></p><p>我们实现了一个名为AbstractQueue的抽象类,该类包含了实现延迟队列所需的核心逻辑。以下是该抽象类的关键部分:</p><ul><li>使用Redisson客户端:通过Spring配置,我们初始化了Redisson客户端,以便与Redis服务器通信。</li><li>定义监听器容器:我们创建了一个名为listenerMap的容器,用于存储与topic相关的队列监听器。</li><li>启动监听线程:在构造函数中,我们开启了一个独立的线程来监听队列的消息。这确保了任务能够及时被处理。</li><li>实现消费逻辑:我们定义了一个consumer方法,用于在监听到消息时处理队列中的任务。该方法首先获取与消息对应的队列监听器,然后执行任务的消费逻辑。</li></ul><p><strong>3. 子类实现</strong></p><p>我们创建了一个名为DelayQueue的子类,它继承了AbstractQueue并实现了其中的抽象方法。以下是该子类的关键部分:</p><ul><li>创建延迟队列:我们使用Redisson客户端创建了RBlockingDeque和RDelayedQueue,这是延迟队列的基本数据结构。</li><li>添加任务:通过offer方法,我们可以向RDelayedQueue中添加延时任务。</li><li>消费任务:在take方法中,我们从RBlockingDeque中获取任务数据,然后开启线程执行消费逻辑。</li></ul><p><strong>使用示例</strong></p><p>最后,我们提供了一个DelayClient类,它用于向延迟队列中添加任务。通过调用offer方法,用户可以方便地将任务加入延迟队列,无需担心类初始化问题。</p><p>实现延迟队列的设计具有以下优点:</p><ol><li>实时性:采用Redis和Redisson库实现的延迟队列无需轮询,任务可以在到期时立即被处理,提供了更好的实时性。</li><li>可扩展性:延迟队列的设计支持应用程序的水平扩展。可以轻松地添加更多的消费者来处理大量的延迟任务,而无需担心性能问题。</li><li>容错性:即使在服务升级或重启时,延迟队列仍能够正常工作,不会丢失任何任务。</li><li>灵活性:使用抽象类和接口的设计,可以根据自己的需求扩展和定制延迟队列的功能。可以添加不同类型的队列监听器,实现各种任务消费逻辑。</li></ol><p><strong>结语</strong></p><p>通过使用Redisson和合理的设计,我们成功优化了延迟队列的任务消费时间,提高了系统的性能和稳定性。这种技术对于需要处理延迟任务的应用程序非常有用,如定时任务、消息通知和消息重试等场景。</p><p>在实际项目中,可以根据自己的需求进一步扩展和优化这一解决方案,以适应不同的应用场景。同时,还可以考虑添加监控和日志记录,以便更好地跟踪和调试延迟队列中的任务。通过合适的工具和设计,延迟队列可以成为提高应用程序效率和可靠性的重要组成部分。</p>]]></content>
<summary type="html"><p>延迟队列在实际应用中是一项重要的技术,可以用于任务调度、定时提醒、消息重试等多种场景。然而,延迟队列的任务有时会在过期时间之后才得以执行,这可能导致应用程序的性能问题和不确定性。在本技术分享中,我将介绍一个优化延迟队列任务消费时间的解决方案,以及如何实现这一解决方案。</p></summary>
<category term="技术分享" scheme="https://alexhuihui.github.io/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
<category term="技术分享" scheme="https://alexhuihui.github.io/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
</entry>
<entry>
<title>【Kafka】Kafka的基本使用</title>
<link href="https://alexhuihui.github.io/article/20230915.html"/>
<id>https://alexhuihui.github.io/article/20230915.html</id>
<published>2023-09-15T11:54:06.000Z</published>
<updated>2025-01-25T12:28:42.133Z</updated>
<content type="html"><![CDATA[<h3 id="消息引擎系统介绍"><a href="#消息引擎系统介绍" class="headerlink" title="消息引擎系统介绍"></a>消息引擎系统介绍</h3><ol><li><p>Apache Kafka是一款开源的消息引擎系统。根据维基百科的定义,消息引擎系统是一组规范。企业利用这组规范在不同系统之间传递语义准确的消息,实现松耦合的异步式数据传递。通俗来讲,就是系统A发送消息到消息引擎系统,系统B从消息引擎系统中读取A发送的消息。</p></li><li><p>消息引擎系统要设定具体的传输协议,常见的有2种:点对点模型;发布订阅模型。Kafka同事支持这两种。</p></li><li><p>主要作用是削峰填谷,避免下游系统因突发流量而崩溃。</p></li></ol><span id="more"></span> <h3 id="术语简介"><a href="#术语简介" class="headerlink" title="术语简介"></a>术语简介</h3><ul><li>消息:Record。Kafka 是消息引擎嘛,这里的消息就是指 Kafka 处理的主要对象。</li><li>主题:Topic。主题是承载消息的逻辑容器,在实际使用中多用来区分具体的业务。</li><li>分区:Partition。一个有序不变的消息序列。每个主题下可以有多个分区。</li><li>消息位移:Offset。表示分区中每条消息的位置信息,是一个单调递增且不变的值。</li><li>副本:Replica。Kafka 中同一条消息能够被拷贝到多个地方以提供数据冗余,这些地方就是所谓的副本。副本还分为领导者副本和追随者副本,各自有不同的角色划分。副本是在分区层级下的,即每个分区可配置多个副本实现高可用。</li><li>生产者:Producer。向主题发布新消息的应用程序。</li><li>消费者:Consumer。从主题订阅新消息的应用程序。</li><li>消费者位移:Consumer Offset。表征消费者消费进度,每个消费者都有自己的消费者位移。</li><li>消费者组:Consumer Group。多个消费者实例共同组成的一个组,同时消费多个分区以实现高吞吐。</li><li>重平衡:Rebalance。消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。</li></ul>]]></content>
<summary type="html"><h3 id="消息引擎系统介绍"><a href="#消息引擎系统介绍" class="headerlink" title="消息引擎系统介绍"></a>消息引擎系统介绍</h3><ol>
<li><p>Apache Kafka是一款开源的消息引擎系统。根据维基百科的定义,消息引擎系统是一组规范。企业利用这组规范在不同系统之间传递语义准确的消息,实现松耦合的异步式数据传递。通俗来讲,就是系统A发送消息到消息引擎系统,系统B从消息引擎系统中读取A发送的消息。</p>
</li>
<li><p>消息引擎系统要设定具体的传输协议,常见的有2种:点对点模型;发布订阅模型。Kafka同事支持这两种。</p>
</li>
<li><p>主要作用是削峰填谷,避免下游系统因突发流量而崩溃。</p>
</li>
</ol></summary>
<category term="Kafka" scheme="https://alexhuihui.github.io/categories/Kafka/"/>
<category term="Kafka" scheme="https://alexhuihui.github.io/tags/Kafka/"/>
</entry>
<entry>
<title>【并发】Java和Go的并发编程对比</title>
<link href="https://alexhuihui.github.io/article/20230616.html"/>
<id>https://alexhuihui.github.io/article/20230616.html</id>
<published>2023-06-16T11:54:06.000Z</published>
<updated>2025-01-25T12:31:22.423Z</updated>
<content type="html"><![CDATA[<p>Go使用的是<code>CSP</code>并发模型,而JAVA使用的是基于传统的内存访问控制的并发模型。他们有以下区别。我们通过示例代码来比对一下Go和 Java的并发编程</p><span id="more"></span> <p>“Communicating Sequential Processes” (CSP) 是一种并发计算的数学模型,最早由计算机科学家Tony Hoare于1978年提出。CSP 的主要目标是描述并发系统中进程之间的交互和通信方式,而不涉及共享内存的概念。与传统的内存访问控制有很大的区别,下面是一些主要的区别:</p><ol><li><strong>通信方式:</strong><ul><li><strong>CSP:</strong> 使用进程之间的明确通信来实现协同工作。进程通过发送和接收消息进行通信,但它们并不共享内存空间。</li><li><strong>传统内存访问控制:</strong> 多个进程可能在同一块内存中进行读写操作,通过共享内存来实现通信。这可能导致诸如竞态条件和死锁等问题。</li></ul></li><li><strong>并发模型:</strong><ul><li><strong>CSP:</strong> 采用事件驱动的方式,进程之间通过消息传递进行通信,以实现并发。并发在这里是通过协作和通信而非共享状态来实现的。</li><li><strong>传统内存访问控制:</strong> 并发通常是通过多个进程或线程共享同一块内存来实现的。这可能引入一系列并发控制问题,如锁和同步。</li></ul></li><li><strong>数据共享:</strong><ul><li><strong>CSP:</strong> 鼓励避免共享数据,而是通过消息传递来传递必要的信息。这样设计有助于减少竞态条件和提高系统的可靠性。</li><li><strong>传统内存访问控制:</strong> 通常涉及多个进程或线程共享相同的内存区域,需要使用锁或其他同步机制来确保数据一致性。</li></ul></li><li><strong>同步和互斥:</strong><ul><li><strong>CSP:</strong> 使用通信机制来进行同步,进程之间通过消息传递协调各自的动作。</li><li><strong>传统内存访问控制:</strong> 常常需要使用锁或信号量等机制来进行同步和互斥,以防止多个进程同时访问共享的内存区域。</li></ul></li></ol><p>总的来说,CSP 提供了一种不同于传统共享内存的并发模型,强调通过明确的通信来实现进程之间的协同工作,以减少并发问题的出现。这种方式更容易推理和调试,并且有助于构建可靠的并发系统。</p><h3 id="Go"><a href="#Go" class="headerlink" title="Go"></a>Go</h3><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><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></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">"fmt"</span><br><span class="line">"sync"</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">func main() {</span><br><span class="line">type Button struct {</span><br><span class="line">Clicked *sync.Cond</span><br><span class="line">}</span><br><span class="line">button := Button{Clicked: sync.NewCond(&sync.Mutex{})}</span><br><span class="line">subscribe := func(c *sync.Cond, fn func()) {</span><br><span class="line">var goroutineRunning sync.WaitGroup</span><br><span class="line">goroutineRunning.Add(1)</span><br><span class="line">go func() {</span><br><span class="line">defer c.L.Unlock()</span><br><span class="line">goroutineRunning.Done()</span><br><span class="line">c.L.Lock()</span><br><span class="line">c.Wait()</span><br><span class="line">fn()</span><br><span class="line">}()</span><br><span class="line">goroutineRunning.Wait()</span><br><span class="line">}</span><br><span class="line">var clickRegistered sync.WaitGroup</span><br><span class="line">clickRegistered.Add(3)</span><br><span class="line">subscribe(button.Clicked, func() {</span><br><span class="line">fmt.Println("Maximizing window.")</span><br><span class="line">clickRegistered.Done()</span><br><span class="line">})</span><br><span class="line">subscribe(button.Clicked, func() {</span><br><span class="line">fmt.Println("Displaying annoying dialog box!")</span><br><span class="line">clickRegistered.Done()</span><br><span class="line">})</span><br><span class="line">subscribe(button.Clicked, func() {</span><br><span class="line">fmt.Println("Mouse clicked.")</span><br><span class="line">clickRegistered.Done()</span><br><span class="line">})</span><br><span class="line">button.Clicked.Broadcast()</span><br><span class="line">clickRegistered.Wait()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这段代码演示了使用条件变量(<code>sync.Cond</code>)实现订阅和发布模式的示例。</p><p>首先,代码定义了一个名为<code>Button</code>的结构体,其中包含一个指向条件变量的指针<code>Clicked</code>。通过<code>sync.NewCond</code>函数,我们创建了一个与互斥锁关联的条件变量。</p><p>接下来,代码定义了一个<code>subscribe</code>函数,用于订阅事件。该函数接收一个条件变量<code>c</code>和一个回调函数<code>fn</code>作为参数。在函数内部,它创建了一个<code>sync.WaitGroup</code>类型的变量<code>goroutineRunning</code>,用于等待goroutine的启动。然后,它启动一个新的goroutine,在其中等待条件变量的信号。一旦接收到信号,它会执行回调函数<code>fn</code>。在等待和执行过程中,它使用互斥锁来保护共享资源。最后,通过<code>goroutineRunning.Wait()</code>确保goroutine已经启动。这样做是为了避免出现竞争条件,确保回调函数在订阅完成之后才会执行。</p><p>在<code>main</code>函数中,我们创建了一个<code>Button</code>实例<code>button</code>,并调用<code>subscribe</code>函数三次,每次传递不同的回调函数。这些回调函数分别打印不同的消息。</p><p>随后,我们创建了一个<code>sync.WaitGroup</code>类型的变量<code>clickRegistered</code>,用于等待所有订阅的回调函数执行完成。通过<code>clickRegistered.Add(3)</code>将等待计数设置为3,因为我们有三个订阅的回调函数。</p><p>最后,我们调用<code>button.Clicked.Broadcast()</code>发送广播信号,通知所有订阅者事件已发生。这将触发所有等待中的<code>subscribe</code>函数中的条件变量的信号,并执行对应的回调函数。</p><p>通过使用条件变量和互斥锁,代码实现了一个简单的订阅和发布模式。当事件发生时,订阅者收到信号并执行相应的回调函数。这种模式可以在并发环境中实现解耦和事件驱动的编程</p><h3 id="Java"><a href="#Java" class="headerlink" title="Java"></a>Java</h3><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><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></pre></td><td class="code"><pre><span class="line">import java.util.concurrent.locks.Condition;</span><br><span class="line">import java.util.concurrent.locks.Lock;</span><br><span class="line">import java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line">public class Main {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> Button button = new Button();</span><br><span class="line"></span><br><span class="line"> Thread thread1 = new Thread(() -> {</span><br><span class="line"> button.subscribe(() -> {</span><br><span class="line"> System.out.println("Maximizing window.");</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> Thread thread2 = new Thread(() -> {</span><br><span class="line"> button.subscribe(() -> {</span><br><span class="line"> System.out.println("Displaying annoying dialog box!");</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> Thread thread3 = new Thread(() -> {</span><br><span class="line"> button.subscribe(() -> {</span><br><span class="line"> System.out.println("Mouse clicked.");</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> thread1.start();</span><br><span class="line"> thread2.start();</span><br><span class="line"> thread3.start();</span><br><span class="line"></span><br><span class="line"> // 模拟按钮点击事件</span><br><span class="line"> button.click();</span><br><span class="line"></span><br><span class="line"> try {</span><br><span class="line"> thread1.join();</span><br><span class="line"> thread2.join();</span><br><span class="line"> thread3.join();</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class Button {</span><br><span class="line"> private Lock lock;</span><br><span class="line"> private Condition condition;</span><br><span class="line"></span><br><span class="line"> public Button() {</span><br><span class="line"> lock = new ReentrantLock();</span><br><span class="line"> condition = lock.newCondition();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void click() {</span><br><span class="line"> lock.lock();</span><br><span class="line"> try {</span><br><span class="line"> condition.signalAll();</span><br><span class="line"> } finally {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void subscribe(Runnable callback) {</span><br><span class="line"> Thread thread = new Thread(() -> {</span><br><span class="line"> lock.lock();</span><br><span class="line"> try {</span><br><span class="line"> condition.await();</span><br><span class="line"> callback.run();</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } finally {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> thread.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在上述代码中,使用了<code>Condition</code>、<code>Lock</code>和<code>ReentrantLock</code>来实现线程间的同步和通信。</p><ol><li><code>Lock</code>接口是Java提供的用于多线程同步的机制之一。<code>ReentrantLock</code>是<code>Lock</code>接口的一个具体实现类,它提供了独占锁的功能。在示例代码中,我们创建了一个<code>ReentrantLock</code>实例,用于保护共享资源的访问。</li><li><code>Condition</code>接口是与锁关联的条件,可以用于实现线程间的等待和通知机制。在示例代码中,我们使用<code>lock.newCondition()</code>创建了一个<code>Condition</code>实例,用于实现订阅者线程的等待和主线程的通知。</li><li><code>lock.lock()</code>和<code>lock.unlock()</code>用于获取和释放锁。通过使用<code>lock.lock()</code>获取锁,可以确保只有一个线程可以执行被保护的代码块,其他线程将被阻塞。一旦线程完成了对共享资源的操作,使用<code>lock.unlock()</code>释放锁,以便其他线程可以获取锁并执行。</li><li><code>condition.await()</code>用于使当前线程进入等待状态,直到其他线程通过调用<code>condition.signal()</code>或<code>condition.signalAll()</code>发出信号。在示例代码中,订阅者线程在收到信号前会调用<code>condition.await()</code>进入等待状态。</li><li><code>condition.signalAll()</code>用于唤醒所有等待在该条件上的线程。在示例代码中,主线程调用<code>button.click()</code>后会调用<code>condition.signalAll()</code>,以通知所有等待的订阅者线程。</li></ol><p>通过使用<code>Condition</code>、<code>Lock</code>和<code>ReentrantLock</code>,我们可以实现更精细的线程同步和通信。它们提供了更灵活的控制机制,使得线程之间的交互更加可控和高效。</p><p>可以使用<code>Object</code>类中的<code>wait()</code>和<code>notifyAll()</code>方法来实现线程间的等待和通知机制,用于替代<code>Condition</code>接口和<code>Lock</code>机制。</p><p>在使用<code>wait()</code>和<code>notifyAll()</code>时,需要注意以下几点:</p><ol><li><code>wait()</code>方法用于使当前线程进入等待状态,直到其他线程调用相同对象的<code>notify()</code>或<code>notifyAll()</code>方法。在等待期间,当前线程会释放对象的锁。</li><li><code>notifyAll()</code>方法用于唤醒所有等待在相同对象上的线程。它会通知所有等待的线程继续执行,但只有在获取到对象的锁之后才能真正执行。</li><li>在使用<code>wait()</code>和<code>notifyAll()</code>时,必须在同步代码块或同步方法中调用,以确保对对象的锁的正确使用。</li><li>通常,你需要使用一个共享的对象作为通信的锁,类似于示例代码中的<code>button</code>对象。</li></ol><p>下面是修改后的示例代码,使用<code>wait()</code>和<code>notifyAll()</code>实现线程间的等待和通知:</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><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></pre></td><td class="code"><pre><span class="line">class Button {</span><br><span class="line"> private final Object lock = new Object();</span><br><span class="line"> </span><br><span class="line"> public void click() {</span><br><span class="line"> synchronized (lock) {</span><br><span class="line"> lock.notifyAll();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void subscribe(Runnable task) {</span><br><span class="line"> synchronized (lock) {</span><br><span class="line"> try {</span><br><span class="line"> lock.wait();</span><br><span class="line"> task.run();</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class Main {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> Button button = new Button();</span><br><span class="line"> Thread subscriber1 = new Thread(() -> {</span><br><span class="line"> button.subscribe(() -> {</span><br><span class="line"> System.out.println("Maximizing window.");</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> Thread subscriber2 = new Thread(() -> {</span><br><span class="line"> button.subscribe(() -> {</span><br><span class="line"> System.out.println("Displaying annoying dialog box!");</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> Thread subscriber3 = new Thread(() -> {</span><br><span class="line"> button.subscribe(() -> {</span><br><span class="line"> System.out.println("Mouse clicked.");</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> subscriber1.start();</span><br><span class="line"> subscriber2.start();</span><br><span class="line"> subscriber3.start();</span><br><span class="line"> </span><br><span class="line"> // 主线程等待订阅者线程完成</span><br><span class="line"> try {</span><br><span class="line"> subscriber1.join();</span><br><span class="line"> subscriber2.join();</span><br><span class="line"> subscriber3.join();</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> button.click();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这个示例中,<code>Button</code>类使用了一个共享的锁对象<code>lock</code>。在订阅者线程中,调用<code>lock.wait()</code>使线程进入等待状态,在主线程中调用<code>lock.notifyAll()</code>唤醒所有等待的线程。主线程使用<code>join()</code>方法等待订阅者线程完成后再调用<code>button.click()</code>。</p><p>使用<code>wait()</code>和<code>notifyAll()</code>可以实现基本的线程间等待和通知机制,但相比<code>Condition</code>和<code>Lock</code>,它们的使用更加基础和低级,需要手动处理锁的获取和释放,并且可能存在更多的风险,如死锁和竞态条件。因此,在实际开发中,建议使用<code>Condition</code>和<code>Lock</code>机制,因为它们提供了更灵活、更可靠的线程同步和通信方式。相比于使用<code>wait()</code>和<code>notifyAll()</code>,<code>Condition</code>和<code>Lock</code>具有以下优势:</p><ol><li>精确的通知机制:<code>Condition</code>接口提供了更细粒度的通知机制,可以选择性地通知等待线程。你可以创建多个<code>Condition</code>实例来控制不同的等待条件,并使用<code>signal()</code>或<code>signalAll()</code>方法通知特定的等待线程。</li><li>更灵活的锁控制:<code>Lock</code>接口提供了更灵活的锁控制机制。它支持可重入锁(ReentrantLock)和读写锁(ReentrantReadWriteLock),以及各种锁的高级功能,如公平性、超时等待和中断响应。</li><li>更安全的并发控制:<code>Condition</code>和<code>Lock</code>提供了更安全的并发控制机制,避免了可能导致死锁、竞态条件和线程饥饿等问题。它们通过显示地获取和释放锁来确保线程的正确同步和协调。</li><li>可扩展性和性能优化:<code>Condition</code>和<code>Lock</code>机制提供了更高级的线程同步功能,可以更好地满足复杂的并发需求。它们支持更多的高级操作,如条件等待、多个等待队列、可中断的等待等,并提供了更好的性能优化选项。</li></ol><p>综上所述,尽管可以使用<code>wait()</code>和<code>notifyAll()</code>来实现简单的线程同步和通信,但在更复杂的并发场景下,使用<code>Condition</code>和<code>Lock</code>会更加可靠和灵活。它们提供了更多的功能和性能优化选项,可以更好地管理线程的状态、等待和唤醒,确保线程间的正确同步和协作。</p>]]></content>
<summary type="html"><p>Go使用的是<code>CSP</code>并发模型,而JAVA使用的是基于传统的内存访问控制的并发模型。他们有以下区别。我们通过示例代码来比对一下Go和 Java的并发编程</p></summary>
<category term="并发" scheme="https://alexhuihui.github.io/categories/%E5%B9%B6%E5%8F%91/"/>
<category term="并发" scheme="https://alexhuihui.github.io/tags/%E5%B9%B6%E5%8F%91/"/>
</entry>
<entry>
<title>【重构】改善既有代码设计的阅读笔记</title>
<link href="https://alexhuihui.github.io/article/20230613.html"/>
<id>https://alexhuihui.github.io/article/20230613.html</id>
<published>2023-06-13T11:54:06.000Z</published>
<updated>2025-01-25T12:32:58.463Z</updated>
<content type="html"><![CDATA[<p>所谓重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减少整理过程中引入错误的几率。这本书告诉你如何以一种可控制且高效率的方式进行重构,如何有条不紊地改进程序结构,而且不会引入错误。</p><span id="more"></span> <h3 id="重构第一个案例"><a href="#重构第一个案例" class="headerlink" title="重构第一个案例"></a>重构第一个案例</h3><p>如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。</p><h4 id="第一步"><a href="#第一步" class="headerlink" title="第一步"></a>第一步</h4><p>构建完整的测试用例,保证重构之后的代码行为表现与之前无异。</p><p>Extract Method</p><p>修改变量命名</p><p>Move Method</p><p>Replace Temp with Query</p><p>Replace Conditional with Polymorphism 用多态取代条件表达式</p><p>Self Encapsulate Field 自封装字段(字段访问保护)</p><p>Replace Type Code with State/Strategy</p><p>用于改善基于类型码(Type Code)的条件逻辑,并通过引入状态模式(State Pattern)或策略模式(Strategy Pattern)来实现更清晰和可扩展的代码结构</p><h3 id="重构原则"><a href="#重构原则" class="headerlink" title="重构原则"></a>重构原则</h3><h4 id="什么是重构"><a href="#什么是重构" class="headerlink" title="什么是重构"></a>什么是重构</h4><p>对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本</p><p>两顶帽子,当你使用重构技术进行开发时,把时间分配到两种不同的行为:添加新功能以及重构。添加新功能时,你不应该修改既有代码,只管添加新功能。</p><p>重构时你就不应该添加新功能而是只管改变程序结构。两顶帽子要随时切换,应该清除意识到你戴的是什么帽子。</p><h4 id="为何重构"><a href="#为何重构" class="headerlink" title="为何重构"></a>为何重构</h4><p>重构改进软件设计</p><p>重构使软件更容易理解</p><h4 id="何时重构"><a href="#何时重构" class="headerlink" title="何时重构"></a>何时重构</h4><p>事不过三,三则重构</p><h3 id="代码的坏味道"><a href="#代码的坏味道" class="headerlink" title="代码的坏味道"></a>代码的坏味道</h3><p>Duplicated Code</p><p>Long Method(过长函数)</p><p>Large Class(过大的类)</p><p>Long Parameter List(过长参数列)</p><p>Divergent Change(发散式变化)</p><p>Shotgun Surgery(霰弹式修改)</p><p>Feature Envy(依恋情结)</p><h3 id="重构列表"><a href="#重构列表" class="headerlink" title="重构列表"></a>重构列表</h3>]]></content>
<summary type="html"><p>所谓重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减少整理过程中引入错误的几率。这本书告诉你如何以一种可控制且高效率的方式进行重构,如何有条不紊地改进程序结构,而且不会引入错误。</p></summary>
<category term="重构" scheme="https://alexhuihui.github.io/categories/%E9%87%8D%E6%9E%84/"/>
<category term="重构" scheme="https://alexhuihui.github.io/tags/%E9%87%8D%E6%9E%84/"/>
</entry>
<entry>
<title>【Mybatis】Mybatis源码解析—基础功能包</title>
<link href="https://alexhuihui.github.io/article/20230608.html"/>
<id>https://alexhuihui.github.io/article/20230608.html</id>
<published>2023-06-08T11:54:06.000Z</published>
<updated>2025-01-25T12:35:47.544Z</updated>
<content type="html"><![CDATA[<p>阅读<code>mybatis</code>源码可以先从外围的基础功能包开始,剥洋葱一样一层一层深入</p><span id="more"></span> <h3 id="type-包"><a href="#type-包" class="headerlink" title="type 包"></a>type 包</h3><h4 id="归类总结"><a href="#归类总结" class="headerlink" title="归类总结"></a>归类总结</h4><ul><li>类型处理器</li><li>类型注册器</li><li>注解类</li></ul><h4 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h4><ol><li>模板模式</li></ol><p> 模板中定义了大体的处理框架,留下一些细节供使用者来完善。在设计模式中,使用一个抽象类定义一整套的操作流程,而抽象类的子类则完成每个操作步骤的实现。</p><h4 id="类型处理器"><a href="#类型处理器" class="headerlink" title="类型处理器"></a>类型处理器</h4><p>用于ORM框架中处理Java类型和数据库类型,使用模板方式定义了一个<code>BaseTypeHandler</code>,<code>getResult</code>方法完成了异常处理等统一的工作,而与具体类型的操作则通过抽象方法由具体的子类类型处理器实现。</p><h4 id="类型注册表"><a href="#类型注册表" class="headerlink" title="类型注册表"></a>类型注册表</h4><p>光有类型处理器还不够,还需要快速查找数据类型对应的类型处理器</p><p>根据一个Java类型和JDBC类型就能确定一个类型处理器</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><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">// TypeHandlerRegistry.class</span><br><span class="line">private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {</span><br><span class="line"> if (ParamMap.class.equals(type)) {</span><br><span class="line"> return null;</span><br><span class="line"> }</span><br><span class="line"> Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);</span><br><span class="line"> TypeHandler<?> handler = null;</span><br><span class="line"> if (jdbcHandlerMap != null) {</span><br><span class="line"> handler = jdbcHandlerMap.get(jdbcType);</span><br><span class="line"> if (handler == null) {</span><br><span class="line"> handler = jdbcHandlerMap.get(null);</span><br><span class="line"> }</span><br><span class="line"> if (handler == null) {</span><br><span class="line"> // #591</span><br><span class="line"> handler = pickSoleHandler(jdbcHandlerMap);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // type drives generics here</span><br><span class="line"> return (TypeHandler<T>) handler;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h3 id="io-包"><a href="#io-包" class="headerlink" title="io 包"></a>io 包</h3><p>io包即输入/输出包,负责完成 MyBatis中与输入/输出相关的操作</p><h4 id="设计模式-1"><a href="#设计模式-1" class="headerlink" title="设计模式"></a>设计模式</h4><h5 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h5><p>单例模式(Singleton Pattern)是一种非常简单的设计模式。使 用了单例模式的类提供一个方法得到该类的对象,并且总保证这个对 象是唯一的。</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></pre></td><td class="code"><pre><span class="line">public class Singleton {</span><br><span class="line"> private static final Singleton INSTANCE = new Singleton();</span><br><span class="line"> </span><br><span class="line"> private Singleton() {};</span><br><span class="line"> </span><br><span class="line"> public static Singleton getInstance() {</span><br><span class="line"> return INSTANCE;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h5><p>代理模式(Proxy Pattern)是指建立某一个对象的代理对象,并且由代理对象控制对原对象的引用。</p><h3 id="logging-包"><a href="#logging-包" class="headerlink" title="logging 包"></a>logging 包</h3><p>logging包负责完成 MyBatis操作中的日志记录工作。</p><h4 id="设计模式-2"><a href="#设计模式-2" class="headerlink" title="设计模式"></a>设计模式</h4><h5 id="适配器模式"><a href="#适配器模式" class="headerlink" title="适配器模式"></a>适配器模式</h5><p>适配器模式(Adapter Pattern)是一种结构型模式,基于该模式 设计的类能够在两个或者多个不兼容的类之间起到沟通桥梁的作用。 转换插头就是一个适配器的典型例子。不同的转换插头能够适配 不同国家的插座标准,从而使得一个电器能在各个国家使用。</p>]]></content>
<summary type="html"><p>阅读<code>mybatis</code>源码可以先从外围的基础功能包开始,剥洋葱一样一层一层深入</p></summary>
<category term="Mybatis" scheme="https://alexhuihui.github.io/categories/Mybatis/"/>
<category term="Mybatis" scheme="https://alexhuihui.github.io/tags/Mybatis/"/>
</entry>
<entry>
<title>【Mybatis】Mybatis运行流程</title>
<link href="https://alexhuihui.github.io/article/20230607.html"/>
<id>https://alexhuihui.github.io/article/20230607.html</id>
<published>2023-06-07T11:54:06.000Z</published>
<updated>2025-01-25T12:39:02.885Z</updated>
<content type="html"><![CDATA[<p>今天让我们来探寻一下<code>Mybatis</code>的运行流程,我们将它的运行流程分为2个阶段。</p><ul><li>第一阶段:<code>MyBatis</code>初始化阶段。该阶段用来完成 <code>MyBatis</code>运行环境的准备工作,只在 <code>MyBatis</code>启动时运行一次。</li><li>第二阶段:数据读写阶段。该阶段由数据读写操作触发,将根据要求完成具体的增、删、改、查等数据库操作。</li></ul><span id="more"></span> <h3 id="初始化阶段"><a href="#初始化阶段" class="headerlink" title="初始化阶段"></a>初始化阶段</h3><ul><li>根据配置文件的位置,获取它的输入流 <code>InputStream</code></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="function">InputStream <span class="title">getResourceAsStream</span><span class="params">(String resource, ClassLoader[] classLoader)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (ClassLoader cl : classLoader) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != cl) {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// try to find the resource as passed</span></span><br><span class="line"> InputStream returnValue = cl.getResourceAsStream(resource);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> == returnValue) {</span><br><span class="line"> returnValue = cl.getResourceAsStream(<span class="string">"/"</span> + resource);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != returnValue) {</span><br><span class="line"> <span class="keyword">return</span> returnValue;</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> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><ul><li>从配置文件的根节点开始,逐层解析配置文件,也包括相关的映射文件。解析过程中不断将解析结果放入 <code>Configuration</code>对象。</li><li>以配置好的 <code>Configuration</code>对象为参数,获取一个<code> SqlSessionFactory</code>对象。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> SqlSessionFactory <span class="title">build</span><span class="params">(Reader reader, String environment, Properties properties)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> XMLConfigBuilder parser = <span class="keyword">new</span> XMLConfigBuilder(reader, environment, properties);</span><br><span class="line"> <span class="keyword">return</span> build(parser.parse());</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="keyword">throw</span> ExceptionFactory.wrapException(<span class="string">"Error building SqlSession."</span>, e);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> ErrorContext.instance().reset();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (reader != <span class="keyword">null</span>) {</span><br><span class="line"> reader.close();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> <span class="comment">// Intentionally ignore. Prefer previous error.</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p> 重要代码注释:</p><blockquote><ol><li>生成了一个 <code>XMLConfigBuilder </code>对象,并调用了其 <code>parse </code>方法,得到一个<code>Configuration</code>对象(因为 parse方法的输出结果为<code> Configuration</code>对象)。</li></ol></blockquote><blockquote><ol start="2"><li>调用了 <code>SqlSessionFactoryBuilder</code> 自身的 <code>build</code>方法, 传入参数为上一步得到的<code>Configuration</code>对象。</li></ol></blockquote><h3 id="数据读写阶段追踪"><a href="#数据读写阶段追踪" class="headerlink" title="数据读写阶段追踪"></a>数据读写阶段追踪</h3><p>进行一次数据库的读或写操作时,<code>MyBatis</code>内部都要经过哪些步骤</p><h4 id="获得SqlSession"><a href="#获得SqlSession" class="headerlink" title="获得SqlSession"></a>获得<code>SqlSession</code></h4><p>通过初始化阶段获取的<code>SqlSessionFactory</code>生成数据库操作中所需要的<code>SqlSession</code></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">SqlSession sqlSession = sqlSessionFactory.openSession();</span><br></pre></td></tr></table></figure><h4 id="映射接口文件与映射文件的绑定"><a href="#映射接口文件与映射文件的绑定" class="headerlink" title="映射接口文件与映射文件的绑定"></a>映射接口文件与映射文件的绑定</h4><p>映射接口文件是指存有Java接口的文件,而映射文件是指存有<code>sql</code>操作的<code>xml</code>文件</p><p>在进行数据查询之前,先通过<code>SqlSession</code>的<code>getMapper()</code>获取映射接口的实现, 该操作通过 Configuration类的 <code>getMapper</code>方法转接,最终进入<code> MapperRegistry</code>类中的<code>getMapper</code>方法。<code>MapperRegistry</code>类中的 <code>getMapper()</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// DefaultSqlSession.class</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">T <span class="title">getMapper</span><span class="params">(Class<T> type)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> configuration.getMapper(type, <span class="keyword">this</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Configuration.class</span></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">T <span class="title">getMapper</span><span class="params">(Class<T> type, SqlSession sqlSession)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> mapperRegistry.getMapper(type, sqlSession);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// MapperRegistry.class</span></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">T <span class="title">getMapper</span><span class="params">(Class<T> type, SqlSession sqlSession)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);</span><br><span class="line"> <span class="keyword">if</span> (mapperProxyFactory == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> BindingException(<span class="string">"Type "</span> + type + <span class="string">" is not known to the MapperRegistry."</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">return</span> mapperProxyFactory.newInstance(sqlSession);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> BindingException(<span class="string">"Error getting mapper instance. Cause: "</span> + e, e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="映射接口的代理"><a href="#映射接口的代理" class="headerlink" title="映射接口的代理"></a>映射接口的代理</h4><p><code>session.getMapper()</code> 得到的是一个<code>mapperProxyFactory.newInstance(sqlSession)</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> T <span class="title">newInstance</span><span class="params">(MapperProxy<T> mapperProxy)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), <span class="keyword">new</span> Class[] { mapperInterface }, mapperProxy);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> T <span class="title">newInstance</span><span class="params">(SqlSession sqlSession)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> MapperProxy<T> mapperProxy = <span class="keyword">new</span> MapperProxy<>(sqlSession, mapperInterface, methodCache);</span><br><span class="line"> <span class="keyword">return</span> newInstance(mapperProxy);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最终数据查询会进入<code>MapperProxy</code>的<code>invoke()</code>,这是因为被代理对象的方法会被代理对象的<code>invoke()</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (Object.class.equals(method.getDeclaringClass())) {</span><br><span class="line"> <span class="keyword">return</span> method.invoke(<span class="keyword">this</span>, args);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cachedInvoker(method).invoke(proxy, method, args, sqlSession);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable t) {</span><br><span class="line"> <span class="keyword">throw</span> ExceptionUtil.unwrapThrowable(t);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后会触发<code>MapperMethod</code>对象的<code>execute()</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">execute</span><span class="params">(SqlSession sqlSession, Object[] args)</span> </span>{</span><br><span class="line"> Object result;</span><br><span class="line"> <span class="keyword">switch</span> (command.getType()) {</span><br><span class="line"> <span class="keyword">case</span> INSERT: {</span><br><span class="line"> Object param = method.convertArgsToSqlCommandParam(args);</span><br><span class="line"> result = rowCountResult(sqlSession.insert(command.getName(), param));</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">case</span> UPDATE: {</span><br><span class="line"> Object param = method.convertArgsToSqlCommandParam(args);</span><br><span class="line"> result = rowCountResult(sqlSession.update(command.getName(), param));</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">case</span> DELETE: {</span><br><span class="line"> Object param = method.convertArgsToSqlCommandParam(args);</span><br><span class="line"> result = rowCountResult(sqlSession.delete(command.getName(), param));</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">case</span> SELECT:</span><br><span class="line"> <span class="keyword">if</span> (method.returnsVoid() && method.hasResultHandler()) {</span><br><span class="line"> executeWithResultHandler(sqlSession, args);</span><br><span class="line"> result = <span class="keyword">null</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (method.returnsMany()) {</span><br><span class="line"> result = executeForMany(sqlSession, args);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (method.returnsMap()) {</span><br><span class="line"> result = executeForMap(sqlSession, args);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (method.returnsCursor()) {</span><br><span class="line"> result = executeForCursor(sqlSession, args);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> Object param = method.convertArgsToSqlCommandParam(args);</span><br><span class="line"> result = sqlSession.selectOne(command.getName(), param);</span><br><span class="line"> <span class="keyword">if</span> (method.returnsOptional() && (result == <span class="keyword">null</span> || !method.getReturnType().equals(result.getClass()))) {</span><br><span class="line"> result = Optional.ofNullable(result);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> FLUSH:</span><br><span class="line"> result = sqlSession.flushStatements();</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> BindingException(<span class="string">"Unknown execution method for: "</span> + command.getName());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (result == <span class="keyword">null</span> && method.getReturnType().isPrimitive() && !method.returnsVoid()) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> BindingException(<span class="string">"Mapper method '"</span> + command.getName()</span><br><span class="line"> + <span class="string">"' attempted to return null from a method with a primitive return type ("</span> + method.getReturnType() + <span class="string">")."</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p><code>MyBatis</code>根据不同数据库操作类型调用了不同的处理方法</p><p><code>executeForMany</code> 方法中,<code>MyBatis </code>开始通过 <code>SqlSession</code> 对象的<code>selectList</code>方法开展后续的查询工作。 追踪到这里,<code>MyBatis </code>已经完成了为映射接口注入实现的过程。 于是,对映射接口中抽象方法的调用转变为了数据查询操作。</p><h4 id="SQL语句的查找"><a href="#SQL语句的查找" class="headerlink" title="SQL语句的查找"></a><code>SQL</code>语句的查找</h4><p>每个<code> MappedStatement</code> 对象对应了我们设置的一个数据库操作节 点,它主要定义了数据库操作语句、输入/输出参数等信息。configuration.getMappedStatement(statement)<code>语句将要执行的</code>MappedStatement<code>对象从 </code>Configuration`对象存储的映射文件信息中找了出来。</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="keyword">private</span> <E> <span class="function">List<E> <span class="title">selectList</span><span class="params">(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> MappedStatement ms = configuration.getMappedStatement(statement);</span><br><span class="line"> dirty |= ms.isDirtySelect();</span><br><span class="line"> <span class="keyword">return</span> executor.query(ms, wrapCollection(parameter), rowBounds, handler);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="keyword">throw</span> ExceptionFactory.wrapException(<span class="string">"Error querying database. Cause: "</span> + e, e);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> ErrorContext.instance().reset();</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="查询结果缓存"><a href="#查询结果缓存" class="headerlink" title="查询结果缓存"></a>查询结果缓存</h4><p>query方法是一个 Executor接口中的抽象方法,实际执行的是<code> CachingExecutor</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// CachingExecutor.class</span></span><br><span class="line"><span class="keyword">public</span> <E> <span class="function">List<E> <span class="title">query</span><span class="params">(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> BoundSql boundSql = ms.getBoundSql(parameterObject);</span><br><span class="line"> CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);</span><br><span class="line"> <span class="keyword">return</span> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p><code>BoundSql</code>是经过层层转化后去除掉 if、where等标签的 <code>SQL</code>语 句,而<code> CacheKey</code>是为该次查询操作计算出来的缓存键</p><h4 id="数据库查询"><a href="#数据库查询" class="headerlink" title="数据库查询"></a>数据库查询</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// CachingExecutor.class</span></span><br><span class="line"><span class="keyword">public</span> <E> <span class="function">List<E> <span class="title">query</span><span class="params">(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler,</span></span></span><br><span class="line"><span class="params"><span class="function"> CacheKey key, BoundSql boundSql)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> Cache cache = ms.getCache();</span><br><span class="line"> <span class="keyword">if</span> (cache != <span class="keyword">null</span>) {</span><br><span class="line"> flushCacheIfRequired(ms);</span><br><span class="line"> <span class="keyword">if</span> (ms.isUseCache() && resultHandler == <span class="keyword">null</span>) {</span><br><span class="line"> ensureNoOutParams(ms, boundSql);</span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> List<E> list = (List<E>) tcm.getObject(cache, key);</span><br><span class="line"> <span class="keyword">if</span> (list == <span class="keyword">null</span>) {</span><br><span class="line"> list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);</span><br><span class="line"> tcm.putObject(cache, key, list); <span class="comment">// issue #578 and #116</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> list;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p><code>MyBatis</code>查看当前的查询操作是否命中缓存。如果 是,则从缓存中获取数据结果;否则,便通过 delegate调用 query方 法。</p><p>delegate调用的 query方法实际上是<code>BaseExecutor</code>类中的 query方法</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// BaseExecutor.class</span></span><br><span class="line"><span class="keyword">public</span> <E> <span class="function">List<E> <span class="title">query</span><span class="params">(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,</span></span></span><br><span class="line"><span class="params"><span class="function"> CacheKey key, BoundSql boundSql)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> ErrorContext.instance().resource(ms.getResource()).activity(<span class="string">"executing a query"</span>).object(ms.getId());</span><br><span class="line"> <span class="keyword">if</span> (closed) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ExecutorException(<span class="string">"Executor was closed."</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (queryStack == <span class="number">0</span> && ms.isFlushCacheRequired()) {</span><br><span class="line"> clearLocalCache();</span><br><span class="line"> }</span><br><span class="line"> List<E> list;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> queryStack++;</span><br><span class="line"> list = resultHandler == <span class="keyword">null</span> ? (List<E>) localCache.getObject(key) : <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (list != <span class="keyword">null</span>) {</span><br><span class="line"> handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> queryStack--;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (queryStack == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">for</span> (DeferredLoad deferredLoad : deferredLoads) {</span><br><span class="line"> deferredLoad.load();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// issue #601</span></span><br><span class="line"> deferredLoads.clear();</span><br><span class="line"> <span class="keyword">if</span> (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {</span><br><span class="line"> <span class="comment">// issue #482</span></span><br><span class="line"> clearLocalCache();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> list;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p><code>MyBatis</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// BaseExecutor.class</span></span><br><span class="line"><span class="keyword">private</span> <E> <span class="function">List<E> <span class="title">queryFromDatabase</span><span class="params">(MappedStatement ms, Object parameter, RowBounds rowBounds,</span></span></span><br><span class="line"><span class="params"><span class="function"> ResultHandler resultHandler, CacheKey key, BoundSql boundSql)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> List<E> list;</span><br><span class="line"> localCache.putObject(key, EXECUTION_PLACEHOLDER);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> localCache.removeObject(key);</span><br><span class="line"> }</span><br><span class="line"> localCache.putObject(key, list);</span><br><span class="line"> <span class="keyword">if</span> (ms.getStatementType() == StatementType.CALLABLE) {</span><br><span class="line"> localOutputParameterCache.putObject(key, parameter);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> list;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p><code>MyBatis</code>先在缓存中放置一个占位符,然 后调用 <code>doQuery</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// SimpleExecutor.class</span></span><br><span class="line"><span class="keyword">public</span> <E> <span class="function">List<E> <span class="title">doQuery</span><span class="params">(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,</span></span></span><br><span class="line"><span class="params"><span class="function"> BoundSql boundSql)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> Statement stmt = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Configuration configuration = ms.getConfiguration();</span><br><span class="line"> StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,</span><br><span class="line"> boundSql);</span><br><span class="line"> stmt = prepareStatement(handler, ms.getStatementLog());</span><br><span class="line"> <span class="keyword">return</span> handler.query(stmt, resultHandler);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> closeStatement(stmt);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>上述方法生成了Statement对象stmt。Statement类并不是MyBatis 中的类,而是java.sql包中的类。Statement类能够执行静态 SQL语句 并返回结果。</p><p>程序还通过 Configuration的 newStatementHandler方法获得了 一个 StatementHandler对象 handler,然后将查询操作交给 StatementHandler对象进行。StatementHandler是一个语句处理器类,其中封 装了很多语句操作方法</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">// PreparedStatementHandler.class</span></span><br><span class="line"><span class="keyword">public</span> <E> <span class="function">List<E> <span class="title">query</span><span class="params">(Statement statement, ResultHandler resultHandler)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> PreparedStatement ps = (PreparedStatement) statement;</span><br><span class="line"> ps.execute();</span><br><span class="line"> <span class="keyword">return</span> resultSetHandler.handleResultSets(ps);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>这里 ps.execute()真正执行了 SQL 语句,然后把执行结果交 给 ResultHandler 对象处理。而PreparedStatement类并不是MyBatis 中的类,因而ps.execute()的执行不再由MyBatis负责,而是由 com.mysql.cj.jdbc包中的类负责,这里不再继续追踪。</p><p>总结:</p><ol><li>在进行数据库查询前,先查询缓存;如果确实需要查询数据 库,则数据库查询之后的结果也放入缓存中。 ·</li><li>SQL 语句的执行经过了层层转化,依次经过了 MappedStatement 对象、Statement对象和 PreparedStatement对象,最后才得以执 行。 ·</li><li>最终数据库查询得到的结果交给 ResultHandler对象处理。</li></ol><h4 id="处理结果集"><a href="#处理结果集" class="headerlink" title="处理结果集"></a>处理结果集</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// DefaultResultSetHandler.class</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List<Object> <span class="title">handleResultSets</span><span class="params">(Statement stmt)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> ErrorContext.instance().activity(<span class="string">"handling results"</span>).object(mappedStatement.getId());</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> List<Object> multipleResults = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> resultSetCount = <span class="number">0</span>;</span><br><span class="line"> ResultSetWrapper rsw = getFirstResultSet(stmt);</span><br><span class="line"></span><br><span class="line"> List<ResultMap> resultMaps = mappedStatement.getResultMaps();</span><br><span class="line"> <span class="keyword">int</span> resultMapCount = resultMaps.size();</span><br><span class="line"> validateResultMapsCount(rsw, resultMapCount);</span><br><span class="line"> <span class="keyword">while</span> (rsw != <span class="keyword">null</span> && resultMapCount > resultSetCount) {</span><br><span class="line"> ResultMap resultMap = resultMaps.get(resultSetCount);</span><br><span class="line"> handleResultSet(rsw, resultMap, multipleResults, <span class="keyword">null</span>);</span><br><span class="line"> rsw = getNextResultSet(stmt);</span><br><span class="line"> cleanUpAfterHandlingResultSet();</span><br><span class="line"> resultSetCount++;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> String[] resultSets = mappedStatement.getResultSets();</span><br><span class="line"> <span class="keyword">if</span> (resultSets != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">while</span> (rsw != <span class="keyword">null</span> && resultSetCount < resultSets.length) {</span><br><span class="line"> ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);</span><br><span class="line"> <span class="keyword">if</span> (parentMapping != <span class="keyword">null</span>) {</span><br><span class="line"> String nestedResultMapId = parentMapping.getNestedResultMapId();</span><br><span class="line"> ResultMap resultMap = configuration.getResultMap(nestedResultMapId);</span><br><span class="line"> handleResultSet(rsw, resultMap, <span class="keyword">null</span>, parentMapping);</span><br><span class="line"> }</span><br><span class="line"> rsw = getNextResultSet(stmt);</span><br><span class="line"> cleanUpAfterHandlingResultSet();</span><br><span class="line"> resultSetCount++;</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> collapseSingleResultList(multipleResults);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>今天让我们来探寻一下<code>Mybatis</code>的运行流程,我们将它的运行流程分为2个阶段。</p>
<ul>
<li>第一阶段:<code>MyBatis</code>初始化阶段。该阶段用来完成 <code>MyBatis</code>运行环境的准备工作,只在 <code>MyBatis</code>启动时运行一次。</li>
<li>第二阶段:数据读写阶段。该阶段由数据读写操作触发,将根据要求完成具体的增、删、改、查等数据库操作。</li>
</ul></summary>
<category term="Mybatis" scheme="https://alexhuihui.github.io/categories/Mybatis/"/>
<category term="Mybatis" scheme="https://alexhuihui.github.io/tags/Mybatis/"/>
</entry>
<entry>
<title>【事务处理】本地事务</title>
<link href="https://alexhuihui.github.io/article/20230517.html"/>
<id>https://alexhuihui.github.io/article/20230517.html</id>
<published>2023-05-17T11:54:06.000Z</published>
<updated>2025-01-25T12:43:12.845Z</updated>
<content type="html"><![CDATA[<p>大家都知道数据库事务中有着<code>AICD</code>的说法,实际上C(一致性)是目的,而对应的A(原子性)、I(隔离性)、D(持久性)是手段,AID -> C。</p><span id="more"></span> <h3 id="概念介绍"><a href="#概念介绍" class="headerlink" title="概念介绍"></a>概念介绍</h3><h4 id="原子性"><a href="#原子性" class="headerlink" title="原子性"></a>原子性</h4><p>原子性保证了事务的多个操作要么都生效要么都不生效,不会存在中间状态</p><h4 id="隔离性"><a href="#隔离性" class="headerlink" title="隔离性"></a>隔离性</h4><p>在不同的业务处理过程中,互不影响各自读写数据</p><h3 id="持久性"><a href="#持久性" class="headerlink" title="持久性"></a>持久性</h3><p>所有提交成功的数据修改都应该被持久化保存</p><h3 id="实现原子性和持久性"><a href="#实现原子性和持久性" class="headerlink" title="实现原子性和持久性"></a>实现原子性和持久性</h3>]]></content>
<summary type="html"><p>大家都知道数据库事务中有着<code>AICD</code>的说法,实际上C(一致性)是目的,而对应的A(原子性)、I(隔离性)、D(持久性)是手段,AID -&gt; C。</p></summary>
<category term="事务" scheme="https://alexhuihui.github.io/categories/%E4%BA%8B%E5%8A%A1/"/>
<category term="事务" scheme="https://alexhuihui.github.io/tags/%E4%BA%8B%E5%8A%A1/"/>
</entry>
<entry>
<title>【项目】分诊知识库系统设计</title>
<link href="https://alexhuihui.github.io/article/20230512.html"/>
<id>https://alexhuihui.github.io/article/20230512.html</id>
<published>2023-05-12T11:45:55.000Z</published>
<updated>2025-01-25T12:45:32.042Z</updated>
<content type="html"><![CDATA[<p>分诊知识库的系统设计</p><span id="more"></span> <p>设计方案:</p><h3 id="数据库设计"><a href="#数据库设计" class="headerlink" title="数据库设计"></a>数据库设计</h3><p>针对疾病、药品、急救和异常分诊案例,分别设计对应的数据表以及关联的数据表。</p><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230515153441.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230515153441.png" alt="img"></a></p><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230515153214.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230515153214.png" alt="img"></a></p><h3 id="Elasticsearch索引设计"><a href="#Elasticsearch索引设计" class="headerlink" title="Elasticsearch索引设计"></a><code>Elasticsearch</code>索引设计</h3><p>在<code>Elasticsearch</code>中,为疾病、药品和急救建立一个统一的索引,方便进行搜索。</p><figure class="highlight json"><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><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"index_patterns"</span>: [</span><br><span class="line"> <span class="string">"local-knowledge-*"</span>,</span><br><span class="line"> <span class="string">"dev-knowledge-*"</span>,</span><br><span class="line"> <span class="string">"sit-knowledge-*"</span>,</span><br><span class="line"> <span class="string">"uat-knowledge-*"</span>,</span><br><span class="line"> <span class="string">"prod-knowledge-*"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"settings"</span>: {</span><br><span class="line"> <span class="attr">"number_of_shards"</span>: <span class="number">1</span>,</span><br><span class="line"> <span class="attr">"number_of_replicas"</span>: <span class="number">1</span>,</span><br><span class="line"> <span class="attr">"analysis"</span>: {</span><br><span class="line"> <span class="attr">"filter"</span>: {</span><br><span class="line"> <span class="attr">"len"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"length"</span>,</span><br><span class="line"> <span class="attr">"min"</span>: <span class="number">2</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"analyzer"</span>: {</span><br><span class="line"> <span class="attr">"custom_ik_max"</span>: {</span><br><span class="line"> <span class="attr">"tokenizer"</span>: <span class="string">"ik_max_word"</span>,</span><br><span class="line"> <span class="attr">"filter"</span>: [</span><br><span class="line"> <span class="string">"len"</span></span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"mappings"</span>: {</span><br><span class="line"> <span class="attr">"properties"</span>: {</span><br><span class="line"> <span class="attr">"name"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"commonName"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"infoType"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"integer"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"introduction"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"typeId"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"long"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"typeName"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"relatedDepartments"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"nested"</span>,</span><br><span class="line"> <span class="attr">"properties"</span>: {</span><br><span class="line"> <span class="attr">"departmentId"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"long"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"departmentName"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"relatedPositions"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"nested"</span>,</span><br><span class="line"> <span class="attr">"properties"</span>: {</span><br><span class="line"> <span class="attr">"positionId"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"long"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"positionName"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"fdaId"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"long"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"fdaName"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"morbidityId"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"long"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"morbidityName"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"keyword"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"symptomDescription"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"checkDescription"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"treatment"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"ik_max_word"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"prevention"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"concurrent"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"diagnosis"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"ik_max_word"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"adverseReaction"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"component"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"contraindication"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"dosage"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"indication"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"content"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"note"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span>,</span><br><span class="line"> <span class="attr">"analyzer"</span>: <span class="string">"custom_ik_max"</span>,</span><br><span class="line"> <span class="attr">"search_analyzer"</span>: <span class="string">"ik_smart"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"crtId"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"long"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"crtName"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"crtTime"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"date"</span>,</span><br><span class="line"> <span class="attr">"format"</span>: <span class="string">"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"updId"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"long"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"updName"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"text"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"updTime"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"date"</span>,</span><br><span class="line"> <span class="attr">"format"</span>: <span class="string">"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="应用程序设计"><a href="#应用程序设计" class="headerlink" title="应用程序设计"></a>应用程序设计</h3><p>为疾病、药品、急救和异常分诊案例提供基础数据管理能力,<code>API</code>如下</p><ol><li>疾病模块API接口</li></ol><table><thead><tr><th align="left">API名称</th><th align="left">请求方式</th><th align="left">接口路径</th><th align="left">请求参数</th><th align="left">返回结果</th><th align="left">备注</th></tr></thead><tbody><tr><td align="left">获取疾病列表</td><td align="left">GET</td><td align="left">/diseases</td><td align="left">-</td><td align="left">疾病列表</td><td align="left">分页获取所有疾病信息</td></tr><tr><td align="left">获取疾病详情</td><td align="left">GET</td><td align="left">/diseases/{id}</td><td align="left">id (疾病ID)</td><td align="left">疾病详情</td><td align="left">根据疾病ID获取疾病详细信息</td></tr><tr><td align="left">创建疾病</td><td align="left">POST</td><td align="left">/diseases</td><td align="left">疾病信息</td><td align="left">创建成功的疾病</td><td align="left">创建新的疾病</td></tr><tr><td align="left">更新疾病</td><td align="left">PUT</td><td align="left">/diseases/{id}</td><td align="left">id (疾病ID)</td><td align="left">更新成功的疾病</td><td align="left">根据疾病ID更新疾病信息</td></tr><tr><td align="left">删除疾病</td><td align="left">DELETE</td><td align="left">/diseases/{id}</td><td align="left">id (疾病ID)</td><td align="left">删除成功的疾病</td><td align="left">根据疾病ID删除疾病</td></tr></tbody></table><ol><li>药品模块API接口</li></ol><table><thead><tr><th align="left">API名称</th><th align="left">请求方式</th><th align="left">接口路径</th><th align="left">请求参数</th><th align="left">返回结果</th><th align="left">备注</th></tr></thead><tbody><tr><td align="left">获取药品列表</td><td align="left">GET</td><td align="left">/drugs</td><td align="left">-</td><td align="left">药品列表</td><td align="left">分页获取所有药品信息</td></tr><tr><td align="left">获取药品详情</td><td align="left">GET</td><td align="left">/drugs/{id}</td><td align="left">id (药品ID)</td><td align="left">药品详情</td><td align="left">根据药品ID获取药品详细信息</td></tr><tr><td align="left">创建药品</td><td align="left">POST</td><td align="left">/drugs</td><td align="left">药品信息</td><td align="left">创建成功的药品</td><td align="left">创建新的药品</td></tr><tr><td align="left">更新药品</td><td align="left">PUT</td><td align="left">/drugs/{id}</td><td align="left">id (药品ID)</td><td align="left">更新成功的药品</td><td align="left">根据药品ID更新药品信息</td></tr><tr><td align="left">删除药品</td><td align="left">DELETE</td><td align="left">/drugs/{id}</td><td align="left">id (药品ID)</td><td align="left">删除成功的药品</td><td align="left">根据药品ID删除药品</td></tr></tbody></table><ol><li>急救模块API接口</li></ol><table><thead><tr><th align="left">API名称</th><th align="left">请求方式</th><th align="left">接口路径</th><th align="left">请求参数</th><th align="left">返回结果</th><th align="left">备注</th></tr></thead><tbody><tr><td align="left">获取急救信息列表</td><td align="left">GET</td><td align="left">/emergencies</td><td align="left">-</td><td align="left">急救信息列表</td><td align="left">分页获取所有急救信息</td></tr><tr><td align="left">获取急救信息详情</td><td align="left">GET</td><td align="left">/emergencies/{id}</td><td align="left">id (急救信息ID)</td><td align="left">急救信息详情</td><td align="left">根据急救信息ID获取急救信息详细信息</td></tr><tr><td align="left">创建急救信息</td><td align="left">POST</td><td align="left">/emergencies</td><td align="left">急救信息</td><td align="left">创建成功的急救信息</td><td align="left">创建新的急救信息</td></tr><tr><td align="left">更新急救信息</td><td align="left">PUT</td><td align="left">/emergencies/{id}</td><td align="left">id (急救信息ID)</td><td align="left">更新成功的急救信息</td><td align="left">根据急救信息ID更新急救信息</td></tr><tr><td align="left">删除急救信息</td><td align="left">DELETE</td><td align="left">/emergencies/{id}</td><td align="left">id (急救信息ID)</td><td align="left">删除成功的急救信息</td><td align="left">根据急救信息ID删除急救信息</td></tr></tbody></table><ol><li>异常分诊案例模块API接口</li></ol><table><thead><tr><th align="left">API名称</th><th align="left">请求方式</th><th align="left">接口路径</th><th align="left">请求参数</th><th align="left">返回结果</th><th align="left">备注</th></tr></thead><tbody><tr><td align="left">获取异常分诊案例列表</td><td align="left">GET</td><td align="left">/case</td><td align="left">-</td><td align="left">异常分诊案例列表</td><td align="left">分页获取所有异常分诊案例信息</td></tr><tr><td align="left">获取异常分诊案例详情</td><td align="left">GET</td><td align="left">/case/{id}</td><td align="left">id (案例ID)</td><td align="left">异常分诊案例详情</td><td align="left">根据案例ID获取异常分诊案例详细信息</td></tr><tr><td align="left">创建异常分诊案例</td><td align="left">POST</td><td align="left">/case</td><td align="left">异常分诊案例信息</td><td align="left">创建成功的异常分诊案例</td><td align="left">创建新的异常分诊案例</td></tr><tr><td align="left">更新异常分诊案例</td><td align="left">PUT</td><td align="left">/case/{id}</td><td align="left">id (案例ID)</td><td align="left">更新成功的异常分诊案例</td><td align="left">根据案例ID更新异常分诊案例信息</td></tr><tr><td align="left">删除异常分诊案例</td><td align="left">DELETE</td><td align="left">/case/{id}</td><td align="left">id (案例ID)</td><td align="left">删除成功的异常分诊案例</td><td align="left">根据案例ID删除异常分诊案例</td></tr><tr><td align="left">获取案例关联疾病列表</td><td align="left">GET</td><td align="left">/case/{id}/diseases</td><td align="left">id (案例ID)</td><td align="left">关联疾病列表</td><td align="left">根据案例ID获取关联的疾病列表</td></tr><tr><td align="left">获取案例关联科室列表</td><td align="left">GET</td><td align="left">/case/{id}/departments</td><td align="left">id (案例ID)</td><td align="left">关联科室列表</td><td align="left"></td></tr></tbody></table><h3 id="安全性设计"><a href="#安全性设计" class="headerlink" title="安全性设计"></a>安全性设计</h3><p>针对敏感数据,例如药品名称、疾病名称和病人隐私信息等,需要实现访问控制和安全保护措施。基于角色和权限的访问控制,以及对数据的加密和解密保护。</p>]]></content>
<summary type="html"><p>分诊知识库的系统设计</p></summary>
<category term="个人项目" scheme="https://alexhuihui.github.io/categories/%E4%B8%AA%E4%BA%BA%E9%A1%B9%E7%9B%AE/"/>
<category term="设计" scheme="https://alexhuihui.github.io/tags/%E8%AE%BE%E8%AE%A1/"/>
</entry>
<entry>
<title>【LLM】LLM入门</title>
<link href="https://alexhuihui.github.io/article/20230414.html"/>
<id>https://alexhuihui.github.io/article/20230414.html</id>
<published>2023-04-14T11:54:06.000Z</published>
<updated>2025-01-25T12:47:02.715Z</updated>
<content type="html"><![CDATA[<h3 id="LLM介绍"><a href="#LLM介绍" class="headerlink" title="LLM介绍"></a><code>LLM</code>介绍</h3><p>语言模型在最近几年内迅速提高,大型语言模型(LLMs)如GPT-3和GPT-4成为中心。这些模型因其能够以惊人的技巧执行各种任务而变得流行。此外,随着这些模型的参数数量(数十亿!)增加,它们不可预测地获得了新的能力。</p><p>在本文中,我们将探讨LLMs、它们可以执行的任务、它们的缺点以及各种提示工程策略。</p><span id="more"></span> <h4 id="什么是LLMs?"><a href="#什么是LLMs?" class="headerlink" title="什么是LLMs?"></a>什么是LLMs?</h4><p>LLMs是神经网络,在大量文本数据上进行了训练。训练过程使模型学习文本中的模式,包括语法、句法和词汇关联。这些学习到的模式被用于生成类似人类文字,使其非常适合自然语言处理(NLP)任务。</p><h4 id="哪些LLM可用?"><a href="#哪些LLM可用?" class="headerlink" title="哪些LLM可用?"></a>哪些LLM可用?</h4><p>有几个LLM可用,其中GPT-4最受欢迎。其他模型包括 LLaMA、PaLM、BERT 和 T5 等等。每个模型都有其优点和缺点,并且一些是开放性使用而其他则只能通过API使用。</p><h4 id="LLM-的局限性"><a href="#LLM-的局限性" class="headerlink" title="LLM 的局限性"></a>LLM 的局限性</h4><p>尽管表现出色, 但 LLMS 也存在一些限制. 其中一个重要缺陷是无法超越提示中提供的信息进行推理。此外,LLMs 可以根据它们所训练的数据生成有偏见的文本。控制 LLMs 的输出也很具挑战性,因此需要使用提示工程策略来实现所需的输出。。</p><h3 id="你能执行哪些任务?"><a href="#你能执行哪些任务?" class="headerlink" title="你能执行哪些任务?"></a>你能执行哪些任务?</h3><p>我们可以通过为每个案例格式化提示来指导LLMs执行特定的任务。以下是这些任务和提示模板以及每个示例的列表。</p><h4 id="文本摘要"><a href="#文本摘要" class="headerlink" title="文本摘要"></a>文本摘要</h4><p>LLMs可以生成长文本的摘要,使内容更易于理解和消化。我们可以使用以下提示模板请求对一段文字进行摘要:</p><figure class="highlight plaintext"><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"><Full text></span><br><span class="line">Summarize the text above: / Explain the text above in <N> sentences:</span><br></pre></td></tr></table></figure><p>例如,我们可以要求按照以下提示对这段文本进行总结:</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></pre></td><td class="code"><pre><span class="line">In a mechanical watch the watch's gears are turned by a spiral spring called a mainspring. In a manual watch energy is stored in the mainspring by turning a knob, the crown on the side of the watch. Then the energy from the mainspring powers the watch movement until it runs down, requiring the spring to be wound again.</span><br><span class="line">A self-winding watch movement has a mechanism which winds the mainspring using the natural motions of the wearer's body. The watch contains an oscillating weight that turns on a pivot. The normal movements of the watch in the user's pocket (for a pocketwatch) or on the user's arm (for a wristwatch) cause the rotor to pivot on its staff, which is attached to a ratcheted winding mechanism. The motion of the watch is thereby translated into circular motion of the weight which, through a series of reverser and reducing gears, eventually winds the mainspring. There are many different designs for modern self-winding mechanisms. Some designs allow winding of the watch to take place while the weight swings in only one direction while other, more advanced, mechanisms have two ratchets and wind the mainspring during both clockwise and anti-clockwise weight motions.</span><br><span class="line">Explain the text above in 2 sentences:</span><br></pre></td></tr></table></figure><p>这将产生以下输出:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">A mechanical watch uses a mainspring to power the movement of the watch's gears. In a manual watch, the mainspring is wound by turning a knob, while a self-winding watch uses the natural motion of the wearer's body to wind the mainspring through an oscillating weight and ratcheted winding mechanism.</span><br></pre></td></tr></table></figure><h4 id="信息提取"><a href="#信息提取" class="headerlink" title="信息提取"></a>信息提取</h4><p>LLMs可以从大量文本中识别和提取特定信息。这在新闻和商业情报等领域非常有用。以下模板指导模型提取信息:</p><figure class="highlight plaintext"><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"><Full text></span><br><span class="line">Mention X in the text above: / List X in the text above:</span><br></pre></td></tr></table></figure><p>例如,我们可以要求从以下文本中提取地点名称:</p><figure class="highlight plaintext"><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">The earliest credible evidence for a successful design is the watch made by the Swiss watchmaker Abraham-Louis Perrelet, who lived in Le Locle. In late 1776 or early 1777, he invented a self-winding mechanism for pocket watches using an oscillating weight inside the watch that moved up and down. The Geneva Society of Arts, reporting on this watch in 1777, stated that 15 minutes walking was necessary to fully wind the watch.</span><br><span class="line">List the names of places in the text above:</span><br></pre></td></tr></table></figure><p>这将产生以下输出:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Le Locle, Geneva.</span><br></pre></td></tr></table></figure><h3 id="问答"><a href="#问答" class="headerlink" title="问答"></a>问答</h3><p>LLMs还可以根据提示提供的信息回答问题。以下模板可用于指导LLM回答问题:</p><p>模板</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></pre></td><td class="code"><pre><span class="line">基于下面的上下文回答问题。<其他说明>。如果不确定,请回复“不确定”。</span><br><span class="line">上下文:<Context></span><br><span class="line">Q:<Question></span><br><span class="line">A:</span><br></pre></td></tr></table></figure><p>例如,使用以下提示:</p><p>提示</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></pre></td><td class="code"><pre><span class="line">基于下面的上下文回答问题。用一句话回答。如果不确定,请回复“不确定”。</span><br><span class="line">上下文:自动表也称为自动绕线表或简单地称为自动表,是一种机械表,在佩戴者的自然运动中提供能量来绕制主发条,使手工绕线变得不必要。它与手动表有所区别,因为手动表必须定期手摇其主发条。</span><br><span class="line">Q:自动腕表需要手动绕吗?</span><br><span class="line">A:</span><br></pre></td></tr></table></figure><p>ChatGPT 回应道:</p><p>输出</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">否,如果足够佩戴,则无需手动缠绕自动腕表,因为佩戴者的自然运动会提供能量来维持其正常运转。</span><br></pre></td></tr></table></figure><h4 id="文本分类"><a href="#文本分类" class="headerlink" title="文本分类"></a>文本分类</h4><p>LLMs可以将文本分类为预定义的类别,例如情感分析或垃圾邮件检测。您可以按以下方式格式化文本分类指令:</p><p>模板</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></pre></td><td class="code"><pre><span class="line">将文本分类为<选择列表>:</span><br><span class="line">文本:<完整文本></span><br><span class="line">分类:</span><br></pre></td></tr></table></figure><p>例如,我们可以尝试对一段文字进行情感分析,判断其情绪是积极的、中性的还是消极的:</p><p>提示</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></pre></td><td class="code"><pre><span class="line">将该段文字归类为积极、中性或消极:</span><br><span class="line">文本:那个投篮选择太糟糕了。</span><br><span class="line">分类:</span><br></pre></td></tr></table></figure><p>输出</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">消极</span><br></pre></td></tr></table></figure><h4 id="对话"><a href="#对话" class="headerlink" title="对话"></a>对话</h4><p>LLM可以生成类似人类的对话,使它们在聊天机器人和客户服务应用程序中非常有用。我们可以指示LLM按照特定方式跟随对话行动。</p><p>模板</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></pre></td><td class="code"><pre><span class="line">以下是与<Actor>的对话。 <Actor>应该回答<Further instructions>。</span><br><span class="line">个人:<Conversation></span><br><span class="line"><Actor>:<Response></span><br><span class="line">个人:<Conversation></span><br><span class="line"><Actor>:</span><br></pre></td></tr></table></figure><p>LLM将填写我们指示其类型演员的响应,并且我们可以通过再次将整个会话及新响应传递给它来继续会话。例如:</p><p>提示</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></pre></td><td class="code"><pre><span class="line">以下是与律师的对话。 律师应简单明了地回答。</span><br><span class="line">个人:您好,我想知道如何争议停车罚单</span><br><span class="line">律师:</span><br></pre></td></tr></table></figure><p>输出</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">当然,我能够提供帮助。第一步是检查罚单是否存在任何错误或失误。如果发现任何问题,则可以使用这些作为争议罚款的理由。如果没有错误,则仍然可以通过提供证据支持您的案件(例如照片或证言)来争议罚款。您还可以请求听证会以亲自陈述您的案件。</span><br></pre></td></tr></table></figure><p>提示</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></pre></td><td class="code"><pre><span class="line">以下是与律师的对话。 律师应简单明了地回答。</span><br><span class="line">个人:您好,我想知道如何争议停车罚单</span><br><span class="line">律师:当然,我能够提供帮助。第一步是检查罚单是否存在任何错误或失误。如果发现任何问题,则可以使用这些作为争议罚款的理由。如果没有错误,则仍然可以通过提供证据支持您的案件(例如照片或证言)来争议罚款。您还可以请求听证会以亲自陈述您的案件。</span><br><span class="line">个人:我如何请求听证会?</span><br><span class="line">律师:</span><br></pre></td></tr></table></figure><p>输出</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">要请求听证会,您需要按照票据背面或网站上的说明进行操作</span><br></pre></td></tr></table></figure><h4 id="代码生成"><a href="#代码生成" class="headerlink" title="代码生成"></a>代码生成</h4><p>LLMs可以生成代码,使开发人员更容易创建复杂的软件程序。我们可以编写一个代码注释来描述我们希望代码执行的操作,LLM将能够尽力完成它。我们希望代码执行的操作越复杂,我们在提示注释中需要提供的信息就越具体和详细。</p><p>模板可能因所需生成的编程语言而异,但我建议大致遵循以下模板:</p><p>模板</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></pre></td><td class="code"><pre><span class="line"><您选择的语言的注释符号(/*或'''或#)></span><br><span class="line">方法:<方法名称></span><br><span class="line">语言:<编程语言></span><br><span class="line">说明:<方法必须执行什么操作></span><br><span class="line">参数:<方法将接收哪些参数></span><br><span class="line">上下文:<实现该方法所需进一步信息(URL、数据库等)></span><br><span class="line">输出:<该方法输出内容></span><br><span class="line"><您选择的语言的注释符号(/*或'''或#)></span><br></pre></td></tr></table></figure><p>例如,如果我们想要获取分页API,则可以编写以下评论:</p><p>提示</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></pre></td><td class="code"><pre><span class="line">/* Method: fetchFirst5Pages</span><br><span class="line"> * Language: Javascript</span><br><span class="line"> * Description: Fetch the first 5 pages from this paginated API: http://example.com/pages/?n=x</span><br><span class="line"> * Context: This API accepts the following URL parameters:</span><br><span class="line"> * n: the number of the page to fetch</span><br><span class="line"> * Output: the content of the first 5 pages</span><br><span class="line"> */</span><br></pre></td></tr></table></figure><p>输出</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><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">这是JavaScript中fetchFirst5Pages函数实现示例,假设使用了fetch函数:</span><br><span class="line"></span><br><span class="line">async function fetchFirst5Pages() {</span><br><span class="line"> const baseUrl = 'http://example.com/pages/?n=';</span><br><span class="line"> const pages = [];</span><br><span class="line"></span><br><span class="line"> for (let i = 1; i <= 5; i++) {</span><br><span class="line"> const url = baseUrl + i;</span><br><span class="line"> const response = await fetch(url);</span><br><span class="line"></span><br><span class="line"> if (!response.ok) {</span><br><span class="line"> throw new Error(`Error fetching page ${i}`);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> const pageData = await response.json();</span><br><span class="line"> pages.push(pageData);</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return pages;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">该函数使用循环通过将页码附加到基本URL来获取API的前5个页面。它检查服务器的响应是否正常,如果不正常则抛出错误。然后,它从响应中解析JSON数据并将其添加到页面数组中。最后,它返回页面数组。</span><br></pre></td></tr></table></figure><h4 id="推理"><a href="#推理" class="headerlink" title="推理"></a>推理</h4><p>法学硕士可能会在一些基本任务上遇到困难,比如算术问题。然而,如果我们要求LLM对其回答进行推理,我们可以帮助它得出正确的答案。这是我推荐的模板:</p><p>模板</p><figure class="highlight plaintext"><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><br><span class="line">答: 让我们逐步解决这个问题,确保我们有正确的答案。首先,<说明>。</span><br></pre></td></tr></table></figure><p>在这个例子中,如果没有推理就提出一个简单的算术问题,则无法成功:</p><p>提示</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></pre></td><td class="code"><pre><span class="line">回答以下问题。 如果不确定答案,请回复“不确定”。</span><br><span class="line">问:以下列表中质数之和是3的倍数:[2, 4, 6, 7, 9, 14]</span><br><span class="line">A: [是或否]</span><br></pre></td></tr></table></figure><p>输出</p><p>不是。 列表[2、4、6、7、9、14]中质数之和不是3的倍数。 因此,答案为“否”。</p><p>但是,如果指示LLM对其回答进行推理,则会得到非常不同的结果:</p><p>提示</p><figure class="highlight plaintext"><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">问:以下列表中质数之和是否为3的倍数? [2、4、6、7、9、14]</span><br><span class="line">A: 让我们逐步解决这个问题以确保我们有正确的答案。 首先找到列表中所有质数并将它们加起来。 然后检查所得数字是否可以被3整除。</span><br></pre></td></tr></table></figure><p>输出</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></pre></td><td class="code"><pre><span class="line">好吧,请让我们逐步解决这个问题。</span><br><span class="line"></span><br><span class="line">列表中的质数是2和7。 所以我们可以将它们加在一起:</span><br><span class="line"></span><br><span class="line">2 + 7 = 9</span><br><span class="line"></span><br><span class="line">结果为9。 现在我们需要检查它是否是3的倍数。 我们可以通过检查9除以3的余数是否等于0来做到这一点:</span><br><span class="line"></span><br><span class="line">9%3 = 0</span><br><span class="line"></span><br><span class="line">由于余数为0,因此我们知道9确实是3的倍数。</span><br><span class="line"></span><br><span class="line">因此,答案是“是”,列表[2、4、6、7、9、14]中质数之和是3的倍数。</span><br></pre></td></tr></table></figure><h4 id="策略"><a href="#策略" class="headerlink" title="策略"></a>策略</h4><p>虽然这些模板可以帮助您挤出LLMs的潜力以满足您的需求,但有时它们仍然可能在某些复杂任务上遇到困难。已经开发了一些技术来帮助LLMs得出令人满意的答案。到目前为止,我们只使用了零-shot提示,现在我们将对其进行定义。但是,我们可以使用更复杂的技术。</p><h5 id="零-shot提示"><a href="#零-shot提示" class="headerlink" title="零-shot提示"></a>零-shot提示</h5><p>在本文中的所有示例中,我们都使用了零-shot提示。零-shot提示意味着向LLM传递一个提示,其中只包含我们想要它回答的问题,并没有任何其他情况下应该如何响应的示例。例如,在早期示例中:</p><p>Prompt</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></pre></td><td class="code"><pre><span class="line">将文本分类为积极、中性或消极:</span><br><span class="line">Text: That shot selection was awful</span><br><span class="line">Classification:</span><br></pre></td></tr></table></figure><p>Output</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Negative</span><br></pre></td></tr></table></figure><p>这可能不足以让模型准确地回答我们想要它回答的问题。让我们看看还有哪些替代方案。</p><h5 id="少量样本提示"><a href="#少量样本提示" class="headerlink" title="少量样本提示"></a>少量样本提示</h5><p>与零-shot提示相反,在少量样本提示中,在提问之前给LLM提供一些如何响应的示例。例如,假设我们想像先前示例那样对文本进行情感分类。但是每次都希望以非常特定格式获得答案。我们可以事先给它一些示例,如下所示:</p><p>Prompt</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></pre></td><td class="code"><pre><span class="line">将文本分类为积极、中性或消极:</span><br><span class="line">Text: Today the weather is fantastic</span><br><span class="line">Classification: Pos</span><br><span class="line">Text: The furniture is small.</span><br><span class="line">Classification: Neu</span><br><span class="line">Text: I don't like your attitude</span><br><span class="line">Classification: Neg</span><br><span class="line">Text: That shot selection was awful</span><br><span class="line">Classification:</span><br></pre></td></tr></table></figure><p>Output</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Neg</span><br></pre></td></tr></table></figure><h5 id="思维链提示"><a href="#思维链提示" class="headerlink" title="思维链提示"></a>思维链提示</h5><p>对于更复杂的任务,我们可以将少量示例提示与使模型推理其答案相结合。 我们可以使用之前的相同示例来查看推理和少量提示如何影响结果:</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></pre></td><td class="code"><pre><span class="line">Q: The sum of prime numbers in the following list are a multiple of 3: [2, 4, 6, 7, 9, 14]</span><br><span class="line">A: The prime numbers in the list are 2 and 7. Their sum is 9. 9 % 3 = 0. The answer is yes.</span><br><span class="line">Q: The sum of prime numbers in the following list are a multiple of 3: [1, 2, 4, 7, 8, 10]</span><br><span class="line">A: The prime numbers in the list are 1, 2 and 7. Their sum is 10. 10 % 3 = 1. The answer is no.</span><br><span class="line">Q: The sum of prime numbers in the following list are a multiple of 3: [1, 2, 3, 4]</span><br><span class="line">A: The prime numbers in the list are 1, 2 and 3. Their sum is 6. 6 % 3 = 0. The answer is yes.</span><br><span class="line">Q: The sum of prime numbers in the following list are a multiple of 3: [5, 6, 7, 8]</span><br><span class="line">A:</span><br><span class="line">The prime numbers in the list are 5 and 7. Their sum is 12. 12 % 3 = 0. The answer is yes.</span><br></pre></td></tr></table></figure><h3 id="最后的注释"><a href="#最后的注释" class="headerlink" title="最后的注释"></a>最后的注释</h3><p>LLM已经彻底改变了自然语言处理领域,但为了最大化其潜力,提示工程至关重要。通过理解LLM可以执行的任务、它们的缺点以及各种提示工程策略,开发人员可以利用LLM的能力来创建创新和有效的解决方案。未来可能会开发更多的策略和技术,因此请继续关注这个领域的进展,以进一步最大化LLM潜力。此外,随着LLMs参数数量不断增加到数十亿级别,我们现在甚至无法想象更多任务将有可能被实现。想到使用这些新工具所能实现什么样子以及它们将为我们服务于哪些用例是非常令人兴奋的事情。</p>]]></content>
<summary type="html"><h3 id="LLM介绍"><a href="#LLM介绍" class="headerlink" title="LLM介绍"></a><code>LLM</code>介绍</h3><p>语言模型在最近几年内迅速提高,大型语言模型(LLMs)如GPT-3和GPT-4成为中心。这些模型因其能够以惊人的技巧执行各种任务而变得流行。此外,随着这些模型的参数数量(数十亿!)增加,它们不可预测地获得了新的能力。</p>
<p>在本文中,我们将探讨LLMs、它们可以执行的任务、它们的缺点以及各种提示工程策略。</p></summary>
<category term="LLM" scheme="https://alexhuihui.github.io/categories/LLM/"/>
<category term="LLM" scheme="https://alexhuihui.github.io/tags/LLM/"/>
</entry>
<entry>
<title>【项目】升级现有的授权系统</title>
<link href="https://alexhuihui.github.io/article/20230411.html"/>
<id>https://alexhuihui.github.io/article/20230411.html</id>
<published>2023-04-11T11:45:55.000Z</published>
<updated>2025-01-25T12:51:42.459Z</updated>
<content type="html"><![CDATA[<p>现有的授权系统已无法满足对业务的支持,所以有必要针对授权系统进行一次升级。在升级之前先详细拆解一下之前的授权系统。</p><span id="more"></span> <h3 id="旧的授权系统"><a href="#旧的授权系统" class="headerlink" title="旧的授权系统"></a>旧的授权系统</h3><h4 id="详情"><a href="#详情" class="headerlink" title="详情"></a>详情</h4><p>认证系统负责颁发令牌,系统一共拥有3种类型的用户,分别是普通用户、系统用户和微服务。其中普通用户和系统用户都是共用的同一种类型的<code>token</code>,叫做<code>UserToken</code>,微服务使用的是<code>ClientToken</code>。这两种不同类型的<code>token</code>在传输过程中使用不同的请求头进行存储,当微服务之间互相调用的时候,会有一个<code>feign</code>拦截器负责把当前线程中的用户<code>token</code>和本身的微服务<code>token</code>,存入到对应的请求头中。发送<code>mq</code>时,也是采用了类似的机制进行传递<code>token</code>。尤其要注意的是在只有微服务<code>token</code>时并且<code>CheckUserToken</code>注解不为空时,拦截器会把微服务的信息转换成用户信息存储到安全上下文中,但不会设置用户<code>token</code>。</p><h4 id="流程图"><a href="#流程图" class="headerlink" title="流程图"></a>流程图</h4><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230411141752.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230411141752.png" alt="img"></a></p><h4 id="旧系统的痛点"><a href="#旧系统的痛点" class="headerlink" title="旧系统的痛点"></a>旧系统的痛点</h4><ul><li>无法做到针对每个接口进行访问控制</li><li>没有<code>token</code>续期功能,用户体验不好</li><li>没有能力提供给外部系统与我们进行对接</li></ul><h3 id="新的授权系统"><a href="#新的授权系统" class="headerlink" title="新的授权系统"></a>新的授权系统</h3><h4 id="对每个接口进行访问控制"><a href="#对每个接口进行访问控制" class="headerlink" title="对每个接口进行访问控制"></a>对每个接口进行访问控制</h4><h5 id="授权过程"><a href="#授权过程" class="headerlink" title="授权过程"></a>授权过程</h5><p>在实际的授权过程中,可以在请求到达接口之前,使用拦截器来进行身份验证和授权处理。在拦截器中,首先从请求头中获取<code>Token</code>,如果<code>Token</code>不存在,则返回“未授权”的错误信息。</p><p>如果<code>Token</code>存在,则使用<code>JWT</code>对<code>Token</code>进行解密和验证,如果解密和验证成功,则从<code>Token</code>中获取用户信息和访问权限信息,并将它们存储在<code>ThreadLocal</code>中,以便在整个请求处理过程中可以方便地访问它们。</p><p>最后,在请求处理完成后,清除<code>ThreadLocal</code>中存储的信息,以避免对下一个请求产生干扰。</p><h5 id="流程图-1"><a href="#流程图-1" class="headerlink" title="流程图"></a>流程图</h5><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230414105016.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230414105016.png" alt="img"></a></p><h4 id="Token续期"><a href="#Token续期" class="headerlink" title="Token续期"></a><code>Token</code>续期</h4><h5 id="Token刷新机制"><a href="#Token刷新机制" class="headerlink" title="Token刷新机制"></a><code>Token</code>刷新机制</h5><p><code>JWT Token</code>自动刷新机制需要在前端和后端同时实现。</p><p>在每次请求的返回中都检查 token 是否快过期,如果临近过期就刷新 token,并且在 response 的 header 中返回给前端。前端可以通过读取响应 header 中的 token 来更新本地的 token。</p><p>具体实现可以在后端的拦截器中添加一个逻辑,在每次请求返回时检查 token 是否快过期,如果是,则进行 token 的刷新。同时,将新的 token 放入 response 的 header 中返回给前端。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 伪代码</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TokenRefreshInterceptor</span> <span class="keyword">implements</span> <span class="title">HandlerInterceptor</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> TokenProvider tokenProvider;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">TokenRefreshInterceptor</span><span class="params">(TokenProvider tokenProvider)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.tokenProvider = tokenProvider;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 检查 token 是否需要刷新</span></span><br><span class="line"> String token = tokenProvider.resolveToken(request);</span><br><span class="line"> <span class="keyword">if</span> (token != <span class="keyword">null</span> && tokenProvider.isTokenNeedRefresh(token)) {</span><br><span class="line"> <span class="comment">// 刷新 token</span></span><br><span class="line"> String newToken = tokenProvider.refreshToken(token);</span><br><span class="line"> <span class="comment">// 将新的 token 放入响应 header 中</span></span><br><span class="line"> response.setHeader(<span class="string">"Authorization"</span>, <span class="string">"Bearer "</span> + newToken);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="流程图-2"><a href="#流程图-2" class="headerlink" title="流程图"></a>流程图</h5><p><a href="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230414134806.png"><img src="https://raw.githubusercontent.com/Alexhuihui/photo/main/20230414134806.png" alt="img"></a>c</p>]]></content>
<summary type="html"><p>现有的授权系统已无法满足对业务的支持,所以有必要针对授权系统进行一次升级。在升级之前先详细拆解一下之前的授权系统。</p></summary>
<category term="个人项目" scheme="https://alexhuihui.github.io/categories/%E4%B8%AA%E4%BA%BA%E9%A1%B9%E7%9B%AE/"/>
<category term="授权" scheme="https://alexhuihui.github.io/tags/%E6%8E%88%E6%9D%83/"/>
</entry>
<entry>
<title>【Redis】数据结构和对象</title>
<link href="https://alexhuihui.github.io/article/20230329.html"/>
<id>https://alexhuihui.github.io/article/20230329.html</id>
<published>2023-03-29T11:21:05.000Z</published>
<updated>2025-01-25T12:55:29.691Z</updated>
<content type="html"><![CDATA[<h3 id="开篇"><a href="#开篇" class="headerlink" title="开篇"></a>开篇</h3><p>本文介绍<code>redis</code>中的数据结构和对象</p><span id="more"></span> <h3 id="简单动态字符串"><a href="#简单动态字符串" class="headerlink" title="简单动态字符串"></a>简单动态字符串</h3><p><code>Redis</code>没有使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串的抽象类型,用在可以被修改的字符串值,比如包含字符串值的键值对。</p><h4 id="SDS的定义"><a href="#SDS的定义" class="headerlink" title="SDS的定义"></a>SDS的定义</h4><p>每个 sds.h/sdshdr结构表示一个 SDS值:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> {</span></span><br><span class="line"> <span class="comment">// 记录已使用字节的数量</span></span><br><span class="line"> <span class="keyword">int</span> len;</span><br><span class="line"> <span class="comment">// 记录buf数组中未使用字节的数量</span></span><br><span class="line"> <span class="keyword">int</span> <span class="built_in">free</span>;</span><br><span class="line"> <span class="comment">// 字节数组,用于保存字符串</span></span><br><span class="line"> chat buf[];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="SDS-与-C-字符串的区别"><a href="#SDS-与-C-字符串的区别" class="headerlink" title="SDS 与 C 字符串的区别"></a>SDS 与 C 字符串的区别</h4><p>根据传统,C语言使用长度为N+1的字符数组来表示长度为N的字符串,并且字符数组的最后一个元素总是空字符·\0’。C语言使用的这种简单的字符串表示方式,并不能满足Rdis对字符串在安全性、效率以及功能方面的要求。</p><ul><li>常数复杂度获取字符串长度</li><li>杜绝缓冲区溢出</li><li>减少修改字符串时带来的内存重分配次数</li><li>二进制安全</li><li>兼容部分C字符串函数</li></ul><h3 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h3><p>链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。作为一种常用数据结构,链表内置在很多高级的编程语言里面,因为Rdis使用的C语言并没有内置这种数据结构,所以Redis构建了自己的链表实现。</p><p>使用场景:</p><ul><li>列表键</li><li>发布订阅</li><li>慢查询</li><li>监视器</li><li>服务端保存各个客户端的状态信息</li><li>使用链表来构建客户端输出缓冲区</li></ul><h4 id="链表和链表节点的实现"><a href="#链表和链表节点的实现" class="headerlink" title="链表和链表节点的实现"></a>链表和链表节点的实现</h4><p>每个链表节点使用一个 adlist.h/listNode结构来表示</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> {</span></span><br><span class="line"> <span class="comment">// 前置节点</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> *<span class="title">prev</span>;</span></span><br><span class="line"> <span class="comment">// 后置节点</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> *<span class="title">next</span>;</span></span><br><span class="line"> <span class="comment">// 节点的值</span></span><br><span class="line"> <span class="keyword">void</span> *value;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>虽然仅仅使用多个listNode结构就可以组成链表,但使用adlist.h/list来持有链表的话,操作起来会更方便:</p><figure class="highlight c"><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">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">list</span> {</span></span><br><span class="line"> <span class="comment">// 表头节点</span></span><br><span class="line"> listNode *head;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 表尾结点</span></span><br><span class="line"> listNode *tail;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 链表所包含的节点数量</span></span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> len;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 节点值复制函数</span></span><br><span class="line"> <span class="keyword">void</span> *(*dup) (<span class="keyword">void</span> *ptr);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 节点值释放函数</span></span><br><span class="line"> <span class="keyword">void</span> *(*<span class="built_in">free</span>) (<span class="keyword">void</span> *ptr);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 节点值对比函数</span></span><br><span class="line"> <span class="keyword">int</span> (*match) (<span class="keyword">void</span> *ptr, <span class="keyword">void</span> *key);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Redis的链表实现的特性可以总结如下:</p><ul><li>双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)</li><li>无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。</li><li>带表头指针和表尾指针:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。</li><li>带链表长度计数器:程序使用1ist结构的1en属性来对1ist持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。</li><li>多态:链表节点使用void*指针来保存节点值,并且可以通过ist结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不<br>同类型的值。</li></ul><h3 id="字典"><a href="#字典" class="headerlink" title="字典"></a>字典</h3><p>字典,又称为符号表(symbol table)关联数组(associative array)或映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构。</p>]]></content>
<summary type="html"><h3 id="开篇"><a href="#开篇" class="headerlink" title="开篇"></a>开篇</h3><p>本文介绍<code>redis</code>中的数据结构和对象</p></summary>
<category term="Redis" scheme="https://alexhuihui.github.io/categories/Redis/"/>
<category term="Redis" scheme="https://alexhuihui.github.io/tags/Redis/"/>
</entry>
<entry>
<title>【ElasticSearch】Elasticsearch简介及其发展历史</title>
<link href="https://alexhuihui.github.io/article/20230228.html"/>
<id>https://alexhuihui.github.io/article/20230228.html</id>
<published>2023-02-28T11:54:06.000Z</published>
<updated>2025-01-25T13:05:22.735Z</updated>
<content type="html"><![CDATA[<p>Elasticsearch 是一种开源的搜索引擎,它采用 Lucene 库作为底层搜索引擎,并且通过 RESTful API 提供分布式搜索和分析功能。 Elasticsearch 能够快速地处理大量的结构化和非结构化数据,并提供了全文搜索、聚合分析、地理空间搜索等多种功能。</p><span id="more"></span> <p>Elasticsearch 的发展历史可以追溯到 2010 年,当时 Shay Banon(现在是 Elasticsearch 公司的 CEO)开始开发一种名为 Compass 的搜索引擎。随着时间的推移,Compass 逐渐发展成为 Elasticsearch。Elasticsearch 在开源社区得到了广泛的关注和使用,并且逐渐成为一种非常流行的搜索引擎。</p><p>Elasticsearch 的一个典型应用场景是日志分析。例如,当一个公司的网络设备、服务器和应用程序发生故障时,日志数据通常会被记录下来。使用 Elasticsearch 可以帮助工程师快速地查找故障原因,例如确定哪些设备、服务器或应用程序出现了问题。Elasticsearch 还可以与 Kibana 等工具集成,使用户可以可视化地分析数据。</p><p>以下是 Elasticsearch 的一些主要特点:</p><p>分布式: Elasticsearch 是一种分布式搜索引擎,可以跨多台服务器分布式地存储和处理数据。这使得 Elasticsearch 具有高可用性、容错性和可扩展性。</p><p>实时搜索和分析: Elasticsearch 可以实时地搜索和分析数据,使用户可以及时地获得最新的结果。</p><p>全文搜索: Elasticsearch 可以对全文进行搜索,支持多种语言和复杂查询。</p><p>聚合分析: Elasticsearch 可以对数据进行聚合分析,例如计算平均值、最大值、最小值等等。</p><p>地理空间搜索: Elasticsearch 支持地理空间搜索,使用户可以在地图上搜索位置相关的信息。</p><p>总之,Elasticsearch 是一种功能强大、可靠的搜索引擎,它在搜索、分析和可视化数据方面都有广泛的应用。</p>]]></content>
<summary type="html"><p>Elasticsearch 是一种开源的搜索引擎,它采用 Lucene 库作为底层搜索引擎,并且通过 RESTful API 提供分布式搜索和分析功能。 Elasticsearch 能够快速地处理大量的结构化和非结构化数据,并提供了全文搜索、聚合分析、地理空间搜索等多种功能。</p></summary>
<category term="ElasticSearch" scheme="https://alexhuihui.github.io/categories/ElasticSearch/"/>
<category term="ElasticSearch" scheme="https://alexhuihui.github.io/tags/ElasticSearch/"/>
</entry>
</feed>