-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
1514 lines (1144 loc) · 104 KB
/
index.html
File metadata and controls
1514 lines (1144 loc) · 104 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222"><meta name="generator" content="Hexo 7.3.0">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
<link rel="mask-icon" href="/images/logo.svg" color="#222">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Serif+Pro:300,300italic,400,400italic,700,700italic%7CLato:300,300italic,400,400italic,700,700italic%7CIBM+Plex+Mono+Light:300,300italic,400,400italic,700,700italic&display=swap&subset=latin,latin-ext">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" integrity="sha256-5eIC48iZUHmSlSUz9XtjRyK2mzQkHScZY1WdMaoz74E=" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Noto+Serif+SC&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.1/animate.min.css" integrity="sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE=" crossorigin="anonymous">
<script class="next-config" data-name="main" type="application/json">{"hostname":"simuleite.github.io","root":"/","images":"/images","scheme":"Muse","darkmode":false,"version":"8.21.0","exturl":false,"sidebar":{"position":"left","width_expanded":320,"width_dual_column":240,"display":"post","padding":18,"offset":12},"hljswrap":true,"copycode":{"enable":false,"style":null},"fold":{"enable":false,"height":500},"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":true,"async":false,"transition":{"menu_item":"fadeInDown","post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果:${query}","hits_time":"找到 ${hits} 个搜索结果(用时 ${time} 毫秒)","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":true,"top_n_per_article":1,"unescape":false,"preload":false}}</script><script src="/js/config.js"></script>
<meta property="og:type" content="website">
<meta property="og:title" content="SMULET's BLOG">
<meta property="og:url" content="http://simuleite.github.io/index.html">
<meta property="og:site_name" content="SMULET's BLOG">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="SIMULEITE">
<meta name="twitter:card" content="summary">
<link rel="canonical" href="http://simuleite.github.io/">
<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":true,"isPost":false,"lang":"zh-CN","comments":"","permalink":"","path":"index.html","title":""}</script>
<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>SMULET's BLOG</title>
<noscript>
<link rel="stylesheet" href="/css/noscript.css">
</noscript>
<link rel="alternate" href="/atom.xml" title="SMULET's BLOG" type="application/atom+xml">
</head>
<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
<div class="headband"></div>
<main class="main">
<div class="column">
<header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="site-brand-container">
<div class="site-nav-toggle">
<div class="toggle" aria-label="切换导航栏" role="button">
<span class="toggle-line"></span>
<span class="toggle-line"></span>
<span class="toggle-line"></span>
</div>
</div>
<div class="site-meta">
<a href="/" class="brand" rel="start">
<i class="logo-line"></i>
<h1 class="site-title">SMULET's BLOG</h1>
<i class="logo-line"></i>
</a>
</div>
<div class="site-nav-right">
<div class="toggle popup-trigger" aria-label="搜索" role="button">
<i class="fa fa-search fa-fw fa-lg"></i>
</div>
</div>
</div>
<nav class="site-nav">
<ul class="main-menu menu"><li class="menu-item menu-item-技术链"><a href="/links/" rel="section"><i class="fa fa-link fa-fw"></i>技术链</a></li><li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li><li class="menu-item menu-item-知识库"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>知识库</a></li><li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li>
<li class="menu-item menu-item-search">
<a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
</a>
</li>
</ul>
</nav>
<div class="search-pop-overlay">
<div class="popup search-popup">
<div class="search-header">
<span class="search-icon">
<i class="fa fa-search"></i>
</span>
<div class="search-input-container">
<input autocomplete="off" autocapitalize="off" maxlength="80"
placeholder="搜索..." spellcheck="false"
type="search" class="search-input">
</div>
<span class="popup-btn-close" role="button">
<i class="fa fa-times-circle"></i>
</span>
</div>
<div class="search-result-container">
<div class="search-result-icon">
<i class="fa fa-spinner fa-pulse fa-5x"></i>
</div>
</div>
</div>
</div>
</header>
<aside class="sidebar">
<div class="sidebar-inner sidebar-overview-active">
<ul class="sidebar-nav">
<li class="sidebar-nav-toc">
文章目录
</li>
<li class="sidebar-nav-overview">
站点概览
</li>
</ul>
<div class="sidebar-panel-container">
<!--noindex-->
<div class="post-toc-wrap sidebar-panel">
</div>
<!--/noindex-->
<div class="site-overview-wrap sidebar-panel">
<div class="site-author animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
<p class="site-author-name" itemprop="name">SIMULEITE</p>
<div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap animated">
<nav class="site-state">
<div class="site-state-item site-state-posts">
<a href="/archives/">
<span class="site-state-item-count">75</span>
<span class="site-state-item-name">日志</span>
</a>
</div>
<div class="site-state-item site-state-tags">
<a href="/tags/">
<span class="site-state-item-count">9</span>
<span class="site-state-item-name">标签</span></a>
</div>
</nav>
</div>
</div>
</div>
</div>
</aside>
</div>
<div class="main-inner index posts-expand">
<div class="post-block">
<article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
<link itemprop="mainEntityOfPage" href="http://simuleite.github.io/ComputerScience/%E7%AC%94%E8%AE%B0/LLM/MoE/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="SIMULEITE">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
<meta itemprop="name" content="undefined | SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/ComputerScience/%E7%AC%94%E8%AE%B0/LLM/MoE/" class="post-title-link" itemprop="url">MoE</a>
</h2>
<div class="post-meta-container">
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2025-10-04 00:00:00 / 修改时间:09:30:04" itemprop="dateCreated datePublished" datetime="2025-10-04T00:00:00+08:00">2025-10-04</time>
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="什么是moe"><a class="markdownIt-Anchor" href="#什么是moe"></a> 什么是MoE</h1>
<p>Mixture of Experts<br />
MoE使用Sparse,MLP替换FFN</p>
<h1 id="为什么moe"><a class="markdownIt-Anchor" href="#为什么moe"></a> 为什么MoE</h1>
<p>不可能三角:Performance、Cost、Size。</p>
<h2 id="成本-性能"><a class="markdownIt-Anchor" href="#成本-性能"></a> 成本-性能</h2>
<p>传统的Dense模型,想要Scale Up,提升Size,一定会带动Computation提升,从而导致Cost太大。<br />
Moe将Performance、Size解耦,Scale Up提升Size,但是Computation不会变化。(只激活部分Expert)</p>
<h2 id="单一职责knowledge"><a class="markdownIt-Anchor" href="#单一职责knowledge"></a> 单一职责Knowledge</h2>
<p>传统模型,Knowledge存储在FFN;MoE将不同领域知识放在不同的Expert里面,有专业化区分,单一职责。<br />
参数量更大,也使MoE模型可以存储更多Knowledge。</p>
<h2 id="sparse"><a class="markdownIt-Anchor" href="#sparse"></a> Sparse</h2>
<p>CNN本质就是引入Sparse。Sparse引领Machine Learning/Deep Learning发展。<br />
Sparse做的事是捕捉信息的low-dimension pattern,提升learning efficiency。(这也是CNN性能好于MLP的原因)</p>
<h1 id="moe比dense好在哪"><a class="markdownIt-Anchor" href="#moe比dense好在哪"></a> MoE比Dense好在哪?</h1>
<ol>
<li>MoE在总参量一样的情况下,都比同样参数的Dense要好(激活参数、总参数都是)</li>
<li>MoE训练时间更短:更低的Training、Validation Loss</li>
</ol>
<h1 id="deepseek-moe"><a class="markdownIt-Anchor" href="#deepseek-moe"></a> DeepSeek MoE</h1>
<h2 id="fine-grain-expert"><a class="markdownIt-Anchor" href="#fine-grain-expert"></a> Fine-Grain Expert</h2>
<p>把比较大的Expert分成多个小Experts</p>
<h2 id="shared-expert"><a class="markdownIt-Anchor" href="#shared-expert"></a> Shared Expert</h2>
<p>所有输入都会经过Shared Expert,不依赖Router</p>
<h2 id="效果验证"><a class="markdownIt-Anchor" href="#效果验证"></a> 效果验证</h2>
<p>从8-32,有提升;但是从32-64,几乎没有;Experts有一个Sweet Point</p>
<p>而Shared Expert其实没有什么用,属于人的错误先验假设</p>
<h1 id="训练时token分类"><a class="markdownIt-Anchor" href="#训练时token分类"></a> 训练时Token分类</h1>
<h2 id="token-choice"><a class="markdownIt-Anchor" href="#token-choice"></a> Token Choice</h2>
<p>根据每个Token做Router选择专家;<br />
存在LoadBalance问题,被集中分配到某些Expert身上。<br />
也就是说,有些Expert没有被充分训练</p>
<h2 id="expert-choice"><a class="markdownIt-Anchor" href="#expert-choice"></a> Expert Choice</h2>
<p>反过来,从Expert的角度分配Token;<br />
存在Token Dropping,有些Token没有被选择到;<br />
在Inference时,因为AutoRegressive,无法看到后面的Token,这个方法会有问题。解决方法是增加一个MLP中间层。</p>
<p>目前,Token Choice表现还是好一点</p>
<h1 id="moe的困难"><a class="markdownIt-Anchor" href="#moe的困难"></a> MoE的困难</h1>
<h2 id="sparse训练困难training-instability"><a class="markdownIt-Anchor" href="#sparse训练困难training-instability"></a> Sparse训练困难:Training Instability</h2>
<p>训练不稳定,容易Loss崩溃</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
</div>
<div class="post-block">
<article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
<link itemprop="mainEntityOfPage" href="http://simuleite.github.io/ComputerScience/%E7%9F%A5%E8%AF%86/LangGraph/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="SIMULEITE">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
<meta itemprop="name" content="undefined | SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/ComputerScience/%E7%9F%A5%E8%AF%86/LangGraph/" class="post-title-link" itemprop="url">LangGraph</a>
</h2>
<div class="post-meta-container">
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2025-08-06 00:00:00" itemprop="dateCreated datePublished" datetime="2025-08-06T00:00:00+08:00">2025-08-06</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2025-09-08 09:06:31" itemprop="dateModified" datetime="2025-09-08T09:06:31+08:00">2025-09-08</time>
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">source venv/bin/activate</span><br><span class="line">pip install langgraph python-dotenv openai</span><br></pre></td></tr></table></figure>
<h1 id="langgraph-demo"><a class="markdownIt-Anchor" href="#langgraph-demo"></a> LangGraph Demo</h1>
<h2 id="10-state"><a class="markdownIt-Anchor" href="#10-state"></a> 1.0 State</h2>
<p>State就是AI流转的全局变量</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">InputState</span>(<span class="title class_ inherited__">TypedDict</span>):</span><br><span class="line"> question: <span class="built_in">str</span></span><br><span class="line"> llm_answer: <span class="type">Optional</span>[<span class="built_in">str</span>] <span class="comment"># None or str</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OutputState</span>(<span class="title class_ inherited__">TypedDict</span>):</span><br><span class="line"> answer: <span class="built_in">str</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OverallState</span>(InputState, OutputState):</span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure>
<h2 id="20-node"><a class="markdownIt-Anchor" href="#20-node"></a> 2.0 Node</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">llm_node</span>(<span class="params">state: InputeState</span>):</span><br><span class="line"> msg = [</span><br><span class="line"> (<span class="string">"system"</span>, readMd(<span class="string">"system.md"</span>)),</span><br><span class="line"> (<span class="string">"human"</span>, state[<span class="string">"question"</span>])</span><br><span class="line"> ]</span><br><span class="line"></span><br><span class="line"> llm = ChatOpenAI(model=<span class="string">"gpt-4o"</span>)</span><br><span class="line"></span><br><span class="line"> resp = llm.invoke(msg)</span><br><span class="line"> <span class="keyword">return</span> {<span class="string">"answer"</span>: resp.content}</span><br></pre></td></tr></table></figure>
<h2 id="30-graph-compile"><a class="markdownIt-Anchor" href="#30-graph-compile"></a> 3.0 Graph Compile</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">builder = StateGraph(OverallState, <span class="built_in">input</span>=InputState, output=OutputState)</span><br><span class="line"></span><br><span class="line">builder.add_node(<span class="string">"llm"</span>, llm_node)</span><br><span class="line">builder.add_edge(START, <span class="string">"llm"</span>)</span><br><span class="line">builder.add_edge(<span class="string">"llm"</span>, END)</span><br><span class="line"></span><br><span class="line">graph = builder.<span class="built_in">compile</span>()</span><br></pre></td></tr></table></figure>
<h2 id="draw-graph"><a class="markdownIt-Anchor" href="#draw-graph"></a> Draw Graph</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">display(Image(graph.get_graph(xray=<span class="literal">True</span>).draw_mermaid_png()))</span><br></pre></td></tr></table></figure>
<h2 id="messages-history"><a class="markdownIt-Anchor" href="#messages-history"></a> Messages History</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">State</span>(<span class="title class_ inherited__">TypedDict</span>):</span><br><span class="line"> msgs: Annotated[<span class="built_in">list</span>, operator.add]</span><br></pre></td></tr></table></figure>
<h1 id="messagegraph"><a class="markdownIt-Anchor" href="#messagegraph"></a> MessageGraph</h1>
<p>使用Reducer追加消息,但是可以对已有消息做更新、合并、删除操作(Context Engine)</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MessageGraph</span>(<span class="title class_ inherited__">StateGraph</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>) -> <span class="literal">None</span>:</span><br><span class="line"> <span class="built_in">super</span>().__init__(Annotated[<span class="built_in">list</span>[AnyMessage], add_message])</span><br></pre></td></tr></table></figure>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">builder = MessageGraph()</span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line">graph = builder.<span class="built_in">compile</span>()</span><br><span class="line"></span><br><span class="line">msgs2 = [HumanMessage(content=<span class="string">"xxx"</span>, <span class="built_in">id</span>=msg1.<span class="built_in">id</span>)]</span><br><span class="line"><span class="comment"># ID相同,覆盖消息</span></span><br><span class="line">add_messages(msgs1, msgs2)</span><br></pre></td></tr></table></figure>
<h1 id="structured-output"><a class="markdownIt-Anchor" href="#structured-output"></a> Structured Output</h1>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UserInfo</span>(<span class="title class_ inherited__">BaseModel</span>):</span><br><span class="line"> name: <span class="built_in">str</span> = Field(description=<span class="string">"The name of the user"</span>)</span><br><span class="line"> <span class="comment"># ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Runnable对象</span></span><br><span class="line">structured_llm = llm.with_structured_output(UserInfo)</span><br><span class="line"></span><br><span class="line"><span class="comment"># UserInfo对象</span></span><br><span class="line">resp = structured_llm.invoke(msg)</span><br></pre></td></tr></table></figure>
<h1 id="tool-calling-agent"><a class="markdownIt-Anchor" href="#tool-calling-agent"></a> Tool Calling Agent</h1>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Tool注解会拿到函数名、函数入参与函数注释</span></span><br><span class="line"><span class="meta">@tool</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">your_tool</span>(<span class="params">args</span>):</span><br><span class="line"> <span class="string">"""Description"""</span></span><br><span class="line"></span><br><span class="line">tools = [your_tool]</span><br><span class="line">tool_node = ToolNode(tools)</span><br></pre></td></tr></table></figure>
<h1 id="react-agent"><a class="markdownIt-Anchor" href="#react-agent"></a> ReAct Agent</h1>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 默认使用Agent State</span></span><br><span class="line"><span class="comment"># 注意,这是一个已经compile的图</span></span><br><span class="line">graph = create_react_agent(llm, tools=tools)</span><br></pre></td></tr></table></figure>
<h2 id="react-graph"><a class="markdownIt-Anchor" href="#react-graph"></a> ReAct Graph</h2>
<p>ReAct = 有条件调用 + 调用必返回</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">workflow = StateGraph(State)</span><br><span class="line"></span><br><span class="line">workflow.add_node(<span class="string">"agent"</span>, call_model)</span><br><span class="line">workflow.add_node(<span class="string">"tools"</span>, tool_model)</span><br><span class="line"></span><br><span class="line">workflow.add_edge(START, <span class="string">"agent"</span>)</span><br><span class="line"></span><br><span class="line">workflow.add_conditional_edges(</span><br><span class="line"> <span class="string">"agent"</span>,</span><br><span class="line"> should_continue,</span><br><span class="line"> [<span class="string">"tools"</span>, END],</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 双向连接</span></span><br><span class="line">workflow.add_edge(<span class="string">"tools"</span>, <span class="string">"agent"</span>)</span><br><span class="line"></span><br><span class="line">app = workflow.<span class="built_in">compile</span>()</span><br></pre></td></tr></table></figure>
<h2 id="should_continue"><a class="markdownIt-Anchor" href="#should_continue"></a> should_continue</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">should_continue</span>(<span class="params">state: State</span>):</span><br><span class="line"> messages = state[<span class="string">"message"</span>]</span><br><span class="line"> last_mesaage = message[-<span class="number">1</span>]</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> last_message.tool_calls:</span><br><span class="line"> <span class="keyword">return</span> END</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"tools"</span></span><br></pre></td></tr></table></figure>
<h2 id="call_model"><a class="markdownIt-Anchor" href="#call_model"></a> call_model</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">call_model</span>(<span class="params">state: State, config: RunnableConfig</span>):</span><br><span class="line"> msgs = state[<span class="string">"messages"</span>]</span><br><span class="line"> resp = <span class="keyword">await</span> model.invoke(msgs, config)</span><br><span class="line"> <span class="keyword">return</span> {<span class="string">"messages"</span>: resp}</span><br></pre></td></tr></table></figure>
<h1 id="stream-output"><a class="markdownIt-Anchor" href="#stream-output"></a> Stream Output</h1>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">print_stream</span>(<span class="params">stream</span>):</span><br><span class="line"> <span class="keyword">for</span> sub_stream <span class="keyword">in</span> stream:</span><br><span class="line"> <span class="built_in">print</span>(sub_stream)</span><br></pre></td></tr></table></figure>
<h1 id="mcp"><a class="markdownIt-Anchor" href="#mcp"></a> MCP</h1>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"mcpServers"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"tool_name"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"command"</span><span class="punctuation">:</span> <span class="string">"npx"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"args"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">"your_arg"</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"transport"</span><span class="punctuation">:</span> <span class="string">"stdio"</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Configuration</span>()</span><br><span class="line"><span class="meta"> @staticmethod</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">load_servers</span>(<span class="params">file_path: <span class="built_in">str</span> = <span class="string">"servers_config.json"</span></span>) -> <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="type">Any</span>]:</span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(file_path, <span class="string">"r"</span>, encoding=<span class="string">"utf-8"</span>) <span class="keyword">as</span> f:</span><br><span class="line"> <span class="keyword">return</span> json.load(f).get(<span class="string">"mcpServers"</span>, {})</span><br><span class="line"></span><br><span class="line">cfg = Configuration()</span><br><span class="line">servers_cfg = Configuration.load_servers()</span><br><span class="line">mcp_client = MultiServerMCPClient(servers_cfg)</span><br><span class="line">tools = <span class="keyword">await</span> mcp_client.get_tools()</span><br><span class="line"></span><br></pre></td></tr></table></figure>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
</div>
<div class="post-block">
<article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
<link itemprop="mainEntityOfPage" href="http://simuleite.github.io/ComputerScience/%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/Redis%E4%BD%BF%E7%94%A8/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="SIMULEITE">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
<meta itemprop="name" content="undefined | SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/ComputerScience/%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/Redis%E4%BD%BF%E7%94%A8/" class="post-title-link" itemprop="url">Redis使用</a>
</h2>
<div class="post-meta-container">
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2024-10-27 00:00:00" itemprop="dateCreated datePublished" datetime="2024-10-27T00:00:00+08:00">2024-10-27</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2025-08-14 08:53:50" itemprop="dateModified" datetime="2025-08-14T08:53:50+08:00">2025-08-14</time>
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="分布式锁"><a class="markdownIt-Anchor" href="#分布式锁"></a> 分布式锁</h1>
<h2 id="redission"><a class="markdownIt-Anchor" href="#redission"></a> Redission</h2>
<p>使用原生Redis设置锁的问题:</p>
<ol>
<li>服务器拿到锁后宕机,锁不能释放,导致阻塞。<br />
设置锁失效时间可以解决上面的问题,但是会导致新的问题:</li>
<li>设置锁失效时间,在服务器负载过高的时候,会发生锁失效业务还没完成的情况,导致业务代码不互斥。</li>
</ol>
<blockquote>
<p>0信任:不要期待网络服务器按照理想情况运行。</p>
</blockquote>
<p>使用Redission自动为锁续命,可以解决上述问题。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> req.getBusinessUniqueKey() + <span class="string">"-"</span> + req.getBusinessCode();</span><br><span class="line"><span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> lock = redissonClient.getLock(lockKey);</span><br><span class="line"> <span class="type">boolean</span> <span class="variable">tryLock</span> <span class="operator">=</span> lock.tryLock(<span class="number">0</span>, TimeUnit.SECONDS);</span><br><span class="line"> <span class="keyword">if</span> (!tryLock) {</span><br><span class="line"> LOG.info(<span class="string">"获取锁失败"</span>);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Business Code</span></span><br><span class="line"></span><br><span class="line">} <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> LOG.info(<span class="string">"释放锁"</span>);</span><br><span class="line"> <span class="keyword">if</span> (lock != <span class="literal">null</span> && lock.isHeldByCurrentThread()) {</span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>锁设计原则:谁持有锁,谁才有资格释放锁。</p>
</blockquote>
<p>不过,使用这个方案有问题:Redis Master加锁后宕机,新的Master没有同步到加锁的数据,就会存在多把锁。<br />
这时我们需要引入ZooKeeper保持强一致性;或者可以使用RedLock,即集群半数以上机器加锁成功,才是真的加锁成功。<br />
然而,这两种方法都会带来性能开销。</p>
<blockquote>
<p>对于难以解决的棘手问题,应该思考如何避免问题发生。</p>
</blockquote>
<h2 id="高性能分布式锁分段锁"><a class="markdownIt-Anchor" href="#高性能分布式锁分段锁"></a> 高性能分布式锁:分段锁</h2>
<p>针对不同段进行加锁,这样就能允许多把锁存在,从而获得一定并发性能。</p>
<h1 id="基本操作"><a class="markdownIt-Anchor" href="#基本操作"></a> 基本操作</h1>
<h2 id="general"><a class="markdownIt-Anchor" href="#general"></a> General</h2>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 返回给定模式的keys</span><br><span class="line">KEYS patter</span><br><span class="line">KEYS * # 返回全部</span><br><span class="line">KEYS set* # 返回set开头的keys</span><br><span class="line">EXISTS key</span><br><span class="line">TYPE key</span><br><span class="line">DEL key</span><br></pre></td></tr></table></figure>
<h2 id="string"><a class="markdownIt-Anchor" href="#string"></a> String</h2>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">SET key value</span><br><span class="line">GET key</span><br><span class="line"># Set Extend Time</span><br><span class="line">SETEX key seconds value</span><br><span class="line"># Set When Key Not Exist</span><br><span class="line">SETNX key value</span><br></pre></td></tr></table></figure>
<h2 id="hash"><a class="markdownIt-Anchor" href="#hash"></a> Hash</h2>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">HSET key field value</span><br><span class="line">HGET key field</span><br><span class="line">HDEL key field</span><br><span class="line"># Get All Fields</span><br><span class="line">HKEYS key</span><br><span class="line"># Get All Values</span><br><span class="line">HVALS key</span><br></pre></td></tr></table></figure>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">flowchart LR</span><br><span class="line"> key[key]</span><br><span class="line"> item[</span><br><span class="line"> field1: value1</span><br><span class="line"> field2: value2</span><br><span class="line"> ]</span><br><span class="line"> key --> item</span><br></pre></td></tr></table></figure>
<h2 id="list"><a class="markdownIt-Anchor" href="#list"></a> List</h2>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">LPUSH key value1 value2</span><br><span class="line"># Get Key From Start To Stop</span><br><span class="line">LRANGE key start stop</span><br><span class="line"># Right POP</span><br><span class="line">RPOP key</span><br><span class="line"># List Length</span><br><span class="line">LLEN key</span><br></pre></td></tr></table></figure>
<h3 id="典型场景"><a class="markdownIt-Anchor" href="#典型场景"></a> 典型场景</h3>
<!--noindex-->
<div class="post-button">
<a class="btn" href="/ComputerScience/%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/Redis%E4%BD%BF%E7%94%A8/#more" rel="contents">
阅读全文 »
</a>
</div>
<!--/noindex-->
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
</div>
<div class="post-block">
<article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
<link itemprop="mainEntityOfPage" href="http://simuleite.github.io/ComputerScience/%E7%9F%A5%E8%AF%86/MQ%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="SIMULEITE">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
<meta itemprop="name" content="undefined | SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/ComputerScience/%E7%9F%A5%E8%AF%86/MQ%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/" class="post-title-link" itemprop="url">MQ消息队列</a>
</h2>
<div class="post-meta-container">
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2025-05-12 00:00:00" itemprop="dateCreated datePublished" datetime="2025-05-12T00:00:00+08:00">2025-05-12</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2025-08-06 22:15:06" itemprop="dateModified" datetime="2025-08-06T22:15:06+08:00">2025-08-06</time>
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="消息队列3大目标"><a class="markdownIt-Anchor" href="#消息队列3大目标"></a> 消息队列3大目标</h1>
<h2 id="异步"><a class="markdownIt-Anchor" href="#异步"></a> 异步</h2>
<p>在生产者-消费者速度不匹配的情况下,使用异步可以减少等待,提高效率。</p>
<h2 id="解耦"><a class="markdownIt-Anchor" href="#解耦"></a> 解耦</h2>
<p>多个生产者可以通过消息队列管道集合成1条链路;也可以将1个生产者的消息负载均衡给多个消费者(只发送1条消息给MQ,MQ广播多份)。例如,增加了一个数据分析业务,这时候不需要修改业务代码,只需要配置MQ发送相应消息到大数据系统Server即可。<br />
同时,生产者只需要关心将消息发送给MQ,无需关心后续处理(消费者挂了怎么办);MQ会负责和消费者通信。</p>
<h2 id="削峰生产者-消费者速度不同步"><a class="markdownIt-Anchor" href="#削峰生产者-消费者速度不同步"></a> 削峰(生产者-消费者速度不同步)</h2>
<p>由于队列本身是一条管道,拥有一定容量,因此可以削峰填谷,解决一些瞬时高并发流量。</p>
<h1 id="消息队列的关键问题"><a class="markdownIt-Anchor" href="#消息队列的关键问题"></a> 消息队列的关键问题</h1>
<h2 id="c-系统一致性"><a class="markdownIt-Anchor" href="#c-系统一致性"></a> C 系统一致性</h2>
<p>A系统通过MQ将消息发送给B、C完成后续业务,B成功而C失败,这时如何保证一致性?</p>
<h2 id="a-系统可用性"><a class="markdownIt-Anchor" href="#a-系统可用性"></a> A 系统可用性</h2>
<p>MQ宕机,依赖MQ管道的服务就不可用。MQ应该有高可用性和稳定性,不应该成为系统薄弱环节。<br />
因此需要MQ集群,这时候又需要新的中间层NameSrv来管理维护MQ集群。</p>
<h2 id="系统复杂度"><a class="markdownIt-Anchor" href="#系统复杂度"></a> 系统复杂度</h2>
<ul>
<li>如何保证消费不丢失?</li>
<li>如何避免重复消费?</li>
<li>如何保证消息顺序?</li>
</ul>
<h2 id="幂等性"><a class="markdownIt-Anchor" href="#幂等性"></a> 幂等性</h2>
<blockquote>
<p>多次消费结果相当于只消费一次。</p>
</blockquote>
<p>可以用业务id作为消息key,对key校验有没有消费过。<br />
如果重复消费,确保多次消费和1次消费的结果相同。</p>
<ul>
<li>发送消息重复:发送后,网络断开,没收到ACK,导致重复发送</li>
<li>消费消息重复:Consumer收到消息并处理完成,但是由于网络问题,Consumer应答没有发送到Broker;Broker遵从<strong>至少消费一次原则</strong>,重新发送。</li>
<li>Rebalance消息重复:Consumer Group的Consumer数量发生变化,触发Rebalance,此时Consumer可能会收到曾经被消费过的消息。</li>
</ul>
<h1 id="message-queue产品"><a class="markdownIt-Anchor" href="#message-queue产品"></a> Message Queue产品</h1>
<table>
<thead>
<tr>
<th>产品</th>
<th>优势</th>
<th>劣势</th>
<th>场景</th>
</tr>
</thead>
<tbody>
<tr>
<td>Kafaka</td>
<td>吞吐量大、性能高、集群高可用</td>
<td>丢数据、功能单一</td>
<td>MapReduce大数据采集、日志分析</td>
</tr>
<tr>
<td>RabbitMQ</td>
<td>消息可靠、功能全面</td>
<td>erlang语言不容易定制,吞吐量较低</td>
<td>小规模服务调用</td>
</tr>
<tr>
<td>Pulsar</td>
<td>Bookeeper,消息可靠性高</td>
<td>使用较少、生态有差距</td>
<td>大规模服务调用</td>
</tr>
<tr>
<td>RocketMQ</td>
<td>高吞吐、高性能、高可用。Java语言容易定制。</td>
<td>Java服务加载慢</td>
<td>功能全面,尤其适合金融、电商、互联网场景</td>
</tr>
</tbody>
</table>
<h1 id="消息队列工作方式"><a class="markdownIt-Anchor" href="#消息队列工作方式"></a> 消息队列工作方式</h1>
<p>RocketMQ和Kafka都使用Topic,每个Topic的内容会分发到多个管道(Partition或MessageQueue)。而Kafka在Topic过多的情况下,吞吐量会严重下降;RocketMQ解决了这个问题。</p>
<h1 id="rocketmq集群"><a class="markdownIt-Anchor" href="#rocketmq集群"></a> RocketMQ集群</h1>
<p>在RocketMQ集群中,多台NameSrv是平等的,而Broker会组成多个主-从结构。<br />
Slave只负责备份,只有Master(brokerId=0)才会发送消息。<br />
然而主从结构的Slave,由于brokerId不为0,不会自动切换为Master,需要人工介入。</p>
<h2 id="dledger高可用集群"><a class="markdownIt-Anchor" href="#dledger高可用集群"></a> Dledger高可用集群</h2>
<p>Dleger是一种Raft算法,实现了Leader选举。<br />
Dledger会从Followers中自动选举Leader,从而保证高可用。</p>
<h1 id="三种发送方式"><a class="markdownIt-Anchor" href="#三种发送方式"></a> 三种发送方式</h1>
<h2 id="单向发送"><a class="markdownIt-Anchor" href="#单向发送"></a> 单向发送</h2>
<p>Producer只发送消息、不处理ACK;MQ也不发送ACK。消息可靠性没有保障。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 返回值为null,不处理ACK。</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendOneWay</span><span class="params">(Message msg)</span> <span class="keyword">throws</span> ...Exception {</span><br><span class="line"> msg.setTopic(withNamespace(msg.getTopic()));</span><br><span class="line"> <span class="built_in">this</span>.defaultMQProducerImpl.sendOneWay(msg);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="同步发送"><a class="markdownIt-Anchor" href="#同步发送"></a> 同步发送</h2>
<p>Producer等待MQ ACK,才继续操作。同步发送可能会发生阻塞。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> SendResult <span class="title function_">sendResult</span><span class="params">(</span></span><br><span class="line"><span class="params"> Collection<Message> msgs)</span> <span class="keyword">throws</span> ...Exception {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.defaultMQProducerImpl.send(batch(msgs));</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="异步发送"><a class="markdownIt-Anchor" href="#异步发送"></a> 异步发送</h2>
<p>Producer不等待MQ ACK(异步ACK,也能保证不丢失消息),直接发送消息。<br />
但是异步发送也有代价,我们不能发送完立刻<code>producer.shutdown()</code>,而需要设置一段延迟,使producer能够捕捉Exception并重发消息。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// send方法本身没有返回值,不会阻塞;但是能够处理Exception</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">send</span><span class="params">(Message msg, </span></span><br><span class="line"><span class="params"> SendCallBack sendCallBack)</span> <span class="keyword">throws</span> ...Exception {</span><br><span class="line"> msg.setTopic(withNamespace(msg.getTopic()));</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">this</span>.getAutoBatch() && !(msg <span class="keyword">instanceof</span> MessageBatch)) {</span><br><span class="line"> sendByAccumulator(msg, <span class="literal">null</span>, sendCallBack);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> sendDirect(msg, <span class="literal">null</span>, sendCallBack);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> sendCallBack.onException(e);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">producer.send(msg, <span class="keyword">new</span> <span class="title class_">SendCallBack</span>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onSuccess</span><span class="params">(SendResult sendResult)</span> {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onException</span><span class="params">(Throwable e)</span> {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<h1 id="两种消费方式"><a class="markdownIt-Anchor" href="#两种消费方式"></a> 两种消费方式</h1>
<h2 id="consumer拉取"><a class="markdownIt-Anchor" href="#consumer拉取"></a> Consumer拉取</h2>
<p>Consumer维护一个轮询拉取,Broker收到拉取请求后发送消息。</p>
<h2 id="broker推送"><a class="markdownIt-Anchor" href="#broker推送"></a> Broker推送</h2>
<p><strong>一般只用推模式</strong>,因为Consumer需要轮询(即使Broker不一定有消息),会消耗部分资源。</p>
<h1 id="消息类型"><a class="markdownIt-Anchor" href="#消息类型"></a> 消息类型</h1>
<h2 id="顺序消息"><a class="markdownIt-Anchor" href="#顺序消息"></a> 顺序消息</h2>
<p>局部有序,实际上是序号相同的消息发送到同一个队列管道,然后消费者从一个管道中拿消息,从而保证有序性。</p>
<h2 id="广播消息"><a class="markdownIt-Anchor" href="#广播消息"></a> 广播消息</h2>
<p>正常情况下,多个Consumer是负载均衡模式,一条消息只会发到其中一个Consumer消费;而在广播模式下,所有的Consumer都会收到消息。<br />
在代码层面,正常情况下<strong>服务端统一</strong>维护消费者位点;而在广播模式下<strong>客户端本地</strong><code>.rocket_offsets</code>维护消费者位点</p>
<h1 id="消息重试"><a class="markdownIt-Anchor" href="#消息重试"></a> 消息重试</h1>
<h2 id="顺序消息-2"><a class="markdownIt-Anchor" href="#顺序消息-2"></a> 顺序消息</h2>
<p>顺序消息要拿到ACK才会发送下一条消息,否则会重发消息</p>
<h2 id="无序消息"><a class="markdownIt-Anchor" href="#无序消息"></a> 无序消息</h2>
<p>为了保障无需消息的消费,MQ设置了一个消息重试间隔时间。如果没有回复,间隔10s-30s-1m-2m…来重发消息,最多重试16次(默认)。<br />
如果达到重试上限还未消费,该消息称为<strong>死信消息</strong>。死信消息会进入<strong>死信队列</strong>。</p>
<h3 id="死信队列"><a class="markdownIt-Anchor" href="#死信队列"></a> 死信队列</h3>
<p>死信队列不归属于Topic、Consumer,而是归属于Group Id。<br />
死信队列的消息不会被再次重复消费,有效期为3天,过期删除。<br />
可以手工在监控平台里处理死信,获取messageId后自己处理。</p>
<h2 id="重复消费"><a class="markdownIt-Anchor" href="#重复消费"></a> 重复消费</h2>
<p>网络闪断(成功执行,MQ没收到ACK)、生产者宕机(成功发送到MQ,生产者没收到ACK)会引发重复消费。</p>
<h1 id="什么是消息事务"><a class="markdownIt-Anchor" href="#什么是消息事务"></a> 什么是消息事务</h1>
<p>消息事务基于消息队列的两阶段提交,将本地事务和发放消息放在了一个分布式事务里。保证原子性。<br />
用法:将一个分布式事务拆分成一个消息事务(A系统本地操作+发消息)+ B系统本地操作。<br />
B系统操作由消息驱动。只要消息事务成功,那么A操作一定成功;这时B系统收到消息执行本地操作,如果本地操作失败,消息会重新投放,直到B操作成功。</p>
<p>上面的方法满足BASE:B基本A可用;S软状态;E最终一致性<br />
BASE是对于CAP中的AP系统的拓展。牺牲强一致性来保证Available和Performance。<br />
满足BASE的事务称为“柔性事务”</p>
<h2 id="什么是exactly-once"><a class="markdownIt-Anchor" href="#什么是exactly-once"></a> 什么是Exactly Once</h2>
<h2 id="at-least-once"><a class="markdownIt-Anchor" href="#at-least-once"></a> At Least Once</h2>
<p>Producer接受Broker ACK来确保信息成功写入Topic。<br />
如果Producer接收ACK超时、或Broker出错时,会重复发送消息。</p>
<p>但是如果Broker已经写入Topic,但是没有来得及发送ACK或ACK超时,Producer重新发送的消息会第二次写入Topic,导致最终Consumer收到重复消息。</p>
<h2 id="at-most-once"><a class="markdownIt-Anchor" href="#at-most-once"></a> At Most Once</h2>
<p>Producer接收ACK超时,或Broker出错时没有重复发消息,会导致消息丢失,没有写入Topic,也没有被Consumer消费。<br />
有些时候我们为了避免重复消费,允许这种情况发生。</p>
<h2 id="exactly-once"><a class="markdownIt-Anchor" href="#exactly-once"></a> Exactly Once</h2>
<p>Exactly Once是说,即使重复发送了消息,Consumer只消费一次。需要消息队列Serv、Producer、Consumer协同才能实现。</p>
<h2 id="rocketmq事务消息"><a class="markdownIt-Anchor" href="#rocketmq事务消息"></a> RocketMQ事务消息</h2>
<ol>
<li>MQ开启一个事务Topic</li>
<li>事务中第一个执行的服务发送1.5条消息(0.5是因为,这条消息在事务提交前,对Consumer不可见)</li>
<li>1.5发送成功后,发送0.5消息的服务开始本地事务;并决定事务提交/回滚。<br />
RocketMQ保证最终一致性</li>
</ol>
<h3 id="如何做到写入消息但是对用户不可见呢"><a class="markdownIt-Anchor" href="#如何做到写入消息但是对用户不可见呢"></a> 如何做到写入消息但是对用户不可见呢?</h3>
<p>0.5消息,备份原消息Topic和MQ,然后改变Topic为HALF_TOPIC,由于Consumer没有订阅这个Topic,所以无法消费。<br />
然后RocketMQ开始定时任务,从HALF_TOPIC中拉取消息消费,并决定提交事务还是回滚。</p>
<h2 id="kafka幂等"><a class="markdownIt-Anchor" href="#kafka幂等"></a> Kafka幂等</h2>
<p>Kafka不确定是否成功发送,就一直重试,Broker保证只消费一次。</p>
<h3 id="幂等producer"><a class="markdownIt-Anchor" href="#幂等producer"></a> 幂等Producer</h3>
<p>Kafka为了保证幂等性,引入ProducerID和SequenceNumber。<br />
<code>new_seq = old_seq+1: 正常消息; new_seq <= old_seq : 重复消息; new_seq > old_seq+1: 消息丢失;</code></p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
</div>
<div class="post-block">
<article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
<link itemprop="mainEntityOfPage" href="http://simuleite.github.io/ComputerScience/%E7%9F%A5%E8%AF%86/Redis%E5%8E%9F%E7%90%86/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="SIMULEITE">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
<meta itemprop="name" content="undefined | SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/ComputerScience/%E7%9F%A5%E8%AF%86/Redis%E5%8E%9F%E7%90%86/" class="post-title-link" itemprop="url">Redis原理</a>
</h2>
<div class="post-meta-container">
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2025-05-20 00:00:00" itemprop="dateCreated datePublished" datetime="2025-05-20T00:00:00+08:00">2025-05-20</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2025-07-10 08:52:55" itemprop="dateModified" datetime="2025-07-10T08:52:55+08:00">2025-07-10</time>
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="跳表"><a class="markdownIt-Anchor" href="#跳表"></a> 跳表</h1>
<p>ZSet的实现方式有跳表、压缩列表。</p>
<ul>
<li>压缩列表:比较方便地搜索头节点和尾节点。数量<128,所有元素长度<64B时使用。</li>
<li>跳表:就是链表二分搜索的数据结构。多级链表,最高级链接的节点最稀疏。可以从高到低寻找,加快效率。</li>
</ul>
<blockquote>
<p>同样,跳表对范围查询支持较好,二分找到开头,然后遍历即可。</p>
</blockquote>
<h2 id="redis为什么不用b树mysql为什么不用跳表"><a class="markdownIt-Anchor" href="#redis为什么不用b树mysql为什么不用跳表"></a> Redis为什么不用b+树?MySQL为什么不用跳表?</h2>
<p>这个问题在于 Redis是直接操作内存的并不需要磁盘io而MySQL需要去读取io,所以mysql要使用b+树的方式减少磁盘io,B+树的原理是 叶子节点存储数据,非叶子节点存储索引,每次读取磁盘页时就会读取一整个节点,每个叶子节点还有指向前后节点的指针,为的是最大限度的降低磁盘的IO;因为数据在内存中读取耗费的时间是从磁盘的IO读取的百万分之一 而Redis是 内存中读取数据,不涉及IO,因此使用了跳表,跳表明显是更快更简单的方式。</p>
<h1 id="单线程网络io-kv读写"><a class="markdownIt-Anchor" href="#单线程网络io-kv读写"></a> 单线程网络IO、KV读写</h1>
<p>Redis的网络IO和KeyValue读写是由一个线程来完成的。<br />
而Redis的持久化、异步删除、集群数据同步是额外的线程执行。</p>
<p>也由于Redis是单线程的,所以要特别小心耗时的操作,这些操作会阻塞后续指令。</p>
<blockquote>
<p>简单来说就是处理事务一套、前台接待一套。不会因为前面办事导致人均等待时间太久。</p>
</blockquote>
<p>Redis使用IO多路复用(epoll),将连接信息、事件放到队列中,使其能够处理并发的客户端连接。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">socket: {</span><br><span class="line"> s0</span><br><span class="line"> s1</span><br><span class="line"> s2</span><br><span class="line"> s3</span><br><span class="line"> "..."</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">IO多路复用: {</span><br><span class="line"> s3 -> s2 -> s1 -> s0</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">事件处理器: {</span><br><span class="line"> 连接处理器</span><br><span class="line"> 命令请求处理器</span><br><span class="line"> 命令回复处理器</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">socket -> IO多路复用 -> 文件事件分派器 -> 事件处理器</span><br></pre></td></tr></table></figure>
<h1 id="详解get-key"><a class="markdownIt-Anchor" href="#详解get-key"></a> 详解GET key</h1>
<p>Redis相当于HashMap,也由于Hash是无序的,因此<code>scan</code>这样的流式查询,在查改场景中,可能会漏扫中途插入到前面下标的元素。</p>
<h1 id="redis持久化"><a class="markdownIt-Anchor" href="#redis持久化"></a> Redis持久化</h1>
<h2 id="rdb-snapshot"><a class="markdownIt-Anchor" href="#rdb-snapshot"></a> RDB Snapshot</h2>
<p>默认情况下,Redis将内存数据快照保存为<code>dump.rdb</code>,可以使用</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">save <time_duration> <row_insertion></span><br></pre></td></tr></table></figure>
<p>指示Redis多少秒内插入多少条数据后持久化到数据库<br />
也可以直接用<code>save</code>和<code>bgsave</code>命令写入数据库</p>
<h3 id="bgsave-异步持久化"><a class="markdownIt-Anchor" href="#bgsave-异步持久化"></a> bgsave 异步持久化</h3>
<p>bgsave使用写时复制COW。bgsave从主线程fork出来,当主线程修改数据时,bgsave线程会将写入数据拷贝一份,然后写入rdb</p>
<h2 id="append-only-file"><a class="markdownIt-Anchor" href="#append-only-file"></a> Append-Only File</h2>
<p>快照不能做到完全持久,假如服务宕机,可能会丢失几条写入。<br />
这时候我们直接做个命令日志AOF,将执行的修改指令写入<code>appendonly.aof</code>中</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">appendonly yes</span><br><span class="line">appendfilename "appendonly.aof"</span><br></pre></td></tr></table></figure>
<p>aof有三种模式<code>appendfsync</code>:</p>
<ul>
<li>always:立刻写入磁盘</li>
<li>everysec:每秒写一次</li>
<li>no:交给OS调度<br />
但是,由于aof是记录命令,需要执行时间,对于持久化大量数据比较耗时间。<br />
对于连续操作(如自增)aof会优化为1条命令,可以用<code>bgrewriteaof</code>命令手动重写</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 最小重构大小</span><br><span class="line">auto-aof-rewrite-min-size 64mb</span><br><span class="line"># 增长了100%,即128mb就重构</span><br><span class="line">auto-aof-rewrite-percentage 100</span><br></pre></td></tr></table></figure>
<h2 id="redis4-混合持久化"><a class="markdownIt-Anchor" href="#redis4-混合持久化"></a> Redis4 混合持久化</h2>
<p>由于Redis重启时优先使用aof恢复数据,rdb利用率不高。因此出现了混合持久化</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 必须同时开启aof</span><br><span class="line">aof-use-rdb-preamle yes</span><br><span class="line"># 可以直接把快照关掉,因为混合持久化都写在aof里面</span><br></pre></td></tr></table></figure>
<p>开启后,当aof重写时,会直接写入rdb,将rdb快照和aof增量存储在一起。<br />
于是Redis重启可以先读rdb,再执行增量aof恢复数据,提高效率。</p>
<h1 id="redis主从"><a class="markdownIt-Anchor" href="#redis主从"></a> Redis主从</h1>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis-<your_port>.conf</span><br><span class="line">pidfile /var/run/redis_<your_port>.pid</span><br><span class="line">logfile "<your_port>.log"</span><br><span class="line"># 数据存放目录</span><br><span class="line">dir /usr/local/redis/data/<your_port></span><br><span class="line"></span><br><span class="line">### 主从复制</span><br><span class="line">replicaof <main_redis_ip> <port></span><br><span class="line"># 从节点,只读</span><br><span class="line">replica-read-only yes</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">### 启动</span><br><span class="line"># 启动从节点</span><br><span class="line">redis-server redis-<your_port>.conf</span><br><span class="line"># 连接到从节点</span><br><span class="line">redis-cli -p <minor_redis_port></span><br></pre></td></tr></table></figure>
<h2 id="主从原理"><a class="markdownIt-Anchor" href="#主从原理"></a> 主从原理</h2>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">master: {</span><br><span class="line"> rdb data</span><br><span class="line"> repl buffer</span><br><span class="line">}</span><br><span class="line">slave</span><br><span class="line"></span><br><span class="line">slave -> master: 1. psync全量复制同步数据(通过socket长连接)</span><br><span class="line">master.rdb data -> master.rdb data: 2.1 收到psync命令,执行bgsave生成最新rdb快照</span><br><span class="line">master.repl buffer -> master.repl buffer: 2.2 主节点将增量写语句更新到buffer</span><br><span class="line">master.rdb data -> slave: 3. 发送rdb数据</span><br><span class="line">slave -> slave: 4. 清空旧数据,加载主节点rdb</span><br><span class="line">master.repl buffer -> slave: 5. 发送缓冲区写命令</span><br><span class="line">slave -> slave: 6. 执行主节点buffer写命令</span><br><span class="line">master -> slave: 7. 主节点通过socket长连接,持续发送写命令给从节点,保持数据一致</span><br></pre></td></tr></table></figure>
<h2 id="断点续传"><a class="markdownIt-Anchor" href="#断点续传"></a> 断点续传</h2>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">master: {</span><br><span class="line"> repl backlog buffer</span><br><span class="line">}</span><br><span class="line">slave</span><br><span class="line"></span><br><span class="line">slave -> master: 1. 连接断开</span><br><span class="line">master.repl backlog buffer -> master.repl backlog buffer: 2. 主节点增量写命令写入buffer</span><br><span class="line">slave -> master: 3. 恢复socket长连接</span><br><span class="line">slave -> master: 4. psync(offset)带偏移量</span><br><span class="line">master -> slave: 5. 若offset在buffer中,断点以后的数据发送给从节点;否则,全量发送</span><br><span class="line">master -> slave: 6. 持续发送buffer写命令,保持数据一致</span><br></pre></td></tr></table></figure>
<p>如果存在很多从节点,那么主节点传输压力会比较大。可以采用树型架构,让从节点再给它的子节点传输数据。</p>
<h1 id="哨兵高可用"><a class="markdownIt-Anchor" href="#哨兵高可用"></a> 哨兵高可用</h1>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">sentinel_cluster: {</span><br><span class="line"> sentinel1 <-> sentinel2 <-> sentinel3 <-> sentinel1</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">client -> master <-> sentinel_cluster</span><br><span class="line">master -> slave1</span><br><span class="line">master -> slave2</span><br><span class="line">client -> sentinel_cluster</span><br><span class="line">sentinel_cluster <-> slave1</span><br><span class="line">sentinel_cluster <-> slave2</span><br></pre></td></tr></table></figure>
<p>哨兵会动态监听redis主节点,如果主节点挂了,哨兵会选择一个新redis示例作为主节点(通知给client端)</p>
<h2 id="开启哨兵"><a class="markdownIt-Anchor" href="#开启哨兵"></a> 开启哨兵</h2>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># sentinel.conf</span><br><span class="line"></span><br><span class="line">port 26379</span><br><span class="line">pidfile <your_file></span><br><span class="line">logfile <your_file></span><br><span class="line">dir "<your_dir>"</span><br><span class="line"></span><br><span class="line"># quorm是指多少个sentinel同时认为主节点挂了,才让master失效,一般设置为一半以上</span><br><span class="line">sentinel monitor mymaster <redis_ip> <redis_port> <quorm></span><br></pre></td></tr></table></figure>
<p>启动哨兵<code>./redis-sentinel sentinel.conf</code></p>
<h1 id="redis-cluster"><a class="markdownIt-Anchor" href="#redis-cluster"></a> Redis Cluster</h1>
<p>当哨兵集群选举新节点的时候,服务会宕机几秒钟。因此我们需要Cluster</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">client1 -> RedisCluster</span><br><span class="line">client2 -> RedisCluster</span><br><span class="line">RedisCluster: Hash slot: CRC16(key) % 16384</span><br><span class="line">RedisCluster -> Redis集群</span><br><span class="line">Redis集群: {</span><br><span class="line"> master1 -> slave1-1</span><br><span class="line"> master1 -> slave1-2</span><br><span class="line"> </span><br><span class="line"> master2 -> slave2-1</span><br><span class="line"> master2 -> slave2-2</span><br><span class="line"> </span><br><span class="line"> master3 -> slave3-1</span><br><span class="line"> master3 -> slave3-2</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在Cluster中,每个master数据是不重叠的,数据会被分片储存。通过Hash算法来决定存储数据到哪一个master节点。<br />
使用Cluster,可以避免Redis服务完全宕机。<br />
2的幂次取模小技巧:</p>
<p><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>X</mi><mspace></mspace><mspace width="0.6666666666666666em"/><mrow><mi mathvariant="normal">m</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">d</mi></mrow><mtext> </mtext><mtext> </mtext><msup><mn>2</mn><mi>n</mi></msup><mo>=</mo><mi>X</mi><mtext> & </mtext><mo stretchy="false">(</mo><msup><mn>2</mn><mi>n</mi></msup><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">X \mod 2^n = X \text{ \& } (2^n - 1)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mspace allowbreak"></span><span class="mspace" style="margin-right:0.6666666666666666em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord"><span class="mord"><span class="mord mathrm">m</span><span class="mord mathrm">o</span><span class="mord mathrm">d</span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.664392em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">n</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mord text"><span class="mord"> & </span></span><span class="mopen">(</span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.664392em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathdefault mtight">n</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span></span></span></span></p>
<h2 id="redis集群搭建"><a class="markdownIt-Anchor" href="#redis集群搭建"></a> Redis集群搭建</h2>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">redis-cluster/</span><br><span class="line">|-- 8000</span><br><span class="line">| `-- redis.conf</span><br><span class="line">|-- 8010</span><br><span class="line">`-- 8020</span><br></pre></td></tr></table></figure>
<ol>
<li>Redis配置</li>
</ol>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># ...其他配置</span><br><span class="line"></span><br><span class="line">daemonize yes</span><br><span class="line">port 8000</span><br><span class="line">dir /path/to/redis-cluster/8000/</span><br><span class="line"># 启用集群</span><br><span class="line">cluster-enabled yes</span><br><span class="line">cluster-config-file nodes-8000.conf</span><br><span class="line">cluster-node-timeout 5000</span><br><span class="line"># 密码</span><br><span class="line">requirepass <your_password></span><br><span class="line">masterauth <your_auth_password></span><br></pre></td></tr></table></figure>
<ol start="2">
<li>启动所有master和slave节点</li>
</ol>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">redis-server /path/to/redis-cluster/80*/redis.conf</span><br><span class="line">ps aux | grep redis</span><br></pre></td></tr></table></figure>
<ol start="3">
<li>开启集群</li>
</ol>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">replicas表示节点的副本,配置为1,则1主1从</span></span><br><span class="line">redis-cli -a <your_auth_password> --cluster create --cluster-replicas 1 \</span><br><span class="line">localhost:8000 localhost:8001 localhost:8002 ...</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注意,第二次启动集群后,就不需要这一步了。节点会自动读取<code>nodes-8000.conf</code>文件,恢复上次集群状态。</p>
</blockquote>
<ol start="4">
<li>进入redis节点验证配置</li>
</ol>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cluster info</span><br><span class="line">cluster nodes</span><br></pre></td></tr></table></figure>
<h1 id="redission原理"><a class="markdownIt-Anchor" href="#redission原理"></a> Redission原理</h1>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Thread1: {</span><br><span class="line"> Redission</span><br><span class="line">}</span><br><span class="line">Thread2: {</span><br><span class="line"> Redission</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Thread1.Redission -> Try Lock</span><br><span class="line">Try Lock -> 守护线程: 加锁成功</span><br><span class="line">守护线程 -> Redis(Master): lock,每隔10s检查线程是否仍持有锁。如果持有,则延长锁失效时间</span><br><span class="line"></span><br><span class="line">Thread2.Redission -> Try Lock</span><br><span class="line">Try Lock -> Thread2.Redission: 加锁失败,使用while自旋尝试加锁</span><br></pre></td></tr></table></figure>
<p>Redission利用了Redis Lua脚本保证原子操作。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
</div>
<div class="post-block">
<article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
<link itemprop="mainEntityOfPage" href="http://simuleite.github.io/ComputerScience/%E7%9F%A5%E8%AF%86/Multi-Agents/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="SIMULEITE">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
<meta itemprop="name" content="undefined | SMULET's BLOG">
<meta itemprop="description" content="">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/ComputerScience/%E7%9F%A5%E8%AF%86/Multi-Agents/" class="post-title-link" itemprop="url">Multi-Agents</a>
</h2>
<div class="post-meta-container">
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2025-06-27 00:00:00" itemprop="dateCreated datePublished" datetime="2025-06-27T00:00:00+08:00">2025-06-27</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2025-06-28 10:02:41" itemprop="dateModified" datetime="2025-06-28T10:02:41+08:00">2025-06-28</time>
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="dont-build-multi-agents"><a class="markdownIt-Anchor" href="#dont-build-multi-agents"></a> Don’t Build Multi-Agents</h1>
<p><a target="_blank" rel="noopener" href="https://cognition.ai/blog/dont-build-multi-agents">Cognition</a><br />
构建长期运行的AI智能体系统,需要解决“可靠性”问题:</p>
<ol>
<li>上下文丢失、过长</li>
<li>状态混乱</li>
<li>错误累积</li>
</ol>
<p>例如,Multi-Agent思路需要构建规划Agent、解释Agent、执行Agent、SOP Agent。<br />
然而,如果仅仅使用两个独立Agent,其生成结果会更加独立、隔绝,而不是相关联。<br />
整体大于局部。局部的完整性不能保证整体的一致性。</p>
<h2 id="shared-data"><a class="markdownIt-Anchor" href="#shared-data"></a> Shared Data</h2>
<blockquote>
<p>Principle 1: Share context, and share full agent traces, not just individual messages.</p>
</blockquote>
<p>我们希望通过共享上下文解决一致性问题,但是不行。<br />
Agent1和Agent2都是基于自己对Shared Data的理解工作,而不知道对方在做什么。<br />
因此我们需要共享Traces,让一个Agent(例如解释Agent)对另一个Agent(例如执行Agent)进行Revision校正。<br />
可是,只有垂直矫正,水平的Agent(多个执行Agents)之间仍然不知道对方在做什么。</p>
<h2 id="actions-mean-desisons"><a class="markdownIt-Anchor" href="#actions-mean-desisons"></a> Actions mean desisons</h2>
<blockquote>
<p>Principle 2: Actions carry implicit decisions, and conflicting decisions carry bad results.</p>
</blockquote>
<p>每个Agent的行为都必须基于同样的预期结果,而不能基于不清楚、有歧义的预期结果;否则整体很难保持风格统一。</p>
<h2 id="single-threaded-linear-agent"><a class="markdownIt-Anchor" href="#single-threaded-linear-agent"></a> Single-threaded Linear Agent</h2>
<p>鉴于上面两条,作者选择使用单Agent线性解决问题。<br />
然而,这样做容易产生<code>context windows overflow</code>上下文溢出(因为线性Agent其实就是不断附带上一次的上下文进行下一次chat)。<br />
我们引入总结压缩LLM解决上下文问题。</p>
<h2 id="claude-code设计模式"><a class="markdownIt-Anchor" href="#claude-code设计模式"></a> Claude Code设计模式</h2>
<p>Calude Code的智能体有两个特点:</p>
<ol>
<li>主Agent与子Agent不会并行运行</li>
<li>子Agent只回答简单问题,而不会编写代码<br />
这样做有几个优点</li>
</ol>
<ul>
<li>避免上下文冲突:子Agent不包括主Agent的上下文,只回答清晰、具体的问题。</li>
<li>节省上下文:子Agent的操作也不保存在主Agent的上下文中。他们是解耦合的。</li>
</ul>
<h1 id="how-we-built-our-multi-agent-research-system"><a class="markdownIt-Anchor" href="#how-we-built-our-multi-agent-research-system"></a> How we built our multi-agent research system</h1>
<p><a target="_blank" rel="noopener" href="https://www.anthropic.com/engineering/built-multi-agent-research-system">How we built our multi-agent research system</a><br />
三种AI模式:</p>
<ol>
<li>Chat AI</li>
<li>Single Agent</li>
<li>Multiple Agents<br />
Multi-Agents的优势在于回答开放、不确定的问题。传统的单Agent不适合研究,而多Agent并行搜索,最终总结出来的信息压缩性更强。</li>
</ol>
<blockquote>