-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.xml
8671 lines (8525 loc) · 675 KB
/
index.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
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
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>HTT - ふわふわ時間</title>
<link>https://k-on.me/</link>
<description>Recent content on HTT - ふわふわ時間</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<lastBuildDate>Wed, 07 Jul 2021 21:06:50 +0800</lastBuildDate>
<atom:link href="https://k-on.me/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>Codis 简要介绍</title>
<link>https://k-on.me/post/redis/codis_intro/</link>
<pubDate>Wed, 07 Jul 2021 21:06:50 +0800</pubDate>
<guid>https://k-on.me/post/redis/codis_intro/</guid>
<description><p><a href="https://github.com/CodisLabs/codis">Codis</a> 是一个基于 Proxy 的 Redis 集群化方案,其具体介绍与使用可参考 <a href="https://github.com/CodisLabs/codis/blob/release3.2/doc/tutorial_zh.md">Codis 使用文档</a>。本文首先从运维角度出发介绍 Codis,便于了解每个操作背后的具体内容,随后简要介绍了 Codis-Proxy 基本的工作原理,最后简单介绍了扩缩容相关操作。</p>
<hr>
<h2 id="1-集群管理">1. 集群管理</h2>
<p>Codis-Dashboard 主要负责集群元数据的管理,在下面的讨论中,我们假设集群的元数据存储在 zk 中。Codis 集群的元数据大致有以下几种:</p>
<ul>
<li><code>Group</code>: 记录该 Group 下 Servers 地址及状态 (是否有 Promoting,是否 out-of-sync, 是否可读等)</li>
<li><code>SlotMapping</code>: 包含 Slot-Group 映射关系,以及该 Slot 的状态 (用于数据迁移)</li>
<li><code>Proxy</code>: 记录 Proxy 的基本信息</li>
<li><code>Sentinel</code>: 记录 Sentinel 的地址及状态 (是否 out-of-sync)</li>
</ul>
<p>Codis-Dashboard 在本地保存了一份集群元数据的缓存 <code>Topom.cache</code>:(Cache Aside Pattern)</p>
<ul>
<li><strong>写操作</strong>:修改集群元数据时,会直接写 zk,不论写 zk 成功与否此时都会将缓存 <code>Topom.cache</code> 中对应的内容标记为失效 (<code>nil</code>)</li>
<li><strong>读操作</strong>:读取元数据时,遍历 <code>Topom.cache</code>,以 zk 中的数据填充失效的缓存部分,随后返回 <code>Topom.cache</code></li>
</ul>
<p>下文操作均会先读缓存,再更新 zk,并将相应的缓存信息标记为失效。值得注意的是,更新 zk 操作不一定会成功执行,但是将缓存信息标记为失效一定会执行。</p>
<h3 id="11-group-相关操作">1.1. Group 相关操作</h3>
<ul>
<li>
<p><strong>添加 Group</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --create-group --gid<span class="o">=</span>ID
</code></pre></td></tr></table>
</div>
</div><p>含义:添加指定 <code>ID</code> 的 Group。</p>
<p>在添加 Group 时,只需注意 Group ID 范围在 $[0, 9999]$ 内,且该 Group ID 目前不在集群内即可。</p>
</li>
<li>
<p><strong>Group 中添加 Server</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --group-add --gid<span class="o">=</span>ID --addr<span class="o">=</span>ADDR <span class="o">[</span>--datacenter<span class="o">=</span>DATACENTER<span class="o">]</span>
</code></pre></td></tr></table>
</div>
</div><p>含义:向 <code>gid = ID</code> 的 Group 中添加地址为 <code>ADDR</code> 的 Server。</p>
<p><img src="https://k-on.me/codis/group_add_server.png" alt="group_add_server"></p>
</li>
<li>
<p><strong>Group 中删除 Server</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --group-del --gid<span class="o">=</span>ID --addr<span class="o">=</span>ADDR
</code></pre></td></tr></table>
</div>
</div><p>含义:从 <code>gid = ID</code> 的 Group 删除地址为 <code>ADDR</code> 的 Server。</p>
<p><img src="https://k-on.me/codis/group_del_server.png" alt="group_del_server"></p>
<p>可以发现,可以直接移除可读从库,因此在移除从库时,建议先取消可读并点击 SYNC 后再移除该从库。</p>
</li>
<li>
<p><strong>删除 Group</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --remove-group --gid<span class="o">=</span>ID
</code></pre></td></tr></table>
</div>
</div><p>含义:删除 <code>gid = ID</code> 的 Group.</p>
<p>在删除 Group 之前,需先清空 Group 中的 Server。</p>
</li>
<li>
<p><strong>建立/取消主从同步</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --sync-action --create --addr<span class="o">=</span>ADDR
</code></pre></td></tr></table>
</div>
</div><p>含义:向地址为 <code>ADDR</code> 发送 <code>SLAVEOF</code> 命令,在 Group 内依据 Server 的位置建立主从关系。</p>
<ul>
<li><code>index = 0</code>: <code>SLAVEOF NO ONE</code></li>
<li><code>index &gt; 0</code>: 成为 <code>index = 0</code> 的 Server 的从库</li>
</ul>
<p><img src="https://k-on.me/codis/group_add_sync.png" alt="group_add_sync"></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --sync-action --remove --addr<span class="o">=</span>ADDR
</code></pre></td></tr></table>
</div>
</div><p>含义:删除地址为 <code>ADDR</code> 的实例上 Pending 的主从同步操作。</p>
<p><img src="https://k-on.me/codis/group_del_sync.png" alt="group_del_sync"></p>
<ul>
<li>该操作极少使用,只有在原主库所在机器宕机后,不想机器上的实例在机器启动时收到 <code>SLAVEOF</code> 操作时会使用。</li>
<li>另外,在人工介入集群的故障恢复过程中时,最好直接<strong>停止</strong>集群中的 Sentinel 实例进程,以免受到 Sentinel 的干扰</li>
</ul>
</li>
<li>
<p><strong>开启/关闭从库可读</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --replica-groups --gid<span class="o">=</span>ID --addr<span class="o">=</span>ADDR <span class="o">(</span>--enable<span class="p">|</span>--disable<span class="o">)</span>
</code></pre></td></tr></table>
</div>
</div><p>含义:依据 enable/disable 开启/关闭特定实例的可读状态。</p>
<p><img src="https://k-on.me/codis/group_replica_read.png" alt="group_replica_read"></p>
</li>
<li>
<p><strong>响应 Sentinel 切主</strong></p>
<p>Codis-Dashboard 通过订阅 Sentinel 的 <code>+switch-master</code> 消息响应 Sentinel 执行故障恢复时的切主行为,当监听到切主时:</p>
<p><img src="https://k-on.me/codis/group_switch_master.png" alt="group_switch_master"></p>
</li>
<li>
<p><strong>Group SYNC</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --resync-group <span class="o">[</span>--gid<span class="o">=</span>ID <span class="p">|</span> --all<span class="o">]</span>
</code></pre></td></tr></table>
</div>
</div><p>含义:将该 Group 相关的 Server 地址,是否从库可读和 Slot-Group 映射关系同步至 Codis-Proxy。</p>
<p>由上述可知,Group 会在以下情况下标记为 <code>out-of-sync</code>:</p>
<ul>
<li>从 Group 中去除一个可读从库</li>
<li>开启/关闭可读</li>
<li>Sentinel 自动切主后</li>
</ul>
</li>
<li>
<p><strong>Promote</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --promote-server --gid<span class="o">=</span>ID --addr<span class="o">=</span>ADDR
</code></pre></td></tr></table>
</div>
</div><p>含义:将 <code>gid = ID</code> 的 Group 中地址为 <code>ADDR</code> 的实例提升为主库。</p>
<p><img src="https://k-on.me/codis/group_promote_server.png" alt="group_promote_server"></p>
</li>
</ul>
<h3 id="12-slot-相关操作">1.2. Slot 相关操作</h3>
<ul>
<li>
<p><strong>分配 Slot</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --slot-action --create --sid<span class="o">=</span>ID --gid<span class="o">=</span>ID
</code></pre></td></tr></table>
</div>
</div><p>含义:将 <code>sid = ID</code> 的 slot 分配至 <code>gid = ID</code> 的 Group</p>
<p><img src="https://k-on.me/codis/slot_2_group.png" alt="slot_2_group"></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --slot-action --create-some --gid-from<span class="o">=</span>ID --gid-to<span class="o">=</span>ID --num-slots<span class="o">=</span>N
</code></pre></td></tr></table>
</div>
</div><p>含义:从 <code>gid-from = ID</code> 的 Group 中迁移不大于 <code>num-slots = N</code> 的 slots 至 <code>gid-to = ID</code> 的 Group</p>
<p>具体操作流程同上。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --slot-action --create-range --beg<span class="o">=</span>ID --end<span class="o">=</span>ID --gid<span class="o">=</span>ID
</code></pre></td></tr></table>
</div>
</div><p>含义:将 $[beg, end]$ 之间的 slots 分配至 <code>gid = ID</code> 的 Group</p>
<p>具体操作流程同上。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --rebalance <span class="o">[</span>--confirm<span class="o">]</span>
</code></pre></td></tr></table>
</div>
</div><p>含义:重分配 Slots,使得各个 Group 所分配到的 Slots 数量大致相等</p>
</li>
<li>
<p><strong>移除 Pending 状态的操作</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --slot-action --remove --sid<span class="o">=</span>ID
</code></pre></td></tr></table>
</div>
</div><p>含义:移除 <code>sid = ID</code> 的 Slot 上 <code>ActionPending</code> 状态的分配操作</p>
</li>
<li>
<p><strong>参数设置</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --slot-action --interval<span class="o">=</span>VALUE
</code></pre></td></tr></table>
</div>
</div><p>含义:设置 Codis-Dashboard 后台执行 slot 相关操作的时间间隔,有效范围为 $[0, 1]$ (秒)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --slot-action --disabled<span class="o">=</span>VALUE
</code></pre></td></tr></table>
</div>
</div><p>含义:暂停/开始 slot 相关操作。</p>
</li>
</ul>
<h3 id="13-codis-proxy-相关操作">1.3. Codis-Proxy 相关操作</h3>
<ul>
<li>
<p><strong>添加 Codis-Proxy</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --create-proxy --addr<span class="o">=</span>ADDR
</code></pre></td></tr></table>
</div>
</div><p><img src="https://k-on.me/codis/add_codis_proxy.png" alt="add_codis_proxy"></p>
</li>
<li>
<p><strong>Online Codis-Proxy</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --online-proxy --addr<span class="o">=</span>ADDR
</code></pre></td></tr></table>
</div>
</div><ul>
<li>若 Codis-Proxy 此前未在集群中,则与添加时的流程完全一致</li>
<li>若 Codis-Proxy 此前已在集群中,则会 <code>Online</code> 现有的 Codis-Proxy</li>
</ul>
</li>
<li>
<p><strong>删除 Codis-Proxy</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --remove-proxy <span class="o">(</span>--addr<span class="o">=</span>ADDR<span class="p">|</span>--token<span class="o">=</span>TOKEN<span class="p">|</span>--pid<span class="o">=</span>ID<span class="o">)</span> <span class="o">[</span>--force<span class="o">]</span>
</code></pre></td></tr></table>
</div>
</div><p><img src="https://k-on.me/codis/del_codis_proxy.png" alt="del_codis_proxy"></p>
</li>
<li>
<p><strong>Codis-Proxy SYNC</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --reinit-proxy <span class="o">(</span>--addr<span class="o">=</span>ADDR<span class="p">|</span>--token<span class="o">=</span>TOKEN<span class="p">|</span>--pid<span class="o">=</span>ID<span class="p">|</span>--all<span class="o">)</span> <span class="o">[</span>--force<span class="o">]</span>
</code></pre></td></tr></table>
</div>
</div><p>初始化主要包括以下三部分</p>
<ul>
<li>将集群 Slots 信息同步至 Codis Proxy</li>
<li>Start Codis Proxy: Codis Proxy &amp; Router Online 状态设置为 true</li>
<li>令 Codis Proxy 订阅 Sentinel 主从切换信息</li>
</ul>
</li>
</ul>
<h3 id="14-sentinel-相关操作">1.4. Sentinel 相关操作</h3>
<ul>
<li>
<p><strong>添加 Sentinel</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --sentinel-add --addr<span class="o">=</span>ADDR
</code></pre></td></tr></table>
</div>
</div><p><img src="https://k-on.me/codis/add_sentinel.png" alt="add_sentinel"></p>
<p>可以发现在添加 Sentinel 时,只是将 Sentinels 的状态标记为 <code>out-of-sync</code>。这是因为此时并未让 Sentinel 监听集群中的 Server 实例,也未令 Codis-Dashboard 和 Codis-Proxy 订阅 Sentinel 的 <code>+switch-master</code> 消息。</p>
</li>
<li>
<p><strong>删除 Sentinel</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --sentinel-del --addr<span class="o">=</span>ADDR <span class="o">[</span>--force<span class="o">]</span>
</code></pre></td></tr></table>
</div>
</div><p><img src="https://k-on.me/codis/del_sentinel.png" alt="del_sentinel"></p>
</li>
<li>
<p><strong>Sentinel SYNC</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-shell" data-lang="shell">codis-admin <span class="o">[</span>-v<span class="o">]</span> --dashboard<span class="o">=</span>ADDR --sentinel-resync
</code></pre></td></tr></table>
</div>
</div><p><img src="https://k-on.me/codis/sentinel_sync.png" alt="sentinel_sync"></p>
</li>
</ul>
<h2 id="2-请求处理">2. 请求处理</h2>
<p><img src="https://k-on.me/codis/codis_proxy.png" alt="codis_proxy"></p>
<p>Codis-Proxy 处理请求的过程大致如上图所示:</p>
<ul>
<li>
<p><code>Session</code> 与 <code>BackendConn</code> 之间的通信</p>
<ul>
<li>每个客户端连接对应一个 <code>Session</code></li>
<li>每个后端 server 对应的 <code>BackendConn</code> 数由配置项 <code>backend_primary_parallel</code> 和 <code>backend_replica_parallel</code> 控制</li>
<li><code>Session</code> 与 <code>BackendConn</code> 之间通过管道 <code>bc.input</code> 通信,每个 <code>BackendConn</code> 负责处理消费一个管道,其容量为固定大小 $1024$,因此当 <code>Session</code> 数量远大于 <code>BackendConn</code> 数量时,在 <code>bc.input</code> 中排队阻塞的概率较大</li>
</ul>
</li>
<li>
<p>按序回复</p>
<ul>
<li><code>Session</code> 中的 <code>tasks</code> 管道保证请求按序回复</li>
<li><code>BackendConn</code> 中的 <code>tasks</code> 管道保证转发至 Servers 的请求,会按序收包</li>
</ul>
</li>
<li>
<p>每个请求会有两个 <code>WaitGroup</code></p>
<ul>
<li><code>r.Batch</code>: <code>Session</code> 依此等待请求处理完成</li>
<li><code>r.Group</code>: 其实际为 <code>Slot.refs</code> (请求哈希至该 <code>Slot</code>, 便会令 <code>r.Group = Slot.refs</code>)。<code>Slot</code> 依此会在每次更新路由表 (Slot-Group 映射) 前等待当前 <code>Slot</code> 上的请求处理完毕</li>
</ul>
</li>
<li>
<p>批量操作命令</p>
<ul>
<li>只要批量操作多个 key 的请求可以拆成多个单次操作并行执行,则 Codis 支持该命令</li>
<li>因为 Codis 是将批量操作拆分成多个请求转发至 Redis,因此批量操作的耗时大致上取决于最慢的那个子请求的耗时</li>
<li>因为批量操作的存在,Codis-Proxy 的扇出一般大于 $1$,因此当 Codis-Proxy 的 QPS 并无大幅上涨时,底层 Servers 的请求也可能上涨 (每个批量操作操作的 key 数量增长所致)</li>
</ul>
</li>
</ul>
<h2 id="3-扩缩容">3. 扩缩容</h2>
<p>在向 Group 中添加 Server 时,我们可以看到只有支持 <code>SlotsInfo</code> 命令的 Server 方可加入集群。事实上 Codis 为了能平滑扩缩容,修改了 Redis 源码以记录当前 Server 所负责的 <code>Slot</code> 以及每个 <code>Slot</code> 中的 key 的信息,这些信息记录在 <a href="https://github.com/CodisLabs/codis/blob/release3.2/extern/redis-3.2.11/src/server.h#L523">redisDb-&gt;hash_slots</a> 中。</p>
<blockquote>
<p>当然,没有扩缩容需求的话,可以实现一个空的 <code>SlotsInfo</code> 请求处理函数便可将 Server 添加至 Codis 集群提供服务。</p>
</blockquote>
<h3 id="31-相关命令">3.1. 相关命令</h3>
<p>修改后的 Redis 也实现了一系列 <code>Slot</code> 相关的命令以支持扩缩容,参考<a href="https://github.com/CodisLabs/codis/blob/release3.2/doc/redis_change_zh.md">redis 修改部分(增加若干指令)</a>、<a href="https://github.com/CodisLabs/codis/blob/release3.2/extern/redis-3.2.11/src/slots.c">slots.c</a> 和 <a href="https://github.com/CodisLabs/codis/blob/release3.2/extern/redis-3.2.11/src/slots_async.c">slots_async.c</a>,介绍如下:</p>
<h4 id="311-调试相关">3.1.1. 调试相关</h4>
<ul>
<li>
<p><code>slotshashkey key1 [key2 …]</code></p>
<ul>
<li>
<p>命令说明:计算并返回给定 key 的 slot 序号</p>
</li>
<li>
<p>命令参数:输入为 1 个或多个 key</p>
</li>
<li>
<p>返回结果: 操作返回 array,其中 <code>slot</code> 表示对应 key 的 slot 序号,即 $hash32(key) \ \% \ \mathrm{NUM\_OF\_SLOTS}$</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">response</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="nx">slot1</span><span class="p">,</span> <span class="nx">slot2</span><span class="o">...</span><span class="p">}</span>
</code></pre></td></tr></table>
</div>
</div></li>
</ul>
</li>
<li>
<p><code>slotsinfo [start] [count]</code></p>
<ul>
<li>
<p>命令说明:获取 redis 中 slot 的个数以及每个 slot 的大小</p>
</li>
<li>
<p>命令参数:缺省查询 $[0, \mathrm{MAX\_SLOT\_NUM})$</p>
<ul>
<li><code>start</code> - 起始的 slot 序号
<blockquote>
<p>缺省 = $0$</p>
</blockquote>
</li>
<li><code>count</code> - 查询的区间的大小,即查询范围为 $[start, start + count)$
<blockquote>
<p>缺省 = $\mathrm{MAX\_SLOT\_NUM}$</p>
</blockquote>
</li>
</ul>
</li>
<li>
<p>返回结果:返回结果是 slotinfo 的 array;slotinfo 本身也是一个 array。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">response</span> <span class="o">:=</span> <span class="p">[]</span><span class="nx">slotinfo</span><span class="p">{</span><span class="nx">slot1</span><span class="p">,</span> <span class="nx">slot2</span><span class="p">,</span> <span class="nx">slot3</span><span class="p">,</span> <span class="o">...</span><span class="p">}</span>
<span class="nx">slotinfo</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="nx">slotnum</span><span class="p">,</span> <span class="nx">slotsize</span><span class="p">}</span>
</code></pre></td></tr></table>
</div>
</div><p>其中:</p>
<ul>
<li><code>slotnum</code>: slot 序号</li>
<li><code>slotsize</code>: slot 内数据个数</li>
</ul>
</li>
</ul>
</li>
<li>
<p><code>slotsscan slotnum cursor [COUNT count]</code></p>
<ul>
<li>
<p>命令说明:获取指定 slotnum 下的 key 列表</p>
</li>
<li>
<p>命令参数:参数说明类似 SCAN 命令</p>
<ul>
<li><code>slotnum</code> - 查询的 slot 序号,$[0, \mathrm{MAX\_SLOT\_NUM})$</li>
<li><code>cursor</code> - 说明参考 SCAN 命令</li>
<li><code>[COUNT count)</code> - 说明参考 SCAN 命令</li>
<li>暂不支持 MATCH 查询</li>
</ul>
</li>
<li>
<p>返回结果:参考 SCAN 命令</p>
<ul>
<li>返回更新后的 cursor 以及一组 key 列表</li>
</ul>
</li>
</ul>
</li>
<li>
<p><code>slotsdel slot1 [slot2 …]</code></p>
<ul>
<li>命令说明:删除 redis 中若干 slot 下的全部 key-value</li>
<li>命令参数:接受至少 1 个 slotnum 作为参数</li>
<li>返回结果:格式参见 slotsinfo,不同的是:slotsize 表示删除后剩余大小,通常为 0</li>
</ul>
</li>
<li>
<p><code>slotscheck</code></p>
<ul>
<li>
<p>命令说明:对 redis 内的 slots 进行一致性检查,即满足如下两条</p>
<ul>
<li>每个 slot 中保存的 key 都能在 db 中找到对应的 val</li>
<li>每个 db 中的 key 都能在对应的 slot 中查找到</li>
</ul>
<blockquote>
<p>该操作比较慢,仅仅作为 redis 开发的调试工具使用,不能在线上使用</p>
</blockquote>
</li>
<li>
<p>命令参数:0 参数</p>
</li>
<li>
<p>返回结果:</p>
<ul>
<li>成功返回字符串 <code>OK</code></li>
<li>如果 check 失败,会返回 <code>ERR</code> 并包含对应出错的 key</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="312-同步迁移">3.1.2. 同步迁移</h4>
<p><strong>通常客户端请求</strong></p>
<ul>
<li>
<p><code>slotsmgrtslot host port timeout slot</code></p>
<ul>
<li>
<p>命令说明:随机选择 slot 下的 1 个 key-value 到迁移到目标机(同步 IO 操作)</p>
<ul>
<li>如果当前 slot 已经空了或者选择的 key 刚好过期,返回 0</li>
<li>如果当前 slot 下面还有 key 则选择一个进行迁移</li>
<li>同时返回当前 slot 剩余 key 的个数</li>
<li>迁移过程在目标机器调用 <code>slotsrestore</code> 命令,迁移会 <strong>覆盖旧值</strong></li>
</ul>
</li>
<li>
<p>命令参数:</p>
<ul>
<li><code>host:port</code> - 目标机地址
<ul>
<li>redis 内部缓存到 <code>host:port</code> 的连接 $30s$,超时或错误则关闭</li>
</ul>
</li>
<li><code>timeout</code> - 操作超时,单位 $ms$
<ul>
<li>过程需要 3 个同步操作:
<ol>
<li>建立连接(可被缓存优化)</li>
<li>发送 key-value 数据</li>
<li>接受目标机返回</li>
</ol>
</li>
<li>指令保证每个操作不超过 <code>timeout</code></li>
</ul>
</li>
<li><code>slot</code> - 指定迁移的 slot 序号</li>
</ul>
</li>
<li>
<p>返回结果: 操作返回 <code>int</code></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">response</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="nx">succ</span><span class="p">,</span><span class="nx">size</span><span class="p">}</span>
</code></pre></td></tr></table>
</div>
</div><p>其中:</p>
<ul>
<li><code>succ</code>: 表示迁移是否成功。
<ul>
<li>0 表示当前 slot 已经空了(迁移成功个数 = 0)</li>
<li>1 表示迁移一个 key 成功,并从本地删除(迁移成功个数 = 1)</li>
</ul>
</li>
<li><code>size</code>: 表示 slot 下剩余 key 的个数</li>
</ul>
</li>
</ul>
</li>
<li>
<p><code>slotsmgrtone host port timeout key</code></p>
<ul>
<li>
<p>命令说明:迁移 key 到目标机,与 <code>slotsmgrtslot</code> 相同</p>
</li>
<li>
<p>命令参数:参见 <code>slotsmgrtslot</code></p>
</li>
<li>
<p>返回结果:操作返回整数,其中 <code>succ</code> 的含义与 <code>slotsmgrtslot</code> 相似</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">response</span> <span class="o">:=</span> <span class="nb">int</span><span class="p">(</span><span class="nx">succ</span><span class="p">)</span>
</code></pre></td></tr></table>
</div>
</div></li>
</ul>
</li>
<li>
<p><code>slotsmgrttagone host port timeout key</code></p>
<ul>
<li>
<p>命令说明:迁移与 key 有相同的 tag 的所有 key 到目标机</p>
<ul>
<li>当 key 中不包含合法 tag 时,命令退化为 <code>slotsmgrtone</code>,时间复杂度为 $O(1)$</li>
<li>当 key 中包含合法 tag 时,命令会计算 tag 的 hash 值,并在 skiplist 中找到所有具有相同 hash 值的 key-value 对,原子地迁移到目标机,时间复杂度为 $O(log(n))$</li>
</ul>
<blockquote>
<p>修改的 redis 中,会将所有含有 tag 的 key,组织在 skiplist 中,并按照 tag 的 hash 值进行排序。当对按照某一 tag 进行迁移数据时,实际操作会将所有具有相同 hash 值的 tag 所涉及到的所有 key 一起迁移。也就是说,真正迁移的数据<strong>可能包含更多的 key</strong>,但是这么设计会减少 tag 迁移过程对字符串的比较次数,显著提升性能。</p>
</blockquote>
</li>
<li>
<p>命令参数:参见 <code>slotsmgrtone</code></p>
</li>
<li>
<p>返回结果:操作返回整数,其中 <code>succ</code> 表示成功迁移的 key 的个数。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">response</span> <span class="o">:=</span> <span class="nb">int</span><span class="p">(</span><span class="nx">succ</span><span class="p">)</span>
</code></pre></td></tr></table>
</div>
</div></li>
</ul>
</li>
<li>
<p><code>slotsmgrttagslot host port timeout slot</code></p>
<ul>
<li>命令说明:与 <code>slotsmgrtslot</code> 对应的迁移指令</li>
<li>其他说明参考 <code>slotsmgrtslot</code> 以及 <code>slotsmgrttagone</code> 的解释即可</li>
</ul>
</li>
</ul>
<p><strong>衍生命令</strong></p>
<ul>
<li><code>slotsrestore key1 ttl1 val1 [key2 ttl2 val2 …]</code>
<ul>
<li>命令说明:该命令是对 redis-2.8 的 <code>restore</code> 命令的扩展
<ul>
<li>可以对 <code>restore</code> 多个 key-value</li>
<li>过程是原子的</li>
</ul>
<blockquote>
<p>与 <code>restore</code> 不同的是,<code>slotsrestore</code> 只支持 replace,即一定<strong>覆盖旧值</strong>。如果旧值已经存在,那么只可能是 redis-slots 或者 proxy 的实现 bug,程序会通过 redisLog 打印一条冲突记录。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h4 id="313-异步迁移">3.1.3. 异步迁移</h4>
<p><strong>通常客户端请求</strong></p>
<ul>
<li>
<p><code>slotsmgrtone-async host port timeout maxbulks maxbytes key1 [key2 ...]</code></p>
<ul>
<li>
<p>命令说明:迁移 key1, key2 &hellip; 到目标机</p>
</li>
<li>
<p>命令参数:</p>
<ul>
<li><code>host:port</code>: 目标机地址</li>
<li><code>timeout</code>: 操作超时
<ul>
<li>默认值 $30s$</li>
<li>当 <code>timeout</code> 时间内未发送 <code>slotsrestore-async</code> 迁移命令时,同步客户端会被服务器关闭</li>
<li>也是所同步 key 在同步途中设置临时过期时间,用于错误处理,保证单个数据的完整性</li>
<li>可在 Codis-Dashboard 的配置文件中通过配置项 <code>migration_timeout</code> 设置</li>
</ul>
</li>
<li><code>maxbulks</code>:大对象进行指令拆分时,单个指令可包含的数据分片个数
<ul>
<li>默认值 $200$,最大值 $512 \times 1024$</li>
<li>可在 Codis-Dashboard 的配置文件中通过配置项 <code>migration_async_maxbulks</code> 设置</li>
</ul>
</li>
<li><code>maxbytes</code>:同步客户端输出缓冲区大小,缓冲区满时不产生新的迁移指令
<ul>
<li>默认值 512 KB,最大值 <code>INT_MAX / 2</code> Byte</li>
<li>同时需满足 Redis 对 Normal Client 的缓冲区大小限制</li>
<li>可在 Codis-Dashboard 的配置文件中通过配置项 <code>migration_async_maxbytes</code> 设置</li>
</ul>
</li>
</ul>
</li>
<li>
<p>返回结果:操作返回整数,表示已迁移 key 的数量</p>
</li>
</ul>
</li>
<li>
<p><code>slotsmgrttagone-async host port timeout maxbulks maxbytes key1 [key2 ...]</code></p>
<ul>
<li>命令说明:迁移与 key1, key2, &hellip; 有相同的 tag 的所有 key 到目标机</li>
<li>其他说明参考 <code>slotsmgrttagone</code> 以及 <code>slotsmgrtone-async</code> 的解释即可</li>
</ul>
</li>
<li>
<p><code>slotsmgrtslot-async host port timeout maxbulks maxbytes slot numkeys</code></p>
<ul>
<li>
<p>命令说明:从指定 <code>slot</code> 中迁移至多 <code>numkeys</code> 个 key 至目标机</p>
</li>
<li>
<p>命令参数:</p>
<ul>
<li><code>slot</code>:slot number</li>
<li><code>numkeys</code>:本次迁移指令的数量上限
<ul>
<li>默认值 $100$</li>
<li>大对象在拆分时,会被当成多个 key 被计算,所以实际迁移的 key 的数量一般小于 <code>numkeys</code></li>
<li>可在 Codis-Dashboard 的配置文件中通过配置项 <code>migration_async_numkeys</code> 设置</li>
</ul>
</li>
<li>其他命令参数说明同 <code>slotsmgrtone-async</code></li>
</ul>
</li>
<li>
<p>返回结果:操作返回两个整数</p>
<ul>
<li>已迁移 key 的数量</li>
<li>该 slot 中剩余 key 的数量</li>
</ul>
</li>
</ul>
</li>
<li>
<p><code>slotsmgrttagslot-async host port timeout maxbulks maxbytes slot numkeys</code></p>
<ul>
<li>命令说明:与 <code>slotsmgrtslot-async</code> 对应的迁移指令,会在迁移 key 时同时迁移具有相同 tag 的 key</li>
<li>其他说明参考 <code>slotsmgrttagone</code> 以及 <code>slotsmgrtslot-async</code> 的解释即可</li>
</ul>
</li>
<li>
<p><code>slotsmgrt-exec-wrapper key command [arg1 ...]</code></p>
<ul>
<li>
<p>命令说明:依赖 <code>key</code> 的状态决定如何执行包装的 command</p>
</li>
<li>
<p>返回结果:操作返回 Arrays</p>
<ul>
<li><code>Arrays[0]</code> 为整数标识
<ul>
<li><code>-1</code> 表示所包装的请求格式错误</li>
<li><code>0</code> 表示所操作的 <code>key</code> 不存在</li>
<li><code>1</code> 表示包装的请求为写操作,且该 <code>key</code> 正在迁移中</li>
<li><code>2</code> 表示所包装的请求可被执行</li>
</ul>
</li>
<li><code>Arrays[1]</code> 返回值类型取决于 <code>Arrays[0]</code>
<ul>
<li><code>Arrays[0] == 2</code> 时,<code>Arrays[1]</code> 表示所包装的 command 执行的结果</li>
<li><code>Arrays[0] != 2</code> 时,<code>Arrays[1]</code> 为一条错误信息</li>
</ul>
</li>
</ul>
</li>
<li>
<p>附加说明:</p>
<ul>
<li>在所包装的请求为读操作时,可直接执行</li>
<li>在所包装的请求为写操作时
<ul>
<li>如果 key 未处于迁移状态,可执行
<blockquote>
<p>需注意的是,此处写操作并不会转发至 AOF 和从库</p>
</blockquote>
</li>
<li>如果 key 处于迁移状态,则不会执行写操作,直接返回 error</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>衍生命令</strong></p>
<ul>
<li>
<p><code>slotsrestore-async-auth passwd</code></p>
<ul>
<li>命令说明:同步客户端认证
<blockquote>
<p>需注意的是会使用本机的 <code>requirepass</code> 向目标机进行认证,因此我们需保证同一集群内的所有 Codis-Server 密码一致</p>
</blockquote>
</li>
</ul>
</li>
<li>
<p><code>slotsrestore-async-select db</code></p>
<ul>
<li>命令说明:同 <code>SELECT db</code>,保证同一 slot 中的 key 即使处于不同 Codis-Server 中,但是 db ID 会是相同的</li>
</ul>
</li>
<li>
<p><code>slotsrestore-async</code> 类命令</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-c" data-lang="c"><span class="cm">/* *
</span><span class="cm"> * SLOTSRESTORE-ASYNC delete $key
</span><span class="cm"> * expire $key $ttl
</span><span class="cm"> * object $key $ttl $payload
</span><span class="cm"> * string $key $ttl $payload
</span><span class="cm"> * list $key $ttl $hint [$elem1 ...]
</span><span class="cm"> * hash $key $ttl $hint [$hkey1 $hval1 ...]
</span><span class="cm"> * dict $key $ttl $hint [$elem1 ...]
</span><span class="cm"> * zset $key $ttl $hint [$elem1 $score1 ...]
</span><span class="cm"> * */</span>
</code></pre></td></tr></table>
</div>
</div><ul>
<li>
<p>命令说明:按照所包装命令对 key 执行删除、设置过期时间或 restore 操作</p>
<ul>
<li><code>delete</code>: 删除该 key</li>
<li><code>expire</code>: 给该 key 设置过期时间</li>
<li><code>object</code>: 表示该 key 的 val (<code>payload</code>) 为 RDB 格式
<ul>
<li>小对象使用 RDB</li>
</ul>
</li>
<li><code>string</code>: 表示该 key 为字符串类型,且其 <code>val</code> 未压缩
<ul>
<li>字符串类型不压缩</li>
</ul>
</li>
<li><code>list/hash/dict/zset</code>:表示该 key 为列表/哈希表/集合/有序集合类型
<ul>
<li>大对象进行指令拆分,单条指令可包含多个数据分片,由参数 <code>maxbulks</code> 控制</li>
<li><code>hint</code>: 该 key 总的元素个数</li>
</ul>
</li>
</ul>
</li>
<li>
<p>于何处衍生该命令?</p>
<ul>
<li>在客户端发送 <code>slotsmgrtone-async</code>、<code>slotsmgrttagone-async</code>、<code>slotsmgrtslot-async</code> 或 <code>slotsmgrttagslot-async</code> 时会尝试在 $500\mu s$ 内发送不少于 $3$ 条 <code>slotsrestore-async</code> 命令至目标机</li>
<li>在源端收到目标机返回的 <code>slotsrestore-async-ack</code> 时,会尝试在 $10\mu s$ 内发送不少于 $2$ 条 <code>slotsrestore-async</code> 命令至目标机</li>
<li>在目标机调用 <code>slotsmgrtone-async-dump</code> 或 <code>slotsmgrttagone-async-dump</code> 时</li>
</ul>
</li>
<li>
<p>返回结果:在目标机收到并处理 <code>slotsrestore-async</code> 命令后,会返回 <code>slotsrestore-async-ack errno message</code> 请求至源端</p>
<ul>
<li><code>errno = -1</code>: 有错误发生,<code>message</code> 会显示具体的错误信息</li>
<li><code>errno = 0</code> : 成功执行相应操作,<code>message</code> 为一个整型字符串,其含义依据所包装的请求而定
<ul>
<li><code>delete</code>: 0 or 1 表示成功删除的 key 个数</li>
<li><code>expire/object/string</code>: 1</li>
<li><code>list/hash/dict/zset</code>: 执行 restore 操作后目标机该 key 中所含的元素个数</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><code>slotsrestore-async-ack errno message</code></p>
<ul>
<li>命令说明:响应来自目标机的 ack 回复,尝试继续发送 <code>slotsrestore-async</code> 命令以迁移数据,当数据迁移完毕时,通知客户端 (发送数据迁移请求的客户端),在本地删除已迁移 key</li>
</ul>
</li>
</ul>
<p><strong>调试命令</strong></p>
<ul>
<li>
<p><code>slotsmgrtone-async-dump timeout maxbulks key1 [key2 ...]</code></p>
<ul>
<li>命令说明:要求服务器迁移 key1, key2, &hellip; 至本客户端</li>
<li>命令参数的说明同 <code>slotsmgrtone-async</code>,唯一不同的是此处 <code>maxbulks</code> 的默认值为 $1000$,最大值为 <code>INT_MAX</code></li>
</ul>
</li>
<li>
<p><code>slotsmgrttagone-async-dump timeout maxbulks key1 [key2 ...]</code></p>
<ul>
<li>命令说明:要求服务器迁移 key1, key2, &hellip; 及其相同 tag 的 key 至本客户端</li>
<li>命令参数的说明同上</li>
</ul>
</li>
<li>
<p><code>slotsmgrt-async-fence</code></p>
<ul>
<li>命令说明:判断或等待数据迁移完毕</li>
<li>返回结果
<ul>
<li><code>OK</code>: 当前该客户端所在 db 无数据迁移任务</li>
<li>error: 表示重复调用 <code>slotsmgrt-async-fence</code> 命令</li>
<li>已迁移完毕的返回值,取决于触发当前迁移任务的请求类型</li>
</ul>
</li>
</ul>
</li>
<li>
<p><code>slotsmgrt-async-cancel</code></p>
<ul>
<li>命令说明:终止数据迁移</li>
<li>返回结果:整型
<ul>
<li>0: 表示当前该客户端所在 db 无数据迁移任务</li>
<li>1: 表示当前该客户端所在 db 数据迁移任务已终止</li>
</ul>
</li>
</ul>
</li>
<li>
<p><code>slotsmgrt-async-status</code></p>
<ul>
<li>命令说明:返回当前该客户端所在 db 数据迁移任务的运行状态</li>
<li>返回结果:bulk strings
<ul>
<li><code>host</code>: 目标机 IP</li>
<li><code>port</code>: 目标机端口号</li>
<li><code>used</code>: 同步客户端是否已通过认证以及选择相应的 db</li>
<li><code>timeout</code>: 客户端操作超时时间</li>
<li><code>lastuse</code>: 表示上一次使用同步客户端发送迁移命令的时间</li>
<li><code>since_lastuse</code>: 当前时间 $-$ <code>lastuse</code></li>
<li><code>sending_msgs</code>: 已发送 <code>slotsrestore-async</code> 类迁移命令但还未收到 <code>slotsrestore-async-ack</code> 的请求数</li>
<li><code>blocked_clients</code>: 阻塞等待当前迁移任务完成的客户端数量</li>
<li><code>batched_iterator</code>: 迁移迭代器状态
<ul>
<li><code>keys</code>: 将要迁移的 key</li>
<li><code>timeout</code>: 临时过期时间 (其值与客户端操作超时时间相同)</li>
<li><code>maxbulks</code>: 大对象进行指令拆分时,单个指令可包含的数据分片个数</li>
<li><code>maxbytes</code>: 同步客户端输出缓冲区大小,缓冲区满时不产生新的迁移指令</li>
<li><code>estimate_msgs</code>: 本次迁移任务的 <code>slotsrestore-async</code> 指令数量</li>
<li><code>removed_keys</code>: 已迁移完成的 key 列表的长度,会随着迁移任务的进行增长,在迁移任务结束时会依据列表 <code>it-&gt;removed_keys</code> 删除本地的 key</li>
<li><code>chunked_vals</code>: 迁移过程中产生的中间值列表的长度,在迁移任务结束时会依据列表 <code>it-&gt;chunked_vals</code> 释放中间值</li>
<li><code>iterators</code>: 当前尚未迁移的 key 的列表</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="32-codis-dashboard-扩缩容流程">3.2. Codis-Dashboard 扩缩容流程</h3>
<p>在 <a href="https://k-on.me/post/codis_intro/#12-slot-%E7%9B%B8%E5%85%B3%E6%93%8D%E4%BD%9C">slot-相关操作</a> 中我们发现 Codis-Dashboard 只是会把相关 slot 标记为 <code>ActionPending</code> 状态,事后具体的 slot 分配以及数据迁移操作会有 Goroutine <code>ProcessSlotAction</code> 执行:</p>
<ul>
<li>并行操作的 slot 数量由配置项 <code>migration_parallel_slots</code> 控制</li>
<li>数据迁移时使用的迁移方式由配置项 <code>migration_method</code> 控制</li>
</ul>
<p>其执行流程如下图所示:</p>
<p><img src="https://k-on.me/codis/process_slot_action.png" alt="process_slot_action"></p>
<p>可以发现其中有多次将 Slot 相关状态信息同步至 Codis-Proxy 的操作,此时 Codis-Proxy 获取的 Slot-Group 映射关系与 Slot 当前 Action 的状态有关:</p>
<table>
<thead>
<tr>
<th style="text-align:left">Slot.Action.State</th>
<th style="text-align:left">Slot-Group Map</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">ActionNothing, ActionPending</td>
<td style="text-align:left">backend = GroupId, Replicas = GroupId.replicas</td>
</tr>
<tr>
<td style="text-align:left">ActionPreparing</td>
<td style="text-align:left">backend = GroupId</td>
</tr>
<tr>
<td style="text-align:left">ActionPrepared</td>
<td style="text-align:left">\</td>
</tr>
<tr>
<td style="text-align:left">ActionMigrating</td>
<td style="text-align:left">backend = Action.TargetId, migrateFrom = GroupId</td>
</tr>
<tr>
<td style="text-align:left">ActionFinished</td>
<td style="text-align:left">backend = Action.TargetId</td>
</tr>
</tbody>
</table>
<p>在迁移 Slot 中的数据时,根据 <code>migration_method</code> 配置选择不同的迁移方案:</p>
<h4 id="321-同步迁移-----sync">3.2.1. 同步迁移 &mdash; sync</h4>
<p>通过循环调用 <code>slotsmgrttagslot host port timeout slot</code> 逐步迁移该 slot 中的 key 至目标机:</p>
<p><img src="https://k-on.me/codis/codis_dashboard_sync_slot.png" alt="codis_dashboard_sync_slot"></p>
<p>同步迁移存在以下问题:</p>
<ul>
<li>迁移速度慢
<ul>
<li>序列化 &amp; 反序列化时间开销大</li>
<li>单个数据迁移以 $ms$ 为单位</li>
<li>迁移单位过小:单次操作 $1$ 个或几个数据</li>
<li>受网络 RTT 以及迁移指令间隔 (Codis 默认 $1ms$) 影响</li>