-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
522 lines (253 loc) · 241 KB
/
atom.xml
File metadata and controls
522 lines (253 loc) · 241 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Blog|CasonMo-Java后端知识学习经验分享</title>
<link href="https://cason.work/atom.xml" rel="self"/>
<link href="https://cason.work/"/>
<updated>2025-03-27T10:31:42.134Z</updated>
<id>https://cason.work/</id>
<author>
<name>Cason Mo</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>企业微信获客助手回调地址配置</title>
<link href="https://cason.work/2025/03/07/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E8%8E%B7%E5%AE%A2%E5%8A%A9%E6%89%8B%E5%9B%9E%E8%B0%83%E5%9C%B0%E5%9D%80%E9%85%8D%E7%BD%AE/"/>
<id>https://cason.work/2025/03/07/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E8%8E%B7%E5%AE%A2%E5%8A%A9%E6%89%8B%E5%9B%9E%E8%B0%83%E5%9C%B0%E5%9D%80%E9%85%8D%E7%BD%AE/</id>
<published>2025-03-07T07:46:01.000Z</published>
<updated>2025-03-27T10:31:42.134Z</updated>
<content type="html"><![CDATA[<h1 id="企业微信获客助手回调地址配置教程"><a href="#企业微信获客助手回调地址配置教程" class="headerlink" title="企业微信获客助手回调地址配置教程"></a>企业微信获客助手回调地址配置教程</h1><p>企业微信开放平台提供的一种功能,允许企业生成带有唯一标识参数的URL链接,用户点击链接可直接跳转至企业微信的“添加联系人”页面,快速完成客户与员工的连接,可以帮助企业更好地获取客户线索</p><p><strong>先判断是否类目支持获客助手链接打开回调</strong></p><blockquote><p> 仅部分经营类目企业支持,当前企业是否支持可在「高级功能-获客助手」中查看该类目对应的功能是否包含「链接打开回调」。</p></blockquote><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20250310151814579.png" alt="image-20250310151814579"></p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20250310151841561.png" alt="image-20250310151841561"></p><h2 id="一、创建自建应用"><a href="#一、创建自建应用" class="headerlink" title="一、创建自建应用"></a>一、创建自建应用</h2><ol><li><strong>登录企业微信管理后台</strong> :使用企业的管理员账号登录企业微信管理后台,网址为 <a href="https://work.weixin.qq.com/%E3%80%82">https://work.weixin.qq.com/。</a></li><li><strong>进入应用管理</strong> :在管理后台导航栏中,找到并点击 “应用管理” 选项。</li><li><strong>创建自建应用</strong> :点击 “创建应用” 按钮,按照页面提示填写应用名称、logo 和应用简介等信息,完成自建应用的创建。</li></ol><h2 id="二、配置应用回调地址"><a href="#二、配置应用回调地址" class="headerlink" title="二、配置应用回调地址"></a>二、配置应用回调地址</h2><ol><li><p><strong>进入应用详情页面</strong> :在应用管理页面中,找到刚刚创建的自建应用,点击其名称进入应用详情页面,接受消息->设置API接收 </p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20250310151654612.png" alt="image-20250310151654612"></p></li><li><p><strong>接收消息服务器配置</strong></p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20250310151713513.png" alt="image-20250310151713513"></p></li><li><p><strong>配置可调用接口的应用</strong></p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20250310151937301.png" alt="image-20250310151937301"></p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20250310151959153.png" alt="image-20250310151959153"></p></li></ol><h2 id="三、通过接口创建获客链接"><a href="#三、通过接口创建获客链接" class="headerlink" title="三、通过接口创建获客链接"></a>三、通过接口创建获客链接</h2><ol><li><p><strong>获取access_token</strong>相关文档地址:<a href="https://developer.work.weixin.qq.com/document/path/91039">https://developer.work.weixin.qq.com/document/path/91039</a></p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs awk">请求方式: GET(HTTPS)<br>请求地址: https:<span class="hljs-regexp">//</span>qyapi.weixin.qq.com<span class="hljs-regexp">/cgi-bin/g</span>ettoken?corpid=ID&corpsecret=SECRET<br></code></pre></td></tr></table></figure></li><li><p><strong>获取接口文档</strong> :企业微信提供了创建获客链接的接口,相关文档可参考 <a href="https://developer.work.weixin.qq.com/document/path/97297#%E5%88%9B%E5%BB%BA%E8%8E%B7%E5%AE%A2%E9%93%BE%E6%8E%A5">创建获客链接接口文档</a>。</p></li><li><p><strong>调用接口创建获客链接</strong> :使用已配置好回调地址的自建应用的 <code>agentid</code> 和企业的 <code>access_token</code> ,按照接口文档中的要求,调用创建获客链接接口。在接口调用过程中,可以设置获客链接的相关参数,如链接名称等。</p></li><li><p><strong>关联应用与获客链接</strong> :通过接口创建的获客链接会自动与对应的自建应用关联,确保企业微信在用户通过获客链接打开链接/发起申请等事件发生时,能够将相关回调信息发送到企业服务器的指定回调地址。</p></li></ol><blockquote><p><strong>重点:</strong>管理后台直接创建的获客链接并不与特定的应用关联,因此无法接收回调。必须通过自建应用并使用接口来创建获客链接,才能确保回调功能正常。且必须要拼接customer channel参数才能接收到回调。</p></blockquote>]]></content>
<summary type="html"><h1 id="企业微信获客助手回调地址配置教程"><a href="#企业微信获客助手回调地址配置教程" class="headerlink" title="企业微信获客助手回调地址配置教程"></a>企业微信获客助手回调地址配置教程</h1><p>企业微信开放平台提供的一种功</summary>
<category term="广告" scheme="https://cason.work/categories/%E5%B9%BF%E5%91%8A/"/>
<category term="广告" scheme="https://cason.work/tags/%E5%B9%BF%E5%91%8A/"/>
<category term="企业微信" scheme="https://cason.work/tags/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1/"/>
<category term="获客助手" scheme="https://cason.work/tags/%E8%8E%B7%E5%AE%A2%E5%8A%A9%E6%89%8B/"/>
</entry>
<entry>
<title>SpringBoot接入Prometheus+Grafana监控资源</title>
<link href="https://cason.work/2025/02/24/SpringBoot%E6%8E%A5%E5%85%A5Prometheus-Grafana%E7%9B%91%E6%8E%A7%E8%B5%84%E6%BA%90/"/>
<id>https://cason.work/2025/02/24/SpringBoot%E6%8E%A5%E5%85%A5Prometheus-Grafana%E7%9B%91%E6%8E%A7%E8%B5%84%E6%BA%90/</id>
<published>2025-02-24T02:37:35.000Z</published>
<updated>2025-03-10T08:09:11.419Z</updated>
<content type="html"><![CDATA[<h4 id="简单介绍"><a href="#简单介绍" class="headerlink" title="简单介绍"></a>简单介绍</h4><p>Prometheus 是一个开源的监控和报警系统,专为容器化和微服务架构设计。它通过拉取(Pull)模式定期从应用程序或系统的指标端点(通常是 HTTP 接口)抓取时间序列数据。数据以时间戳+指标名称+标签的形式存储,支持高效查询和聚合。Prometheus 使用 PromQL(Prometheus 查询语言)来查询、聚合和展示数据。</p><p>Prometheus 提供强大的告警机制,通过定义规则,在数据满足特定条件时触发告警。告警规则根据预设的时间间隔进行评估,并将告警信息发送到告警管理系统(如 Alertmanager)。</p><p>Prometheus 自带内存存储引擎,支持按时间范围自动删除过期数据。它还支持通过Push Gateway接收推送数据,以及与其他系统集成(如 Grafana)进行可视化展示。总体来说,Prometheus 的设计强调高效、可扩展、灵活,适合动态环境下的实时监控。</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250224150420029.png" alt="img"></p><center><small>(图源自网络)</small></center><h4 id="docker-compose-安装promethus-Grafana"><a href="#docker-compose-安装promethus-Grafana" class="headerlink" title="docker-compose 安装promethus+Grafana"></a>docker-compose 安装promethus+Grafana</h4><blockquote><p>已安装可忽略</p></blockquote><ul><li><p>编辑prometheus配置文件</p><p>/prometheus/prometheus.yml</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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment"># 全局配置</span><br><span class="hljs-attr">global:</span><br> <span class="hljs-comment"># 设置抓取目标的默认时间间隔,表示 Prometheus 每 15 秒抓取一次目标数据</span><br> <span class="hljs-attr">scrape_interval:</span> <span class="hljs-string">15s</span><br> <span class="hljs-comment"># 设置评估规则的默认时间间隔,表示 Prometheus 每 15 秒评估一次告警规则</span><br> <span class="hljs-attr">evaluation_interval:</span> <span class="hljs-string">15s</span><br> <span class="hljs-comment"># 默认抓取超时设置为10秒</span><br> <span class="hljs-comment"># scrape_timeout is set to the global default (10s).</span><br><br><span class="hljs-comment"># 告警配置</span><br><span class="hljs-attr">alerting:</span><br> <span class="hljs-comment"># 配置告警管理器,指定告警接收的目标地址</span><br> <span class="hljs-attr">alertmanagers:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">static_configs:</span><br> <span class="hljs-comment"># 告警接收地址,Prometheus 会将告警信息发送到此地址</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">targets:</span> [<span class="hljs-string">'127.0.0.1:9093'</span>]<br><br><span class="hljs-comment"># 加载规则文件,并根据全局“评估间隔”定期评估规则</span><br><span class="hljs-attr">rule_files:</span><br> <span class="hljs-comment"># 指定规则文件的位置</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"/etc/prometheus/rules.yml"</span><br><br><span class="hljs-comment"># 控制 Prometheus 监控哪些资源</span><br><span class="hljs-comment"># 默认配置中,Prometheus 会配置一个名为 'prometheus' 的作业,该作业用于抓取 Prometheus 服务器本身公开的时间序列数据</span><br><span class="hljs-attr">scrape_configs:</span><br> <span class="hljs-comment"># 作业名称会作为标签“job=<job_name>`添加到此配置中获取的任何数据</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">'prometheus'</span><br> <span class="hljs-comment"># 静态配置,指定 Prometheus 要抓取的目标地址</span><br> <span class="hljs-attr">static_configs:</span><br> <span class="hljs-comment"># 目标地址,Prometheus 会从此地址抓取数据(通常是 Prometheus 自己暴露的指标)</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">targets:</span> [<span class="hljs-string">'localhost:9090'</span>]<br><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">'node'</span><br> <span class="hljs-comment"># 静态配置,指定 Prometheus 要抓取的目标地址</span><br> <span class="hljs-attr">static_configs:</span><br> <span class="hljs-comment"># 目标地址,Prometheus 会从此地址抓取数据(通常是 Node Exporter)</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">targets:</span> [<span class="hljs-string">'localhost:9100'</span>]<br> <span class="hljs-comment"># 为此目标添加标签,用于标识环境和角色</span><br> <span class="hljs-attr">labels:</span><br> <span class="hljs-attr">env:</span> <span class="hljs-string">dev</span><br> <span class="hljs-attr">role:</span> <span class="hljs-string">docker</span><br><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">'prometheusapp'</span><br> <span class="hljs-comment"># 设置自定义的 metrics 路径,告诉 Prometheus 从哪里抓取应用程序的指标数据</span><br> <span class="hljs-attr">metrics_path:</span> <span class="hljs-string">'/actuator/prometheus'</span><br> <span class="hljs-attr">static_configs:</span><br> <span class="hljs-comment"># 目标地址,Prometheus 会从该地址抓取 Prometheus 应用程序的指标数据</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">targets:</span> [<span class="hljs-string">'host.docker.internal:8080'</span>]<br><br></code></pre></td></tr></table></figure></li><li><p>编辑告警规则文件</p><p>/prometheus/rules.yml</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><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><code class="hljs yaml"><span class="hljs-attr">groups:</span><br><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">example</span><br> <span class="hljs-attr">rules:</span><br> <span class="hljs-comment"># Alert for any instance that is unreachable for >5 minutes.</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">alert:</span> <span class="hljs-string">InstanceDown</span><br> <span class="hljs-attr">expr:</span> <span class="hljs-string">up</span> <span class="hljs-string">==</span> <span class="hljs-number">0</span><br> <span class="hljs-attr">for:</span> <span class="hljs-string">1m</span><br> <span class="hljs-attr">labels:</span><br> <span class="hljs-attr">serverity:</span> <span class="hljs-string">page</span><br> <span class="hljs-attr">annotations:</span><br> <span class="hljs-attr">summary:</span> <span class="hljs-string">"Instance <span class="hljs-template-variable">{{ $labels.instance }}</span> down"</span><br> <span class="hljs-attr">description:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ $labels.instance }}</span> of job <span class="hljs-template-variable">{{ $labels.job }}</span> has been down for more than 5 minutes."</span><br></code></pre></td></tr></table></figure></li><li><p>编辑告警配置文件</p><p>/alertmanager/alertmanager.yml</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><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><code class="hljs yaml"><span class="hljs-attr">global:</span><br> <span class="hljs-attr">resolve_timeout:</span> <span class="hljs-string">5m</span><br> <span class="hljs-attr">smtp_smarthost:</span> <span class="hljs-string">'xxx@xxx:587'</span><br> <span class="hljs-attr">smtp_from:</span> <span class="hljs-string">'zhaoysz@xxx'</span><br> <span class="hljs-attr">smtp_auth_username:</span> <span class="hljs-string">'xxx@xxx'</span><br> <span class="hljs-attr">smtp_auth_password:</span> <span class="hljs-string">'xxxx'</span><br> <span class="hljs-attr">smtp_require_tls:</span> <span class="hljs-literal">true</span><br><span class="hljs-attr">route:</span><br> <span class="hljs-attr">group_by:</span> [<span class="hljs-string">'alertname'</span>]<br> <span class="hljs-attr">group_wait:</span> <span class="hljs-string">10s</span><br> <span class="hljs-attr">group_interval:</span> <span class="hljs-string">10s</span><br> <span class="hljs-attr">repeat_interval:</span> <span class="hljs-string">1h</span><br> <span class="hljs-attr">receiver:</span> <span class="hljs-string">'test-mails'</span><br><span class="hljs-attr">receivers:</span><br><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'test-mails'</span><br> <span class="hljs-attr">email_configs:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">to:</span> <span class="hljs-string">'xxxxxx@qq.com'</span><br></code></pre></td></tr></table></figure></li><li><p>编辑docker-compose</p><p>docker-compose.yml</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><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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.8'</span><br><br><span class="hljs-attr">services:</span><br> <span class="hljs-attr">prometheus:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">prom/prometheus</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-number">9090</span><span class="hljs-string">:9090</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">/prometheus/:/etc/prometheus/</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">prometheus_data:/prometheus</span><br> <span class="hljs-attr">command:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">'--config.file=/etc/prometheus/prometheus.yml'</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">'--storage.tsdb.path=/prometheus'</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">'--web.external-url=http://prometheus:9090/'</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">'--web.enable-lifecycle'</span><br> <span class="hljs-attr">networks:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">monitor_net</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span><br><br> <span class="hljs-attr">alertmanager:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">prom/alertmanager</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">/alertmanager/:/etc/alertmanager/</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">alertmanager_data:/alertmanager</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-number">9093</span><span class="hljs-string">:9093</span><br> <span class="hljs-attr">command:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">'--config.file=/etc/alertmanager/alertmanager.yml'</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">'--storage.path=/alertmanager'</span><br> <span class="hljs-attr">networks:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">monitor_net</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span><br><br> <span class="hljs-attr">grafana:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/grafana</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-number">3000</span><span class="hljs-string">:3000</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">/grafana/:/etc/grafana/provisioning/</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">grafana_data:/var/lib/grafana</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">GF_INSTALL_PLUGINS=camptocamp-prometheus-alertmanager-datasource</span><br> <span class="hljs-attr">depends_on:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">prometheus</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">alertmanager</span><br> <span class="hljs-attr">networks:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">monitor_net</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span><br><br><span class="hljs-attr">volumes:</span><br> <span class="hljs-attr">prometheus_data:</span> {}<br> <span class="hljs-attr">grafana_data:</span> {}<br> <span class="hljs-attr">alertmanager_data:</span> {}<br><br><span class="hljs-attr">networks:</span><br> <span class="hljs-attr">monitor_net:</span><br> <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span><br></code></pre></td></tr></table></figure></li><li><p>启动composer</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">docker-compose -f docker-compose.yml up -d<br></code></pre></td></tr></table></figure><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250224150433800.png" alt="image-20250224113243071"></p><blockquote><p>关闭命令:docker-compose -f docker-compose.yml down</p></blockquote></li><li><p>访问端点<br><a href="http://localhost:9090/">http://localhost:9090</a> Prometheus server主页<br><a href="http://localhost:9090/metrics">http://localhost:9090/metrics</a> Prometheus server自身指标<br><a href="http://localhost:3000/">http://localhost:3000</a> Grafana</p></li><li><p>配置Grafana</p><blockquote><p>访问<a href="http://localhost:3000/%EF%BC%8C%E5%88%9D%E5%A7%8B%E7%99%BB%E5%BD%95%E8%B4%A6%E5%8F%B7/%E5%AF%86%E7%A0%81%EF%BC%9A">http://localhost:3000/,初始登录账号/密码:</a> admin/admin</p></blockquote><p>创建Prometheus数据源<br>左侧菜单Connection -> Data sources -> Add data source<br>选择“ Prometheus”作为类型。<br>设置适当的Prometheus服务器网址(例如,<a href="http://prometheus:9090/%EF%BC%89">http://prometheus:9090/)</a><br><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250224150438814.png" alt="image-20250224105335976"><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250224150500328.png" alt="image-20250224105319683"></p></li></ul><h3 id="Springboot集成promethus"><a href="#Springboot集成promethus" class="headerlink" title="Springboot集成promethus"></a>Springboot集成promethus</h3><ul><li><p>pom.xml 添加以下dependency</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-comment"><!-- Spring Boot Actuator --></span><br><span class="hljs-tag"><<span class="hljs-name">dependency</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-actuator<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">version</span>></span>2.7.15<span class="hljs-tag"></<span class="hljs-name">version</span>></span><br><span class="hljs-tag"></<span class="hljs-name">dependency</span>></span><br><span class="hljs-comment"><!-- Micrometer Prometheus Registry --></span><br><span class="hljs-tag"><<span class="hljs-name">dependency</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>io.micrometer<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>micrometer-registry-prometheus<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">version</span>></span>1.9.14<span class="hljs-tag"></<span class="hljs-name">version</span>></span><br><span class="hljs-tag"></<span class="hljs-name">dependency</span>></span><br></code></pre></td></tr></table></figure></li><li><p>application.yml</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><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><code class="hljs yaml"><span class="hljs-attr">management:</span><br> <span class="hljs-comment"># 配置 Spring Boot 的管理端点(如健康检查、指标等)</span><br> <span class="hljs-attr">endpoints:</span><br> <span class="hljs-comment"># 配置管理端点的 web 相关暴露设置</span><br> <span class="hljs-attr">web:</span><br> <span class="hljs-comment"># 设置暴露的端点,'include: *' 表示暴露所有管理端点</span><br> <span class="hljs-attr">exposure:</span><br> <span class="hljs-attr">include:</span> <span class="hljs-string">'*'</span><br> <br> <span class="hljs-comment"># 配置 Spring Boot 的指标收集和导出设置</span><br> <span class="hljs-attr">metrics:</span><br> <span class="hljs-comment"># 配置 Prometheus 导出设置</span><br> <span class="hljs-attr">export:</span><br> <span class="hljs-attr">prometheus:</span><br> <span class="hljs-comment"># 启用 Prometheus 数据导出功能,允许 Prometheus 从 Spring Boot 应用中抓取指标</span><br> <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span><br> <br> <span class="hljs-comment"># 设置 Spring Boot 应用的标签信息</span><br> <span class="hljs-attr">tags:</span><br> <span class="hljs-comment"># 为导出的 Prometheus 数据添加标签,这里使用应用程序的名称作为标签值</span><br> <span class="hljs-attr">application:</span> <span class="hljs-string">'${spring.application.name}'</span><br></code></pre></td></tr></table></figure></li><li><p>启动Springboot应用</p><p>访问<a href="http://localhost:8080/actuator/prometheus">http://localhost:8080/actuator/prometheus</a></p><p><img src="https://raw.githubusercontent.com/CasonMo/pic-go/master/img/20250224150442343.png" alt="image-20250224105134383"></p></li><li><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Resource</span><br>MeterRegistry meterRegistry;<br><br><span class="hljs-meta">@RequestMapping(value = "/", method = RequestMethod.GET)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">hello</span><span class="hljs-params">()</span> {<br> meterRegistry.counter(<span class="hljs-string">"requests_count"</span>).increment();<br>}<br></code></pre></td></tr></table></figure></li><li><p>监控指标+制作Grafana看板</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250224150445272.png" alt="image-20250224105241055"><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250224150448905.png" alt="image-20250224105303039"></p></li></ul>]]></content>
<summary type="html"><h4 id="简单介绍"><a href="#简单介绍" class="headerlink" title="简单介绍"></a>简单介绍</h4><p>Prometheus 是一个开源的监控和报警系统,专为容器化和微服务架构设计。它通过拉取(Pull)模式定期从应用程序或系统</summary>
<category term="Springboot" scheme="https://cason.work/categories/Springboot/"/>
<category term="promethus" scheme="https://cason.work/tags/promethus/"/>
<category term="Grafana" scheme="https://cason.work/tags/Grafana/"/>
</entry>
<entry>
<title>Logback-异常日志企业微信机器人通知</title>
<link href="https://cason.work/2025/02/11/Logback-%E5%BC%82%E5%B8%B8%E6%97%A5%E5%BF%97%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%80%9A%E7%9F%A5/"/>
<id>https://cason.work/2025/02/11/Logback-%E5%BC%82%E5%B8%B8%E6%97%A5%E5%BF%97%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%80%9A%E7%9F%A5/</id>
<published>2025-02-11T01:58:17.000Z</published>
<updated>2025-03-10T08:09:11.419Z</updated>
<content type="html"><![CDATA[<p>在生产环境中,异常日志往往涉及系统故障、接口调用失败或关键业务流程异常,必须及时被相关人员关注并处理。将异常日志实时推送到企业微信,可以确保运维团队和开发人员第一时间收到警报,避免因日志沉淀在服务器而错过关键问题。相比于传统的邮件或短信告警,企业微信具备更高的即时性,并支持群聊讨论,提高协作效率。此外,企业微信的 Webhook 机制使得日志推送简单灵活,能够根据日志级别、异常类型等条件筛选重要日志推送,减少无效告警,提高告警的精准度和响应速度。</p><blockquote><p>如果需要告警钉钉调整机器人接口即可</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WecomLogAlertAppender</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AppenderBase</span><ILoggingEvent> {<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 抛弃策略的线程池</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">ThreadPoolExecutor</span> <span class="hljs-variable">THREAD_POOL_EXECUTOR</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ThreadPoolExecutor</span>(<span class="hljs-number">2</span>, <span class="hljs-number">8</span>, <span class="hljs-number">60_000</span>, TimeUnit.MILLISECONDS<br> , <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedBlockingQueue</span><>(<span class="hljs-number">1000</span>), (Runnable r) -> {<br> <span class="hljs-type">Thread</span> <span class="hljs-variable">thread</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(r);<br> thread.setName(<span class="hljs-string">"WeChatAppender-"</span> + thread.getId());<br> <span class="hljs-keyword">return</span> thread;<br> }, <span class="hljs-keyword">new</span> <span class="hljs-title class_">ThreadPoolExecutor</span>.AbortPolicy());<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 初始化OkHttp客户端</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">OkHttpClient</span> <span class="hljs-variable">client</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">OkHttpClient</span>.Builder()<br> .connectionPool(<span class="hljs-keyword">new</span> <span class="hljs-title class_">ConnectionPool</span>(<span class="hljs-number">5</span>, <span class="hljs-number">5</span>, TimeUnit.MINUTES))<br> .connectTimeout(<span class="hljs-number">5</span>, TimeUnit.SECONDS)<br> .writeTimeout(<span class="hljs-number">5</span>, TimeUnit.SECONDS)<br> .readTimeout(<span class="hljs-number">10</span>, TimeUnit.SECONDS)<br> .retryOnConnectionFailure(<span class="hljs-literal">true</span>)<br> .build();<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Environment environment;<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">ObjectMapper</span> <span class="hljs-variable">mapper</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ObjectMapper</span>();<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">webhookUrl</span> <span class="hljs-operator">=</span> <span class="hljs-string">"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key="</span>;<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 获取需要推送的日志级别</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Set<Level> <span class="hljs-title function_">getLogLevels</span><span class="hljs-params">()</span> {<br> <span class="hljs-type">String</span> <span class="hljs-variable">levelConfig</span> <span class="hljs-operator">=</span> Optional.ofNullable(getEnvironment()).map(env -> env.getProperty(<span class="hljs-string">"whisper.log.levels"</span>)).orElse(<span class="hljs-string">"ERROR"</span>);<br> <span class="hljs-keyword">return</span> Arrays.stream(levelConfig.split(<span class="hljs-string">","</span>))<br> .map(String::trim)<br> .map(String::toUpperCase)<br> .map(Level::toLevel)<br> .collect(Collectors.toSet());<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 双重检查锁定模式获取environment</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Environment <span class="hljs-title function_">getEnvironment</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">if</span> (environment == <span class="hljs-literal">null</span>) {<br> <span class="hljs-keyword">synchronized</span> (WecomLogAlertAppender.class) {<br> <span class="hljs-keyword">if</span> (environment == <span class="hljs-literal">null</span>) {<br> environment = SpringContextUtil.getBean(Environment.class);<br> }<br> }<br> }<br> <span class="hljs-keyword">return</span> environment;<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 获取企业微信机器人key</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">getRobotKey</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">if</span> (Objects.isNull(getEnvironment())) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br> }<br> <span class="hljs-keyword">return</span> getEnvironment().getProperty(<span class="hljs-string">"whisper.robotKey"</span>);<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 获取是否启用播报企业微信</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">getEnable</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">if</span> (Objects.isNull(getEnvironment())) {<br> <span class="hljs-keyword">return</span> Boolean.FALSE;<br> }<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">return</span> Boolean.parseBoolean(getEnvironment().getProperty(<span class="hljs-string">"whisper.enable"</span>));<br> } <span class="hljs-keyword">catch</span> (Exception e) {<br> <span class="hljs-keyword">return</span> Boolean.FALSE;<br> }<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 获取企业微信机器人key</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span></span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">getProfiles</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">if</span> (Objects.isNull(getEnvironment())) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br> }<br> <span class="hljs-keyword">return</span> getEnvironment().getProperty(<span class="hljs-string">"spring.profiles.active"</span>);<br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">append</span><span class="hljs-params">(ILoggingEvent event)</span> {<br> <span class="hljs-keyword">if</span> (!getEnable()) {<br> <span class="hljs-keyword">return</span>;<br> }<br> <span class="hljs-keyword">if</span> (StringUtils.isBlank(getRobotKey())) {<br> <span class="hljs-keyword">return</span>;<br> }<br> <span class="hljs-comment">// 仅发送配置的日志级别</span><br> <span class="hljs-keyword">if</span> (!getLogLevels().contains(event.getLevel())) {<br> <span class="hljs-keyword">return</span>;<br> }<br> THREAD_POOL_EXECUTOR.execute(() -> {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> mapper.writeValueAsString(createMessage(event));<br> <span class="hljs-type">RequestBody</span> <span class="hljs-variable">body</span> <span class="hljs-operator">=</span> RequestBody.create(json, MediaType.get(<span class="hljs-string">"application/json; charset=utf-8"</span>));<br> <span class="hljs-type">Request</span> <span class="hljs-variable">request</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Request</span>.Builder()<br> .url(webhookUrl + getRobotKey())<br> .post(body)<br> .build();<br> <span class="hljs-keyword">try</span> (<span class="hljs-type">Response</span> <span class="hljs-variable">response</span> <span class="hljs-operator">=</span> client.newCall(request).execute()) {<br> <span class="hljs-keyword">if</span> (!response.isSuccessful()) {<br> addError(<span class="hljs-string">"Failed to send log message to WeChat:"</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Exception</span>(response.body().string()));<br> }<br> }<br> } <span class="hljs-keyword">catch</span> (JsonProcessingException e) {<br> addError(<span class="hljs-string">"Failed to convert log message to JSON"</span>, e);<br> } <span class="hljs-keyword">catch</span> (Exception e) {<br> addError(<span class="hljs-string">"Failed to send log message to WeChat"</span>, e);<br> }<br> });<br> }<br><br> <span class="hljs-keyword">private</span> Map<String, Object> <span class="hljs-title function_">createMessage</span><span class="hljs-params">(ILoggingEvent event)</span> {<br> <span class="hljs-type">SimpleDateFormat</span> <span class="hljs-variable">sdf</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">SimpleDateFormat</span>(<span class="hljs-string">"yyyy-MM-dd HH:mm:ss"</span>);<br> <span class="hljs-type">String</span> <span class="hljs-variable">exceptionMessage</span> <span class="hljs-operator">=</span> getExceptionMessage(event);<br><br> <span class="hljs-comment">// 根据日志级别设置颜色</span><br> String color;<br> <span class="hljs-keyword">if</span> (event.getLevel() == Level.ERROR) {<br> color = <span class="hljs-string">"#F24956"</span>; <span class="hljs-comment">// 红色</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.getLevel() == Level.WARN) {<br> color = <span class="hljs-string">"#F2AA27"</span>; <span class="hljs-comment">// 黄色</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.getLevel() == Level.INFO) {<br> color = <span class="hljs-string">"#000000"</span>; <span class="hljs-comment">// 黑色</span><br> } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// DEBUG 及其他</span><br> color = <span class="hljs-string">"#A0A0A0"</span>; <span class="hljs-comment">// 浅灰色</span><br> }<br><br> <span class="hljs-type">String</span> <span class="hljs-variable">content</span> <span class="hljs-operator">=</span> String.format(<span class="hljs-string">"[%s] <font color=\"%s\">**%s**</font> %s\n> %s \n%s"</span>,<br> getProfiles(), color, event.getLevel(), sdf.format(event.getTimeStamp()),<br> event.getFormattedMessage(),<br> exceptionMessage.substring(<span class="hljs-number">0</span>, Math.min(exceptionMessage.length(), <span class="hljs-number">1000</span>)));<br><br> Map<String, Object> message = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>();<br> message.put(<span class="hljs-string">"msgtype"</span>, <span class="hljs-string">"markdown"</span>);<br> Map<String, String> markdown = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>();<br> markdown.put(<span class="hljs-string">"content"</span>, content.substring(<span class="hljs-number">0</span>, Math.min(content.length(), <span class="hljs-number">4095</span>)));<br> message.put(<span class="hljs-string">"markdown"</span>, markdown);<br><br> <span class="hljs-keyword">return</span> message;<br> }<br><br> <span class="hljs-keyword">private</span> String <span class="hljs-title function_">getExceptionMessage</span><span class="hljs-params">(ILoggingEvent event)</span> {<br> <span class="hljs-type">IThrowableProxy</span> <span class="hljs-variable">throwableProxy</span> <span class="hljs-operator">=</span> event.getThrowableProxy();<br> <span class="hljs-keyword">if</span> (throwableProxy != <span class="hljs-literal">null</span>) {<br> <span class="hljs-type">StringBuilder</span> <span class="hljs-variable">sb</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringBuilder</span>();<br> sb.append(throwableProxy.getClassName()).append(<span class="hljs-string">": "</span>).append(throwableProxy.getMessage()).append(<span class="hljs-string">"\n"</span>);<br> <span class="hljs-keyword">for</span> (StackTraceElementProxy element : throwableProxy.getStackTraceElementProxyArray()) {<br> sb.append(element.toString()).append(<span class="hljs-string">"\n"</span>);<br> }<br> <span class="hljs-keyword">return</span> sb.toString();<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SpringContextUtil</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">ApplicationContextAware</span> {<br><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ApplicationContext applicationContext;<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 通过bean名称获取bean</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> beanName bean的名称</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> <T> bean的类型</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 获取到的bean</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <T> T <span class="hljs-title function_">getBean</span><span class="hljs-params">(String beanName, Class<T> beanType)</span> {<br> <span class="hljs-keyword">return</span> applicationContext.getBean(beanName, beanType);<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 通过bean名称获取bean</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> beanName bean的名称</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 获取到的bean</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title function_">getBean</span><span class="hljs-params">(String beanName)</span> {<br> <span class="hljs-keyword">return</span> applicationContext.getBean(beanName);<br> }<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 通过bean类型获取bean</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> beanType bean的类型</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> <T> bean的类型</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> 获取到的bean</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <T> T <span class="hljs-title function_">getBean</span><span class="hljs-params">(Class<T> beanType)</span> {<br> <span class="hljs-keyword">return</span> applicationContext.getBean(beanType);<br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setApplicationContext</span><span class="hljs-params">(ApplicationContext applicationContext)</span> {<br> SpringContextUtil.applicationContext = applicationContext;<br> }<br>}<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LogbackConfigurer</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">ApplicationListener</span><ContextRefreshedEvent> {<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onApplicationEvent</span><span class="hljs-params">(ContextRefreshedEvent event)</span> {<br> <span class="hljs-type">LoggerContext</span> <span class="hljs-variable">context</span> <span class="hljs-operator">=</span> (LoggerContext) LoggerFactory.getILoggerFactory();<br> <span class="hljs-type">WecomLogAlertAppender</span> <span class="hljs-variable">wecomAppender</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">WecomLogAlertAppender</span>();<br> wecomAppender.start();<br> <span class="hljs-type">Logger</span> <span class="hljs-variable">rootLogger</span> <span class="hljs-operator">=</span> context.getLogger(<span class="hljs-string">"ROOT"</span>);<br> rootLogger.addAppender(wecomAppender);<br> }<br><br>}<br></code></pre></td></tr></table></figure><h4 id="声明式配置"><a href="#声明式配置" class="headerlink" title="声明式配置"></a>声明式配置</h4><p>logback.xml</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs XML"><span class="hljs-tag"><<span class="hljs-name">appender</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"WECOM"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"com.****.WecomLogAlertAppender"</span>></span><span class="hljs-tag"></<span class="hljs-name">appender</span>></span><br><br><span class="hljs-tag"><<span class="hljs-name">root</span> <span class="hljs-attr">level</span>=<span class="hljs-string">"INFO"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">appender-ref</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"WECOM"</span>/></span><br><span class="hljs-tag"></<span class="hljs-name">root</span>></span><br></code></pre></td></tr></table></figure><h4 id="简单使用"><a href="#简单使用" class="headerlink" title="简单使用"></a>简单使用</h4><p>application.properites</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></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-comment">#企业微信日志告警配置</span><br><span class="hljs-attr">whisper.robotKey</span>=<span class="hljs-number">13</span>xx18b-xxxx-<span class="hljs-number">0</span>ecaxxxee36<br><span class="hljs-attr">whisper.enable</span>=<span class="hljs-literal">true</span><br><span class="hljs-attr">whisper.log.levels</span>=WARN,ERROR<br></code></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>在生产环境中,异常日志往往涉及系统故障、接口调用失败或关键业务流程异常,必须及时被相关人员关注并处理。将异常日志实时推送到企业微信,可以确保运维团队和开发人员第一时间收到警报,避免因日志沉淀在服务器而错过关键问题。相比于传统的邮件或短信告警,企业微信具备更高的即时性,并支持</summary>
<category term="Springboot" scheme="https://cason.work/categories/Springboot/"/>
<category term="log" scheme="https://cason.work/tags/log/"/>
</entry>
<entry>
<title>广告数据同步系统的限流失败重试策略</title>
<link href="https://cason.work/2025/02/07/%E5%B9%BF%E5%91%8A%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5%E7%B3%BB%E7%BB%9F%E7%9A%84%E9%99%90%E6%B5%81%E5%A4%B1%E8%B4%A5%E9%87%8D%E8%AF%95%E7%AD%96%E7%95%A5/"/>
<id>https://cason.work/2025/02/07/%E5%B9%BF%E5%91%8A%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5%E7%B3%BB%E7%BB%9F%E7%9A%84%E9%99%90%E6%B5%81%E5%A4%B1%E8%B4%A5%E9%87%8D%E8%AF%95%E7%AD%96%E7%95%A5/</id>
<published>2025-02-07T10:38:48.000Z</published>
<updated>2025-03-10T08:09:11.420Z</updated>
<content type="html"><![CDATA[<h1 id="广告数据同步系统的失败重试策略"><a href="#广告数据同步系统的失败重试策略" class="headerlink" title="广告数据同步系统的失败重试策略"></a><strong>广告数据同步系统的失败重试策略</strong></h1><p>在与外部广告 API(如 Facebook Marketing API)交互时,可能会遇到超时、限流等问题。为了保证数据同步的可靠性,我们可以采用以下方法来优化失败重试机制。</p><h2 id="1-持久化上下文"><a href="#1-持久化上下文" class="headerlink" title="1. 持久化上下文"></a><strong>1. 持久化上下文</strong></h2><p><strong>持久化任务的执行状态</strong></p><ul><li>使用数据库记录任务进度<ul><li>任务唯一标识(广告账户ID+日期/时间+任务类型)</li><li>任务执行状态(成功 / 进行中 / 失败 / 待处理 / 待重试 / 暂时失败)</li></ul></li><li>数据库持久化任务历史<ul><li>适用于长期存储,可用于任务回溯和统计分析</li></ul></li><li>清理机制<ul><li>定期清除旧的或已完成的任务记录,避免历史数据占用过多存储空间</li></ul></li></ul><p>为了提高数据同步效率,避免因同步任务粒度过大导致的性能瓶颈,我们将每个同步任务拆解至账户+日期+任务类型维度。每个任务只关注单一账户的数据,减少层级依赖,简化任务调度和失败处理的复杂度。这样即使某个账户的任务失败,也不会影响其他账户的任务,从而提高了系统的并发能力和容错能力。</p><p>此外,通过这种方式,可以快速定位失败的账户,并在失败时通过任务状态标记,将失败的账户快速捞出来进行重试,避免因任务失败导致整个数据同步流程中断,从而减少了重复请求,提升了效率。</p><h2 id="2-消息队列"><a href="#2-消息队列" class="headerlink" title="2. 消息队列"></a><strong>2. 消息队列</strong></h2><p>使用消息队列实现异步任务处理,确保失败任务能够自动重试。</p><ul><li><strong>延迟队列</strong>:<ul><li>当 API 超时或被限流时,将失败任务放入延迟队列,稍后自动重试</li><li>适用于短期失败(例如 API 速率限制)</li></ul></li><li><strong>死信队列(DLQ)</strong>:<ul><li>任务多次重试仍然失败时,进入死信队列,人工或异步修正后再处理</li><li>避免失败任务无限重试影响系统健康</li></ul></li></ul><h2 id="3-指数退避重试"><a href="#3-指数退避重试" class="headerlink" title="3. 指数退避重试"></a><strong>3. 指数退避重试</strong></h2><p>避免对 API 进行无效的高频请求,导致进一步的限流。</p><ul><li><strong>重试时间呈指数增长(如 1s -> 2s -> 4s -> 8s)</strong></li><li><strong>设定最大重试次数,避免无限循环</strong></li><li><strong>结合随机抖动,防止多个请求同时重试导致雪崩效应</strong></li></ul><h2 id="4-分页-并发优化"><a href="#4-分页-并发优化" class="headerlink" title="4. 分页 + 并发优化"></a><strong>4. 分页 + 并发优化</strong></h2><p>API 查询时,合理设计分页策略,减少单次请求的超时风险,并提高吞吐量。</p><ul><li><strong>分页获取数据</strong>:<ul><li>限制每次请求的数据量,减少超时风险</li><li>API 可能提供 <code>limit</code> 和 <code>offset</code> 参数来分页</li></ul></li><li><strong>并发请求优化</strong>:<ul><li>控制同时执行的任务数量,避免 API 负载过高</li><li>根据 API 速率限制动态调整并发度</li></ul></li></ul><h2 id="5-分散任务执行时间"><a href="#5-分散任务执行时间" class="headerlink" title="5. 分散任务执行时间"></a><strong>5. 分散任务执行时间</strong></h2><p>为了避免多任务集中请求导致 API 限流,可以将任务执行的时间进行分散处理。</p><ul><li>定时任务拆分<ul><li>将任务划分为多个时间点执行,避免所有任务在同一时间同时执行</li><li>根据任务的数量与优先级,合理分配执行时间,降低API接口瞬时压力</li></ul></li><li>时间错峰<ul><li>利用时间窗口来平衡任务的执行量,避免短时间内的请求洪峰</li></ul></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a><strong>总结</strong></h2><p>为了优化广告数据同步系统的失败重试机制,我们可以采用: </p><p>✅ <strong>持久化上下文</strong> 记录任务进度,避免重复拉取<br>✅ <strong>消息队列</strong> 进行异步重试,避免阻塞<br>✅ <strong>指数退避</strong> 避免高频失败<br>✅ <strong>分页 + 并发优化</strong> 提高 API 调用效率<br>✅ <strong>分散任务执行时间</strong> 防止流量峰值,降低 API 限流风险</p><p>这种优化方案能够有效提高数据同步的稳定性,提升系统的吞吐量,同时降低失败重试带来的额外开销,使广告数据同步更加高效可靠。</p>]]></content>
<summary type="html"><h1 id="广告数据同步系统的失败重试策略"><a href="#广告数据同步系统的失败重试策略" class="headerlink" title="广告数据同步系统的失败重试策略"></a><strong>广告数据同步系统的失败重试策略</strong></h1><p>在</summary>
<category term="广告" scheme="https://cason.work/categories/%E5%B9%BF%E5%91%8A/"/>
<category term="广告" scheme="https://cason.work/tags/%E5%B9%BF%E5%91%8A/"/>
</entry>
<entry>
<title>巨量星图-点击监测和数据回传</title>
<link href="https://cason.work/2025/02/07/%E5%B7%A8%E9%87%8F%E6%98%9F%E5%9B%BE-%E7%82%B9%E5%87%BB%E7%9B%91%E6%B5%8B%E5%92%8C%E6%95%B0%E6%8D%AE%E5%9B%9E%E4%BC%A0/"/>
<id>https://cason.work/2025/02/07/%E5%B7%A8%E9%87%8F%E6%98%9F%E5%9B%BE-%E7%82%B9%E5%87%BB%E7%9B%91%E6%B5%8B%E5%92%8C%E6%95%B0%E6%8D%AE%E5%9B%9E%E4%BC%A0/</id>
<published>2025-02-07T02:17:14.000Z</published>
<updated>2025-03-10T08:09:11.420Z</updated>
<content type="html"><![CDATA[<p>巨量星图后台:<a href="https://www.xingtu.cn/ad/creator/index">https://www.xingtu.cn/ad/creator/index</a></p><p>星图监测联调工具使用说明:<a href="https://www.xingtu.cn/help-center/demander/121314">https://www.xingtu.cn/help-center/demander/121314</a></p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250207110837781.png" alt="image-20250207110834573"></p><h4 id="点击监测"><a href="#点击监测" class="headerlink" title="点击监测"></a>点击监测</h4><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250207113044484.png" alt="diagram-6457048858840653735"></p><ul><li><p>指派类、投稿和招募非转化/付费分佣类任务 点击监测链接举例</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc">https://域名/路径?os=<span class="hljs-emphasis">__OS__</span>&TIMESTAMP=<span class="hljs-emphasis">__TS__</span>&model=<span class="hljs-emphasis">__MODEL__</span>&ua=<span class="hljs-emphasis">__UA__</span>&ip=<span class="hljs-emphasis">__IP__</span>&callback=<span class="hljs-emphasis">__CALLBACK_PARAM__</span>&callbackUrl=<span class="hljs-emphasis">__CALLBACK_URL__</span>&itemId=<span class="hljs-emphasis">__ITEM_ID__</span>&demandId=<span class="hljs-emphasis">__DEMAND_ID__</span>&awemeAuthorId=<span class="hljs-emphasis">__AWEME_AUTHOR_ID__</span><br></code></pre></td></tr></table></figure><blockquote><p> 更多具体宏参数参考:<a href="https://www.xingtu.cn/help-center/demander/109173">https://www.xingtu.cn/help-center/demander/109173</a></p></blockquote></li><li><p>投稿和招募按转化、付费分佣结算任务 点击监测链接举例</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc">https://域名/路径?os=<span class="hljs-emphasis">__OS__</span>&TIMESTAMP=<span class="hljs-emphasis">__TS__</span>&model=<span class="hljs-emphasis">__MODEL__</span>&ua=<span class="hljs-emphasis">__UA__</span>&ip=<span class="hljs-emphasis">__IP__</span>&callback=<span class="hljs-emphasis">__CALLBACK_PARAM__</span>&callbackUrl=<span class="hljs-emphasis">__CALLBACK_URL__</span>&itemId=<span class="hljs-emphasis">__ITEM_ID__</span>&demandId=<span class="hljs-emphasis">__DEMAND_ID__</span><br></code></pre></td></tr></table></figure><blockquote><p> 更多具体宏参数参考:<a href="https://www.xingtu.cn/help-center/demander/109134">https://www.xingtu.cn/help-center/demander/109134</a></p></blockquote></li></ul><h4 id="数据回传"><a href="#数据回传" class="headerlink" title="数据回传"></a>数据回传</h4><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250207113031308.png" alt="diagram-10853842925606986107"></p><ul><li><p>手机号码加密代码</p><blockquote><p>加密算法文档地址:<a href="https://bytedance.larkoffice.com/docx/YNEGdIF6IoKupfxP9CBcYOSInse">https://bytedance.larkoffice.com/docx/YNEGdIF6IoKupfxP9CBcYOSInse</a></p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">encrypt</span><span class="hljs-params">(String str)</span> <span class="hljs-keyword">throws</span> Exception {<br> <span class="hljs-type">String</span> <span class="hljs-variable">publicKeyAsString</span> <span class="hljs-operator">=</span><br> <span class="hljs-string">"-----BEGIN PUBLIC KEY-----\n"</span> +<br> <span class="hljs-string">"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmnVrcCkHV8b+2BaMSC95DY0z9\n"</span> +<br> <span class="hljs-string">"9B7MgFoHMxXaZn0k6fvUXJTgxXtYleGFdoifHSVsrJIB0P1V4hazTIyDLnHGFOCp\n"</span> +<br> <span class="hljs-string">"UnYkLE2L8M/l0msAiKmShlxQAPB9IBNLOLFcp3qubLjfGGB1xfsDXHY9dAxYY8XC\n"</span> +<br> <span class="hljs-string">"edrYgXwkA0i3/dL+UQIDAQAB\n"</span> +<br> <span class="hljs-string">"-----END PUBLIC KEY-----"</span>;<br> <br> <span class="hljs-type">String</span> <span class="hljs-variable">publicKeyPem</span> <span class="hljs-operator">=</span> publicKeyAsString<br> .replace(<span class="hljs-string">"-----BEGIN PUBLIC KEY-----"</span>, <span class="hljs-string">""</span>)<br> .replaceAll(<span class="hljs-string">"\\n"</span>, <span class="hljs-string">""</span>)<br> .replace(<span class="hljs-string">"-----END PUBLIC KEY-----"</span>, <span class="hljs-string">""</span>);<br> <br> <span class="hljs-type">byte</span>[] keyContentAsBytes = Base64.getDecoder().decode(publicKeyPem);<br> <span class="hljs-type">KeyFactory</span> <span class="hljs-variable">fact</span> <span class="hljs-operator">=</span> KeyFactory.getInstance(<span class="hljs-string">"RSA"</span>);<br> <span class="hljs-type">X509EncodedKeySpec</span> <span class="hljs-variable">pubKeySpec</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">X509EncodedKeySpec</span>(keyContentAsBytes);<br> <span class="hljs-type">String</span> <span class="hljs-variable">publicKey</span> <span class="hljs-operator">=</span> Base64.getEncoder().encodeToString(fact.generatePublic(pubKeySpec).getEncoded());<br> <span class="hljs-type">byte</span>[] decoded = Base64.getDecoder().decode(publicKey);<br> <span class="hljs-type">RSAPublicKey</span> <span class="hljs-variable">pubKey</span> <span class="hljs-operator">=</span> (RSAPublicKey) KeyFactory.getInstance(<span class="hljs-string">"RSA"</span>).generatePublic(<span class="hljs-keyword">new</span> <span class="hljs-title class_">X509EncodedKeySpec</span>(decoded));<br> <span class="hljs-type">Cipher</span> <span class="hljs-variable">cipher</span> <span class="hljs-operator">=</span> Cipher.getInstance(<span class="hljs-string">"RSA"</span>);<br> cipher.init(Cipher.ENCRYPT_MODE, pubKey);<br> <span class="hljs-keyword">return</span> Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));<br>}<br></code></pre></td></tr></table></figure></li><li><p>表单提交回调</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">https:<span class="hljs-regexp">//</span>ad.oceanengine.com<span class="hljs-regexp">/track/</span>activate/?callback=<clickId>&event_type=<span class="hljs-number">3</span>&phone_num=<加密后的手机号码><br></code></pre></td></tr></table></figure></li><li><p>付费回调</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">https:<span class="hljs-regexp">//</span>ad.oceanengine.com<span class="hljs-regexp">/track/</span>activate/?callback=<clickid>&event_type=<span class="hljs-number">2</span>&props={<span class="hljs-string">"pay_amount"</span>:<span class="hljs-string">"1000"</span>}<br></code></pre></td></tr></table></figure></li></ul><h4 id="效果展示"><a href="#效果展示" class="headerlink" title="效果展示"></a>效果展示</h4><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250207113055996.png" alt="image-20250207110815245"></p><h4 id="点击监测和数据回传配置"><a href="#点击监测和数据回传配置" class="headerlink" title="点击监测和数据回传配置"></a>点击监测和数据回传配置</h4><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/20250207113058164.png" alt="image-20250207111330839"></p>]]></content>
<summary type="html"><p>巨量星图后台:<a href="https://www.xingtu.cn/ad/creator/index">https://www.xingtu.cn/ad/creator/index</a></p>
<p>星图监测联调工具使用说明:<a href="https://w</summary>
<category term="广告" scheme="https://cason.work/categories/%E5%B9%BF%E5%91%8A/"/>
<category term="广告" scheme="https://cason.work/tags/%E5%B9%BF%E5%91%8A/"/>
<category term="巨量星图" scheme="https://cason.work/tags/%E5%B7%A8%E9%87%8F%E6%98%9F%E5%9B%BE/"/>
</entry>
<entry>
<title>可靠分布式系统-paxos的直观解释</title>
<link href="https://cason.work/2024/11/12/%E5%8F%AF%E9%9D%A0%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F-paxos%E7%9A%84%E7%9B%B4%E8%A7%82%E8%A7%A3%E9%87%8A/"/>
<id>https://cason.work/2024/11/12/%E5%8F%AF%E9%9D%A0%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F-paxos%E7%9A%84%E7%9B%B4%E8%A7%82%E8%A7%A3%E9%87%8A/</id>
<published>2024-11-12T09:38:41.000Z</published>
<updated>2025-02-07T02:09:54.676Z</updated>
<content type="html"><![CDATA[<blockquote><p>转载自<a href="https://zhuanlan.zhihu.com/p/145044486">https://zhuanlan.zhihu.com/p/145044486</a></p></blockquote><p>update 2020-11-26: 本文讲的算法, 好多同学问起实现细节的问题, 基本可以在下面这篇找到想要的答案:</p><p><a href="https://zhuanlan.zhihu.com/p/275710507">drdr xp:200行代码实现基于paxos的kv存储262 赞同 · 37 评论文章<img src="https://pic4.zhimg.com/v2-77d183bb5e17f35687d077506a020529_180x120.jpg" alt="img"></a></p><p>之前在每个呆过的公司都做过至少1次paxos的培训(在别人家公司也做过几次), paxos其实挺好理解, 但是术语造成了障碍. 如果直接从问题出发, 先提出准确的问题, 再通过例子来回答问题, 比从概念来解释更容易被人接受. 就按这个套路来:</p><p>>>> 例子贼多图贼多.</p><p>原文链接: <a href="https://link.zhihu.com/?target=https://blog.openacid.com/algo/paxos/">https://blog.openacid.com/algo/paxos/</a></p><p><img src="https://pica.zhimg.com/v2-8e4de0756f52a90e02b5f5067612ca04_b.jpg" alt="img"></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a><strong>前言</strong></h2><p><strong>paxos是什么</strong>?</p><ul><li>在分布式系统中保证多副本数据强一致的算法.</li></ul><p><strong>paxos有啥用</strong>?</p><ul><li>没有paxos的一堆机器, 叫做分布式;</li><li>有paxos协同的一堆机器, 叫分布式系统.</li></ul><p>Google Chubby的作者Mike Burrows说过:</p><blockquote><p><em>这个世界上只有一种一致性算法,那就是Paxos …</em></p></blockquote><p>其他一致性算法, 都可以看做paxos在实现中的变体和扩展.</p><p>另外一个经常被提及的<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=%E5%88%86%E5%B8%83%E5%BC%8F%E7%AE%97%E6%B3%95&zhida_source=entity">分布式算法</a>是<a href="https://link.zhihu.com/?target=https://raft.github.io/">raft</a>, raft的贡献在于把一致性算法落地. 因为 <a href="https://link.zhihu.com/?target=http://www.lamport.org/">Leslie Lamport</a> 的理论很抽象, 要想把他的理论应用到现实中, 还需要工程师完全掌握他的理论再添加工程必要的环节才能跑起来.</p><p>经常有人问起raft和paxos的区别, 或在实现中应该选择哪个, 在不了解paxos之前可能会有这种疑问. 对于这个问题, 就像是被问及<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=%E5%9B%9B%E5%88%99%E8%BF%90%E7%AE%97&zhida_source=entity">四则运算</a>和算盘有什么区别, 小店老板应该使用四则远算还是用算盘结账一样.</p><p>记得 Leslie Lamport 2015年时来了一次北京, 那时会场上有人也问了老爷子 paxos和raft有啥区别.</p><p>老爷子当时给出的回答是: 没听过raft…</p><p><img src="https://picx.zhimg.com/v2-83540d3b7c424ff4a38b645601ba24a3_b.jpg" alt="img"></p><p>raft的核心可以认为是multi paxos的一个应用, 对于要掌握一致性算法的核心内容, 从paxos入手, 更容易去掉无关干扰, 直达问题本质. 所以我们选择paxos作为了解一致性算法的入口, 聊开了聊透了.</p><p>网络上raft比paxos流行, 因为raft的描述更直白一些, 实际上raft比paxos更复杂. raft详细的解释了”HOW”, 缺少”WHY”的解释. paxos从根本上解释清楚了”WHY”, 但一直缺少一份通俗易懂的教程. 以至于没有被更广泛的接受. 所以就有了本文, 一篇paxos入门教程, 从基本的分布式中的复制的问题出发, 通过逐步解决和完善这几个问题, 最后推导出paxos的算法.</p><p>分2个部分:</p><ul><li>前1部分是分布式一致性问题的讨论和解决方案的逐步完善, 用人话得出<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=paxos%E7%AE%97%E6%B3%95&zhida_source=entity">paxos算法</a>的过程. 如果只希望理解paxos而不打算花太多时间深入细节, 只阅读这1部分就可以啦.</li><li>第2部分是paxos算法和协议的严格描述. 这部分可以作为paxos原paper的实现部分的概括. 如果你打算实现自己的paxos或类似协议, 需要仔细了解协议细节, 希望这部分内容可以帮你节省阅读原paper的时间.</li></ul><p><strong>分布式系统要解决的问题</strong></p><p><strong>slide-00</strong></p><p><a href="http://weixin.qq.com/r/RHRYQNjEiYX2rZwj9yFW">http://weixin.qq.com/r/RHRYQNjEiYX2rZwj9yFW</a> (二维码自动识别)</p><p><strong>slide-01</strong> paxos的工作, 就是把一堆运行的机器协同起来, 让多个机器成为一个整体系统. 在这个系统中, 每个机器都必须让系统中的状态达成一致, 例如三副本集群如果一个机器上上传了一张图片, 那么另外2台机器上也必须复制这张图片过来, 整个系统才处于一个<strong>一致</strong>的状态.</p><p><img src="https://pic3.zhimg.com/v2-d2ccbe5e87f31aea1313158da16cc5d2_b.jpg" alt="img"></p><p><strong>slide-02</strong> 我是无需解释的目录页.</p><p><img src="https://pic4.zhimg.com/v2-581488ab432677491b28036393beb8e1_b.jpg" alt="img"></p><p><strong>slide-03</strong> 分布式系统的一致性问题最终都归结为<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=%E5%88%86%E5%B8%83%E5%BC%8F%E5%AD%98%E5%82%A8&zhida_source=entity">分布式存储</a>的一致性. 像aws的<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8&zhida_source=entity">对象存储</a>可靠性要求是9~13个9. 而这么高的可靠性都是建立在可靠性没那么高的硬件上的.</p><p><img src="https://pica.zhimg.com/v2-3ce5a5a0603796e979ac66eed469dd50_b.jpg" alt="img"></p><p><strong>slide-04</strong> 几乎所有的分布式存储(甚至单机系统, 参考<a href="https://link.zhihu.com/?target=https://blog.openacid.com/storage/ec-1">EC第一篇:原理</a>, <a href="https://link.zhihu.com/?target=https://blog.openacid.com/storage/ec-2">EC第二篇:实现</a>, <a href="https://link.zhihu.com/?target=https://blog.openacid.com/storage/ec-3">EC第三篇:极限</a>) 都必须用某种冗余的方式在廉价硬件的基础上搭建高可靠的存储. 而冗余的基础就是多副本策略, 一份数据存多份. 多副本保证了可靠性, 而副本之间的一致, 就需要paxos这类分布式一致性算法来保证.</p><p><img src="https://picx.zhimg.com/v2-066242ba40a4354cacaae7af2517dcf9_b.jpg" alt="img"></p><p><strong>slide-05</strong> 在早些年各种各样的复制策略都被提出来来解决各种场景下的需要. 除了复制的份数之外, 各种各样的算法实际上都是在尝试解决一致的问题. 从下一页开始简单回顾下各种复制策略, 看看他们的优缺点以及paxos如何解决副本之间一致性的问题.</p><p><img src="https://pic3.zhimg.com/v2-cdb2f48ff9170be2a7d86e3d1e42a90e_b.jpg" alt="img"></p><h2 id="不太完美的复制策略"><a href="#不太完美的复制策略" class="headerlink" title="不太完美的复制策略"></a><strong>不太完美的复制策略</strong></h2><p><strong>slide-06</strong> 无需解释的目录页</p><p><img src="https://pic3.zhimg.com/v2-27642fe3ef8046742db4e647f8a3f3d4_b.jpg" alt="img"></p><p><strong>slide-07</strong> <strong>主从异步复制</strong>是最简单的策略之一, 它很容易实现, 但存在一个问题: 客户端收到一个<strong>数据已经安全</strong>(OK)的信息, 跟<strong>数据真正安全</strong>(数据复制到全部的机器上)在时间上有一个空隙, 这段时间负责接收客户端请求的那个机器(master)如果被闪电击中或被陨石砸到或被打扫卫生的大姐踢断了电源, 那数据就可能会丢失. 因此它不是一个可靠的复制策略(使用主从异步复制要求你必须相信宇宙中不存在闪电陨石和扫地大姐).</p><p><img src="https://pic1.zhimg.com/v2-a1b980dcb9483fec1b5f51b21f033c84_b.jpg" alt="img"></p><p><strong>slide-08</strong> 跟主从异步复制相比, <strong><a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=%E4%B8%BB%E4%BB%8E%E5%90%8C%E6%AD%A5&zhida_source=entity">主从同步</a>复制</strong>提供了完整的可靠性: 直到数据真的安全的复制到全部的机器上之后, master才告知客户端<strong>数据已经安全</strong>.</p><p>但主从<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=2&q=%E5%90%8C%E6%AD%A5%E5%A4%8D%E5%88%B6&zhida_source=entity">同步复制</a>有个致命的缺点就是整个系统中有任何一个机器宕机, 写入就进行不下去了. 相当于系统的可用性随着副本数量指数降低.</p><p><img src="https://pica.zhimg.com/v2-66dc3da46d671bf644a53af9a560dc30_b.jpg" alt="img"></p><p><strong>slide-09</strong> 然鹅, 在同步和异步之间, 做一个折中, 看起来是一个不错的方案. 这就是**<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=%E5%8D%8A%E5%90%8C%E6%AD%A5%E5%A4%8D%E5%88%B6&zhida_source=entity">半同步复制</a><strong>. 它要求master在应答客户端之前必须把数据复制到</strong>足够多**的机器上, 但不需要是全部. <strong>这样副本数够多可以提供比较高的可靠性; 1台机器宕机也不会让整个系统停止写入</strong>.</p><p>但是它还是不完美, 例如数据a复制到slave-1, 但没有到达slave-2; 数据b复制达到了slave-2但没有到达slave-1, 这时如果master挂掉了需要从某个slave恢复出数据, 任何一个slave都不能提供完整的数据. 所以在整个系统中, 数据存在某种<strong>不一致</strong>.</p><p><img src="https://picx.zhimg.com/v2-8097f9f12b3878bc66efc4d026927adf_b.jpg" alt="img"></p><p><strong>slide-10</strong> 为了解决半同步复制<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=%E4%B8%AD%E6%95%B0%E6%8D%AE&zhida_source=entity">中数据</a>不一致的问题, 可以将这个复制策略再做一改进: <strong>多数派读写</strong>: 每条数据必须写入到<strong>半数以上</strong>的机器上. 每次读取数据都必须检查<strong>半数以上</strong>的机器上是否有这条数据.</p><p>在这种策略下, 数据可靠性足够, 宕机容忍足够, 任一机器故障也能读到全部数据.</p><p><img src="https://picx.zhimg.com/v2-381d667a3039b1d5eb6b440b2a327737_b.jpg" alt="img"></p><p><strong>slide-11</strong> 然鹅多数派读写的策略也有个<strong>但是</strong>, 就是对于一条数据的更新时, 会产生不一致的状态. 例如:</p><ul><li>node-1, node-2都写入了a=x,</li><li>下一次更新时node-2, node-3写入了a=y.</li></ul><p>这时, 一个要进行读取a的客户端如果联系到了node-1和node-2, 它将看到2条<strong>不同</strong>的数据.</p><p>为了不产生歧义, 多数派读写还必须给每笔写入增加一个<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=%E5%85%A8%E5%B1%80%E9%80%92%E5%A2%9E&zhida_source=entity">全局递增</a>的<strong>时间戳</strong>. 更大时间戳的记录如果被看见, 就应该忽略小时间戳的记录. 这样在读取过程中, 客户端就会看到a=x₁, a=y₂ 这2条数据, 通过比较时间戳1和2, 发现y是更新的数据, 所以忽略a=x₁. 这样保证多次更新一条数据不产生歧义.</p><p><img src="https://pica.zhimg.com/v2-7b7c7b515e2e18c79c930c83b1a58084_b.jpg" alt="img"></p><p><strong>slide-12</strong> 是的, <strong>但是</strong>又来了. 这种带时间戳的<strong>多数派读写</strong>依然有问题. 就是在客户端没有完成一次完整的多数派写的时候: 例如, 上面的例子中写入, a=x₁写入了node-1和node-2, a=y₂时只有node-3 写成功了, 然后客户端进程就挂掉了, 留下系统中的状态如下:</p><p><img src="https://pic2.zhimg.com/v2-cee8e660ba022a04d4a5b22f2dfa73e9_b.jpg" alt="img"></p><p>这时另一个读取的客户端来了,</p><ul><li>如果它联系到node-1和node-2, 那它得到的结果是a=x₁.</li><li>如果它联系到node-2和node-3, 那它得到的结果是a=y₂.</li></ul><p>整个系统对外部提供的信息仍然是不一致的.</p><p><img src="https://pica.zhimg.com/v2-8a1d86f4235015a6b31c873406396d18_b.jpg" alt="img"></p><p><strong>slide-13</strong> 现在我们已经非常接近最终奥义了, paxos可以认为是多数派读写的进一步升级, paxos中通过2次原本并不严谨的多数派读写, 实现了严谨的强一致consensus算法.</p><p><img src="https://pic2.zhimg.com/v2-30c367a001de3ffd18a4d044a7e183eb_b.jpg" alt="img"></p><h2 id="从多数派读写到paxos的推导"><a href="#从多数派读写到paxos的推导" class="headerlink" title="从多数派读写到paxos的推导"></a><strong>从多数派读写到paxos的推导</strong></h2><p><strong>slide-14</strong> 首先为了清晰的呈现出分布式系统中的核心问题: 一致性问题, 我们先设定一个假象的存储系统, 在这个系统上, 我们来逐步实现一个强一致的存储, 就得到了paxos对一致性问题的解决方法.</p><p><img src="https://picx.zhimg.com/v2-8eb822a33c46b6f224daa4b21134e977_b.jpg" alt="img"></p><p><strong>slide-15</strong> 在实现中, set命令直接实现为一个多数派写, 这一步非常简单. 而inc操作逻辑上也很简单, 读取一个变量的值i₁, 给它加上一个数字得到i₂, 再通过多数派把i₂写回到系统中.</p><p><img src="https://pic4.zhimg.com/v2-54bce7e711819a8b1a7fed0943016e51_b.jpg" alt="img"></p><p><strong>slide-16</strong> 冰雪如你一定已经看到了这种实现方式中的问题: 如果有2个并发的客户端进程同时做这个inc的操作, 在多数派读写的实现中, 必然会产生一个Y客户端覆盖X客户端的问题. 从而产生了数据更新点的丢失.</p><p>而paxos就是为了解决这类问题提出的, 它需要让Y能检测到这种并发冲突, 进而采取措施避免更新丢失.</p><p><img src="https://pic3.zhimg.com/v2-d5106d56d5af104f614899626545768c_b.jpg" alt="img"></p><p><strong>slide-17</strong> 提取一下上面提到的问题: 让Y去更新的时候不能直接更新i₂, 而是应该能检测到i₂的存在, 进而将自己的结果保存在下一个版本i₃中, 再写回系统中.</p><p>而这个问题可以转化成: i的每个版本只能被写入一次, 不允许修改. 如果系统设计能满足这个要求, 那么X和Y的inc操作就都可以正确被执行了.</p><p><img src="https://pic1.zhimg.com/v2-521503c70b2f65dea7deecfcfbf37866_b.jpg" alt="img"></p><p><strong>slide-18</strong> 于是我们的问题就转化成一个更简单, 更基础的问题: 如何确定一个值(例如iⱼ)已经被写入了.</p><p>直观来看, 解决方法也很简单, 在X或Y写之前先做一次<strong>多数派读</strong>, 以便确认是否有其他客户端进程已经在写了, 如果有, 则放弃.</p><p><img src="https://pica.zhimg.com/v2-c55a998e4c767edcf45bab38fe3ba6d0_b.jpg" alt="img"></p><p><strong>slide-19</strong> <strong>但是</strong>!!!, 这里还有个并发问题, X和Y可能同时做这个<strong>写前读取</strong>的操作, 并且同时得出一个结论: 还没有其他进程在写入, 我可以写. 这样还是会造成更新丢失的问题.</p><p><img src="https://pic3.zhimg.com/v2-429652b0634cde717b49fee6445d491e_b.jpg" alt="img"></p><p><strong>slide-20</strong> 为了解决上面的问题, 存储节点还需要增加一个功能, 就是它必须记住谁最后一个做过<strong>写前读取</strong>的操作. 并且只允许最后一个完成<strong>写前读取</strong>的进程可以进行后续写入, 同时拒绝之前做过<strong>写前读取</strong>的进程写入的权限.</p><p>可以看到, 如果每个节点都记得谁<strong>读</strong>过, 那么当Y最后完成了<strong>写前读取</strong>的操作后, 整个系统就可以阻止过期的X的写入.</p><p>这个方法之所以能工作也是因为多数派写中, 一个系统最多只能允许一个多数派写成功. paxos也是通过2次多数派读写来实现的强一致.</p><p><img src="https://pic4.zhimg.com/v2-6fea346168c259f84df8a5668d9f40a1_b.jpg" alt="img"></p><p><strong>slide-21</strong> 以上就是paxos算法的全部核心思想了, 是不是很简单? 剩下的就是如何实现的简单问题了: 如何标识一个客户端如X和Y, 如何确认谁是最后一个完成<strong>写前读写</strong>的进程, 等等.</p><p><img src="https://pic2.zhimg.com/v2-70aca3ffb1490ed42fe777189ee53cdd_b.jpg" alt="img"></p><p><strong>slide-22</strong> <a href="https://link.zhihu.com/?target=http://www.lamport.org/">Leslie Lamport</a> 就这么把这么简单的一个算法写了个paper就获得了图领奖! 骚年, 改变世界就这么容易!</p><p><img src="https://pic1.zhimg.com/v2-af4e5a8ff8494f8796c0b7d9cb1109b6_b.jpg" alt="img"></p><h2 id="paxos算法描述"><a href="#paxos算法描述" class="headerlink" title="paxos算法描述"></a><strong>paxos算法描述</strong></h2><p>接下来的篇幅中我们将用计算机的语言准确的描述整个paxos运行的过程.</p><p><strong>slide-23</strong> 首先明确要解决的问题:</p><p><img src="https://pic1.zhimg.com/v2-00ae7aed7ebafcb87b55e509aba4eb16_b.jpg" alt="img"></p><p><strong>slide-24</strong> 我们要介绍的paxos实际上是最朴实的classic paxos, 在这之后我们顺提下几个老爷子对paxos的优化, multi paxso和fast paxos, 它们都是针对paxos的理论层面的优化.</p><p><img src="https://pic1.zhimg.com/v2-d8f7323a28d68b29e588e9c43aff0798_b.jpg" alt="img"></p><p><strong>slide-25</strong> paxos算法中解决了如何在不可靠硬件基础上构建一个可靠的分布式系统的方法. 但paxos核心算法中只解决网络延迟/乱序的问题, 它不试图解决存储不可靠和消息错误的问题, 因为这两类问题本质上跟分布式关系不大, 属于数据校验层面的事情.</p><p>有兴趣可以参考 <a href="https://link.zhihu.com/?target=https://en.wikipedia.org/wiki/Paxos_(computer_science)%23Byzantine_Paxos">Byzantine Paxos</a> 的介绍.</p><p><img src="https://pic4.zhimg.com/v2-d307d23c82638fdbccf1df4ef748b575_b.jpg" alt="img"></p><p><strong>slide-26</strong> 本文尽量按照 <a href="https://link.zhihu.com/?target=http://lamport.azurewebsites.net/pubs/pubs.html%23paxos-simple">Classic Paxos</a> 的术语来描述,</p><blockquote><p><em>老爷子后面的一篇</em> <em><a href="https://link.zhihu.com/?target=http://lamport.azurewebsites.net/pubs/pubs.html%23fast-paxos">Fast Paxos</a></em> <em>实现了fast-paxos, 同时包含了classic-paxos, 但使用了一些不同的术语表示.</em></p></blockquote><ul><li>Proposer 可以理解为客户端.</li><li>Acceptor 可以理解为<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=2&q=%E5%AD%98%E5%82%A8%E8%8A%82%E7%82%B9&zhida_source=entity">存储节点</a>.</li><li>Quorum 在99%的场景里都是指多数派, 也就是半数以上的Acceptor.</li><li>Round 用来标识一次paxos算法实例, 每个round是2次多数派读写: 算法描述里分别用phase-1和phase-2标识. 同时为了简单和明确, 算法中也规定了每个Proposer都必须生成全局单调递增的round, 这样round既能用来区分先后也能用来区分不同的Proposer(客户端).</li></ul><p><img src="https://picx.zhimg.com/v2-0fb6fda91f543b6760c5329e82e7ad95_b.jpg" alt="img"></p><p><strong>slide-27</strong> 在存储端(Acceptor)也有几个概念:</p><ul><li>last_rnd 是Acceptor记住的最后一次进行<strong>写前读取</strong>的Proposer(客户端)是谁, 以此来决定谁可以在后面真正把一个值写到存储中.</li><li>v 是最后被写入的值.</li><li>vrnd 跟v是一对, 它记录了在哪个Round中v被写入了.</li></ul><p>v和vrnd是用于恢复一次未完成的paxos用的. 一次未完成的paxos算法运行可能留下一些没有达到多数派的值的写入(就像原生的多数派写的脏读的问题), paxos中通过vrnd来决定哪些值是最后写入的, 并决定恢复哪个未完成的paxos运行. 后面我们会通过几个例子来描述vrnd的作用.</p><p><img src="https://pic2.zhimg.com/v2-288515af9b4118e774bab93cb74f2857_b.jpg" alt="img"></p><p><strong>slide-28</strong> 首先是paxos的phase-1, 它相当于之前提到的写前读取过程. 它用来在存储节点(Acceptor)上记录一个标识: 我后面要写入; 并从Acceptor上读出是否有之前未完成的paxos运行. 如果有则尝试恢复它; 如果没有则继续做自己想做的事情.</p><p>我们用类似yaml的格式来描述phase-1的请求/应答的格式:</p><p><img src="https://pic1.zhimg.com/v2-8825fd94059fc33b8bdc272f283b23ec_b.jpg" alt="img"></p><p>phase-1成后, acceptor应该记录X的rnd=1, 并返回自己之前保存的v和vrnd.</p><p><img src="https://pic4.zhimg.com/v2-5148b510c03ba42e1221001b2dec4e99_b.jpg" alt="img"></p><p><strong>slide-29</strong> Proposer X收到多数(quorum)个应答, 就认为是可以继续运行的.如果没有联系到多于半数的<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=2&q=acceptor&zhida_source=entity">acceptor</a>, 整个系统就hang住了, 这也是paxos声称的只能运行少于半数的节点失效.</p><p>这时Proposer面临2种情况:</p><ul><li>所有应答中都没有任何非空的v, 这表示系统之前是干净的, 没有任何值已经被其他paxos客户端完成了写入(因为一个多数派读一定会看到一个多数派写的结果). 这时Proposer X继续将它要写的值在phase-2中真正写入到多于半数的Acceptor中.</li><li>如果收到了某个应答包含被写入的v和vrnd, 这时, Proposer X 必须假设有其他客户端(Proposer) 正在运行, 虽然X不知道对方是否已经成功结束, 但任何已经写入的值都不能被修改!, 所以X必须保持原有的值. 于是X将看到的最大vrnd对应的v作为X的phase-2将要写入的值. 这时实际上可以认为X执行了一次(不知是否已经中断的)其他客户端(Proposer)的修复.</li></ul><p><img src="https://pica.zhimg.com/v2-57fb1a930d3c41c3725137a669dd7a40_b.jpg" alt="img"></p><p><strong>slide-30</strong> 在第2阶段phase-2, Proposer X将它选定的值写入到Acceptor中, 这个值可能是它自己要写入的值, 或者是它从某个Acceptor上读到的v(修复).</p><p>同样用类似yaml的方式描述请求应答:</p><p><img src="https://pic2.zhimg.com/v2-615293fece633418ce61ea9f1adb1985_b.jpg" alt="img"></p><p><img src="https://pic2.zhimg.com/v2-33233d9740cad6ca291711123592d69d_b.jpg" alt="img"></p><p><strong>slide-31</strong> 当然这时(在X收到phase-1应答, 到发送phase-2请求的这段时间), 可能已经有其他Proposer又完成了一个rnd更大的phase-1, 所以这时X不一定能成功运行完phase-2.</p><p>Acceptor通过比较phase-2请求中的rnd, 和自己本地记录的rnd, 来确定X是否还有权写入. 如果请求中的<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=13&q=rnd&zhida_source=entity">rnd</a>和Acceptor本地记录的rnd一样, 那么这次写入就是被允许的, Acceptor将v写入本地, 并将phase-2请求中的rnd记录到本地的vrnd中.</p><p><img src="https://pic3.zhimg.com/v2-c050e21d9d3f12bb24150f8f19d102c8_b.jpg" alt="img"></p><h2 id="用例子看paxos运行"><a href="#用例子看paxos运行" class="headerlink" title="用例子看paxos运行"></a><strong>用例子看paxos运行</strong></h2><p>好了paxos的算法描述也介绍完了. 这些抽象的算法描述, 其中的规则覆盖了实际所有可能遇到的情况的处理方式. 一次不太容易看清楚它们的作用, 所以我们接下来通过几个例子来看看paxos如何处理各种不同状态并最终使整个系统的状态达成一致.</p><p><strong>slide-32</strong> 没冲突的例子不解释了</p><p><img src="https://pica.zhimg.com/v2-0a3a5e25cf6963617c306d02d07e506c_b.jpg" alt="img"></p><p><strong>slide-33</strong> X和Y同时运行paxos, Y迫使X中断的例子:</p><ul><li>X成功完成了写前读取(phase-1), 将rnd=1写入到左边2个Acceptor.</li><li>Y用更大的rnd=2, 覆盖了X的rnd, 将rnd=2写入到右边2个Acceptor.</li><li>X以为自己还能运行phase-2, 但已经不行了, X只能对最左边的Acceptor成功运行phase-2, 而中间的Acceptor拒绝了X的phase-2.</li><li>Y对右边2个Acceptor成功运行了phase-2, 完成写入v=y, vrnd=2.</li></ul><p><img src="https://picx.zhimg.com/v2-6472642b4a1ad5dc246c79d0229425b7_b.jpg" alt="img"></p><p><strong>slide-34</strong> 继续上面的例子, 看X如何处理被抢走写入权的情况:</p><p>这时X的phase-2没成功, 它需要重新来一遍, 用更大的rnd=3.</p><ul><li>X成功在左边2个Acceptor上运行phase-1之后, X发现了2个被写入的值: v=x, vrnd=1 和 v=y, vrnd=2; 这时X就不能再写入自己想要写入的值了. 它这次paxos运行必须不能修改已存在的值, 这次X的paxos的运行唯一能做的就是, 修复(可能)已经中断的其他proposer的运行.</li><li>这里v=y, vrnd=2 是可能在phase-2达到多数派的值. v=x, vrnd=1不可能是, 因为其他proposer也必须遵守算法约定, 如果v=x, vrnd=1在某个phase-2达到多数派了, Y一定能在phase-1中看到它, 从而不会写入v=y, vrnd=2.</li></ul><p>因此这是X选择v=y, 并使用rnd=3继续运行, 最终把v=y, vrnd=3写入到所有Acceptor中.</p><p><img src="https://pic4.zhimg.com/v2-01aeaa06dd92aa08f1322db83d5446c1_b.jpg" alt="img"></p><p><strong>slide-35</strong> Paxos 还有一个不太重要的角色Learner, 是为了让系统完整加入的, 但并不是整个算法执行的关键角色, 只有在最后在被通知一下.</p><p><img src="https://picx.zhimg.com/v2-9e9a92b3f6188dc00dace8f3d43497f3_b.jpg" alt="img"></p><h2 id="Paxos-优化"><a href="#Paxos-优化" class="headerlink" title="Paxos 优化"></a><strong>Paxos 优化</strong></h2><p><strong>slide-36</strong> 第一个优化 <strong><a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=1&q=multi-paxos&zhida_source=entity">multi-paxos</a></strong>:</p><p>paxos诞生之初为人诟病的一个方面就是每写入一个值就需要2轮rpc:<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=10&q=phase-1&zhida_source=entity">phase-1</a>和phase-2. 因此一个寻常的优化就是用一次rpc为多个paxos实例运行phase-1.</p><p>例如, Proposer X可以一次性为i₁~i₁₀这10个值, 运行phase-1, 例如为这10个paxos实例选择rnd为1001, 1002…1010. 这样就可以节省下9次rpc, 而所有的写入平均下来只需要1个rpc就可以完成了.</p><p>这么看起来就有点像raft了:</p><ul><li>再加上commit概念(commit可以理解为: 值v送达到多数派这件事情是否送达到多数派了),</li><li>和组成员变更(将quorum的定义从”多于半数”扩展到”任意2个quourm必须有交集”).</li></ul><p><img src="https://pic1.zhimg.com/v2-c97028ee3e786bc779ba30238f2f2ece_b.jpg" alt="img"></p><p><strong>slide-37</strong> 第二个优化 <strong>fast-paxos</strong>:</p><p>fast-paxos通过增加quorum的数量来达到一次rpc就能达成一致的目的. 如果fast-paxos没能在一次rpc达成一致, 则要退化到classic paxos.</p><p><img src="https://pic4.zhimg.com/v2-52d5cb71ced2fc1c959e5f4540063fcf_b.jpg" alt="img"></p><p><strong>slide-38</strong> fast-paxos为了能在退化成classic paxos时不会选择不同的值, 就必须扩大<a href="https://zhida.zhihu.com/search?content_id=120364113&content_type=Article&match_order=4&q=quorum&zhida_source=entity">quorum</a>的值. 也就是说fast-round时, quorum的大小跟classic paxos的大小不一样. 同样我们先来看看为什么fast-quorum不能跟classic-quorum一样, 这样的配置会引起classic阶段回复时选择错误的值 y₀:</p><p><img src="https://pica.zhimg.com/v2-ccae6a05e4ab73007852f15b20219cde_b.jpg" alt="img"></p><p><strong>slide-39</strong> 要解决这个问题, 最粗暴的方法是把fast-quorum设置为n, 也就是全部的acceptor都写入成功才认为fast-round成功(实际上是退化到了主从同步复制). 这样, 如果X和Y两个proposer并发写入, 谁也不会成功, 因此X和Y都退化到classic paxos进行修复, 选任何值去修复都没问题. 因为之前没有Proposer认为自己成功写入了.</p><p>如果再把问题深入下, 可以得出, 如果classic paxos的quorum是n/2+1, 那么fast-round的quorum应该是大于¾n, ¾的由来可以简单理解为: 在最差情况下, 达到fast-quorum的acceptor在classic-quorum中必须大于半数, 才不会导致修复进程选择一个跟fast-round不同的值.</p><p><img src="https://pica.zhimg.com/v2-b173bdc6d18b0868a14b232aa4136d72_b.jpg" alt="img"></p><p><strong>slide-40</strong> 下面是一个fast-round中X成功, Y失败的冲突的例子:</p><p>X已经成功写入到4(fast-quorum>¾n)个acceptor, Y只写入1个, 这时Y进入classic-round进行息修复, 可以看到, 不论Y选择哪3(classic quorum)个acceptor, 都可以看到至少2个x₀, 因此Y总会选择跟X一样的值, 保证了<strong>写入的值就不会被修改</strong>的条件.</p><p><img src="https://pica.zhimg.com/v2-51ca437c5db5aea11071655cca66a2a0_b.jpg" alt="img"></p><p><strong>slide-41</strong> 再来看一个X和Y都没有达到fast-quorum的冲突:</p><p>这时X和Y都不会认为自己的fast-round成功了, 因此修复过程选择任何值都是可以的. 最终选择哪个值, 就回归到X和Y两个classic-paxos进程的竞争问题了. 最终会选择x₀或y₀中的一个.</p><p><img src="https://picx.zhimg.com/v2-e8cd5df6b1192a075c32eff3a63534b9_b.jpg" alt="img"></p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a><strong>其他</strong></h2><p><strong>slide-42</strong> 一个很容易验证的优化, 各种情况下都能得到一致的结果.</p><p><img src="https://pic1.zhimg.com/v2-8216575fa4c8b9c2ff727af1961390f6_b.jpg" alt="img"></p><p><strong>slide-43</strong> 广告页, 不解释了</p><p><a href="http://weixin.qq.com/r/RHRYQNjEiYX2rZwj9yFW">http://weixin.qq.com/r/RHRYQNjEiYX2rZwj9yFW</a> (二维码自动识别)</p><p>本次的 pdf 可以下载和在线看哦:</p><ul><li><a href="https://link.zhihu.com/?target=https://blog.openacid.com/post-res/paxos/%E5%8F%AF%E9%9D%A0%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F-paxos%E7%9A%84%E7%9B%B4%E8%A7%82%E8%A7%A3%E9%87%8A.pdf">可靠分布式系统-paxos的直观解释.pdf</a></li><li><a href="https://link.zhihu.com/?target=https://blog.openacid.com/post-res/paxos/%E5%8F%AF%E9%9D%A0%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F-paxos%E7%9A%84%E7%9B%B4%E8%A7%82%E8%A7%A3%E9%87%8A.html">可靠分布式系统-paxos的直观解释.html</a></li></ul><p>本文链接: <a href="https://link.zhihu.com/?target=https://blog.openacid.com/algo/paxos/">https://blog.openacid.com/algo/paxos/</a></p><p>update 2020-11-26: 本文讲的算法, 好多同学问起实现细节的问题, 基本可以在下面这篇找到想要的答案:</p>]]></content>
<summary type="html"><blockquote>
<p>转载自<a href="https://zhuanlan.zhihu.com/p/145044486">https://zhuanlan.zhihu.com/p/145044486</a></p>
</blockquote>
<p>update 2</summary>
</entry>
<entry>
<title>广告体系架构简单整理</title>
<link href="https://cason.work/2024/11/12/%E5%B9%BF%E5%91%8A%E4%BD%93%E7%B3%BB%E6%9E%B6%E6%9E%84%E7%AE%80%E5%8D%95%E6%95%B4%E7%90%86/"/>
<id>https://cason.work/2024/11/12/%E5%B9%BF%E5%91%8A%E4%BD%93%E7%B3%BB%E6%9E%B6%E6%9E%84%E7%AE%80%E5%8D%95%E6%95%B4%E7%90%86/</id>
<published>2024-11-12T03:22:24.000Z</published>
<updated>2025-02-07T02:09:54.676Z</updated>
<content type="html"><![CDATA[<p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20241112110051158.png" alt="image-20241112110051158"></p><h3 id="1-数据来源"><a href="#1-数据来源" class="headerlink" title="1. 数据来源"></a>1. <strong>数据来源</strong></h3><ul><li><strong>H5 和 App</strong>:通过 JavaScript 和 SDK 实现用户行为数据的埋点采集。</li><li><strong>广告平台</strong>:通过 API 与广告平台进行交互,采集广告相关数据。</li><li><strong>HTTP</strong>:其他通过 HTTP 接口传输的数据。对接广告平台(媒体)API获取投放的广告的报表数据,或者MMP的webhook回调等等</li></ul><h3 id="2-数据采集"><a href="#2-数据采集" class="headerlink" title="2. 数据采集"></a>2. <strong>数据采集</strong></h3><ul><li><strong>Kafka</strong>:作为消息队列,用于存储和传输采集到的用户行为数据。Kafka 负责高效处理来自多个来源的大量实时数据。</li></ul><h3 id="3-大数据处理"><a href="#3-大数据处理" class="headerlink" title="3. 大数据处理"></a>3. <strong>大数据处理</strong></h3><ul><li>实时计算 (Flink)<ul><li>Flink 负责从 Kafka 获取用户行为数据流,对数据进行实时清洗和处理以及用户打标签</li><li>将处理后的数据持久化到数据库(例如 MySQL)。</li></ul></li><li>归档与数据存储<ul><li>将历史数据归档到 Hive,以便进行长期存储和查询、以及分担实时库的查询压力。</li></ul></li><li>离线计算 (Spark)<ul><li>使用 Spark 编写 SQL 作业,通过小海豚调度系统来执行。</li><li>离线计算用于分析用户画像,整合和处理用户行为数据。</li></ul></li></ul><h3 id="4-数据与服务监控"><a href="#4-数据与服务监控" class="headerlink" title="4. 数据与服务监控"></a>4. <strong>数据与服务监控</strong></h3><ul><li><strong>Prometheus</strong>:用于监控系统状态,收集和处理监控指标。api的调用的成功率,kafka的堆积情况等等。</li><li><strong>Logstash/Filebeat</strong>:用于日志收集和转发,将日志数据发送到 Elasticsearch。系统的报错日志等等</li><li><strong>Elasticsearch</strong>:用作存储和检索日志数据的搜索引擎。</li><li><strong>Grafana 和 Kibana</strong>:用作数据可视化工具,分别连接到 Prometheus 和 Elasticsearch,提供实时的监控图表和分析视图。</li></ul><h3 id="5-数据展示"><a href="#5-数据展示" class="headerlink" title="5. 数据展示"></a>5. <strong>数据展示</strong></h3><ul><li><strong>Redash</strong>:用于数据展示和报表生成,帮助分析和可视化数据。</li><li><strong>Presto</strong>:访问多种类型的数据源在大规模数据集上进行快速、交互式的查询</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a><strong>总结</strong></h3><p>整个系统架构展示了如何从不同数据来源采集用户行为数据,并通过流式计算(Flink)和批处理(Spark)进行数据清洗、处理和分析。实时数据被用于快速响应,而离线数据则被用来构建详细的用户画像。监控和可视化层面,通过 Elasticsearch、Grafana 和 Kibana 等工具来进行数据和服务的监控。Redash 最终将分析结果以用户友好的方式展示。</p>]]></content>
<summary type="html"><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20241112110051158.png" alt="image-20241112110051158"></p>
<h3 </summary>
<category term="广告" scheme="https://cason.work/tags/%E5%B9%BF%E5%91%8A/"/>
</entry>
<entry>
<title>操作系统-mmap、sendFile、splice三种零拷贝技术介绍</title>
<link href="https://cason.work/2024/10/29/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-mmap%E3%80%81sendFile%E3%80%81splice%E4%B8%89%E7%A7%8D%E9%9B%B6%E6%8B%B7%E8%B4%9D%E6%8A%80%E6%9C%AF%E4%BB%8B%E7%BB%8D/"/>
<id>https://cason.work/2024/10/29/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-mmap%E3%80%81sendFile%E3%80%81splice%E4%B8%89%E7%A7%8D%E9%9B%B6%E6%8B%B7%E8%B4%9D%E6%8A%80%E6%9C%AF%E4%BB%8B%E7%BB%8D/</id>
<published>2024-10-29T09:15:31.000Z</published>
<updated>2025-02-07T02:09:54.677Z</updated>
<content type="html"><![CDATA[<blockquote><p>转载文章</p></blockquote><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>在传统网络数据传输的过程中,数据会被来回拷贝很多次,而其中有一些是不必要拷贝,而零拷贝技术就是为了<code>减少这些不必要的数据复制操作</code>。下面会详细介绍各种数据拷贝的详细过程,这也是一个非常高频的面试问题。</p><h2 id="传统数据拷贝"><a href="#传统数据拷贝" class="headerlink" title="传统数据拷贝"></a>传统数据拷贝</h2><p>当我们通过网络从服务器上获取数据时,数据整体的传输过程是这样子的,如图(可以放大看):</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20241112111431751.png" alt="image-20241112111431751"> 详细过程:</p><ol><li>等待cpu调度,通过cpu发起io请求,通过read()方法读取数据,此时用户态切换为内核态;</li><li>DMA对硬盘发起IO请求;</li><li>DMA从硬盘中把数据拷贝到pageCache中;</li><li>DMA拷贝完成后发送完成信息;</li><li><code>cpu从pageCache中把数据拷贝到用户缓冲区;</code></li><li>此时read()方法调用完成,内核态切换为用户态;</li><li>等待cpu调度,通过cpu发起io请求,通过write()方法写数据,此时用户态切换为内核态;</li><li><code>cpu把数据从用户缓冲区拷贝到socket缓冲区;</code></li><li>DMA通知网卡设备要发起IO请求;</li><li>DMA开始进行数据拷贝;</li><li>DMA拷贝完成,通知写完成信息;</li><li>write()方法调用完成,内核态切换为用户态。</li></ol><p>上述过程中出现了4次上下文切换,2次cpu拷贝,2次DMA拷贝;可以发现这里面有很多是不必要的操作,如:从pageCache拷贝到用户缓冲区,再从用户缓冲区拷贝到socket缓冲区;而零拷贝的出现就是为了节省这些cpu拷贝、上下文切换,从而提高服务的整体性能。目前linux提供了mmap、sendfile、splice等都是为了省去不必要的操作从而提升服务整体性能。</p><h2 id="DMA拷贝"><a href="#DMA拷贝" class="headerlink" title="DMA拷贝"></a>DMA拷贝</h2><figure class="highlight x86asm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs x86asm">DMA其实就是在主板上安装了一块独立的芯片,它的作用当我们在内存和IO设备上传输设备的时候不需要<span class="hljs-meta">cpu</span>来控制数据的传输,而是直接通过DMA控制器来传输;这样就可以释放<span class="hljs-meta">cpu</span>来做其他的事情;但DMA只能用于设备之间的数据拷贝,所以传统数据传输只有在硬盘、网卡数据拷贝时才用得上。<br></code></pre></td></tr></table></figure><h2 id="mmap-write"><a href="#mmap-write" class="headerlink" title="mmap + write"></a>mmap + write</h2><p>mmap是linux内核提供的一种内存映射文件的方式,将一个进程的虚拟地址映射到磁盘文件地址。它可以将内核缓冲区的地址与用户缓冲区的地址进行映射,从而实现内核缓冲区到用户缓冲区的内存共享。省去数据从内核缓冲区拷贝到用户缓冲区的过程。具体过程如下:</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20241112111538855.png" alt="image-20241112111538855"></p><ol><li>发起mmap()调用,建立用户缓冲区和pageCache的地址映射;</li><li>DMA对硬盘发起IO请求;</li><li>DMA从硬盘中把数据拷贝到pageCache中;</li><li>DMA拷贝完成后发送完成信息;</li><li>mmap()调用完成,内核态切换为用户态;</li><li>等待cpu调度,通过cpu发起io请求,通过write()方法写数据,此时用户态切换为内核态;</li><li><code>cpu把数据从用户缓冲区拷贝到socket缓冲区;</code></li><li>DMA通知网卡设备要发起IO请求;</li><li>DMA开始进行数据拷贝;</li><li>DMA拷贝完成,通知写完成信息;</li><li>write()方法调用完成,内核态切换为用户态。</li></ol><p>后续的write()方法和传统数据拷贝的过程是相同的,整体上发生了4次上下文切换,一次cpu拷贝,<code>节省了一次从pageCache拷贝到用户缓冲区的cpu拷贝过程</code>;同时用户态空间的共享区使用的是虚拟内存,并不会占用过多的物理内存。</p><p>优点:针对大文件可以极大的提高IO性能,但是对于小文件,内存映射反而会导致碎片空间的浪费。</p><h2 id="sendfile"><a href="#sendfile" class="headerlink" title="sendfile"></a>sendfile</h2><p>sendfile系统调用是Linux2.1引入的目的简化网络通过两个通道之间的数据传输;它可以使数据直接在内核空间进行IO传输,省去了用户空间和内核空间来回拷贝的过程。如图:</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20241112111554791.png" alt="image-20241112111554791"></p><ol><li>发起sendfile()调用,用户态切换为内核态;</li><li>DMA对硬盘发起IO请求;</li><li>DMA从硬盘中把数据拷贝到pageCache中;</li><li>DMA拷贝完成后发送完成信息;</li><li><code>cpu从pageCache拷贝到socket缓冲区;</code></li><li>DMA通知网卡设备要发起IO请求;</li><li>DMA开始进行数据拷贝;</li><li>DMA拷贝完成,通知写完成信息;</li><li>sendfile()调用完成,内核态切换为用户态。</li></ol><p>整个过程中只发生了2次上下文切换,1次cpu拷贝;相比mmap+write又节省了<code>2次上下文切换</code>。 而在linux2.4内核版本开始,又增加了<code>gather操作</code>,可以把仅有的这次cpu拷贝也省去掉。过程如下:</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20241112111609402.png" alt="image-20241112111609402"></p><p>上图红色字体的过程为改进点。它把数据描述信息读到socket的缓冲区中,DMA Gather Copy根据socket缓冲的数据描述信息批量的从pageCache中读取到网卡设备上。至此剩余的一次<code>pageCache到socket缓冲的cpu拷贝</code>也被节省掉了。</p><h2 id="splice"><a href="#splice" class="headerlink" title="splice"></a>splice</h2><p>上面的零拷贝,都是大家平时刷文章经常刷到的,这里再介绍一种不那么常见的<code>splice</code>。过程图如下:</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20241112111622999.png" alt="image-20241112111622999"></p><p>在第5步是创建一个splice管道,并且把该管道的写端绑定在pageCache上,读端绑定到socket缓冲区上,再通过DMA拷贝到网卡设备上,也实现真正意义上的零cpu拷贝。</p><blockquote><p>作者:想打游戏的程序猿<br>链接:<a href="https://juejin.cn/post/7404036819107102739">https://juejin.cn/post/7404036819107102739</a><br>来源:稀土掘金<br>著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。</p></blockquote>]]></content>
<summary type="html"><blockquote>
<p>转载文章</p>
</blockquote>
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>在传统网络数据传输的过程中,数据会被来回拷贝很多次,而其中有一</summary>
<category term="操作系统" scheme="https://cason.work/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
<category term="零拷贝" scheme="https://cason.work/tags/%E9%9B%B6%E6%8B%B7%E8%B4%9D/"/>
<category term="mmap" scheme="https://cason.work/tags/mmap/"/>
<category term="sendFile" scheme="https://cason.work/tags/sendFile/"/>
<category term="splice" scheme="https://cason.work/tags/splice/"/>
</entry>
<entry>
<title>抓包精灵-上传功能服务端代码</title>
<link href="https://cason.work/2024/07/29/%E6%8A%93%E5%8C%85%E7%B2%BE%E7%81%B5-%E4%B8%8A%E4%BC%A0%E5%8A%9F%E8%83%BD%E6%9C%8D%E5%8A%A1%E7%AB%AF%E4%BB%A3%E7%A0%81/"/>
<id>https://cason.work/2024/07/29/%E6%8A%93%E5%8C%85%E7%B2%BE%E7%81%B5-%E4%B8%8A%E4%BC%A0%E5%8A%9F%E8%83%BD%E6%9C%8D%E5%8A%A1%E7%AB%AF%E4%BB%A3%E7%A0%81/</id>
<published>2024-07-29T08:53:18.000Z</published>
<updated>2025-02-07T02:09:54.677Z</updated>
<content type="html"><![CDATA[<p>服务端代码</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);<br><span class="hljs-keyword">const</span> multiparty = <span class="hljs-built_in">require</span>(<span class="hljs-string">'multiparty'</span>);<br><span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);<br><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);<br><span class="hljs-keyword">const</span> os = <span class="hljs-built_in">require</span>(<span class="hljs-string">'os'</span>);<br><br><span class="hljs-keyword">const</span> app = <span class="hljs-title function_">express</span>();<br><br><span class="hljs-comment">// 确保上传目录存在</span><br>app.<span class="hljs-title function_">post</span>(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =></span> {<br> <span class="hljs-keyword">const</span> form = <span class="hljs-keyword">new</span> multiparty.<span class="hljs-title class_">Form</span>();<br> form.<span class="hljs-property">uploadDir</span> = <span class="hljs-title function_">generateSavePath</span>(req); <span class="hljs-comment">// 设置上传目录</span><br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(form.<span class="hljs-property">uploadDir</span>)<br> <span class="hljs-keyword">if</span> (!fs.<span class="hljs-title function_">existsSync</span>(form.<span class="hljs-property">uploadDir</span>)) {<br> fs.<span class="hljs-title function_">mkdirSync</span>(form.<span class="hljs-property">uploadDir</span>, {<span class="hljs-attr">recursive</span>: <span class="hljs-literal">true</span>});<br> }<br> form.<span class="hljs-property">keepExtensions</span> = <span class="hljs-literal">true</span>; <span class="hljs-comment">// 保持文件扩展名</span><br> form.<span class="hljs-title function_">parse</span>(req, <span class="hljs-function">(<span class="hljs-params">err, fields, files</span>) =></span> {<br> <span class="hljs-keyword">if</span> (err) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">'Error parsing form:'</span>, err);<br> <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">500</span>).<span class="hljs-title function_">json</span>({<span class="hljs-attr">error</span>: <span class="hljs-string">'Error parsing form'</span>});<br> }<br><br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Fields:'</span>, fields);<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Files:'</span>, files);<br><br> res.<span class="hljs-title function_">json</span>({<span class="hljs-attr">message</span>: <span class="hljs-string">'File uploaded successfully.'</span>});<br> });<br>});<br><span class="hljs-keyword">const</span> <span class="hljs-title function_">generateSavePath</span> = (<span class="hljs-params">req</span>) => {<br> <span class="hljs-keyword">const</span> vpnStart = req.<span class="hljs-property">headers</span>[<span class="hljs-string">'vpn_start_time'</span>];<br> <span class="hljs-keyword">const</span> appName = req.<span class="hljs-property">headers</span>[<span class="hljs-string">'app_name'</span>];<br> <span class="hljs-keyword">const</span> ipAndPort = req.<span class="hljs-property">headers</span>[<span class="hljs-string">'ipandport'</span>];<br> <span class="hljs-keyword">return</span> path.<span class="hljs-title function_">join</span>(__dirname, <span class="hljs-string">'uploads'</span>, vpnStart, appName, ipAndPort);<br>};<br><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">getLocalIP</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">const</span> interfaces = os.<span class="hljs-title function_">networkInterfaces</span>();<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> name <span class="hljs-keyword">of</span> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">keys</span>(interfaces)) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> net <span class="hljs-keyword">of</span> interfaces[name]) {<br> <span class="hljs-keyword">if</span> (net.<span class="hljs-property">family</span> === <span class="hljs-string">'IPv4'</span> && !net.<span class="hljs-property">internal</span>) {<br> <span class="hljs-keyword">return</span> net.<span class="hljs-property">address</span>;<br> }<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-string">'0.0.0.0'</span>;<br>}<br><br>app.<span class="hljs-title function_">listen</span>(<span class="hljs-number">3000</span>, <span class="hljs-function">() =></span> {<br> <span class="hljs-keyword">const</span> localIP = <span class="hljs-title function_">getLocalIP</span>();<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Server is running on http://<span class="hljs-subst">${localIP}</span>:3000`</span>);<br>});<br><br></code></pre></td></tr></table></figure><p>下载运行必要npm包</p><p><code>npm install express</code></p><p><code>npm install multiparty</code></p><p><code>npm install fs</code></p><p><code>npm install path</code></p><p><code>npm install os</code></p><p>运行</p><p><code>node server.js</code></p>]]></content>
<summary type="html"><p>服务端代码</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</sp</summary>
</entry>
<entry>
<title>Tunnelblick身份验证自动通过Google Authenticator Token(谷歌验证器验证码)</title>
<link href="https://cason.work/2024/07/01/Tunnelblick%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81%E8%87%AA%E5%8A%A8%E9%80%9A%E8%BF%87Google-Authenticator-Token-%E8%B0%B7%E6%AD%8C%E9%AA%8C%E8%AF%81%E5%99%A8%E9%AA%8C%E8%AF%81%E7%A0%81/"/>
<id>https://cason.work/2024/07/01/Tunnelblick%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81%E8%87%AA%E5%8A%A8%E9%80%9A%E8%BF%87Google-Authenticator-Token-%E8%B0%B7%E6%AD%8C%E9%AA%8C%E8%AF%81%E5%99%A8%E9%AA%8C%E8%AF%81%E7%A0%81/</id>
<published>2024-07-01T10:34:27.000Z</published>
<updated>2025-02-07T02:09:54.672Z</updated>
<content type="html"><![CDATA[<p>文档地址</p><blockquote><p><a href="https://tunnelblick.net/cUsingScripts.html">Using Scripts - Tunnelblick | Free open source OpenVPN VPN client server software GUI for Mac OS X.</a></p><p><a href="https://tunnelblick.net/cMultiFactorAuthentication.html">https://tunnelblick.net/cMultiFactorAuthentication.html</a></p></blockquote><p>原理大概就是:Tunnelblick开启了Google Authenticator Token(谷歌验证器验证码)MFA验证,可以通过配置了static-challenge-response.user.sh,自动从脚本里获取stdout来输入到Google Authenticator Token(谷歌验证器验证码)</p><h4 id="提取绑定二维码获取以下信息,然后提取secret"><a href="#提取绑定二维码获取以下信息,然后提取secret" class="headerlink" title="提取绑定二维码获取以下信息,然后提取secret"></a>提取绑定二维码获取以下信息,然后提取secret</h4><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ruby"><span class="hljs-symbol">otpauth:</span>/<span class="hljs-regexp">/totp/xxxxx</span><span class="hljs-symbol">:xxxxx?secret=<secret>&issuer=<issuer></span><br></code></pre></td></tr></table></figure><h4 id="把secret存储到钥匙串访问(Keychain)"><a href="#把secret存储到钥匙串访问(Keychain)" class="headerlink" title="把secret存储到钥匙串访问(Keychain)"></a>把secret存储到钥匙串访问(Keychain)</h4><ul><li>命令行添加</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">/usr/bin/security add-generic-password -s "Tunnelblick-Auth-****" -a "otp-secret" -w "add-your-otp-secret-here"<br></code></pre></td></tr></table></figure><ul><li>设置-钥匙串访问-添加</li></ul><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240701192824848.png" alt="image-20240701192824848"></p><h4 id="安装oath-toolkit"><a href="#安装oath-toolkit" class="headerlink" title="安装oath-toolkit"></a>安装oath-toolkit</h4><blockquote><p>OATH Toolkit提供了构建一次性密码身份验证系统的组件。支持的技术包括基于事件的HOTP算法(RFC 4226)、基于时间的TOTP算法(RFC 6238)和可移植对称密钥容器(PSKC, RFC 6030),用于管理密钥数据</p></blockquote><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs mipsasm"><span class="hljs-keyword">brew </span><span class="hljs-keyword">install </span>oath-toolkit<br></code></pre></td></tr></table></figure><h4 id="在-x2F-Library-x2F-Application-Support-x2F-Tunnelblick-x2F-Shared-x2F-lt-gt-tblk-x2F-Contents-x2F-Resources目录下创建static-challenge-response-user-sh"><a href="#在-x2F-Library-x2F-Application-Support-x2F-Tunnelblick-x2F-Shared-x2F-lt-gt-tblk-x2F-Contents-x2F-Resources目录下创建static-challenge-response-user-sh" class="headerlink" title="在 ~/Library/Application Support/Tunnelblick/Shared/<********>.tblk/Contents/Resources目录下创建static-challenge-response.user.sh"></a>在 ~/Library/Application Support/Tunnelblick/Shared/<********>.tblk/Contents/Resources目录下创建static-challenge-response.user.sh</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs shell"><span class="hljs-meta prompt_">#</span><span class="language-bash">!/bin/bash -e</span><br>otp_pw_name="Tunnelblick-Auth-<****>"<br>otp_pw_account="otp-secret"<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">Get otp-secret from keychain:</span><br>if ! otp_secret=$(/usr/bin/security find-generic-password -w -s "$otp_pw_name" -a "$otp_pw_account"); then<br> exit 1<br>fi<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">Generate otp code</span><br>otp_code=$(/opt/homebrew/Cellar/oath-toolkit/2.6.11/bin/oathtool --totp -b "$otp_secret")<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">Output the otp_code without a trailing newline</span><br>printf "%s" "$otp_code"<br></code></pre></td></tr></table></figure><p>然后就可以了自动输入谷歌验证码连接了</p>]]></content>
<summary type="html"><p>文档地址</p>
<blockquote>
<p><a href="https://tunnelblick.net/cUsingScripts.html">Using Scripts - Tunnelblick | Free open source OpenVPN VPN </summary>
</entry>
<entry>
<title>对深度链接的简单理解</title>
<link href="https://cason.work/2024/06/24/%E5%AF%B9%E6%B7%B1%E5%BA%A6%E9%93%BE%E6%8E%A5%E7%9A%84%E7%AE%80%E5%8D%95%E7%90%86%E8%A7%A3/"/>
<id>https://cason.work/2024/06/24/%E5%AF%B9%E6%B7%B1%E5%BA%A6%E9%93%BE%E6%8E%A5%E7%9A%84%E7%AE%80%E5%8D%95%E7%90%86%E8%A7%A3/</id>
<published>2024-06-24T09:54:30.000Z</published>
<updated>2025-06-24T10:12:28.987Z</updated>
<content type="html"><![CDATA[<h3 id="深度链接(DeepLink)"><a href="#深度链接(DeepLink)" class="headerlink" title="深度链接(DeepLink)"></a>深度链接(DeepLink)</h3><p>深度链接是一种基于URI架构的技术协议,允许直接跳转到应用内的特定功能模块或内容页面(如微信扫码 <code>weixin://dl/scan</code>),而不仅是简单唤起App。其核心价值在于解决两大痛点:</p><ol><li><strong>打破应用孤岛</strong>:实现跨应用的无缝跳转(如广告跳转电商商品页)</li><li><strong>精准场景直达</strong>:避免用户手动查找路径(直接打开抖音特定视频)</li></ol><h4 id="技术实现方式"><a href="#技术实现方式" class="headerlink" title="技术实现方式"></a>技术实现方式</h4><ul><li>URL Scheme(基础方案):<ul><li>格式:<code>[scheme]://[host]/[path]?[query]</code></li><li>缺点:无法处理未安装场景,多应用冲突时需用户手动选择</li></ul></li><li>平台级方案:<ul><li>Android App Links / iOS Universal Links</li><li>使用HTTP/HTTPS链接(如 <code>https://app.com/product/123</code>)</li><li>优势:未安装时自动跳转网页,安装后直通App内容</li></ul></li></ul><hr><h3 id="延迟深度链接(Deferred-Deeplink)"><a href="#延迟深度链接(Deferred-Deeplink)" class="headerlink" title="延迟深度链接(Deferred Deeplink)"></a>延迟深度链接(Deferred Deeplink)</h3><p>当用户点击链接时若未安装应用,传统深度链接会失效。<strong>延迟深度链接通过上下文还原技术,实现安装启动后自动跳转到原始目标页</strong>,典型流程如下:</p><ol><li>用户在广告H5页点击「立即购买」</li><li>系统检测应用未安装 → 引导下载</li><li>安装后首次启动时自动打开商品购买页</li></ol><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20250624175615323.png" alt="image-20250624175615323"></p><h4 id="关键技术实现"><a href="#关键技术实现" class="headerlink" title="关键技术实现"></a>关键技术实现</h4><table><thead><tr><th align="center">技术方案</th><th align="center">实现原理</th><th align="center">适用场景</th></tr></thead><tbody><tr><td align="center">剪贴板数据传递</td><td align="center">下载时将参数加密存入剪贴板(点击下载的时候,将场景中的一些参数记录下,生成一条链接或者更为复杂得多口令码,启动的时候从剪贴板中读取参数)</td><td align="center">安卓/iOS通用</td></tr><tr><td align="center">设备指纹匹配</td><td align="center">记录设备ID/IP,云端匹配用户行为(将当前网络的IP、系统、可以获取的ID信息记录下来,某个时间段可以匹配上的用户可以认为是同一个用户。)</td><td align="center">广告投放场景</td></tr></tbody></table><blockquote><p>典型案例:小红书H5页→下载→启动后自动打开《10分钟早餐》笔记</p></blockquote><hr><h3 id="上下文深度链接(Contextual-Deeplink)"><a href="#上下文深度链接(Contextual-Deeplink)" class="headerlink" title="上下文深度链接(Contextual Deeplink)"></a>上下文深度链接(Contextual Deeplink)</h3><p>在基础深度链接上增加<strong>环境参数传递能力</strong>,实现智能化场景适配:</p><figure class="highlight 1c"><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><code class="hljs 1c"><span class="hljs-comment">// 示例:携程酒店深度链接携带用户偏好</span><br>ctrip:<span class="hljs-comment">//hotels/detail?id=12345&</span><br> checkin=<span class="hljs-number">2023</span>-<span class="hljs-number">06</span>-<span class="hljs-number">20</span><span class="hljs-meta">&</span><br> adult=<span class="hljs-number">2</span><span class="hljs-meta">&</span><br> children=<span class="hljs-number">1</span><span class="hljs-meta">&</span><br> utm_source=weibo_ad<br></code></pre></td></tr></table></figure><h4 id="核心价值"><a href="#核心价值" class="headerlink" title="核心价值"></a>核心价值</h4><ol><li><strong>动态内容加载</strong>:根据参数展示定制化页面(如不同促销活动)</li><li><strong>用户行为跟踪</strong>:通过UTM参数追踪转化路径</li><li><strong>跨平台一致性</strong>:保持网页与App体验无缝衔接</li><li><strong>条件路由</strong>:根据设备类型/地理位置自动分流</li></ol><blockquote><p>实际应用:美团在不同城市广告中植入地理参数,实现「点击即打开本地优惠页」</p></blockquote>]]></content>
<summary type="html"><h3 id="深度链接(DeepLink)"><a href="#深度链接(DeepLink)" class="headerlink" title="深度链接(DeepLink)"></a>深度链接(DeepLink)</h3><p>深度链接是一种基于URI架构的技术协议,允许</summary>
<category term="广告" scheme="https://cason.work/categories/%E5%B9%BF%E5%91%8A/"/>
<category term="广告" scheme="https://cason.work/tags/%E5%B9%BF%E5%91%8A/"/>
<category term="深度链接" scheme="https://cason.work/tags/%E6%B7%B1%E5%BA%A6%E9%93%BE%E6%8E%A5/"/>
<category term="延迟深度链接" scheme="https://cason.work/tags/%E5%BB%B6%E8%BF%9F%E6%B7%B1%E5%BA%A6%E9%93%BE%E6%8E%A5/"/>
<category term="上下文深度链接" scheme="https://cason.work/tags/%E4%B8%8A%E4%B8%8B%E6%96%87%E6%B7%B1%E5%BA%A6%E9%93%BE%E6%8E%A5/"/>
</entry>
<entry>
<title>移动测量合作伙伴(MMP)的广告归因链路</title>
<link href="https://cason.work/2024/05/30/%E7%A7%BB%E5%8A%A8%E6%B5%8B%E9%87%8F%E5%90%88%E4%BD%9C%E4%BC%99%E4%BC%B4%EF%BC%88MMP%EF%BC%89%E7%9A%84%E5%B9%BF%E5%91%8A%E5%BD%92%E5%9B%A0%E9%93%BE%E8%B7%AF/"/>
<id>https://cason.work/2024/05/30/%E7%A7%BB%E5%8A%A8%E6%B5%8B%E9%87%8F%E5%90%88%E4%BD%9C%E4%BC%99%E4%BC%B4%EF%BC%88MMP%EF%BC%89%E7%9A%84%E5%B9%BF%E5%91%8A%E5%BD%92%E5%9B%A0%E9%93%BE%E8%B7%AF/</id>
<published>2024-05-30T07:02:22.000Z</published>
<updated>2025-02-07T02:09:54.678Z</updated>
<content type="html"><![CDATA[<h3 id="移动测量合作伙伴(MMP)的广告归因链路"><a href="#移动测量合作伙伴(MMP)的广告归因链路" class="headerlink" title="移动测量合作伙伴(MMP)的广告归因链路"></a>移动测量合作伙伴(MMP)的广告归因链路</h3><p>移动测量合作伙伴(Mobile Measurement Partner,简称MMP)是一类专门提供移动应用广告归因和分析服务的第三方平台。MMP的主要功能是帮助广告主和应用开发者跟踪和分析广告效果,确定用户的来源,从而优化广告投放策略,提高广告投放的投资回报率(ROI)。广告归因是MMP的核心功能之一,通过收集和分析用户的点击和安装数据,确定用户是通过哪个广告平台、广告活动甚至具体的广告创意下载并安装了应用。</p><h4 id="无链接广告的归因链路"><a href="#无链接广告的归因链路" class="headerlink" title="无链接广告的归因链路"></a>无链接广告的归因链路</h4><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240530145119242.png" alt="image-20240530145119242"></p><p>无链接广告通常指那些不包含深度链接或特定归因链接的广告。这类广告的归因依赖于设备标识符和时间窗口。以下是无链接广告的归因链路详细过程:</p><ol><li><strong>广告展示与点击</strong>:<ul><li>用户在广告平台(如Google、Facebook)上看到广告并点击。</li><li>广告平台记录下点击事件,包括设备ID(如IDFA、GAID)和点击时间。</li><li>为了能够进行准确的归因,广告平台(如Google、Facebook)在用户点击广告时,会将点击数据(如点击时间、设备ID等)发送到MMP。</li></ul></li><li><strong>应用下载与安装</strong>:<ul><li>用户被引导到应用商店,下载并安装应用。</li></ul></li><li><strong>应用激活与MMP SDK的作用</strong>:<ul><li>用户首次打开应用时,应用内集成的MMP SDK(如Adjust、AppsFlyer)会触发一次激活事件。</li><li>SDK收集设备标识符、安装时间等信息,并发送到MMP服务器。</li></ul></li><li><strong>广告归因处理</strong>:<ul><li>MMP服务器将接收到的激活数据与之前从广告平台获取的点击数据进行匹配。</li><li>通过时间窗口(通常为点击后的24至48小时)和设备标识符,MMP确定用户的安装行为与哪个点击事件相关联。</li></ul></li><li><strong>归因报告</strong>:<ul><li>MMP回传数据告知是通过哪个广告平台的广告安装了应用。</li></ul></li></ol><p>这种归因方法依赖于设备标识符和时间窗口来推断用户行为,可能会因设备ID的变化或其他因素影响归因准确性。</p><h4 id="有链接广告的归因链路"><a href="#有链接广告的归因链路" class="headerlink" title="有链接广告的归因链路"></a>有链接广告的归因链路</h4><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240530145130921.png" alt="image-20240530145130921"></p><p>有链接广告包含深度链接或特定的归因链接,例如Branch链接或AppsFlyer Onelink。这些链接能直接追踪用户的点击行为,使归因过程更为精确。以下是有链接广告的归因链路详细过程:</p><ol><li><strong>广告展示与点击</strong>:<ul><li>用户在广告平台上看到包含归因链接的广告(如Branch链接或AppsFlyer Onelink)。</li><li>用户点击广告链接。</li></ul></li><li><strong>链接重定向与归因信息收集</strong>:<ul><li>点击广告链接后,用户会被重定向到归因平台的中间页面。</li><li>中间页面收集用户点击信息,包括设备ID、点击时间、广告来源等。</li></ul></li><li><strong>重定向到应用商店</strong>:<ul><li>收集完必要信息后,用户被重定向到应用商店的应用下载页面。</li></ul></li><li><strong>应用下载与安装</strong>:<ul><li>用户下载并安装应用。</li></ul></li><li><strong>应用激活与SDK的作用</strong>:<ul><li>用户首次打开应用时,应用内集成的归因平台SDK(如Branch SDK、AppsFlyer SDK)会触发一次激活事件。</li><li>SDK将设备标识符和安装时间等信息发送到归因平台服务器。</li></ul></li><li><strong>归因匹配</strong>:<ul><li>归因平台服务器将激活事件与之前收集的点击数据进行匹配。</li><li>由于链接中包含了具体的归因信息,归因平台能够直接将用户安装归因到特定的广告点击。</li></ul></li><li><strong>归因报告</strong>:<ul><li>归因平台回调告知是通过哪个广告平台和具体的广告链接下载并安装了应用。</li></ul></li></ol><p>有链接广告的归因方法由于使用了明确的点击信息和广告来源,归因过程更加直接和精确。</p><h4 id="无链接广告与有链接广告的区别"><a href="#无链接广告与有链接广告的区别" class="headerlink" title="无链接广告与有链接广告的区别"></a>无链接广告与有链接广告的区别</h4><ol><li><strong>归因信息收集</strong>:<ul><li><strong>无链接广告</strong>:依赖于设备标识符和时间窗口进行归因,需要更多的匹配步骤。</li><li><strong>有链接广告</strong>:通过深度链接或归因链接直接收集点击信息,归因过程更加直接和精确。</li></ul></li><li><strong>中间页面重定向</strong>:<ul><li><strong>无链接广告</strong>:用户点击广告后直接跳转到应用商店,归因信息通过后台匹配。</li><li><strong>有链接广告</strong>:用户点击广告后首先重定向到归因平台的中间页面,收集归因信息后再跳转到应用商店。</li></ul></li><li><strong>准确性</strong>:<ul><li><strong>无链接广告</strong>:归因准确性可能受限于设备标识符的变化和点击后的时间窗口。</li><li><strong>有链接广告</strong>:归因更准确,因为链接中包含了明确的点击信息和广告来源。</li></ul></li><li><strong>归因速度</strong>:<ul><li><strong>无链接广告</strong>:归因过程可能需要更多时间来匹配和确认。</li><li><strong>有链接广告</strong>:归因过程更加迅速,因为点击信息在链接中已经明确。</li></ul></li></ol>]]></content>
<summary type="html"><h3 id="移动测量合作伙伴(MMP)的广告归因链路"><a href="#移动测量合作伙伴(MMP)的广告归因链路" class="headerlink" title="移动测量合作伙伴(MMP)的广告归因链路"></a>移动测量合作伙伴(MMP)的广告归因链路</h3><</summary>
<category term="广告" scheme="https://cason.work/tags/%E5%B9%BF%E5%91%8A/"/>
</entry>
<entry>
<title>Springboot-Redisson实现延迟消息队列</title>
<link href="https://cason.work/2024/04/29/Springboot-Redisson%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/"/>
<id>https://cason.work/2024/04/29/Springboot-Redisson%E5%AE%9E%E7%8E%B0%E5%BB%B6%E8%BF%9F%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/</id>
<published>2024-04-29T03:42:13.000Z</published>
<updated>2025-02-07T02:09:54.670Z</updated>
<content type="html"><![CDATA[<p>Redisson是一个基于Redis的Java驻留库,旨在简化Java应用程序对Redis的操作。它提供了丰富的功能和易于使用的API,使得在Java应用中集成Redis变得更加简单和高效。Redisson的主要功能包括分布式对象、分布式锁、分布式队列、分布式调度器等,使得在分布式环境中进行协作变得更加容易。</p><p>延迟队列是Redisson提供的一个功能强大的组件之一。它允许开发者在指定的延迟时间之后自动执行任务,这对于需要在一定时间后执行操作的应用程序非常有用,如消息通知、订单处理、定时任务等。</p><p>Redisson实现延迟队列的原理基于Redis的有序集合(Sorted Set)和Redis的过期时间机制</p><h3 id="生产者"><a href="#生产者" class="headerlink" title="生产者"></a>生产者</h3><p>因为<code>RDelayedQueue</code>并不直接存储任务。相反,它是一种装饰器或包装器,用于添加延迟功能到一个标准的Redisson队列(如<code>RBlockingQueue</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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Slf4j</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RedissonDelayQueueProducer</span> {<br> <span class="hljs-meta">@Autowired</span><br> <span class="hljs-keyword">private</span> RedissonClient redissonClient;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addDelayedTask</span><span class="hljs-params">(String topic, Object task, <span class="hljs-type">long</span> delay, TimeUnit timeUnit)</span> {<br> log.info(<span class="hljs-string">"redis的延迟队列 {} (添加)的key:{},time:{},当前时间:{}"</span>, topic, task, delay, LocalDateTimeUtil.formatNormal(LocalDateTime.now()));<br> redissonClient.getDelayedQueue(redissonClient.getBlockingQueue(topic)).offerAsync(task, delay, timeUnit);<br> }<br><br> <span class="hljs-keyword">public</span> Boolean <span class="hljs-title function_">cancelDelayedTask</span><span class="hljs-params">(String topic, Object task)</span> {<br> log.info(<span class="hljs-string">"redis的延迟队列{} (移除)的key:{},当前时间:{}"</span>, topic, task, LocalDateTimeUtil.formatNormal(LocalDateTime.now()));<br> <span class="hljs-comment">// 从延迟队列中移除任务</span><br> <span class="hljs-keyword">return</span> redissonClient.getDelayedQueue(redissonClient.getBlockingQueue(topic)).remove(task);<br> }<br>}<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Slf4j</span><br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RedissonDelayedQueueConsumer</span> {<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">static</span> <span class="hljs-type">String</span> <span class="hljs-variable">topic</span> <span class="hljs-operator">=</span> <span class="hljs-string">"topic"</span>;<br> <span class="hljs-meta">@Autowired</span><br> <span class="hljs-keyword">private</span> RedissonClient redissonClient;<br><br> <span class="hljs-meta">@PostConstruct</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">init</span><span class="hljs-params">()</span> {<br> <span class="hljs-comment">// 创建延迟队列</span><br> log.info(<span class="hljs-string">"{} 开始监听"</span>, topic);<br> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -> {<br> <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {<br> <span class="hljs-keyword">try</span> {<br> RBlockingQueue<Object> aaa = redissonClient.getBlockingQueue(topic);<br> RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(aaa);<br> <span class="hljs-comment">// 从延迟队列中取出任务</span><br> <span class="hljs-type">Object</span> <span class="hljs-variable">take</span> <span class="hljs-operator">=</span> aaa.take();<br> <span class="hljs-comment">// 处理延迟任务,例如执行某个操作</span><br> log.info(<span class="hljs-string">"redis的延迟队列:{},当前时间:{}"</span>, take, LocalDateTimeUtil.formatNormal(LocalDateTime.now()));<br> } <span class="hljs-keyword">catch</span> (Exception e) {<br> log.error(<span class="hljs-string">"redis的延迟队列抛出异常"</span>, e);<br> }<br> }<br> }).start();<br> }<br>}<br><br></code></pre></td></tr></table></figure><h4 id="效果展示"><a href="#效果展示" class="headerlink" title="效果展示"></a>效果展示</h4><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240429115537300.png" alt="image-20240429115537300"></p><h4 id="redisson配置"><a href="#redisson配置" class="headerlink" title="redisson配置"></a>redisson配置</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">dependency</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.redisson<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>redisson<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">version</span>></span>3.17.4<span class="hljs-tag"></<span class="hljs-name">version</span>></span><br><span class="hljs-tag"></<span class="hljs-name">dependency</span>></span><br><span class="hljs-tag"><<span class="hljs-name">dependency</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-data-redis<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br><span class="hljs-tag"></<span class="hljs-name">dependency</span>></span><br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RedissonConfig</span> {<br><br> <span class="hljs-meta">@Value("${spring.data.redis.host}")</span><br> <span class="hljs-keyword">private</span> String redisHost;<br> <span class="hljs-meta">@Value("${spring.data.redis.port}")</span><br> <span class="hljs-keyword">private</span> String redisPort;<br> <span class="hljs-meta">@Value("${spring.data.redis.password}")</span><br> <span class="hljs-keyword">private</span> String password;<br><br> <span class="hljs-meta">@Bean(destroyMethod = "shutdown")</span><br> <span class="hljs-keyword">public</span> RedissonClient <span class="hljs-title function_">redissonClient</span><span class="hljs-params">()</span> {<br> <span class="hljs-type">Config</span> <span class="hljs-variable">config</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Config</span>();<br> config.useSingleServer().setAddress(<span class="hljs-string">"redis://"</span> + redisHost + <span class="hljs-string">":"</span> + redisPort);<br> <span class="hljs-keyword">if</span>(StringUtils.isBlank(password)) {<br> config.useSingleServer().setPassword(<span class="hljs-literal">null</span>);<br> } <span class="hljs-keyword">else</span> {<br> config.useSingleServer().setPassword(password);<br> }<br> <span class="hljs-keyword">return</span> Redisson.create(config);<br> }<br>}<br></code></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>Redisson是一个基于Redis的Java驻留库,旨在简化Java应用程序对Redis的操作。它提供了丰富的功能和易于使用的API,使得在Java应用中集成Redis变得更加简单和高效。Redisson的主要功能包括分布式对象、分布式锁、分布式队列、分布式调度器等,使得</summary>
<category term="Springboot" scheme="https://cason.work/categories/Springboot/"/>
<category term="Redisson" scheme="https://cason.work/tags/Redisson/"/>
<category term="Redis" scheme="https://cason.work/tags/Redis/"/>
</entry>
<entry>
<title>实用的 IntelliJ 插件</title>
<link href="https://cason.work/2024/04/22/%E5%AE%9E%E7%94%A8%E7%9A%84-IntelliJ-%E6%8F%92%E4%BB%B6/"/>
<id>https://cason.work/2024/04/22/%E5%AE%9E%E7%94%A8%E7%9A%84-IntelliJ-%E6%8F%92%E4%BB%B6/</id>
<published>2024-04-22T03:13:42.000Z</published>
<updated>2025-02-07T02:09:54.676Z</updated>
<content type="html"><![CDATA[<h2 id="Translation"><a href="#Translation" class="headerlink" title="Translation"></a>Translation</h2><p>最好用过的翻译工具</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422111533246.png" alt="image-20240422111533246"></p><h2 id="RoboPOJOGenerator"><a href="#RoboPOJOGenerator" class="headerlink" title="RoboPOJOGenerator"></a>RoboPOJOGenerator</h2><p>IntelliJ Idea 和 Android Studio 的插件,用于将 JSON 转换为 POJO。 从 JSON 生成 Java、Java Records 和 Kotlin 的 POJO 文件:GSON、FastJSON、AutoValue(GSON)、Logan Square、Jackson、Lombok、Jakarta JSON Binding、空注解模板。支持:原始类型、多个内部 JSONArray。</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422112843612.png" alt="image-20240422112843612"></p><h2 id="Java-Bean-to-Json"><a href="#Java-Bean-to-Json" class="headerlink" title="Java Bean to Json"></a>Java Bean to Json</h2><p> Java类对象转换成一个 json</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422111653856.png" alt="image-20240422111653856"></p><h2 id="SequenceDiagram"><a href="#SequenceDiagram" class="headerlink" title="SequenceDiagram"></a>SequenceDiagram</h2><p> Java类直接生成时序图</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422111717408.png" alt="image-20240422111717408"></p><h2 id="CamelCase"><a href="#CamelCase" class="headerlink" title="CamelCase"></a>CamelCase</h2><p>可以轻松地在kebab-case、SNAKE_CASE、PascalCase、camelCase、snake_case或者space case之间切换。查看编辑菜单或使用 ⇧ + ⌥ + U / Shift + Alt + U。</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422111856198.png" alt="image-20240422111856198"></p><h2 id="GenerateAllSetter"><a href="#GenerateAllSetter" class="headerlink" title="GenerateAllSetter"></a>GenerateAllSetter</h2><p>一键调用一个对象的所有的set方法,get方法等,在方法上生成两个对象的转换</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422112011976.png" alt="image-20240422112011976"></p><h2 id="Key-Promoter-X"><a href="#Key-Promoter-X" class="headerlink" title="Key Promoter X"></a>Key Promoter X</h2><p>Key Promoter X能帮助你在工作时学习必要的快捷键。当你在IDE内使用鼠标点击按钮时,Key Promoter X会显示你应该使用的键盘快捷键。这为学习如何用键盘按键替换繁琐的鼠标操作提供了一种简单的方法,并有助于过渡到更快速、无需鼠标的开发方式。Key Promoter X工具窗口会显示你最常使用的鼠标操作的清单,并直接提供可替代的快捷键。对于没有快捷键的按钮,Key Promoter X会提示你直接创建一个快捷键。</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422112110446.png" alt="image-20240422112110446"></p><h2 id="LeetCode-Editor"><a href="#LeetCode-Editor" class="headerlink" title="LeetCode Editor"></a>LeetCode Editor</h2><p>支持leetcode.com和leetcode.cn,可以测试和提交问题</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422112201974.png" alt="image-20240422112201974"></p><h2 id="Maven-Helper"><a href="#Maven-Helper" class="headerlink" title="Maven Helper"></a>Maven Helper</h2><p>一个必备的 Maven 工作插件。 </p><ul><li>分析和排除冲突依赖的简便方式。 </li><li>在包含当前文件的模块或根模块上运行/调试 Maven 目标的操作。 </li><li>在当前 Maven 模块路径打开终端的操作。 运行/调试当前测试文件的操作。</li></ul><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422112321393.png" alt="image-20240422112321393"></p><h2 id="MyBatis-Log-Free"><a href="#MyBatis-Log-Free" class="headerlink" title="MyBatis Log Free"></a>MyBatis Log Free</h2><p>将MyBatis的SQL日志还原为原始的完整可执行SQL。</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422112434638.png" alt="image-20240422112434638"></p><h2 id="MyBatisX"><a href="#MyBatisX" class="headerlink" title="MyBatisX"></a>MyBatisX</h2><p>MybatisX插件功能:</p><ul><li>Mapper和XML之间可以相互跳转</li><li>提示MyBatis.xml和Mapper.xml文件</li><li>Mapper和XML支持像JPA一样的自动提示(参考MybatisCodeHelperPro)</li><li>集成MyBatis Generator GUI(从免费MyBatis插件复制)</li></ul><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422112524955.png" alt="image-20240422112524955"></p><h2 id="Rainbow-Brackets"><a href="#Rainbow-Brackets" class="headerlink" title="Rainbow Brackets"></a>Rainbow Brackets</h2><p>彩虹色的花括号</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240422112711489.png" alt="image-20240422112711489"></p>]]></content>
<summary type="html"><h2 id="Translation"><a href="#Translation" class="headerlink" title="Translation"></a>Translation</h2><p>最好用过的翻译工具</p>
<p><img src="https:/</summary>
<category term="IntelliJ" scheme="https://cason.work/tags/IntelliJ/"/>
</entry>
<entry>
<title>腾讯云大文件迁移教程-内网互联+scp</title>
<link href="https://cason.work/2024/04/15/%E8%85%BE%E8%AE%AF%E4%BA%91%E5%A4%A7%E6%96%87%E4%BB%B6%E8%BF%81%E7%A7%BB%E6%95%99%E7%A8%8B-%E5%86%85%E7%BD%91%E4%BA%92%E8%81%94-scp/"/>
<id>https://cason.work/2024/04/15/%E8%85%BE%E8%AE%AF%E4%BA%91%E5%A4%A7%E6%96%87%E4%BB%B6%E8%BF%81%E7%A7%BB%E6%95%99%E7%A8%8B-%E5%86%85%E7%BD%91%E4%BA%92%E8%81%94-scp/</id>
<published>2024-04-15T06:08:55.000Z</published>
<updated>2025-02-07T02:09:54.678Z</updated>
<content type="html"><![CDATA[<p>超过几十GB的大文件传输,通过公网会非常的慢几乎2mb/s,腾讯云提供了内网互联的功能,内网传输就会很快上100mb/s</p><h4 id="创建内网互联"><a href="#创建内网互联" class="headerlink" title="创建内网互联"></a>创建内网互联</h4><ul><li><p><a href="https://console.cloud.tencent.com/vpc/ccn">云联网 - 私有网络 - 控制台 (tencent.com)</a></p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240415141102214.png" alt="image-20240415141102214"></p></li><li><p>获取账号ID和云联网ID</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240415141202604.png" alt="image-20240415141202604"></p></li></ul><h4 id="云服务器关联云联网"><a href="#云服务器关联云联网" class="headerlink" title="云服务器关联云联网"></a>云服务器关联云联网</h4><ul><li><p>点击实例->网络信息->所属网络 点击进去</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240415141409631.png" alt="image-20240415141409631"></p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240415141540933.png" alt="image-20240415141540933"></p></li></ul><p></p><ul><li><p>输入刚刚获取的账号ID和云联网ID</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240415141546636.png" alt="image-20240415141546636"></p></li><li><p>同意加入云联网</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240415141716611.png" alt="image-20240415141716611"></p></li></ul><p>同意了以后,就可以通过腾讯云提供的内网ip进行内网数据传输了</p><h4 id="数据传输"><a href="#数据传输" class="headerlink" title="数据传输"></a>数据传输</h4><ul><li><p>借助screen来后台执行传输job,断开ssh也不会停止</p><p>Linux 的 <code>screen</code> 命令是一种非常实用的工具,它可以让用户在单个终端窗口中创建多个会话,并能够在这些会话之间自由切换。<code>screen</code> 还能够让这些会话在用户断开连接后继续运行,这对于远程工作和长时间运行的进程非常有用。</p><p>如果没有安装screen可以使用yum安装</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">yum install screen<br></code></pre></td></tr></table></figure></li><li><p>创建一个名为restore的回话</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">screen -S restore<br></code></pre></td></tr></table></figure></li><li><p>使用scp从指定服务器拷贝数据到本地</p><p><code>scp</code> 是一个在 Linux 和 Unix 系统中用于在不同主机之间安全地传输文件的命令行工具</p><p>命令结构是:scp [选项] [源文件] [目标位置]</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">scp -r xxx.xx.xx.xx:/home/data /tmp/data<br></code></pre></td></tr></table></figure></li><li><p>输入ctrl + z 暂停</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">[1]+ 已停止 scp -r xxx.xx.xx.xx:/home/data /tmp/data<br></code></pre></td></tr></table></figure></li><li><p>查看任务,输入命令:jobs</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">[root@localhost /]# jobs<br>[1]+ 已停止 scp -r xxx.xx.xx.xx:/home/data /tmp/data<br></code></pre></td></tr></table></figure></li><li><p>bg将其放入后台,输入命令:bg %1</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">[root@localhost /]# bg %1<br>[1]+ scp -r xxx.xx.xx.xx:/home/data /tmp/data<br></code></pre></td></tr></table></figure></li><li><p>查看任务是否在后台</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs awk">[root@localhost /]<span class="hljs-comment"># jobs</span><br>[<span class="hljs-number">1</span>]+ 运行中 scp -r xxx.xx.xx.xx:<span class="hljs-regexp">/home/</span>data <span class="hljs-regexp">/tmp/</span>data<br></code></pre></td></tr></table></figure></li></ul>]]></content>
<summary type="html"><p>超过几十GB的大文件传输,通过公网会非常的慢几乎2mb&#x2F;s,腾讯云提供了内网互联的功能,内网传输就会很快上100mb&#x2F;s</p>
<h4 id="创建内网互联"><a href="#创建内网互联" class="headerlink" title="创建</summary>
</entry>
<entry>
<title>Springboot Guava Eventbus简单使用</title>
<link href="https://cason.work/2024/04/12/Springboot-Guava-Eventbus%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/"/>
<id>https://cason.work/2024/04/12/Springboot-Guava-Eventbus%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/</id>
<published>2024-04-12T10:16:31.000Z</published>
<updated>2025-02-07T02:09:54.670Z</updated>
<content type="html"><![CDATA[<h4 id="简单介绍"><a href="#简单介绍" class="headerlink" title="简单介绍"></a>简单介绍</h4><p>EventBus是 Google Guava 提供的一个事件总线库,用于简化组件之间的通信。它基于发布-订阅模式,允许组件在不直接依赖彼此的情况下进行通信。在一个应用程序中,当某个组件触发了一个事件,所有订阅了该事件的组件都会收到通知并执行相应的操作。</p><p>简单来说,<code>EventBus</code> 可以帮助你解耦代码,使得组件之间更加灵活和独立。</p><h4 id="简单使用"><a href="#简单使用" class="headerlink" title="简单使用"></a>简单使用</h4><ol><li><p><strong>事件(Event)</strong></p><p>事件是系统中某一特定状态的表示,可以是用户操作、系统状态改变等。通常是一个普通的POJO对象,用于封装事件相关的数据。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyEvent</span> {<br> <span class="hljs-keyword">private</span> String message;<br> ..........<br>}<br></code></pre></td></tr></table></figure></li><li><p><strong>事件发布者(Event Publisher)</strong></p><p>负责发布事件的组件,通常是某个服务、控制器或其他业务逻辑组件。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Slf4j</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyEventBus</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">EventBus</span> {<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">register</span><span class="hljs-params">(Object object)</span> {<br> log.info(<span class="hljs-string">"{} 订阅成功 "</span>, object.getClass().getSimpleName());<br> <span class="hljs-built_in">super</span>.register(object);<br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">unregister</span><span class="hljs-params">(Object object)</span> {<br> log.info(<span class="hljs-string">"{} 移除订阅成功 "</span>, object.getClass().getSimpleName());<br> <span class="hljs-built_in">super</span>.unregister(object);<br> }<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">post</span><span class="hljs-params">(Object event)</span> {<br> log.info(<span class="hljs-string">"notify : {}"</span>, JSON.toJSONString(event));<br> <span class="hljs-built_in">super</span>.post(event);<br> }<br>}<br></code></pre></td></tr></table></figure></li><li><p><strong>事件订阅者(Event Subscriber)</strong></p><p>负责订阅并处理特定类型事件的组件,通常是一个或多个。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyEventBusSubscribe</span>{<br><br> <span class="hljs-meta">@Autowired</span><br> <span class="hljs-keyword">private</span> MyEventBus eventBus;<br><br> <span class="hljs-meta">@PostConstruct</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">register</span><span class="hljs-params">()</span> {<br> eventBus.register(<span class="hljs-built_in">this</span>);<br> }<br><br> <span class="hljs-meta">@Subscribe</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">update</span><span class="hljs-params">(MyEvent t)</span>{<br> <span class="hljs-comment">//执行业务</span><br> }<br>}<br></code></pre></td></tr></table></figure></li><li><p><strong>使用</strong> </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//依赖注入</span><br><span class="hljs-meta">@Autowired</span><br><span class="hljs-keyword">private</span> MyEventBus myEventBus;<br><br><span class="hljs-comment">//定义消息</span><br>MyEvent myevent=<span class="hljs-keyword">new</span> <span class="hljs-title class_">MyEvent</span>();<br>myevent.setMessage(<span class="hljs-string">"message"</span>);<br><span class="hljs-comment">//发送消息</span><br>myEventBus.post(myevent);<br></code></pre></td></tr></table></figure></li></ol><h4 id="同步消费-EventBus"><a href="#同步消费-EventBus" class="headerlink" title="同步消费-EventBus"></a>同步消费-EventBus</h4><p><code>EventBus</code> 是按照先到先服务(FIFO)的顺序依次处理事件的。也就是说,当一个事件被发布后,<code>EventBus</code> 会依次通知所有订阅者并让它们处理该事件,只有当当前事件的所有订阅者处理完成后,<code>EventBus</code> 才会发布下一个事件。</p><blockquote><p>验证代码</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> com.google.common.eventbus.EventBus;<br><span class="hljs-keyword">import</span> com.google.common.eventbus.Subscribe;<br><span class="hljs-keyword">import</span> lombok.AllArgsConstructor;<br><span class="hljs-keyword">import</span> lombok.Data;<br><br><span class="hljs-keyword">import</span> java.util.Random;<br><br><span class="hljs-comment">// 定义一个事件类</span><br><span class="hljs-meta">@Data</span><br><span class="hljs-meta">@AllArgsConstructor</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MessageEvent</span> {<br> <span class="hljs-keyword">private</span> String message;<br>}<br><br><span class="hljs-comment">// 定义一个订阅者</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MessageSubscriber</span> {<br> <span class="hljs-meta">@Subscribe</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">receiveMessage</span><span class="hljs-params">(MessageEvent event)</span> <span class="hljs-keyword">throws</span> InterruptedException {<br> System.out.println(<span class="hljs-string">"Received message: "</span> + event.getMessage());<br> Thread.sleep((<span class="hljs-keyword">new</span> <span class="hljs-title class_">Random</span>().nextInt(<span class="hljs-number">5</span>) + <span class="hljs-number">1</span>) * <span class="hljs-number">1000</span>);<br> }<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">demo</span> {<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {<br> <span class="hljs-comment">// 创建一个事件总线</span><br> <span class="hljs-type">EventBus</span> <span class="hljs-variable">eventBus</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">EventBus</span>();<br> <span class="hljs-comment">// 注册订阅者</span><br> <span class="hljs-type">MessageSubscriber</span> <span class="hljs-variable">subscriber</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageSubscriber</span>();<br> eventBus.register(subscriber);<br> <span class="hljs-comment">// 发布事件</span><br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus1!"</span>));<br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus2!"</span>));<br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus3!"</span>));<br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus4!"</span>));<br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus5!"</span>));<br> }<br>}<br></code></pre></td></tr></table></figure><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240412183032548.png" alt="image-20240412183032548"></p><center>(图片展示为即使消费速度快慢不同仍依次消费)</center><h4 id="异步消费-AsyncEventBus"><a href="#异步消费-AsyncEventBus" class="headerlink" title="异步消费-AsyncEventBus"></a>异步消费-AsyncEventBus</h4><p>对于AsyncEventBus.post方法会立即将事件加入到事件队列中,但它并不会等待事件的消费者(订阅者)处理完一条消息再继续发布下一条消息。相反,它会在将事件放入队列后立即返回,允许代码继续执行。</p><p>异步事件处理的机制会确保订阅者在后台线程中处理事件,因此即使在一条事件还未被完全处理完成时,<code>AsyncEventBus</code> 也可以接受和处理其他事件。</p><p>这意味着,在使用 <code>AsyncEventBus</code> 时,即使在短时间内多次调用 <code>post</code> 方法来发布多个事件,它们也会被立即加入到事件队列中,并在后台线程中异步处理。</p><blockquote><p>验证代码</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> com.google.common.eventbus.AsyncEventBus;<br><span class="hljs-keyword">import</span> com.google.common.eventbus.Subscribe;<br><span class="hljs-keyword">import</span> lombok.AllArgsConstructor;<br><span class="hljs-keyword">import</span> lombok.Data;<br><br><span class="hljs-keyword">import</span> java.util.Random;<br><span class="hljs-keyword">import</span> java.util.concurrent.Executors;<br><br><span class="hljs-comment">// 定义一个事件类</span><br><span class="hljs-meta">@Data</span><br><span class="hljs-meta">@AllArgsConstructor</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MessageEvent</span> {<br> <span class="hljs-keyword">private</span> String message;<br>}<br><br><span class="hljs-comment">// 定义一个订阅者</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MessageSubscriber</span> {<br> <span class="hljs-meta">@Subscribe</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">receiveMessage</span><span class="hljs-params">(MessageEvent event)</span> <span class="hljs-keyword">throws</span> InterruptedException {<br> System.out.println(<span class="hljs-string">"Received message: "</span> + event.getMessage());<br> Thread.sleep((<span class="hljs-keyword">new</span> <span class="hljs-title class_">Random</span>().nextInt(<span class="hljs-number">5</span>) + <span class="hljs-number">1</span>) * <span class="hljs-number">1000</span>);<br> }<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">demo</span> {<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {<br> <span class="hljs-comment">// 创建一个异步事件总线</span><br> <span class="hljs-type">AsyncEventBus</span> <span class="hljs-variable">eventBus</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AsyncEventBus</span>(Executors.newCachedThreadPool());<br> <span class="hljs-comment">// 注册订阅者</span><br> <span class="hljs-type">MessageSubscriber</span> <span class="hljs-variable">subscriber</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageSubscriber</span>();<br> eventBus.register(subscriber);<br> <span class="hljs-comment">// 发布事件</span><br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus1!"</span>));<br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus2!"</span>));<br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus3!"</span>));<br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus4!"</span>));<br> eventBus.post(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MessageEvent</span>(<span class="hljs-string">"Hello, EventBus5!"</span>));<br> }<br>}<br></code></pre></td></tr></table></figure><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240412185436477.png" alt="image-20240412185436477"></p><center>(图片展示为消费速度快慢不同导致执行结果次序不同)</center>]]></content>
<summary type="html"><h4 id="简单介绍"><a href="#简单介绍" class="headerlink" title="简单介绍"></a>简单介绍</h4><p>EventBus是 Google Guava 提供的一个事件总线库,用于简化组件之间的通信。它基于发布-订阅模式,允许组件在</summary>
<category term="Springboot" scheme="https://cason.work/categories/Springboot/"/>
<category term="EventBus" scheme="https://cason.work/tags/EventBus/"/>
</entry>
<entry>
<title>Hexo giscus配置评论功能</title>
<link href="https://cason.work/2024/04/07/Hexo-giscus%E9%85%8D%E7%BD%AE%E8%AF%84%E8%AE%BA%E5%8A%9F%E8%83%BD/"/>
<id>https://cason.work/2024/04/07/Hexo-giscus%E9%85%8D%E7%BD%AE%E8%AF%84%E8%AE%BA%E5%8A%9F%E8%83%BD/</id>
<published>2024-04-07T09:55:23.000Z</published>
<updated>2025-02-07T02:09:54.667Z</updated>
<content type="html"><![CDATA[<blockquote><p>注意:本文以Fluid主题当示例来介绍如何配置评论,其他主题请根据对应用户手册配置</p></blockquote><p>Giscus 允许你将 GitHub Discussions 集成到你的网站上作为评论系统。这样做的好处是评论数据存储在 GitHub 上,不需要第三方服务。</p><p>官方地址:<a href="https://giscus.app/">https://giscus.app/</a></p><ul><li><p>首先先看看自己的theme/fluid下是否包含giscus相关的文件,如果fluid版本过旧没有相关文件就要先更新fluid支持giscus</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm update --save hexo-theme-fluid<br></code></pre></td></tr></table></figure></li><li><p>新建github仓库</p></li></ul><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/202205081715639.png" alt="image-20220508171452067"></p><p>(我这里已经创建过了,所以显示报错)</p><ul><li>github仓库启用Discussions功能 settings->General->Features</li></ul><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240407175728062.png" alt="image-20240407175728062"></p><ul><li>点击链接安装gisacus <a href="https://github.com/apps/giscus">https://github.com/apps/giscus</a></li></ul><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240407175530551.png" alt="image-20240407175530551"></p><ul><li>然后根据官网提示填写对应信息</li></ul><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240407175638999.png" alt="image-20240407175638999"></p><ul><li>然后就能获取到repo-id等相关信息</li></ul><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240407175938792.png" alt="image-20240407175938792"></p><ul><li>修改_config.fluid.yml文件</li></ul><figure class="highlight nestedtext"><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><code class="hljs nestedtext"><span class="hljs-attribute">giscus</span><span class="hljs-punctuation">:</span><br> <span class="hljs-attribute">repo</span><span class="hljs-punctuation">:</span> <span class="hljs-string">xxxx/xxxx</span><br> <span class="hljs-attribute">repo-id</span><span class="hljs-punctuation">:</span> <span class="hljs-string">xxxx</span><br> <span class="hljs-attribute">category</span><span class="hljs-punctuation">:</span> <span class="hljs-string">Announcements</span><br> <span class="hljs-attribute">category-id</span><span class="hljs-punctuation">:</span> <span class="hljs-string">xxxx</span><br> <span class="hljs-comment"># Available values: pathname | url | title | og:title</span><br> <span class="hljs-attribute">mapping</span><span class="hljs-punctuation">:</span> <span class="hljs-string">pathname</span><br> <span class="hljs-comment"># Available values: 0 | 1</span><br> <span class="hljs-attribute">reactions-enabled</span><span class="hljs-punctuation">:</span> <span class="hljs-string">1</span><br> <span class="hljs-comment"># Available values: 0 | 1</span><br> <span class="hljs-attribute">emit-metadata</span><span class="hljs-punctuation">:</span> <span class="hljs-string">0</span><br> <span class="hljs-comment"># Available values: light | dark | dark_high_contrast | transparent_dark | preferred-color-scheme</span><br> <span class="hljs-attribute">theme</span><span class="hljs-punctuation">:</span> <span class="hljs-string">preferred_color_scheme</span><br> <span class="hljs-comment"># Available values: en | zh-CN</span><br> <span class="hljs-attribute">lang</span><span class="hljs-punctuation">:</span> <span class="hljs-string">zh-CN</span><br> <span class="hljs-comment"># Place the comment box above the comments</span><br> <span class="hljs-attribute">input-position</span><span class="hljs-punctuation">:</span> <span class="hljs-string">top</span><br></code></pre></td></tr></table></figure><figure class="highlight coq"><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><code class="hljs coq">comments:<br> enable: true<br> # 指定的插件,需要同时设置对应插件的必要参数<br> # The specified plugin needs to <span class="hljs-built_in">set</span> the necessary parameters <span class="hljs-built_in">at</span> the same <span class="hljs-built_in">time</span><br> # <span class="hljs-keyword">Options</span>: utterances | <span class="hljs-type">disqus</span> | <span class="hljs-type">gitalk</span> | <span class="hljs-type">valine</span> | <span class="hljs-type">waline</span> | <span class="hljs-type">changyan</span> | <span class="hljs-type">livere</span> | <span class="hljs-type">remark42</span> | <span class="hljs-type">twikoo</span> | <span class="hljs-type">cusdis</span><br> type: giscus<br></code></pre></td></tr></table></figure><ul><li>打开自己的网站看到以下评论框就大功告成</li></ul><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240407183951630.png" alt="image-20240407183951630"></p><p>注意:以前用Utterances的可以把github issue 转成discussion</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240407185028973.png" alt="image-20240407185028973"></p>]]></content>
<summary type="html"><blockquote>
<p>注意:本文以Fluid主题当示例来介绍如何配置评论,其他主题请根据对应用户手册配置</p>
</blockquote>
<p>Giscus 允许你将 GitHub Discussions 集成到你的网站上作为评论系统。这样做的好处是评论数据存储在 </summary>
<category term="Hexo" scheme="https://cason.work/categories/Hexo/"/>
<category term="Hexo" scheme="https://cason.work/tags/Hexo/"/>
</entry>
<entry>
<title>使用Github-Action持续部署Springboot或vue</title>
<link href="https://cason.work/2024/01/11/%E4%BD%BF%E7%94%A8Github-Action%E6%8C%81%E7%BB%AD%E9%83%A8%E7%BD%B2Springboot%E6%88%96vue/"/>
<id>https://cason.work/2024/01/11/%E4%BD%BF%E7%94%A8Github-Action%E6%8C%81%E7%BB%AD%E9%83%A8%E7%BD%B2Springboot%E6%88%96vue/</id>
<published>2024-01-11T02:45:13.000Z</published>
<updated>2025-02-07T02:09:54.675Z</updated>
<content type="html"><![CDATA[<p>GitHub Actions 是 GitHub 提供的一项持续集成 (CI) 和持续部署 (CD) 服务。它允许你在代码仓库中定义和运行自动化的工作流程,以响应存储库中的事件或调度。GitHub Actions 可以用于构建、测试、打包和部署项目,也可以执行其他自动化任务。</p><blockquote><p>详细github action相关信息请看最底部</p></blockquote><p><strong>第一步需要先配置secrets,保证我们的服务器信息不暴露</strong></p><p>GitHub Actions 中的 secrets 和 variables 都是用于存储和访问敏感信息或配置的机制</p><p><img src="https://picgo-1258041929.cos.ap-guangzhou.myqcloud.com/img/image-20240111105350117.png" alt="image-20240111105350117"></p><p>因为我的服务器用的宝塔面板环境,加上构建出来的包和文件并不是很大,所以使用的方式是先在github action的环境构建打包,然后将包发送到目标服务器。你也可以在github action里让目标服务器执行命令去做git,构建,部署,启动等等相关的</p><h2 id="Springboot"><a href="#Springboot" class="headerlink" title="Springboot"></a>Springboot</h2><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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Java</span> <span class="hljs-string">CI</span> <span class="hljs-string">with</span> <span class="hljs-string">Maven</span><br><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span> [ <span class="hljs-string">"master"</span> ]<br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">build:</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <br> <span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">JDK</span> <span class="hljs-number">17</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-java@v3</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">java-version:</span> <span class="hljs-string">'17'</span><br> <span class="hljs-attr">distribution:</span> <span class="hljs-string">'temurin'</span><br> <span class="hljs-attr">cache:</span> <span class="hljs-string">maven</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">with</span> <span class="hljs-string">Maven</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">mvn</span> <span class="hljs-string">-B</span> <span class="hljs-string">package</span> <span class="hljs-string">--file</span> <span class="hljs-string">pom.xml</span><br><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">copy</span> <span class="hljs-string">jar</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">cross-the-world/ssh-scp-ssh-pipelines@latest</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">host:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.HOST</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">user:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.USERNAME</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">pass:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.PASSWORD</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">port:</span> <span class="hljs-number">22</span><br> <span class="hljs-attr">scp:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> ./target/springboot.jar => /home/</span><br><span class="hljs-string"></span> <span class="hljs-attr">last_ssh:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> JAR_NAME="springboot.jar"; PID=$(ps aux | grep "$JAR_NAME" | grep -v grep | awk '{print $2}'); if [ -n "$PID" ]; then kill -9 $PID && echo "进程 $PID 已被杀死"; else echo "未找到与 $JAR_NAME 相关的进程"; fi</span><br><span class="hljs-string"> /usr/bin/java -jar -Xmx1024M -Xms256M /home/springboot.jar &</span><br></code></pre></td></tr></table></figure><h2 id="Vue"><a href="#Vue" class="headerlink" title="Vue"></a>Vue</h2><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><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><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Server</span><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span> [ <span class="hljs-string">"master"</span> ]<br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">deploy:</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span> <br><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v3</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">node-version:</span> <span class="hljs-number">16</span><br><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">yarn</span> <br><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">project</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">yarn</span> <span class="hljs-string">generate</span> <br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">zip</span> <span class="hljs-string">dist</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">zip</span> <span class="hljs-string">-r</span> <span class="hljs-string">dist.zip</span> <span class="hljs-string">dist/</span><br><br> <span class="hljs-comment"># 部署到服务器</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">🚀</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">cross-the-world/ssh-scp-ssh-pipelines@latest</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">host:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.HOST</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">user:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.USERNAME</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">pass:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.PASSWORD</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">port:</span> <span class="hljs-number">22</span><br> <span class="hljs-attr">scp:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> './dist.zip' => /home</span><br><span class="hljs-string"></span> <span class="hljs-attr">last_ssh:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> cd /home/</span><br><span class="hljs-string"> rm -rf dist/ || true</span><br><span class="hljs-string"> unzip dist.zip</span><br><span class="hljs-string"></span><br></code></pre></td></tr></table></figure><h2 id="常见的-GitHub-Actions-YAML-语法和关键概念"><a href="#常见的-GitHub-Actions-YAML-语法和关键概念" class="headerlink" title="常见的 GitHub Actions YAML 语法和关键概念"></a>常见的 GitHub Actions YAML 语法和关键概念</h2><p>GitHub Actions 使用 YAML 文件来定义工作流程。以下是一些常见的 GitHub Actions YAML 语法和关键概念:</p><h3 id="工作流程(Workflow)的定义:"><a href="#工作流程(Workflow)的定义:" class="headerlink" title="工作流程(Workflow)的定义:"></a>工作流程(Workflow)的定义:</h3><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><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><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">My</span> <span class="hljs-string">Workflow</span><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">main</span><br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">my_job:</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">Repository</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span><br> <span class="hljs-comment"># 其他步骤...</span><br></code></pre></td></tr></table></figure><ul><li><code>name</code>: 工作流程的名称。</li><li><code>on</code>: 触发工作流程的事件,例如 <code>push</code> 到特定分支。</li><li><code>jobs</code>: 包含一个或多个任务的部分。</li></ul><h3 id="任务(Job)的定义:"><a href="#任务(Job)的定义:" class="headerlink" title="任务(Job)的定义:"></a><strong>任务(Job)的定义:</strong></h3><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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">my_job:</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Step</span> <span class="hljs-number">1</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"Hello, World!"</span><br> <span class="hljs-comment"># 其他步骤...</span><br></code></pre></td></tr></table></figure><ul><li><code>runs-on</code>: 指定任务运行的操作系统和环境。</li><li><code>steps</code>: 包含一个或多个步骤的列表。</li></ul><h3 id="步骤(Steps)的定义:"><a href="#步骤(Steps)的定义:" class="headerlink" title="步骤(Steps)的定义:"></a><strong>步骤(Steps)的定义:</strong></h3><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><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Step</span> <span class="hljs-number">1</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"Hello, World!"</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Step</span> <span class="hljs-number">2</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v3</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">node-version:</span> <span class="hljs-string">'14'</span><br> <span class="hljs-comment"># 其他步骤...</span><br></code></pre></td></tr></table></figure><ul><li><code>name</code>: 步骤的名称。</li><li><code>run</code>: 执行的命令或脚本。</li><li><code>uses</code>: 使用的动作(可以是内置动作或自定义动作)。</li><li><code>with</code>: 传递给动作的参数。</li></ul><h3 id="触发器(Trigger)的定义:"><a href="#触发器(Trigger)的定义:" class="headerlink" title="触发器(Trigger)的定义:"></a><strong>触发器(Trigger)的定义:</strong></h3><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><code class="hljs yaml"><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">main</span><br> <span class="hljs-attr">pull_request:</span><br> <span class="hljs-attr">branches:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">feature/*</span><br></code></pre></td></tr></table></figure><ul><li><code>on</code>: 触发工作流程的事件。</li><li><code>push</code>: 代码推送事件。</li><li><code>pull_request</code>: 合并请求事件。</li></ul><h3 id="环境变量(Environment-Variables)的定义:"><a href="#环境变量(Environment-Variables)的定义:" class="headerlink" title="环境变量(Environment Variables)的定义:"></a><strong>环境变量(Environment Variables)的定义:</strong></h3><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><code class="hljs yaml"><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">my_job:</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">env:</span><br> <span class="hljs-attr">MY_VARIABLE:</span> <span class="hljs-string">my_value</span><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-comment"># 步骤...</span><br></code></pre></td></tr></table></figure><ul><li><code>env</code>: 定义任务运行时的环境变量。</li></ul><h2 id="Secrets-和-Variables"><a href="#Secrets-和-Variables" class="headerlink" title="Secrets 和 Variables"></a>Secrets 和 Variables</h2><h3 id="使用-Secrets:"><a href="#使用-Secrets:" class="headerlink" title="使用 Secrets:"></a>使用 Secrets:</h3><p>GitHub Actions 的 secrets 是用于存储敏感信息,例如 API 密钥、访问令牌等。这些 secrets 可以被工作流程中的步骤引用,但它们是加密的,并且只有在运行工作流程时才会暴露给步骤。</p><ol><li><p><strong>在 GitHub 存储库中创建 secret:</strong></p><ul><li>转到 GitHub 存储库的页面。</li><li>在存储库顶部导航栏中,点击 “Settings”。</li><li>在左侧边栏中,选择 “Secrets”。</li><li>点击 “New repository secret”,然后输入 secret 名称和值。</li></ul></li><li><p><strong>在工作流程中使用 secret:</strong></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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">my_job:</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">secret</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.MY_SECRET</span> <span class="hljs-string">}}</span><br></code></pre></td></tr></table></figure></li></ol><p>这里 <code>MY_SECRET</code> 是你创建的 secret 的名称,可以在步骤中使用 <code>${{ secrets.MY_SECRET }}</code> 引用它。</p><h3 id="使用-Variables:"><a href="#使用-Variables:" class="headerlink" title="使用 Variables:"></a>使用 Variables:</h3><p>GitHub Actions 的 variables 是由 GitHub 提供的一组默认环境变量,同时你也可以定义自己的环境变量。这些变量可以在工作流程的任何步骤中使用。</p><ol><li><p><strong>在工作流程中使用 GitHub 提供的 variables:</strong></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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">my_job:</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">GitHub-provided</span> <span class="hljs-string">variable</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">${{</span> <span class="hljs-string">github.event_name</span> <span class="hljs-string">}}</span><br></code></pre></td></tr></table></figure><p>这里的 <code>github.event_name</code> 是 GitHub 提供的一个默认变量,表示触发工作流程的事件的名称。</p></li><li><p><strong>在工作流程中定义自己的 variables:</strong></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><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">my_job:</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">env:</span><br> <span class="hljs-attr">MY_VARIABLE:</span> <span class="hljs-string">my_value</span><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">custom</span> <span class="hljs-string">variable</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">$MY_VARIABLE</span><br></code></pre></td></tr></table></figure></li></ol><p>在 <code>env</code> 部分定义自己的环境变量,然后在步骤中使用 <code>$MY_VARIABLE</code> 引用它。</p><p>注意:GitHub Actions 中的环境变量(包括 secrets 和 variables)在步骤中的引用方式使用 <code>${{ ... }}</code> 语法。此外,secrets 只能在工作流程的同一存储库中使用,而 variables 则可以在不同存储库的工作流程中共享。</p>]]></content>
<summary type="html"><p>GitHub Actions 是 GitHub 提供的一项持续集成 (CI) 和持续部署 (CD) 服务。它允许你在代码仓库中定义和运行自动化的工作流程,以响应存储库中的事件或调度。GitHub Actions 可以用于构建、测试、打包和部署项目,也可以执行其他自动化任务。</summary>
<category term="github action" scheme="https://cason.work/tags/github-action/"/>
<category term="springboot" scheme="https://cason.work/tags/springboot/"/>
<category term="vue" scheme="https://cason.work/tags/vue/"/>
</entry>
<entry>
<title>Easypoi-大数据量导出</title>
<link href="https://cason.work/2024/01/09/Easypoi-%E5%A4%A7%E6%95%B0%E6%8D%AE%E9%87%8F%E5%AF%BC%E5%87%BA/"/>
<id>https://cason.work/2024/01/09/Easypoi-%E5%A4%A7%E6%95%B0%E6%8D%AE%E9%87%8F%E5%AF%BC%E5%87%BA/</id>
<published>2024-01-09T08:52:27.000Z</published>
<updated>2025-02-07T02:09:54.666Z</updated>
<content type="html"><![CDATA[<p>EasyExcel 在处理 Excel 文件时,需要占用一定的内存。如果导出的数据量过大,可能导致内存占用过高,从而引发 CPU 过高的问题。并发访问导出接口也会导致性能问题,所以尽量在数据量比较大的导出接口添加锁控制。</p><blockquote><p>以下是虚化业务后的代码,记录一下遇到的问题解决方案</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 导出大数据量Excel文件</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> response HttpServletResponse对象,用于设置响应头和输出流</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> requestForm 包含查询参数的表单对象</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@throws</span> ParseException 当日期解析发生错误时抛出异常</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@throws</span> IOException 当输入或输出操作发生错误时抛出异常</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">exportBigExcelist</span><span class="hljs-params">(HttpServletResponse response, RequestForm requestForm)</span> <span class="hljs-keyword">throws</span> ParseException, IOException {<br> <span class="hljs-comment">// 设置Excel导出参数,包括标题和sheet名称</span><br> <span class="hljs-type">ExportParams</span> <span class="hljs-variable">params</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ExportParams</span>(<span class="hljs-string">"标题"</span>, <span class="hljs-string">"sheet名称"</span>);<br><br> <span class="hljs-comment">// 使用EasyExcel工具类导出大数据Excel文件</span><br> <span class="hljs-type">Workbook</span> <span class="hljs-variable">workbook</span> <span class="hljs-operator">=</span> ExcelExportUtil.exportBigExcel(params, ExcelDto.class, <span class="hljs-keyword">new</span> <span class="hljs-title class_">IExcelExportServer</span>() {<br> <span class="hljs-meta">@SneakyThrows</span><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> List<Object> <span class="hljs-title function_">selectListForExcelExport</span><span class="hljs-params">(Object queryParams, <span class="hljs-type">int</span> page)</span> {<br> <span class="hljs-comment">// 强制转换查询参数为RequestForm对象</span><br> <span class="hljs-type">RequestForm</span> <span class="hljs-variable">tempForm</span> <span class="hljs-operator">=</span> (RequestForm) queryParams;<br> <br> <span class="hljs-comment">// 设置分页参数</span><br> tempForm.setPage(page);<br> tempForm.setPageSize(<span class="hljs-number">10000</span>);<br> <br> <span class="hljs-comment">// 调用业务服务进行分页查询逻辑</span><br> List<ExcelDto> list = service.selectlist(tempForm);<br> <br> <span class="hljs-comment">// 将查询结果转化为List<Object></span><br> List<Object> objects = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span><>(list);<br> <br> <span class="hljs-comment">// 如果查询结果为空,则返回null</span><br> <span class="hljs-keyword">if</span> (list.size() == <span class="hljs-number">0</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br> }<br> <br> <span class="hljs-keyword">return</span> objects;<br> }<br> }, requestForm);<br><br> <span class="hljs-comment">// 设置响应头,指定导出文件类型为Excel</span><br> response.setHeader(<span class="hljs-string">"content-Type"</span>, <span class="hljs-string">"application/vnd.ms-excel"</span>);<br> <span class="hljs-comment">// 设置响应头,指定导出文件的名称,并对文件名进行UTF-8编码</span><br> response.setHeader(<span class="hljs-string">"Content-Disposition"</span>, <span class="hljs-string">"attachment;filename="</span> + URLEncoder.encode(<span class="hljs-string">"filename.xls"</span>, <span class="hljs-string">"UTF-8"</span>));<br> <br> <span class="hljs-comment">// 将生成的Excel文件写入到响应输出流</span><br> workbook.write(response.getOutputStream());<br>}<br><br></code></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>EasyExcel 在处理 Excel 文件时,需要占用一定的内存。如果导出的数据量过大,可能导致内存占用过高,从而引发 CPU 过高的问题。并发访问导出接口也会导致性能问题,所以尽量在数据量比较大的导出接口添加锁控制。</p>
<blockquote>
<p>以下是虚化业</summary>
<category term="Springboot" scheme="https://cason.work/categories/Springboot/"/>
<category term="Easypoi" scheme="https://cason.work/tags/Easypoi/"/>
<category term="Excel" scheme="https://cason.work/tags/Excel/"/>
</entry>
<entry>
<title>Threadlocal-内存泄露问题</title>
<link href="https://cason.work/2024/01/03/Threadlocal-%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E9%97%AE%E9%A2%98/"/>
<id>https://cason.work/2024/01/03/Threadlocal-%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E9%97%AE%E9%A2%98/</id>
<published>2024-01-03T06:57:14.000Z</published>
<updated>2025-02-07T02:09:54.671Z</updated>
<content type="html"><![CDATA[<blockquote><p><a href="https://cason.work/2022/06/16/Threadlocal%E7%9A%84%E7%AE%80%E5%8D%95%E7%90%86%E8%A7%A3%E5%92%8C%E9%83%A8%E5%88%86%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/">Threadlocal的简单理解和部分源码分析 - Blog|CasonMo</a></p></blockquote><p>在ThreadLocalMap中,用Entry来保存K-V结构数据的。Entry的构造方法已经限定Entry中key只能是ThreadLocal对象。</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><code class="hljs java"><span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Entry</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">WeakReference</span><ThreadLocal> {<br> <span class="hljs-comment">/** The value associated with this ThreadLocal. */</span><br> Object value;<br><br> Entry(ThreadLocal k, Object v) {<br> <span class="hljs-built_in">super</span>(k);<br> value = v;<br> }<br>}<br></code></pre></td></tr></table></figure><p>Entry继承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用类型的,Value并非弱引用。(问题马上就来了)</p><blockquote><h4 id="四种引用类型"><a href="#四种引用类型" class="headerlink" title="四种引用类型"></a>四种引用类型</h4><ol><li>强引用(StrongReference)</li></ol><p> 类似Object obj = new Object()这类似的引用,强引用在程序代码中普遍存在,只要强引用在,垃圾搜集器永远不会搜集被引用的对象。也就是说,<strong>宁愿出现内存溢出(OutOfMemoryError),也不会回收这些对象</strong></p><ol start="2"><li>软引用(SoftReference)</li></ol><p> 在Java中用java.lang.ref.SoftReference类来表示。<strong>对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象</strong></p><ol start="3"><li>弱引用(WeakReference)</li></ol><p> 弱引用也是用来描述非必需对象的,用java.lang.ref.WeakReference类来表示,<strong>当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。</strong></p><ol start="4"><li>虚引用 (PhantomReference)</li></ol><p> java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,<strong>在任何时候都可能被垃圾回收器回收</strong>。</p><p> 虚引用与软引用和弱引用的区别:<strong>虚引用必须和引用队列联合使用。</strong></p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">Object</span> <span class="hljs-variable">object</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Object</span>();<br><span class="hljs-type">ReferenceQueue</span> <span class="hljs-variable">queue</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ReferenceQueue</span> ();<br><span class="hljs-type">PhantomReference</span> <span class="hljs-variable">pr</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">PhantomReference</span> (object, queue); <br></code></pre></td></tr></table></figure></blockquote><p>由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收。</p><p>当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在<code>ThreadLocalMap<null, Object></code>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。</p><h2 id="如何避免泄漏"><a href="#如何避免泄漏" class="headerlink" title="如何避免泄漏"></a>如何避免泄漏</h2><p>为了防止此类情况的出现,我们有两种手段。</p><ul><li><p>使用完线程共享变量后,显示调用<code>ThreadLocalMap.remove</code>方法清除线程共享变量;</p><p>既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的<code>get()</code>、<code>set()</code>方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。</p></li><li><p>JDK建议ThreadLocal定义为<code>private static</code>,这样ThreadLocal的弱引用问题则不存在了。</p></li></ul>]]></content>
<summary type="html"><blockquote>
<p><a href="https://cason.work/2022/06/16/Threadlocal%E7%9A%84%E7%AE%80%E5%8D%95%E7%90%86%E8%A7%A3%E5%92%8C%E9%83%A8%E5%88%86%E</summary>
<category term="并发编程" scheme="https://cason.work/categories/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"/>
<category term="ThreadLocal" scheme="https://cason.work/tags/ThreadLocal/"/>
</entry>
</feed>