-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1823 lines (1519 loc) · 234 KB
/
index.html
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>
<head>
<meta charset="utf-8">
<title>牛古说</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="ljun51(牛古)的个人网站,分享IT技术、生活点滴。包括JAVA、GOLANG、JAVASCRIPT等前端、后端的技术,还有各种开发工具、开发平台的技术。">
<meta property="og:type" content="website">
<meta property="og:title" content="牛古说">
<meta property="og:url" content="https://ljun51.github.io/index.html">
<meta property="og:site_name" content="牛古说">
<meta property="og:description" content="ljun51(牛古)的个人网站,分享IT技术、生活点滴。包括JAVA、GOLANG、JAVASCRIPT等前端、后端的技术,还有各种开发工具、开发平台的技术。">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="牛古">
<meta name="twitter:card" content="summary">
<link rel="alternate" href="/atom.xml" title="牛古说" type="application/atom+xml">
<link rel="shortcut icon" href="/favicon.png">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/fancybox/jquery.fancybox.min.css">
<meta name="generator" content="Hexo 7.1.1"></head>
<body>
<div id="container">
<div id="wrap">
<header id="header">
<div id="banner"></div>
<div id="header-outer" class="outer">
<div id="header-title" class="inner">
<h1 id="logo-wrap">
<a href="/" id="logo">牛古说</a>
</h1>
<h2 id="subtitle-wrap">
<a href="/" id="subtitle">不动笔墨不读书。</a>
</h2>
</div>
<div id="header-inner" class="inner">
<nav id="main-nav">
<a id="main-nav-toggle" class="nav-icon"><span class="fa fa-bars"></span></a>
<a class="main-nav-link" href="/">Home</a>
<a class="main-nav-link" href="/archives">Archives</a>
</nav>
<nav id="sub-nav">
<a class="nav-icon" href="/atom.xml" title="RSS 订阅"><span class="fa fa-rss"></span></a>
<a class="nav-icon nav-search-btn" title="搜索"><span class="fa fa-search"></span></a>
</nav>
<div id="search-form-wrap">
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="搜索"><button type="submit" class="search-form-submit"></button><input type="hidden" name="sitesearch" value="https://ljun51.github.io"></form>
</div>
</div>
</div>
</header>
<div class="outer">
<section id="main">
<article id="post-aws" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
<div class="article-meta">
<a href="/2024/03/21/aws/" class="article-date">
<time class="dt-published" datetime="2024-03-21T10:12:25.000Z" itemprop="datePublished">2024-03-21</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="p-name article-title" href="/2024/03/21/aws/">AWS 服务介绍</a>
</h1>
</header>
<div class="e-content article-entry" itemprop="articleBody">
<h2 id="Amazon-EC2"><a href="#Amazon-EC2" class="headerlink" title="Amazon EC2"></a>Amazon EC2</h2><p>Amazon Elastic Compute Cloud(Amazon EC2)提供最广泛、最深入的计算平台,拥有超过 500 个实例,可选择最新的处理器、存储、网络、操作系统和购买模型,以帮助您最好地满足工作负载的需求。我们是首家支持英特尔、AMD 和 Arm 处理器的主要云提供商,既是唯一具有按需 EC2 Mac 实例的云,也是唯一具有 400 Gbps 以太网网络的云。我们为机器学习培训提供最佳性价比,同时也为云中的每个推理实例提供了最低的成本。与任何其他云相比,有更多的 SAP、高性能计算 (HPC)、机器学习 (ML) 和 Windows 工作负载在 AWS 上运行。</p>
<h2 id="Amazon-S3"><a href="#Amazon-S3" class="headerlink" title="Amazon S3"></a>Amazon S3</h2><p>Amazon Simple Storage Service (Amazon S3) 是一种对象存储服务,提供行业领先的可扩展性、数据可用性、安全性和性能。各种规模和行业的客户可以为几乎任何使用案例存储和保护任意数量的数据,例如数据湖、云原生应用程序和移动应用程序。借助高成本效益的存储类和易于使用的管理功能,您可以优化成本、组织数据并配置精细调整过的访问控制,从而满足特定的业务、组织和合规性要求。</p>
<h2 id="Amazon-Aurora"><a href="#Amazon-Aurora" class="headerlink" title="Amazon Aurora"></a>Amazon Aurora</h2><p>Amazon Aurora 在全球范围内提供无与伦比的高性能和可用性,完全兼容 MySQL 和 PostgreSQL,而成本仅为商业数据库的十分之一。Aurora 的吞吐量是 MySQL 的 5 倍,是 PostgreSQL 的 3 倍。Aurora 拥有广泛的合规性标准和一流的安全功能。Aurora 通过使数据在 3 个可用区内持久耐用(客户只需支付 1 个副本的费用)来提供存储弹性。Aurora 的可用性高达 99.99%,跨 AWS 区域部署时,客户可以使用全球数据库访问本地读取性能。使用无服务器功能,Aurora 可在不到一秒钟的时间内扩展到能够处理数十万个事务的能力。Aurora 与 Amazon Redshift 的零 ETL 集成可近乎实时地对事务数据进行分析。</p>
<h2 id="Amazon-DynamoDB"><a href="#Amazon-DynamoDB" class="headerlink" title="Amazon DynamoDB"></a>Amazon DynamoDB</h2><p>Amazon DynamoDB 是一项无服务器的 NoSQL、完全托管的数据库,在任何规模下均具有个位数毫秒级的性能,您可以通过它来开发任何规模的现代应用程序。作为无服务器数据库,您只需按使用量为其付费,DynamoDB 可以扩展到零,没有冷启动,没有版本升级,没有维护窗口,没有修补,也没有停机维护。DynamoDB 提供一系列广泛的安全控制措施和合规性标准。对于全球分布式应用程序,DynamoDB 全局表是一个多区域、多活动数据库,具有 99.999% 的可用性 SLA 和更高的弹性。托管备份、时间点恢复等功能有助于确保 DynamoDB 的可靠性。借助 DynamoDB 流,您可以构建无服务器的事件驱动型应用程序。</p>
<h2 id="Amazon-RDS"><a href="#Amazon-RDS" class="headerlink" title="Amazon RDS"></a>Amazon RDS</h2><p>Amazon Relational Database Service(Amazon RDS)是一个托管式服务的集合,可以简化在云中设置、运营和扩展数据库的过程。提供八种热门引擎以供选择:Amazon Aurora PostgreSQL 兼容版、Amazon Aurora MySQL 兼容版、RDS for PostgreSQL、RDS for MySQL、RDS for MariaDB、RDS for SQL Server、RDS for Oracle 和 RDS for Db2。 使用 Amazon RDS 在 AWS Outposts 上进行本地部署,或者使用 Amazon RDS Custom 提高对底层操作系统和数据库环境的访问权限。</p>
<h2 id="AWS-Lambda"><a href="#AWS-Lambda" class="headerlink" title="AWS Lambda"></a>AWS Lambda</h2><p>AWS Lambda 是一项无服务器事件驱动型计算服务,该服务使您可以运行几乎任何类型的应用程序或后端服务的代码,而无需预置或管理服务器。您可以从 200 多个 AWS 服务和软件即服务 (SaaS) 应用程序中触发 Lambda,且只需按您的使用量付费。</p>
<h2 id="Amazon-VPC"><a href="#Amazon-VPC" class="headerlink" title="Amazon VPC"></a>Amazon VPC</h2><p>Amazon Virtual Private Cloud (Amazon VPC) 让您能够全面地控制自己的虚拟网络环境,包括资源放置、连接性和安全性。首先在 AWS 服务控制台中设置 VPC。然后,向其中添加资源,例如 Amazon Elastic Compute Cloud (EC2) 和 Amazon Relational Database Service (RDS) 实例。最后,您可以定义 VPC 相互之间以及跨账户、可用区或 AWS 区域通信的方式。</p>
<h2 id="Amazon-Lightsail"><a href="#Amazon-Lightsail" class="headerlink" title="Amazon Lightsail"></a>Amazon Lightsail</h2><p>Amazon Lightsail 以经济实惠的月度价格提供易于使用的虚拟专用服务器 (VPS) 实例、容器、存储、数据库等。虚拟专用服务器,价格低廉且可预测。只需几次点击即可创建网站或应用程序。自动配置联网、访问和安全环境。随着您的发展轻松扩展,或将您的资源迁移到更广泛的 AWS 生态系统,例如 Amazon EC2。</p>
<p>使用案例</p>
<ul>
<li>启动简单的 Web 应用程序。使用预配置的开发堆栈,如 LAMP、Nginx、MEAN 和 Node.js,以快速轻松地上网。</li>
<li>创建自定义网站。使用预配置的应用程序,如 WordPress、Magento、Prestashop 和 Joomla,只需几次点击,就可以构建和个性化您的博客、电子商务或个人网站。</li>
<li>构建小型业务应用程序。启动业务线软件,如文件存储和共享、备份、财务和会计软件等等。</li>
<li>启动测试环境。轻松创建和删除开发沙箱和测试环境,您可以在其中无风险地尝试新想法。</li>
</ul>
<h2 id="Amazon-SageMaker"><a href="#Amazon-SageMaker" class="headerlink" title="Amazon SageMaker"></a>Amazon SageMaker</h2><p>Amazon SageMaker 通过完全托管的基础设施、工具和工作流程为任何用例构建、训练和部署机器学习(ML)模型。Amazon SageMaker 是一项完全托管的服务,它汇集了大量工具,可为任何使用案例提供高性能、低成本的机器学习(ML)。借助 SageMaker,您可以使用笔记本、调试器、分析器、管道、MLOps 等工具大规模构建、训练和部署机器学习模型——这一切都在一个集成式开发环境(IDE)中完成。SageMaker 通过简化的访问控制和机器学习项目的透明度来支持治理要求。此外,您可以使用专门构建的工具来微调、实验、再训练和部署基础模型,构建自己的基础模型(在海量数据集上训练过的大型模型)。 SageMaker 提供对数百个预训练模型的访问权限,包括公开的基础模型,您只需点击几下即可部署这些模型。</p>
</div>
<footer class="article-footer">
<a data-url="https://ljun51.github.io/2024/03/21/aws/" data-id="clu1aiuro0001gpg008cw7y1r" data-title="AWS 服务介绍" class="article-share-link"><span class="fa fa-share">分享</span></a>
<ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/AWS/" rel="tag">AWS</a></li></ul>
</footer>
</div>
</article>
<article id="post-git-svn" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
<div class="article-meta">
<a href="/2022/08/30/git-svn/" class="article-date">
<time class="dt-published" datetime="2022-08-30T05:37:21.000Z" itemprop="datePublished">2022-08-30</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="p-name article-title" href="/2022/08/30/git-svn/">git-svn</a>
</h1>
</header>
<div class="e-content article-entry" itemprop="articleBody">
<h1 id="git-svn"><a href="#git-svn" class="headerlink" title="git-svn"></a>git-svn</h1><p><code>svn</code> 和 <code>git</code> 都是常用的版本管理软件,但是git无论在理念或是功能上都比svn更为先进。<br>但是有的公司是以svn作为中央仓库,这时git与svn代码的同步就可以通过 git-svn这个软件进行,从而用git管理svn代码。<br>最后的效果相当于把svn仓库当作git的一个remote(远程仓库),而你本地的代码都是通过git来管理,只有push到svn时才会把你本地的commit同步到svn。</p>
<p>详细说明参考:<a target="_blank" rel="noopener" href="https://www.cnblogs.com/h2zZhou/p/6136948.html">git-svn:通过git来管理svn代码</a>,但是我们还是推荐完成从svn迁移到git。</p>
<h2 id="从svn克隆"><a href="#从svn克隆" class="headerlink" title="从svn克隆"></a>从svn克隆</h2><p>下面以将乐短信服务仓库为例:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git svn clone https://140.143.33.11/svn/ykhl/代码/平台/将乐/sms/MobileServer --no-metadata MobileServer</span><br></pre></td></tr></table></figure>
<h2 id="关联git远程仓库"><a href="#关联git远程仓库" class="headerlink" title="关联git远程仓库"></a>关联git远程仓库</h2><p>先在git仓库创建一个mobileserver的仓库</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cd MobileServer</span><br><span class="line">git remote add origin [email protected]:lijun/mobileserver.git</span><br><span class="line">git remote –v</span><br></pre></td></tr></table></figure>
<h2 id="提交记录至git"><a href="#提交记录至git" class="headerlink" title="提交记录至git"></a>提交记录至git</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push -u origin master</span><br></pre></td></tr></table></figure>
</div>
<footer class="article-footer">
<a data-url="https://ljun51.github.io/2022/08/30/git-svn/" data-id="clu1aiurp0004gpg0470ubqws" data-title="git-svn" class="article-share-link"><span class="fa fa-share">分享</span></a>
<ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/git-svn/" rel="tag">git-svn</a></li></ul>
</footer>
</div>
</article>
<article id="post-vitess" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
<div class="article-meta">
<a href="/2021/07/30/vitess/" class="article-date">
<time class="dt-published" datetime="2021-07-30T02:26:37.000Z" itemprop="datePublished">2021-07-30</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="p-name article-title" href="/2021/07/30/vitess/">一文读懂Vitess</a>
</h1>
</header>
<div class="e-content article-entry" itemprop="articleBody">
<h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><h2 id="什么是Vitess"><a href="#什么是Vitess" class="headerlink" title="什么是Vitess"></a>什么是Vitess</h2><p>Vitess是一个为部署、扩展、管理大型集群的开源数据库解决方案。当前支持MySQL和MariaDB。在专用硬件上为高效的运行公有、私有云架构而设计。Vitess结合并扩展了许多重要的SQL功能和NoSQL数据库的可扩展性。Vitess可以解决下面的问题:</p>
<ol>
<li>通过对SQL数据库进行分片来扩展SQ 数据库,同时将应用程序更改保持在最低限度。</li>
<li>支持从裸机部署到共有或私有云。</li>
<li>支持部署和管理大量的SQL实例。</li>
</ol>
<p>Vitess支持JDBC和Go数据库驱动使用native查询协议。此外,它实现了几乎与任何其他语言兼容的MySQL server协议。</p>
<p>Vitess在YouTube使用超过5年,许多企业也考虑在生产环境Vitess。</p>
<h3 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h3><p><strong>性能</strong></p>
<ul>
<li>Connection pooling: 将前端应用程序查询多路复用到 MySQL 连接池以优化性能。</li>
<li>Query de-duping: 对于重复的查询请求,重用结果。</li>
<li>Transaction Manager: 限制并发事务的数量并管理超时,优化整体吞吐量。</li>
</ul>
<p><strong>保护</strong></p>
<ul>
<li>Query rewriting and sanitization: 添加限制并避免非确定性更新。</li>
<li>Query blacklisting: 自定义规则,防止有问题的查询攻击数据库</li>
<li>Query killer: 中断长时间的查询</li>
<li>Table ACLs: 对数据表指定访问控制列表</li>
</ul>
<p><strong>监控</strong></p>
<ul>
<li>性能分析工具帮助你监控、诊断、分析数据库性能</li>
</ul>
<p><strong>拓扑管理工具</strong></p>
<ul>
<li>Master管理工具</li>
<li>基于Web的GUI</li>
<li>支持管理多数据中心</li>
</ul>
<p><strong>分片</strong></p>
<ul>
<li>几乎无缝的动态重新分片</li>
<li>支持水平、垂直分片</li>
<li>多种分片方案,支持自定义实现</li>
</ul>
<h2 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h2><p>Vitess平台由许多服务器进程、命令行工具和Web工具组成,并由一致的元数据存储提供支持。</p>
<p>通过一系列流程可以实现一个完整的Vitess。比如,如果要从头开始构建服务,那么使用 Vitess 的第一步就是定义数据库拓扑。但是,如果需要扩展现有数据库,可能会从部署连接代理开始。</p>
<p>Vitess的工具或服务可以提供帮助,无论是从一开始快速开始就创建一个完整的数据或是一步一步从小处着手。往小处讲,vttablet的连接池和查询重新特性可以让你体验到它的好处;往大处说,Vitess的自动化工具可以提供快速创建一个数据库。</p>
<p>下面的图展示了Vitess的组件:<br><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gt3kw5e4e9j310m0k43z5.jpg" alt="Vitess Runtime"></p>
<h2 id="支持的数据库"><a href="#支持的数据库" class="headerlink" title="支持的数据库"></a>支持的数据库</h2><p>Vitess当前支持MySQL、Percona和MariaDB数据库。</p>
<p><strong>MySQL 5.6-8.0</strong></p>
<p>Vitess支持MySQL 5.6-8.0的关键特性,但是也有一些限制。Vitess同样支持Percona Server for MySQL 5.6-8.0版本。MySQL 5.6在2021年2月不再维护,建议使用MySQL 5.7及以上版本。</p>
<p><strong>MariaDB 10.0-10.3</strong></p>
<p>Vitess支持MariaDB 10.0-10.3的关键特性,暂不支持10.4版本。</p>
<h2 id="可扩展性理念"><a href="#可扩展性理念" class="headerlink" title="可扩展性理念"></a>可扩展性理念</h2><p>许多方式都可以解决扩展性问题,下面看看Vitess是怎么做的:</p>
<p><strong>小实例</strong></p>
<p>一般数据库要分片,自然想到的就是适合一台物理机的大小的分片。通常做法是一台物理机一个实例。</p>
<p>Vitess 建议将实例分解为可管理的块(每个 MySQL 服务器 250GB),并且不回避在每个主机上运行多个实例。净资源使用量将大致相同。但是当 MySQL 实例较小时,可管理性大大提高。跟踪端口和分离 MySQL 实例的路径很复杂。然而,一旦跨越了这个障碍,其他一切都会变得更简单。</p>
<p>需要担心的是会有锁竞争,复制会更频繁;但中断对生产的影响变得更小,备份和恢复运行得更快,并且可以实现更多次要优势。例如,您可以对实例进行混洗以获得更好的机器或机架多样性,从而减少对生产中断的影响,并提高资源使用率。</p>
<p><strong>通过复制获得持久性</strong></p>
<p>传统数据存储软件在数据刷新到磁盘后立即将其视为持久数据。然而,这种方法在当今的商品硬件世界中是不切实际的。这种方法也不能解决灾难场景。</p>
<p>新的持久性方法是通过将数据复制到多台机器甚至地理位置来实现的。这种形式的持久性解决了设备故障和灾难的现代问题。</p>
<p>Vitess 中的许多工作流都是用这种方法构建的。例如,强烈建议开启半同步复制。这允许 Vitess 在 master 宕机时故障转移到新副本,而不会丢失数据。 Vitess 还建议避免恢复崩溃的数据库。而是从最近的备份中创建一个新的并让它赶上。</p>
<p>依靠复制还允许您放宽一些基于磁盘的持久性设置。例如,您可以关闭sync_binlog,这将大大减少对磁盘的IOPS数,从而提高有效吞吐量。</p>
<p><strong>一致性模型</strong></p>
<p>在将表分片或移动到不同的键空间之前,需要验证(或更改)应用程序,以便它可以容忍以下更改:</p>
<ul>
<li>跨分片读取可能彼此不一致。相反,分片决策也应尽量减少此类事件的发生,因为跨分片读取的成本更高。</li>
<li>在<code>best-effort mode</code>模式下,跨分片事务可能会在中间失败并导致部分提交。您可以改为使用<code>2PC mode</code>事务,提供分布式原子保证。但是,选择此选项会使写入成本增加约 50%。</li>
</ul>
<p>单个分片事务仍然保持 ACID。</p>
<p>如果可以容忍轻微陈旧数据的只读情况,则应将查询发送到用于 OLTP 的 REPLICA 片,以及用于 OLAP 工作负载的 RDONLY 片。这可以更轻松地扩展读流量,并使您能够在地理上分布它们。</p>
<p>这种权衡允许以过时或可能不一致的读取为代价获得更好的吞吐量,因为随着数据的变化(并且可能在不同分片上具有不同的延迟),读取可能落后于 MASTER 服务器。为了缓解这种情况,VTGate 服务器能够监控副本滞后,并且可以配置为避免副本滞后超过 X 秒的数据。</p>
<p>对于真正的快照,必须在事务中将查询发送到 master。对于<code>read-after-write</code>一致性,在没有事务的情况下从 master 读取就足够了。</p>
<p>总而言之,支持的各种等级的一致性:</p>
<ul>
<li>__REPLICA/RDONLY read__:服务器可以在地理上扩展。本地读取速度很快,但可能会因副本滞后而过时。</li>
<li>__MASTER read__:每个分片只有一个Master。来自远程位置的读取将受到网络延迟和可靠性的影响,但数据将是最新的(<code>read-after-write</code>一致性)。隔离级别为 READ_COMMITTED。</li>
<li>__MASTER transactions__:它们表现出与<code>MASTER read</code>相同的属性。但是,对于单个分片,您可以获得 REPEATABLE_READ 一致性和 ACID 写入。对跨分片原子交易的支持正在进行中。</li>
</ul>
<p>至于原子性,支持以下级别:</p>
<ul>
<li>__SINGLE__:禁止多数据库事务。</li>
<li>__MULTI__:尽最而为的多数据库事务。</li>
<li>__TWOPC__:具有 2PC 提交的多数据库事务。</li>
</ul>
<p><strong>不支持多主(Master)</strong></p>
<p>Vitess 不支持多主配置。它具有解决大多数通常由多主解决的用例的替代方法:</p>
<ul>
<li>可扩展性:在某些情况下,多Master会为您提供一些额外的运行方式。但是,由于这些语句最终必须应用于所有Master,因此这不是一个可持续的策略。 Vitess 通过分片解决了这个问题,分片可以无限扩展。</li>
<li>高可用性:Vitess 与 Orchestrator 集成,能够在检测到故障后几秒钟内执行故障转移到新主服务器。这对于大多数应用程序来说通常是足够的。</li>
<li>低延迟地理分布式写入:这是 Vitess 未解决的一种情况。当前的建议是避免长距离往返的延迟成本的写入。如果数据分布允许,您仍然可以选择基于地理亲和力进行分片。然后,您可以为不同的分片设置主节点,使其位于不同的地理位置。这样,大多数 master 写入仍然可以是本地的。</li>
</ul>
<p><strong>多cell</strong></p>
<p>Vitess 旨在在多个数据中心/区域/单元中运行。在这里,我们将使用<code>Cell</code>来表示一组非常接近的服务器,并共享相同的区域可用性。</p>
<p>一个 cell 通常包含一组 tablet、一个 vtgate 池和使用 Vitess 集群的应用服务器。使用 Vitess 可以根据需要配置和启动所有组件:</p>
<ul>
<li>分片的主节点可以在任何 cell 中。如果需要cross-cell master 访问,可以通过 vtgate 轻松配置(通过将包含 master 的 cell 作为要监视的单元传递)。</li>
<li>master cell比read-only cell配置得更多的情况也不少见。这些具有master-capable的单元可能需要一个更多的副本来处理可能的故障转移,同时仍保持相同的副本服务容量。</li>
<li>从一个cell中的主节点故障转移到不同cell中的主节点与本地故障转移没有区别。它对流量和延迟有影响,但如果应用流量也被重定向到新cell,最终结果是稳定的。</li>
<li>也可以有一些分片与master在一个cell中,而其他一些分片与其他master在另一个cell中。vtgate 只会将流量路由到正确的位置,仅在远程访问时会产生额外的延迟成本。例如,在拥有美国的master数据库中创建美国用户记录,在欧洲master的数据库中创建欧洲用户记录很容易做到。副本无论如何都可以存在于每个cell中,并快速为副本流量提供服务。</li>
<li>副本服务单元是减少用户可见延迟的一个很好的折衷方案:它们只包含副本服务器,并且master访问始终是远程完成的。如果应用程序的主要场景是读取,这非常有效。</li>
<li>并非所有cell都需要 rdonly(或批处理)实例。只有运行批处理作业或 OLAP 作业的cell才真正需要。</li>
</ul>
<p>注意 Vitess 首先使用本地cell数据,并且对于任何cell宕机都非常有弹性,Vitess的大多数进程都会优雅地处理这种情况。</p>
<h2 id="Cloud-Native"><a href="#Cloud-Native" class="headerlink" title="Cloud Native"></a>Cloud Native</h2><p>Vitess 非常适合云部署,因为它使数据库能够逐步增加容量。 运行 Vitess 的最简单方法是通过 Kubernetes。</p>
<p>Kubernetes 可以使用 Docker 容器编排系统,Vitess 可以感知 Kubernetes 云原生环境运行分布式数据库。</p>
<p>Kubernetes 处理计算集群中节点的调度,主动管理这些节点上的工作负载,并将包含应用程序的容器分组以便于管理和发现。这为 Vitess 在 YouTube 中运行的方式提供了一个类似的开源环境,这也是 Kubernetes 的前身。</p>
<h2 id="历史"><a href="#历史" class="headerlink" title="历史"></a>历史</h2><p>Vitess 创建于 2010 年,旨在解决 YouTube 团队面临的 MySQL 可扩展性挑战。本节简要总结了 Vitess 发展的一系列事件:</p>
<ol>
<li>YouTube 的 MySQL 数据库达到了高峰流量并将很快超过数据库服务能力的地步。为了暂时缓解这个问题,YouTube 创建了一个用于写入流量的主数据库和一个用于读取流量的副本数据库。</li>
<li>由于对视频的需求空前高涨,只读流量仍然使副本数据库过载。所以 YouTube 增加了更多的副本,再次提供了一个临时解决方案。</li>
<li>最终,写入流量变得太高,主数据库无法处理,需要 YouTube 对数据进行分片来处理传入流量。顺便说一句,如果数据库的整体大小对于单个 MySQL 实例来说变得太大,分片也将变得必要。</li>
<li>YouTube 的应用层经过修改,以便在执行任何数据库操作之前,代码可以识别正确的数据库分片以接收特定查询。</li>
</ol>
<p>Vitess 让 YouTube 从源代码中删除了该逻辑,在应用程序和数据库之间引入了一个代理来路由和管理数据库交互。从那时起,YouTube 将其用户群扩大了 50 多倍,大大提高了其访问页面、处理新上传视频等的能力。更重要的是,Vitess 是一个不断扩展的平台。</p>
<p>CNCF 是许多快速增长的开源项目的中立供应商。2018 年 2 月,技术监督委员会 (TOC) 投票接受 Vitess 作为 CNCF 孵化项目。 Vitess 成为 2019 年 11 月毕业的第八个 CNCF 项目,加入了 Kubernetes、Prometheus、Envoy、CoreDNS、containerd、Fluentd 和 Jaeger的 CNCF。</p>
<h1 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h1><h2 id="通过Docker本地安装"><a href="#通过Docker本地安装" class="headerlink" title="通过Docker本地安装"></a>通过Docker本地安装</h2><p>本指南说明了如何通过 Docker 运行本地 Vitess 测试环境。 Vitess 环境与本地安装相同,除了 Docker 无需安装其他软件。</p>
<h3 id="检出仓库"><a href="#检出仓库" class="headerlink" title="检出仓库"></a>检出仓库</h3><ul>
<li><p>SSH:<code>git clone [email protected]:vitessio/vitess.git</code>,或者:</p>
</li>
<li><p>HTTP:<code>git clone https://github.com/vitessio/vitess.git</code></p>
<p> cd vitess</p>
</li>
</ul>
<h3 id="构建docker镜像"><a href="#构建docker镜像" class="headerlink" title="构建docker镜像"></a>构建docker镜像</h3><pre><code>make docker_local
</code></pre>
<p>将创建一个名为<code>vitess/local</code>的docker镜像(<code>vitess/local:lateest</code>)</p>
<h3 id="运行docker镜像"><a href="#运行docker镜像" class="headerlink" title="运行docker镜像"></a>运行docker镜像</h3><pre><code>./docker/local/run.sh
</code></pre>
<p>这一步将安装 MySQL replication拓扑,以及 <code>etcd</code>、<code>vtctld</code> 和 <code>vtgate</code> 服务。</p>
<ul>
<li><code>vtgate</code> 监听 <a target="_blank" rel="noopener" href="http://127.0.0.1:15001/debug/status">http://127.0.0.1:15001/debug/status</a></li>
<li><code>vtctld</code> 监听 <a target="_blank" rel="noopener" href="http://127.0.0.1:15000/debug/status">http://127.0.0.1:15000/debug/status</a></li>
<li>控制面板通过 <a target="_blank" rel="noopener" href="http://localhost:15000/app/">http://localhost:15000/app/</a> 访问</li>
</ul>
<p>为方便起见,在 docker shell 中设置了别名。尝试使用以下 <code>mysql</code> 命令连接到各种 tablets:</p>
<ul>
<li><code>mysql commerce</code></li>
<li><code>mysql commerce@master</code></li>
<li><code>mysql commerce@replica</code></li>
<li><code>mysql commerce@rdonly</code></li>
</ul>
<p>你会发现 Vitess 运行在一个单keyspace、单分片的集群。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>在这个例子中,我们部署了一个名为 <code>commerce</code> 的未分片的keyspace。未分片的keyspace有一个名为 <code>0</code> 的分片。以下schema反映了由脚本创建的常见电子商务场景:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> product (</span><br><span class="line"> sku <span class="type">varbinary</span>(<span class="number">128</span>),</span><br><span class="line"> description <span class="type">varbinary</span>(<span class="number">128</span>),</span><br><span class="line"> price <span class="type">bigint</span>,</span><br><span class="line"> <span class="keyword">primary</span> key(sku)</span><br><span class="line">);</span><br><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> customer (</span><br><span class="line"> customer_id <span class="type">bigint</span> <span class="keyword">not</span> <span class="keyword">null</span> auto_increment,</span><br><span class="line"> email <span class="type">varbinary</span>(<span class="number">128</span>),</span><br><span class="line"> <span class="keyword">primary</span> key(customer_id)</span><br><span class="line">);</span><br><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> corder (</span><br><span class="line"> order_id <span class="type">bigint</span> <span class="keyword">not</span> <span class="keyword">null</span> auto_increment,</span><br><span class="line"> customer_id <span class="type">bigint</span>,</span><br><span class="line"> sku <span class="type">varbinary</span>(<span class="number">128</span>),</span><br><span class="line"> price <span class="type">bigint</span>,</span><br><span class="line"> <span class="keyword">primary</span> key(order_id)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>上面的schema仅仅列出比较重要的属性:</p>
<ul>
<li><code>product</code> 表包含所有产品的产品信息。</li>
<li><code>customer</code> 表有一个 auto_increment 的<code>customer_id</code>列,一个典型的customer表包含很多列,甚至还包含扩展表。</li>
<li><code>corder</code> 表(应该命名为order,因为和SQL关键字冲突)有一个 auto_increment 的<code>order_id</code>列,同时有两个外键<code>customer(customer_id)</code>和<code>product(sku)</code>。</li>
</ul>
<h2 id="本地安装"><a href="#本地安装" class="headerlink" title="本地安装"></a>本地安装</h2><h2 id="通过Homebrew本地安装"><a href="#通过Homebrew本地安装" class="headerlink" title="通过Homebrew本地安装"></a>通过Homebrew本地安装</h2><h2 id="Vitess-Operator-for-Kubernetes"><a href="#Vitess-Operator-for-Kubernetes" class="headerlink" title="Vitess Operator for Kubernetes"></a>Vitess Operator for Kubernetes</h2><p>PlanetScale 为 Kubernetes 提供了一个 Vitess Operator,在 Apache 2.0 许可下发布。 以下步骤显示了如何开始使用 Minikube 安装:</p>
<h3 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h3><p>在开始之前,需要准备 Kubernetes环境:</p>
<ol>
<li><p>安装 Minikube 并启动 Minikube,推荐使用1.14版本,方便跨公有云:</p>
<p> minikube start –kubernetes-version=v1.14.10 –cpus=8 –memory=11000 –disk-size=50g</p>
</li>
</ol>
<p>如果你没有一台有 11GB 内存的机器,也可以考虑使用 GKE 来代替。可以使用以下命令从 Cloud Shell 部署等效设置:</p>
<pre><code>gcloud container clusters create vitess --cluster-version 1.14 --zone us-east1-b --num-nodes 5
</code></pre>
<ol start="2">
<li><p>安装 kubectl 并确保在<code>PATH</code>下。比如,Linux下:</p>
<p> curl -LO <a target="_blank" rel="noopener" href="https://storage.googleapis.com/kubernetes-release/release/v1.14.9/bin/linux/amd64/kubectl">https://storage.googleapis.com/kubernetes-release/release/v1.14.9/bin/linux/amd64/kubectl</a></p>
</li>
<li><p>本地安装 MySQL 客户端。比如,Ubuntu下:</p>
<p> apt install mysql-client</p>
</li>
<li><p>本地安装 vtctlclient :</p>
</li>
</ol>
<p>如果熟悉 GO 开发环境,最简单的方式是:</p>
<pre><code>go get vitess.io/vitess/go/cmd/vtctlclient
</code></pre>
<p>如果没有,可以下载最新的 Vitess 版本并从中提取 vtctlclient。</p>
<h3 id="安装-Operator"><a href="#安装-Operator" class="headerlink" title="安装 Operator"></a>安装 Operator</h3><p>切换到operator example目录下:</p>
<pre><code>git clone [email protected]:vitessio/vitess.git
cd vitess/examples/operator
</code></pre>
<p>安装operator:</p>
<pre><code>kubectl apply -f operator.yaml
</code></pre>
<h3 id="初始化集群"><a href="#初始化集群" class="headerlink" title="初始化集群"></a>初始化集群</h3><p>在此目录中,您将看到一组 yaml 文件。每个文件名的第一位数字表示示例的阶段。接下来的两位数字表示执行它们的顺序。例如,101_initial_cluster.yaml 是第一阶段的第一个文件。现在将执行:</p>
<pre><code>kubectl apply -f 101_initial_cluster.yaml
</code></pre>
<blockquote>
<p>我们提供了一个示例 yaml,用于使用实验性 <code>vtorc</code> 组件启动 Vitess。您可以使用以下命令进行尝试:<code>kubectl apply -f vtorc_example.yaml</code>。一旦 vtorc 正式发布,示例将相应更新。</p>
</blockquote>
<h3 id="验证集群"><a href="#验证集群" class="headerlink" title="验证集群"></a>验证集群</h3><p>可以使用 <code>kubectl get pods</code> 检查集群的状态。几分钟后,它应该显示所有 Pod 都处于运行状态:</p>
<pre><code>$ kubectl get pods
NAME READY STATUS RESTARTS AGE
example-etcd-faf13de3-1 1/1 Running 0 78s
example-etcd-faf13de3-2 1/1 Running 0 78s
example-etcd-faf13de3-3 1/1 Running 0 78s
example-vttablet-zone1-2469782763-bfadd780 3/3 Running 1 78s
example-vttablet-zone1-2548885007-46a852d0 3/3 Running 1 78s
example-zone1-vtctld-1d4dcad0-59d8498459-kwz6b 1/1 Running 2 78s
example-zone1-vtgate-bc6cde92-6bd99c6888-vwcj5 1/1 Running 2 78s
vitess-operator-8454d86687-4wfnc 1/1 Running 0 2m29s
</code></pre>
<h3 id="设置-Port-forward"><a href="#设置-Port-forward" class="headerlink" title="设置 Port-forward"></a>设置 Port-forward</h3><blockquote>
<p>port-forward 只会转发到特定的 pod。目前,由于应用/升级操作导致 pod 消失,<code>kubectl</code> 不会自动终止端口转发。需要手动重新配置端口转发。</p>
</blockquote>
<p>为了方便使用,Vitess 提供了一个脚本来将 Kubernetes 端口转发到您的本地机器。此脚本还建议为 <code>mysql</code> 和 <code>vtctlclient</code> 设置别名:</p>
<pre><code>./pf.sh &
alias vtctlclient="vtctlclient -server=localhost:15999"
alias mysql="mysql -h 127.0.0.1 -P 15306 -u user"
</code></pre>
<p>设置别名将 <code>mysql</code> 更改为始终连接到 Vitess 以进行当前会话。要还原这个,输入 <code>unalias mysql && unalias vtctlclient</code> 或关闭会话。</p>
<h3 id="创建-Schema"><a href="#创建-Schema" class="headerlink" title="创建 Schema"></a>创建 Schema</h3><p>载入初始化schema:</p>
<pre><code>vtctlclient ApplySchema -sql="$(cat create_commerce_schema.sql)" commerce
vtctlclient ApplyVSchema -vschema="$(cat vschema_commerce_initial.json)" commerce
</code></pre>
<h3 id="连接到集群"><a href="#连接到集群" class="headerlink" title="连接到集群"></a>连接到集群</h3><p>现在应该能够使用 MySQL 客户端连接到集群中的 VTGate 服务器:</p>
<pre><code>~/vitess/examples/operator$ mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.9-Vitess MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+-----------+
| Databases |
+-----------+
| commerce |
+-----------+
1 row in set (0.00 sec)
</code></pre>
<h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><p>在这个例子中,我们部署了一个名为 <code>commerce</code> 的未分片的keyspace。未分片的keyspace有一个名为 <code>0</code> 的分片。以下schema反映了由脚本创建的常见电子商务场景:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> product (</span><br><span class="line"> sku <span class="type">varbinary</span>(<span class="number">128</span>),</span><br><span class="line"> description <span class="type">varbinary</span>(<span class="number">128</span>),</span><br><span class="line"> price <span class="type">bigint</span>,</span><br><span class="line"> <span class="keyword">primary</span> key(sku)</span><br><span class="line">);</span><br><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> customer (</span><br><span class="line"> customer_id <span class="type">bigint</span> <span class="keyword">not</span> <span class="keyword">null</span> auto_increment,</span><br><span class="line"> email <span class="type">varbinary</span>(<span class="number">128</span>),</span><br><span class="line"> <span class="keyword">primary</span> key(customer_id)</span><br><span class="line">);</span><br><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> corder (</span><br><span class="line"> order_id <span class="type">bigint</span> <span class="keyword">not</span> <span class="keyword">null</span> auto_increment,</span><br><span class="line"> customer_id <span class="type">bigint</span>,</span><br><span class="line"> sku <span class="type">varbinary</span>(<span class="number">128</span>),</span><br><span class="line"> price <span class="type">bigint</span>,</span><br><span class="line"> <span class="keyword">primary</span> key(order_id)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>上面的schema仅仅列出比较重要的属性:</p>
<ul>
<li><code>product</code> 表包含所有产品的产品信息。</li>
<li><code>customer</code> 表有一个 auto_increment 的<code>customer_id</code>列,一个典型的customer表包含很多列,甚至还包含扩展表。</li>
<li><code>corder</code> 表(应该命名为order,因为和SQL关键字冲突)有一个 auto_increment 的<code>order_id</code>列,同时有两个外键<code>customer(customer_id)</code>和<code>product(sku)</code>。</li>
</ul>
<h3 id="删除集群"><a href="#删除集群" class="headerlink" title="删除集群"></a>删除集群</h3><pre><code>kubectl delete -f 101_initial_cluster.yaml
</code></pre>
<h2 id="Vttestserver-Docker镜像"><a href="#Vttestserver-Docker镜像" class="headerlink" title="Vttestserver Docker镜像"></a>Vttestserver Docker镜像</h2><p>本指南涵盖使用 vttestserver docker 映像进行测试。这也是我们在 Vitess Framewok Testing 中用来测试的 docker 镜像。</p>
<h3 id="获取docker镜像"><a href="#获取docker镜像" class="headerlink" title="获取docker镜像"></a>获取docker镜像</h3><p>第一步是获取docker镜像,有两种方式获取:</p>
<ol>
<li>从vitessio/vitess仓库获取</li>
</ol>
<h4 id="检出仓库-1"><a href="#检出仓库-1" class="headerlink" title="检出仓库"></a>检出仓库</h4><ul>
<li><p>SSH:<code>git clone [email protected]:vitessio/vitess.git</code>,或者:</p>
</li>
<li><p>HTTP:<code>git clone https://github.com/vitessio/vitess.git</code></p>
<p> cd vitess</p>
</li>
</ul>
<h4 id="构建docker镜像-1"><a href="#构建docker镜像-1" class="headerlink" title="构建docker镜像"></a>构建docker镜像</h4><pre><code>make docker_vttestserver
</code></pre>
<p>这将创建 2 个名为 <code>vitess/vttestserver:mysql57</code> 和 <code>vitess/vttestserver:mysql80</code> 的 docker 镜像。</p>
<ol start="2">
<li><p>从docker hub拉取<br>或者,您可以从 docker hub 获取最新的 docker 镜像。在 shell 中,执行:</p>
<p> docker pull vitess/vttestserver:mysql57<br> docker pull vitess/vttestserver:mysql80</p>
</li>
</ol>
<h3 id="运行docker镜像-1"><a href="#运行docker镜像-1" class="headerlink" title="运行docker镜像"></a>运行docker镜像</h3><p>此时,您应该有一个名为 <code>vitess/vttestserver:mysql57</code> 或 <code>vitess/vttestserver:mysql80</code> 的 docker 镜像。</p>
<h4 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h4><p>docker 镜像需要设置一些环境变量才能正常运行。下表列出了所有可用的环境变量及其用法。</p>
<table>
<thead>
<tr>
<th>Environment variable</th>
<th>Required</th>
<th>Use</th>
</tr>
</thead>
<tbody><tr>
<td>KEYSPACES</td>
<td>yes</td>
<td>Specifies the names of the keyspaces to be created as a comma separated value.</td>
</tr>
<tr>
<td>NUM_SHARDS</td>
<td>yes</td>
<td>Specifies the number of shards in each keyspace. It is a comma separated value as well, read in conjunction with the KEYSPACES.</td>
</tr>
<tr>
<td>PORT</td>
<td>yes</td>
<td>The starting of the port addresses that vitess will use to register its components like vtgate, etc.</td>
</tr>
<tr>
<td>MYSQL_MAX_CONNECTIONS</td>
<td>no</td>
<td>Maximum number of connections that the MySQL instance will support. If unspecified, it defaults to 1000.</td>
</tr>
<tr>
<td>MYSQL_BIND_HOST</td>
<td>no</td>
<td>Which host to bind the MySQL listener to. If unspecified, it defaults to 127.0.0.1.</td>
</tr>
<tr>
<td>MYSQL_SERVER_VERSION</td>
<td>no</td>
<td>MySQL server version to advertise. If unspecified, it defaults to 8.0.21-vitess or 5.7.9-vitess according to the version of vttestserver run.</td>
</tr>
<tr>
<td>CHARSET</td>
<td>no</td>
<td>Default charset to use. If unspecified, it defaults to utf8mb4.</td>
</tr>
<tr>
<td>FOREIGN_KEY_MODE</td>
<td>no</td>
<td>This is to provide how to handle foreign key constraint in create/alter table. Valid values are: allow (default), disallow.</td>
</tr>
<tr>
<td>ENABLE_ONLINE_DDL</td>
<td>no</td>
<td>Allow users to submit, review and control Online DDL. Valid values are: true (default), false.</td>
</tr>
<tr>
<td>ENABLE_DIRECT_DDL</td>
<td>no</td>
<td>Allow users to submit direct DDL statements. Valid values are: true (default), false.</td>
</tr>
</tbody></table>
<p>在docker环境变量可以通过 <code>-e</code> 或 <code>--env</code> 指定。</p>
<h4 id="从外面发生请求给-vttestserver"><a href="#从外面发生请求给-vttestserver" class="headerlink" title="从外面发生请求给 vttestserver"></a>从外面发生请求给 vttestserver</h4><p>vtgate 在3个以上的 PORT 环境变量上侦听 MySQL 连接。即,如果您将 <code>PORT</code> 指定为 33574,则 vtgate 将在 33577 上监听连接,主机地址 <code>MYSQL_BIND_HOST</code> 默认为 localhost,但是这个端口将在 docker 容器端。要从 MySQL 客户端外部连接到 vtgate,还需要发布该端口并将 <code>MYSQL_BIND_HOST</code> 指定为 0.0.0.0,这可以通过 docker 的 <code>-p</code> 或 <code>--publish</code> 标志来完成。例如:将 -p 33577:33577 添加到 <code>docker run</code> 命令会将容器的 33577 端口发布到本地的 33577 端口,就可用于连接到 vtgate。</p>
<h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>运行 docker 镜像的示例命令如下:</p>
<pre><code>docker run --name=vttestserver -p 33577:33577 -e PORT=33574 -e KEYSPACES=test,unsharded -e NUM_SHARDS=2,1 -e MYSQL_MAX_CONNECTIONS=70000 -e MYSQL_BIND_HOST=0.0.0.0 --health-cmd="mysqladmin ping -h127.0.0.1 -P33577" --health-interval=5s --health-timeout=2s --health-retries=5 vitess/vttestserver:mysql57
</code></pre>
<p>现在,可以从 MySQL 客户端连接到 vtgate,如下所示:</p>
<pre><code>mysql --host 127.0.0.1 --port 33577 --user "root"
</code></pre>
<p>这里有 2 个可以使用的键空间,<code>test</code> 有2个分片,<code>unsharded</code> 有1个分片。</p>
<h1 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h1><h2 id="Cell"><a href="#Cell" class="headerlink" title="Cell"></a>Cell</h2><blockquote>
<p>数据中心、可用区或计算资源组</p>
</blockquote>
<p>cell是一组服务器和网络基础设施并置在一个区域中,并与其他单元中的故障隔离。它通常是完整的数据中心或数据中心的子集,有时称为区域或可用性区域。 Vitess 可以优雅地处理cell-level故障,例如当一个cell断网时。</p>
<p>Vitess 实现中的每个单元都有一个本地拓扑服务,该服务托管在该单元中。拓扑服务在其单元中包含有关 Vitess tablet的大部分信息。这使得一个单元能够被删除或重建。</p>
<p>Vitess 限制数据和元数据的跨单元访问。虽然具有将读取流量路由到单个单元格的能力可能很有用,但 Vitess 目前仅支持来自本地单元格的读取流量。必要时,写入将跨单元进行,写入该分片的主节点所在的任何位置。</p>
<h2 id="Execution-Plans"><a href="#Execution-Plans" class="headerlink" title="Execution Plans"></a>Execution Plans</h2><p>Vitess 在 VTGate 和 VTablet 层解析查询,以评估执行查询的最佳方法。这种评估称为查询计划,并产生查询执行计划。</p>
<p>执行计划取决于查询和关联的 VSchema。 Vitess 规划策略的基本目标之一是将尽可能多的工作下推到底层 MySQL 实例。当这不可能时,Vitess 将使用从多个来源收集输入并合并结果以生成正确查询结果的计划。</p>
<p><strong>评估模型</strong></p>
<p>一个执行计划由操作符组成,每个操作符执行一个特定的工作。运算符组合成一个树状结构,代表整体执行计划。该计划将每个运算符表示为树中的一个节点。每个运算符将零或更多行作为输入,并产生零或更多行作为输出。这意味着一个操作符的输出成为下一个操作符的输入。连接树中两个分支的运算符组合来自两个传入流的输入并产生单个输出。</p>
<p>执行计划的评估从树的叶节点开始。叶节点从 VTablet、拓扑服务中提取数据,并且在某些情况下还能够在本地评估表达式值。每个叶节点不会有来自其他算子的输入,并且将它们产生的任何节点通过管道传输到其父节点。然后,父节点将通过管道将节点传送到它们的父节点,一直到根节点。根节点产生查询的最终结果并将结果传递给用户。</p>
<p><strong>观察执行计划</strong></p>
<p>通过浏览 <code>/queryz</code> 端点,可以在 VTGate 级别观察缓存的执行计划。</p>
<p>从 Vitess 6 开始,还可以使用 <code>EXPLAIN FORMAT=vitess <query></code> 观察单个语句计划。</p>
<h2 id="Keyspace"><a href="#Keyspace" class="headerlink" title="Keyspace"></a>Keyspace</h2><p>键空间是一个逻辑数据库。如果你使用分片,一个键空间映射到多个 MySQL 数据库;如果您不使用分片,键空间将直接映射到 MySQL 数据库名称。在任何一种情况下,从应用程序的角度来看,键空间都显示为单个数据库。</p>
<p>从键空间读取数据就像从 MySQL 数据库读取数据。但是,根据读取操作的一致性要求,Vitess 可能会从主数据库或副本中获取数据。 通过将每个查询路由到适当的数据库,Vitess 允许您的代码像从单个 MySQL 数据库读取一样。</p>
<h2 id="Keyspace-ID"><a href="#Keyspace-ID" class="headerlink" title="Keyspace ID"></a>Keyspace ID</h2><p>键空间 ID 是用于决定给定行所在的分片的值。基于范围的分片是指创建每个覆盖特定范围的键空间 ID 的分片。</p>
<p>使用此技术意味着您可以通过用两个或更多新分片替换、拆分给定分片,这些新分片组合在一起以覆盖键空间 ID 的原始范围,而无需移动其他分片中的任何记录。</p>
<p>键空间 ID 本身是使用数据中某些列的函数计算的,例如用户 ID。Vitess 允许您从各种函数(vindexes)中进行选择来执行此映射。这使您可以选择正确的方法来实现数据在分片之间的最佳分布。</p>
<h2 id="MoveTables"><a href="#MoveTables" class="headerlink" title="MoveTables"></a>MoveTables</h2><p>MoveTables 是一种基于 VReplication 的新工作流。它使您能够在键空间之间重新定位表,从而在不停机的情况下重新定位物理 MySQL 实例。</p>
<p><strong>识别候选表</strong></p>
<p>建议将需要相互连接的表保留在同一键空间中,因此 MoveTables 操作的典型候选对象是一组逻辑上组合在一起或以其他方式隔离的表。</p>
<p>如果您有多组表作为候选,最有意义的移动可能取决于您的环境的具体情况。例如,一个更大的表将需要更多的时间来移动,但移动之后你能够利用额外的或更新的硬件,这些硬件在你需要执行额外的操作(如分片)之前有更多的空间。</p>
<p>同样,更新频率高的表也可能会增加移动时间。</p>
<p><strong>对生产交通的影响</strong></p>
<p>在内部,MoveTables 操作由表副本和对表所做的所有更改的订阅组成。Vitess 使用批处理来提高表复制和应用订阅更改的性能,更新率较低的表移动得更快。</p>
<p>在主动移动过程中,数据是从副本而不是主服务器复制的。这有助于确保最小的生产流量影响。</p>
<p>在 MoveTables 操作的 SwitchWrites 阶段,Vitess 可能暂时不可用。这种不可用性通常是几秒钟,取决于主服务器到副本的复制延迟。</p>
<h2 id="Query-Rewriting"><a href="#Query-Rewriting" class="headerlink" title="Query Rewriting"></a>Query Rewriting</h2><p>Vitess 努力营造一种用户与单个数据库的单一连接的错觉。实际上,单个查询可能与多个数据库交互,也可能多个连接连接到同一数据库。在这里,我们将讨论 Vitess 的作用以及它对您的影响。</p>
<p><strong>查询拆分</strong></p>
<p>具有交叉分片连接的复杂查询可能需要首先从 vindex 查找表的 tablet 中获取信息。然后使用此信息查询两个不同的分片以获取更多数据,然后将传入的结果连接到用户接收的单个结果中。 MySQL 获取的查询通常只是原始查询的一部分,最终结果将在 vtgate 级别组装。</p>
<p><strong>连接池</strong></p>
<p>当 tablet 与 MySQL 执行查询时,它不会为每个用户使用专用连接,而是会在用户之间共享底层连接。这意味着在会话中存储任何状态都是不安全的,因为你不能确定它会继续在同一个连接上执行查询,你也不能确定这个连接以后是否会被其他用户使用。</p>
<p><strong>用户定义变量</strong></p>
<p>使用 MySQL 时,用户定义的变量在会话状态保持。可以使用 SET 为它们赋值:</p>
<pre><code>SET @my_user_variable = 'foobar'
</code></pre>
<p>可以使用 SELECT 进行查询:</p>
<pre><code>> SELECT @my_user_variable;
+-------------------+
| @my_user_variable |
+-------------------+
| foobar |
+-------------------+
</code></pre>
<p>如果您针对 VTGate 执行这些查询,则第一个 SET 查询不会发送到 MySQL。而是在 VTGate 中进行计算,并且 VTGate 将为您保留此状态。第二个查询也没有发送下来。像这样的琐碎查询实际上完全在 VTGate 上执行。</p>
<p>如果我们尝试需要来自 MySQL 的数据的更复杂的查询,VTGate 将在发送之前重写查询。 如果我们要写这样的东西:</p>
<pre><code>WHERE col = @my_user_variable
</code></pre>
<p>MySQL看到的是:</p>
<pre><code>WHERE col = 'foobar'
</code></pre>
<p>这样,就不需要会话状态来计算 MySQL 中的查询。</p>
<p><strong>服务器系统变量</strong></p>
<p>用户可能还想更改 MySQL 公开的许多不同系统变量。Vitess 以四种不同方式之一处理系统变量:</p>
<ul>
<li>No op。对于某些设置,Vitess 会默默地忽略该设置。这适用于在分片设置中没有多大意义的系统变量,并且不会改变 MySQL 的行为。</li>
<li>Check and fail if not already set。这些是不应更改的设置,但 Vitess 将允许 SET 语句尝试将变量设置为已经是的任何值。</li>
<li>Not supported。对于这些设置,尝试更改它们总是会导致错误。</li>
<li>Vitess aware。这些是改变 Vitess 行为的设置,不会发送到 MySQL</li>
<li>Reserved connection。对于某些设置,允许设置它们是有意义的,但这也意味着我们不能为此用户使用共享连接。这意味着代表该用户完成的每个连接都需要首先设置这些系统变量,然后保持连接专用。连接池对 Vitess 的性能很重要,保留的连接不能被池化,所以这不应该是在 Vitess 上运行应用程序的正常方式。只需确保将全局变量设置为应用程序将设置的相同值,Vitess 就可以使用连接池。</li>
</ul>
<p>除此之外,Vitess 确保@@version 包含 MySQL 版本和 Vitess 版本,例如:<code>5.7.9-vitess-10.0.0-SNAPSHOT</code>。可以使用 vtgate 标志 <code>-mysql_server_version</code> 更改此值。</p>
<p><strong>特殊功能</strong></p>
<p>Vitess 可以处理一些特殊功能,而无需委托给 MySQL。</p>
<ul>
<li>DATABASE() - 键空间名称和基础数据库名称不必相等。Vitess 将重写这些调用以使用文本字符串作为键空间名称。(这也适用于同义词 SCHEMA())</li>
<li>ROW_COUNT() 和 FOUND_ROWS() - 这些函数返回上次查询影响/返回的行数。因为这可能是在不同的连接上执行的,所以这些被重写为使用返回行数的文字值。</li>
<li>LAST_INSERT_ID() - 与 FOUND_ROWS() 非常相似,我们不能相信这些函数调用的池化连接,因此它们在命中 MySQL 之前会被重写。</li>
</ul>
<h2 id="Replication-Graph"><a href="#Replication-Graph" class="headerlink" title="Replication Graph"></a>Replication Graph</h2><p>Replication Graph标识了主数据库与其各自副本之间的关系。在主故障转移期间,Replication Graph使 Vitess 能够将所有现有副本指向新指定的主数据库,以便复制可以继续。</p>
<h2 id="Shard"><a href="#Shard" class="headerlink" title="Shard"></a>Shard</h2><p>shard 是 keyspace 的子集。一个键空间将始终包含一个或多个分片。一个分片通常包含一个 MySQL 主节点和潜在的多个 MySQL 副本。</p>
<p>分片中的每个 MySQL 实例都具有相同的数据(如果忽略任何复制延迟)。副本可以提供只读流量(具有最终一致性保证)、执行长时间运行的数据分析查询或执行管理任务(备份、恢复、差异等)。</p>
<p>未分片的键空间是只有一个分片的键空间。 Vitess 按照惯例将分片命名为 <code>0</code>(或有时 <code>-</code>)。分片时,一个键空间有 <code>N</code> 个不重叠数据的分片。一个键空间中的分片数量可以根据用例和负载特性而变化,一些 Vitess 用户在某些键空间中有数百个分片。</p>
<p><strong>分片命名</strong></p>
<p>分片名称具有以下特征:</p>
<ul>
<li>代表无符号整数空间中的一个范围,其中包括左边的数字,但不包括右边的数字。</li>
<li>符号是十六进制的。</li>
<li>它们左对齐,右填充零。</li>
<li>一个 <code>-</code> 前缀意味着:任何小于右边值的意思。</li>
<li>一个 <code>-</code> 后缀表示:任何大于或等于左边值的意思。</li>
<li>普通 <code>-</code> 表示完整的键范围。</li>
</ul>
<p>因此:<code>-80</code> == <code>00-80</code> == <code>0000-8000</code> == <code>000000-800000</code> == <code>0000000000000000-8000000000000000</code></p>
<p><code>80-</code> 与 <code>80-FF</code> 不等价。为什么呢?</p>
<p><code>80-FF</code> == <code>8000-FF00</code>。因此 <code>FFFF</code> 将超出 <code>80-FF</code> 范围。</p>
<p><code>80-</code> 表示:任何大于或等于 <code>0x80</code></p>
<p>散列类型 vindex 产生一个无符号的 64 位整数作为输出。这意味着所有小于 <code>0x8000000000000000</code> 的整数都将落入分片 <code>-80</code>。任何设置了最高位的数字将 >= <code>0x8000000000000000</code>,因此属于分片 <code>80-</code>。</p>
<p>这种左对齐的方法允许您拥有任意长度的键空间 ID。因此最重要的位是左边的位。</p>
<p>例如,一个 <code>md5</code> 哈希产生 16 个字节。这也可以用作键空间 ID。</p>
<p>任意长度的 varbinary 也可以按原样映射到 keyspace id。这就是二进制 vindex 所做的。</p>
<p><strong>重新分片</strong></p>
<p>Vitess 支持重新分片,即在实时集群上更改分片的数量。这可以是将一个或多个分片拆分为更小的部分,或者将相邻的分片合并为更大的部分。</p>
<p>在重新分片期间,源分片中的数据被复制到目标分片中,允许赶上复制,然后与原始分片进行比较以确保数据完整性。然后将实时服务基础设施转移到目标分片,并删除源分片。</p>
<h2 id="Tablet"><a href="#Tablet" class="headerlink" title="Tablet"></a>Tablet</h2><p>一个tablet是一个mysqld进程和一个对应的vttablet进程的组合,通常运行在同一台机器上。每个tablet都被分配了一个tablet类型,它指定了它当前执行的角色。</p>
<p>查询通过 VTGate 服务器路由到tablet。</p>
<p><strong>tablet类型</strong></p>
<ul>
<li>master - 一个副本tablet,恰好当前是其分片的 MySQL master。</li>
<li>replica - 有资格提升为主节点的 MySQL 副本。通常,这些副本作为候选者用于服务实时的、面向用户的请求(例如来自网站的前端)。</li>
<li>rdonly - 无法提升为 master 的 MySQL 副本。通常,这些用于后台处理作业,例如备份、将数据转储到其他系统、大量分析查询、MapReduce 和重新分片。</li>
<li>backup - 已在一致性快照处停止复制的tablet,因此它可以为其分片上传新备份。完成后,它将恢复复制并返回到以前的类型。</li>
<li>restore - 启动时没有数据的tablet,并且正在从最新备份中恢复自身。完成后,它将在备份的 GTID 位置开始复制,并成为 replica 或 rdonly。</li>
<li>drained - 由 Vitess 后台进程保留的tablet(例如用于重新分片的 rdonly tablet)。</li>
</ul>
<h2 id="Topology-Service"><a href="#Topology-Service" class="headerlink" title="Topology Service"></a>Topology Service</h2><blockquote>
<p>也称为 TOPO 或 lock service</p>
</blockquote>
<p>拓扑服务是一组运行在不同服务器上的后端进程。这些服务器存储了拓扑数据并提供分布式锁定服务。</p>
<p>Vitess 使用plug-in系统来支持存储拓扑数据的各种后端,前提是这些后端提供分布式、一致的键值存储。默认的拓扑服务插件是 etcd2。</p>
<p>拓扑服务的存在有几个原因:</p>
<ul>
<li>它使tablet能够作为一个集群在它们之间进行协调。</li>
<li>它使 Vitess 能够发现tablet,因此它知道将查询路由到哪里。</li>
<li>它存储集群中许多不同服务器需要的数据库管理员提供的 Vitess 配置,并且必须在服务器重新启动期间保持不变。</li>
</ul>
<p>一个 Vitess 集群有一个全局拓扑服务,每个单元有一个本地拓扑服务。</p>
<p><strong>全局拓扑</strong></p>
<p>全局拓扑服务存储不经常更改的 Vitess 级别的数据。具体来说,它包含有关密钥空间和分片的数据以及每个分片的主tablet别名。</p>
<p>全局拓扑用于一些操作,包括 reparenting 和 resharding。按照设计,全局拓扑服务的使用并不多。</p>
<p>为了在任何单个单元发生故障时容灾,全局拓扑服务应该在多个单元中具有节点,以便在单元发生故障时维持仲裁。</p>
<p><strong>本地拓扑</strong></p>
<p>每个本地拓扑都包含与其自身单元相关的信息。具体来说,它包含有关单元格中tablet的数据、该单元的 keyspace graph 以及该单元的 replication graph。</p>
<p>本地拓扑服务必须可供 Vitess 使用以发现tablet并在tablet往返时调整路由。但是,在稳定状态下提供查询服务的关键路径中不会调用拓扑服务。这意味着在拓扑暂时不可用期间仍会提供查询服务。</p>
<h2 id="VSchema"><a href="#VSchema" class="headerlink" title="VSchema"></a>VSchema</h2><p>VSchema 允许您描述数据在键空间和分片中的组织方式。此信息用于路由查询,也用于重新分片操作。</p>
<p>对于 Keyspace,您可以指定它是否被分片。对于sharded keyspace,您可以为每个表指定 vindexes 列表。</p>
<p>Vitess 还支持序列生成器,可用于生成新的 id,其工作方式类似于 MySQL 自动增量列。 VSchema 允许您将table columns关联到sequence tables。如果没有为这样的列指定值,那么 VTGate 将使用sequence table为它生成一个新值。</p>
<h2 id="VStream"><a href="#VStream" class="headerlink" title="VStream"></a>VStream</h2><p>VStream 是一种可通过 VTGate 访问的更改通知服务。 VStream 的目的是从 Vitess 集群的底层 MySQL 分片提供与 MySQL 二进制日志等效的信息。gRPC 客户端,包括 Vitess 组件,如 VTablets,可以订阅 VStream 以接收来自其他分片的更改事件。VStream从VTTablet实例上的一个或多个VStreamer实例拉取事件,后者又从底层MySQL实例的二进制日志拉取事件。这允许有效执行诸如 VReplication 之类的功能,其中订阅者可以从一个或多个 MySQL 实例分片的二进制日志中间接接收事件,然后将其应用于目标实例。用户可以利用 VStream 获取有关给定 Vitess 键空间、分片和位置的数据更改事件的详细信息。单个 VStream 还可以整合来自键空间中多个分片的更改事件,使其成为从 Vitess 数据存储向下游提供 CDC(Change Data Capture)过程的便捷工具。</p>
<p>作为参考,请参考下图:<br><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gt73e2yso2j30ll0gqwer.jpg" alt="VStream"></p>
<blockquote>
<p>注意:VStream 不同于 VStreamer。前者位于VTGate,后者位于VTTablet。</p>
</blockquote>
<h2 id="vtctl"><a href="#vtctl" class="headerlink" title="vtctl"></a>vtctl</h2><p>vtctl 是一个命令行工具,用于管理 Vitess 集群。它可作为独立工具 (vtctl) 和客户端-服务器(vtctlclient 与 vtctld 结合使用)。建议使用客户端-服务器,因为它在远程使用客户端时提供了额外的安全层。</p>
<p>使用 vtctl,您可以识别主数据库和副本数据库、创建表、启动故障转移、执行重新分片操作等。</p>
<p>随着 vtctl 执行操作,拓扑服务会根据需要进行更新。其他 Vitess 服务器会观察这些变化并做出相应的反应。例如,如果您使用 vtctl 故障转移到新的 master 数据库,vtgate 会看到更改并将未来的写入操作定向到新的 master。</p>
<h2 id="vtctld"><a href="#vtctld" class="headerlink" title="vtctld"></a>vtctld</h2><p>vtctld 是一个 HTTP 服务器,可让您浏览存储在拓扑服务中的信息。它对于故障排除或获取服务器及其当前状态的概览信息很有用。</p>
<p>vtctld 还充当 vtctlclient 连接的服务器。</p>
<h2 id="VTGate"><a href="#VTGate" class="headerlink" title="VTGate"></a>VTGate</h2><p>VTGate 是一个轻量级的代理服务器,它可以将流量路由到正确的 VTTablet 服务器并将合并的结果返回给客户端。它同时使用 MySQL 协议和 Vitess gRPC 协议。因此,您的应用程序可以像连接 MySQL 服务器一样连接到 VTGate。</p>
<p>在将查询路由到适当的 VTablet 服务器时,VTGate 会考虑分片方案、所需的延迟以及表及其底层 MySQL 实例的可用性。</p>
<h1 id="用户手册"><a href="#用户手册" class="headerlink" title="用户手册"></a>用户手册</h1><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><h1 id="设计文档"><a href="#设计文档" class="headerlink" title="设计文档"></a>设计文档</h1><h1 id="FAQ"><a href="#FAQ" class="headerlink" title="FAQ"></a>FAQ</h1>
</div>
<footer class="article-footer">
<a data-url="https://ljun51.github.io/2021/07/30/vitess/" data-id="clu1aiurw0016gpg0aor70nbs" data-title="一文读懂Vitess" class="article-share-link"><span class="fa fa-share">分享</span></a>
</footer>
</div>
</article>
<article id="post-zgc" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
<div class="article-meta">
<a href="/2021/07/28/zgc/" class="article-date">
<time class="dt-published" datetime="2021-07-28T01:44:53.000Z" itemprop="datePublished">2021-07-28</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="p-name article-title" href="/2021/07/28/zgc/">一文读懂ZGC</a>
</h1>
</header>
<div class="e-content article-entry" itemprop="articleBody">
<h1 id="关于ZGC"><a href="#关于ZGC" class="headerlink" title="关于ZGC"></a>关于ZGC</h1><p>ZGC(Z Garbage Collector)是一种可扩展的低延迟垃圾回收器,旨在满足以下目标:</p>
<ul>
<li>亚毫秒(Sub-millisencond)级的最大暂停时间</li>
<li>暂停时间不会随着heap、live-set、root-set的增大而增加</li>
<li>可以处理8MB到16TB的堆大小</li>
</ul>
<p>ZGC支持:</p>
<ul>
<li>并发(Concurrent)</li>
<li>基于Region</li>
<li>压缩(Compacting)</li>
<li>NUMA-aware</li>
<li>使用着色指针</li>
<li>使用负载屏障</li>
</ul>
<p>ZGC的核心是一个并发垃圾收集器,这意味着所有繁重的工作都在Java线程执行的同时完成。这极大地限制了垃圾收集对应用程序响应时间的影响。</p>
<h1 id="JVM如何设置"><a href="#JVM如何设置" class="headerlink" title="JVM如何设置"></a>JVM如何设置</h1><p>JVM一般通过JAVA_OPTS环境变量设置,如果使用Tomcat,可以使用CATALINA_OPTS设置。JAVA_OPTS与CATALINA_OPTS的不同是:</p>
<ul>
<li>[JAVA_OPTS]: (optional) Java runtime options used when the “start”, “stop” or “run” command is executed</li>
<li>[CATALINA_OPTS]: (optional) Java runtime options used when the “start” or “run” command is executed</li>
</ul>
<h1 id="支持的平台"><a href="#支持的平台" class="headerlink" title="支持的平台"></a>支持的平台</h1><table>
<thead>
<tr>
<th>Platform</th>
<th>Supported</th>
<th>Since</th>
<th>Comment</th>
</tr>
</thead>
<tbody><tr>
<td>Linux/AArch64</td>
<td>支持</td>
<td>JDK 13</td>
<td></td>
</tr>
<tr>
<td>Linux/x64</td>
<td>支持</td>
<td>JDK 11</td>
<td></td>
</tr>
<tr>
<td>macOS</td>
<td>支持</td>
<td>JDK 14</td>
<td></td>
</tr>
<tr>
<td>Windows</td>
<td>支持</td>
<td>JDK 14</td>
<td>Requires Windows version 1803 (Windows 10 or Windows Server 2019) or later.</td>
</tr>
</tbody></table>
<h1 id="快速开始"><a href="#快速开始" class="headerlink" title="快速开始"></a>快速开始</h1><p>如果您是第一次尝试 ZGC,请从使用以下 GC 选项开始:</p>
<blockquote>
<p>-XX:+UseZGC -Xmx<size> -Xlog:gc</p>
</blockquote>
<p>如需更详细的日志记录,请使用以下选项:(在VSCode中加*,启动报错)</p>
<blockquote>
<p>-XX:+UseZGC -Xmx<size> -Xlog:gc*</p>
</blockquote>
<p>示例代码:</p>
<blockquote>
<p>JAVA_OPTS=”-XX:+UseZGC -Xmx1024m -Xlog:gc”</p>
</blockquote>
<h1 id="配置和调优"><a href="#配置和调优" class="headerlink" title="配置和调优"></a>配置和调优</h1><h2 id="General-GC-Options"><a href="#General-GC-Options" class="headerlink" title="General GC Options"></a>General GC Options</h2><ul>
<li>-XX:MinHeapSize, -Xms</li>
<li>-XX:InitialHeapSize, -Xms</li>
<li>-XX:MaxHeapSize, -Xmx</li>
<li>-XX:SoftMaxHeapSize</li>
<li>-XX:ConcGCThreads</li>
<li>-XX:ParallelGCThreads</li>
<li>-XX:UseLargePages</li>
<li>-XX:UseTransparentHugePages</li>
<li>-XX:UseNUMA</li>
<li>-XX:SoftRefLRUPolicyMSPerMB</li>
<li>-XX:AllocateHeapAt</li>
</ul>
<h2 id="ZGC-Options"><a href="#ZGC-Options" class="headerlink" title="ZGC Options"></a>ZGC Options</h2><ul>
<li>-XX:ZAllocationSpikeTolerance</li>
<li>-XX:ZCollectionInterval</li>
<li>-XX:ZFragmentationLimit</li>
<li>-XX:ZMarkStackSpaceLimit</li>
<li>-XX:ZProactive</li>
<li>-XX:ZUncommit</li>
<li>-XX:ZUncommitDelay</li>
</ul>
<h2 id="ZGC-Diagnostic-Options-XX-UnlockDiagnosticVMOptions"><a href="#ZGC-Diagnostic-Options-XX-UnlockDiagnosticVMOptions" class="headerlink" title="ZGC Diagnostic Options (-XX:+UnlockDiagnosticVMOptions)"></a>ZGC Diagnostic Options (-XX:+UnlockDiagnosticVMOptions)</h2><ul>
<li>-XX:ZStatisticsInterval</li>
<li>-XX:ZVerifyForwarding</li>
<li>-XX:ZVerifyMarking</li>
<li>-XX:ZVerifyObjects</li>
<li>-XX:ZVerifyRoots</li>
<li>-XX:ZVerifyViews</li>
</ul>
<h2 id="启用ZGC"><a href="#启用ZGC" class="headerlink" title="启用ZGC"></a>启用ZGC</h2><p>使用<code>-XX:+UseZGC</code>参数启用ZGC。</p>
<h2 id="设置Heap大小"><a href="#设置Heap大小" class="headerlink" title="设置Heap大小"></a>设置Heap大小</h2><p>ZGC最重要的调优选项是设置最大堆(Heap)大小 (<code>-Xmx<size></code>)。由于ZGC是并发收集器,因此必须选择最大堆大小:</p>
<ol>
<li>堆可以容纳应用程序的live-set,</li>
<li>在GC运行期间堆中有足够的空间分配给应用程序。</li>
</ol>
<p>需要多少空间取决于应用程序的分配率和实时设置大小。一般来说,你给ZGC的内存越多越好。但同时,浪费内存也是不可取的,所以这一切都是为了在内存使用和GC需要运行的频率之间找到平衡。</p>
<h2 id="设置并发GC线程数"><a href="#设置并发GC线程数" class="headerlink" title="设置并发GC线程数"></a>设置并发GC线程数</h2><p>第二个调优选项是设置并发 GC 线程的数量 (<code>-XX:ConcGCThreads=<number></code>)。 ZGC 有启发式自动选择这个数字。这种启发式通常效果很好,但根据应用程序的特性,这可能需要进行调整。这个选项本质上决定了应该给 GC 多少 CPU 时间。给它太多,GC 会从应用程序中窃取太多 CPU 时间。给它太少,应用程序可能会比 GC 收集垃圾的速度更快地收集垃圾。</p>
<blockquote>
<p>一般来说,如果低延迟(即低应用程序响应时间)对您的应用程序很重要,那么永远不要过度配置您的系统。理想情况下,您的系统的 CPU 利用率不应超过 70%。</p>
</blockquote>
<h2 id="将未使用的内存归还给操作系统"><a href="#将未使用的内存归还给操作系统" class="headerlink" title="将未使用的内存归还给操作系统"></a>将未使用的内存归还给操作系统</h2><p>默认情况下,ZGC 不提交未使用的内存给操作系统。这对于关注内存占用的应用程序和环境很有用。可以使用 <code>-XX:-ZUncommit</code>禁用此功能。此外,内存不会未提交,因此堆大小会缩小到最小堆大小 (-Xms) 以下。这意味着如果最小堆大小 (-Xms) 配置为等于最大堆大小 (-Xmx),则此功能将被隐式禁用。<br>可以使用<code>-XX:ZUncommitDelay=<senconds></code>(默认为 300 秒)配置取消提交延迟。此延迟指定内存在有资格取消提交之前应该被使用多长时间。</p>
<blockquote>
<p>在 Linux 上,取消提交未使用的内存需要具有 FALLOC_FL_PUNCH_HOLE 支持的 fallocate(2),它首先出现在内核版本 3.5(用于 tmpfs)和 4.3(用于 Hugetlbfs)中。</p>
</blockquote>
<h2 id="在Linux上使用Large-Pages"><a href="#在Linux上使用Large-Pages" class="headerlink" title="在Linux上使用Large Pages"></a>在Linux上使用Large Pages</h2><p>将 ZGC 配置为使用大页面通常会产生更好的性能(在吞吐量、延迟和启动时间方面)并且没有真正的缺点,只是设置稍微复杂一些。设置过程通常需要 root 权限,这就是默认情况下不启用它的原因。</p>
<p>在 Linux/x86 上,large pages(也称为“huge pages”)的大小为 2MB。</p>
<p>假设您想要一个 16G 的 Java 堆。这意味着您需要 16G / 2M = 8192 个大页面。<br>首先为大页面池分配至少 16G(8192 页)的内存。 “至少”部分很重要,因为在 JVM 中启用大页面意味着不仅 GC 将尝试将这些用于 Java 堆,而且 JVM 的其他部分将尝试将它们用于各种内部数据结构(代码堆、标记位图等)。因此,在此示例中,我们将保留 9216 个页面 (18G) 以允许 2G 的非 Java 堆分配以使用大页面。<br>配置系统的大页面池,使其拥有所需数量的页面(需要root权限):</p>
<pre><code>$ echo 9216 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
</code></pre>
<p>请注意,如果内核找不到足够的空闲大页面来满足请求,则不能保证上述命令会成功。另请注意,内核处理请求可能需要一些时间。在继续之前,请检查分配给池的大页面数量以确保请求成功并已完成。</p>
<pre><code>$ cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
9216
</code></pre>
<blockquote>
<p>如果您使用的是<code>Linux kernel >= 4.14</code>内核,则可以跳过下一步(您挂载 Hugetlbfs 文件系统的位置)。否则,如果您使用的是较旧的内核,则 ZGC 需要通过 Hugetlbfs 文件系统访问大页面。<br>挂载一个hugetlbfs 文件系统(需要root 权限)并使运行JVM 的用户可以访问它(在本例中,我们假设该用户的uid 为123)。</p>
</blockquote>
<pre><code>$ mkdir /hugepages
$ mount -t hugetlbfs -o uid=123 nodev /hugepages
</code></pre>
<p>现在使用<code>-XX:+UseLargePages</code>选项启动 JVM。</p>
<pre><code>$ java -XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages ...
</code></pre>
<p>如果有多个可访问的 Hugetlbfs 文件系统可用,那么(并且只有这样)您还必须使用<code>-XX:AllocateHeapAt</code>来指定要使用的文件系统的路径。例如,假设安装了多个可访问的hugetlbfs 文件系统,但您特别希望使用它的文件系统安装在/hugepages 上,然后使用以下选项。</p>
<pre><code>$ java -XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages -XX:AllocateHeapAt=/hugepages ...
</code></pre>
<blockquote>
<p>除非采取必要的措施,否则巨页池的配置和 Hugetlbfs 文件系统的安装在重新启动后会丢失。</p>
</blockquote>
<h2 id="在Linux上启用Transparent-Huge-Pages"><a href="#在Linux上启用Transparent-Huge-Pages" class="headerlink" title="在Linux上启用Transparent Huge Pages"></a>在Linux上启用Transparent Huge Pages</h2><p>使用显式大页面(如上所述)的替代方法是使用透明大页面。对于延迟敏感的应用程序,通常不推荐使用透明大页面,因为它往往会导致不必要的延迟峰值。但是,可能值得尝试一下,看看您的工作负载是否/如何受到它的影响。但请注意,您的里程可能会有所不同。</p>
<blockquote>
<p>在 Linux 上,在启用透明大页面的情况下使用 ZGC 需要<code>kernel >= 4.7</code>。</p>
</blockquote>
<p>使用以下选项在 VM 中启用透明大页面:</p>
<pre><code>-XX:+UseLargePages -XX:+UseTransparentHugePages
</code></pre>
<p>这些选项告诉 JVM 为其映射的内存发出 madvise(…, MADV_HUGEPAGE) 调用,这在 madvise 模式下使用透明大页面时很有用。<br>要启用透明大页面,您还需要通过启用 madvise 模式来配置内核。</p>
<pre><code>$ echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
$ echo advise > /sys/kernel/mm/transparent_hugepage/shmem_enabled
</code></pre>
<h2 id="启用NUMA支持"><a href="#启用NUMA支持" class="headerlink" title="启用NUMA支持"></a>启用NUMA支持</h2><p>ZGC 具有 NUMA 支持,这意味着它会尽量将 Java 堆分配定向到 NUMA 本地内存。<code>默认启用此功能</code>。但是,如果 JVM 检测到它绑定到系统中的 CPU 子集,它将自动禁用。通常,您无需担心此设置,但如果您想明确覆盖 JVM 的决定,您可以使用<code>-XX:+UseNUMA</code>或<code>-XX:-UseNUMA</code>选项来实现。</p>
<p>在 NUMA 机器(例如多路 x86 机器)上运行时,启用 NUMA 支持通常会显着提升性能。</p>
<h2 id="启用GC-Logging"><a href="#启用GC-Logging" class="headerlink" title="启用GC Logging"></a>启用GC Logging</h2><p>使用以下命令行选项启用 GC 日志记录:</p>
<pre><code>-Xlog:<tag set>,[<tag set>, ...]:<log file>
</code></pre>
<p>有关此选项的一般信息/帮助:</p>
<pre><code>-Xlog:help
</code></pre>
<p>要启用基本日志记录(每个 GC 输出一行):</p>
<pre><code>-Xlog:gc:gc.log
</code></pre>
<p>要启用对调优/性能分析有用的 GC 日志记录:</p>
<pre><code>-Xlog:gc*:gc.log
</code></pre>
<p>其中 gc* 表示记录包含 gc 标记的所有标记组合,而 :gc.log 表示将日志写入名为 gc.log 的文件。</p>
</div>
<footer class="article-footer">
<a data-url="https://ljun51.github.io/2021/07/28/zgc/" data-id="clu1aiurs000fgpg0025g575j" data-title="一文读懂ZGC" class="article-share-link"><span class="fa fa-share">分享</span></a>
<ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/java/" rel="tag">java</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/jvm/" rel="tag">jvm</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/zgc/" rel="tag">zgc</a></li></ul>
</footer>
</div>
</article>
<article id="post-Storage" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
<div class="article-meta">
<a href="/2021/07/21/Storage/" class="article-date">
<time class="dt-published" datetime="2021-07-21T04:04:12.000Z" itemprop="datePublished">2021-07-21</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="p-name article-title" href="/2021/07/21/Storage/">一文读懂分布式存储</a>
</h1>
</header>
<div class="e-content article-entry" itemprop="articleBody">
<h1 id="分布式理论"><a href="#分布式理论" class="headerlink" title="分布式理论"></a>分布式理论</h1><h2 id="分布式系统的特性"><a href="#分布式系统的特性" class="headerlink" title="分布式系统的特性"></a>分布式系统的特性</h2><ul>
<li>可拓展</li>
<li>低成本</li>
<li>高性能</li>
<li>易用性。提供易用的对外接口,具备完善的监控、运维工具,并方便和其他系统集成。</li>
</ul>
<p>分布式存储的数据需求可以分为三类:</p>
<ul>
<li>非结构化数据:包括所有格式的办公文档、文本、图片、图像、音频和视频信息。</li>
<li>结构化数据:一般存储在关系数据库中,可以用二维关系表结构来表示。结构化数据的模式(Schema,包括属性、数据类型以及数据之间的关系)和内容是分开的,数据的模式需要预先定义。</li>
<li>半结构化数据:介于非结构化数据和结构化数据之间,HTML文档就数据半结构化数据。它一般是自描述的,与结构化数据最大的区别在于,半结构化数据的模式结构和内容混在一起,没有明显的区分,也不需要预先定义数据的模式结构。</li>
</ul>
<h2 id="分布式存储系统"><a href="#分布式存储系统" class="headerlink" title="分布式存储系统"></a>分布式存储系统</h2><h3 id="分布式文件系统"><a href="#分布式文件系统" class="headerlink" title="分布式文件系统"></a>分布式文件系统</h3><p>互联网应用需要存储大量的图片、照片、视频等非结构化数据对象,这类数据以对象的形式组织,对象之间没有关联,这样的数据一般成为Blob(Binary Large Object,二进制大对象)数据。</p>
<p>分布式文件系统用于存储Blob对象,典型的系统有Facebook Haystack以及Taobao File System(TFS)。另外,分布式文件系统常作为分布式表格系统、分布式数据库的底层存储,如谷歌的GFS(Google File System)可以作为分布式表格系统Google Bigtable的底层存储,Amazon的EBS(Elastic Block Store)系统可以作为分布式数据库Amazon RDS的底层存储。</p>
<p>总体上看,分布式文件系统存储三种类型的数据:Blob对象、定长块、大文件。在系统实现层面,分布式文件系统内部按照数据块(chunk)来组织数据,每个数据块的大小大致相同,每个数据块可以包含多个Blob对象或者定长块,一个大文件也可以拆分为多个数据块。分布式文件系统将这些数据块分散到存储集群,处理数据复制、一致性、负载均衡、容错等分布式系统难题,并将用户对Blob对象、定长块以及大文件的操作映射为对底层数据块的操作。<br><img src="https://res.weread.qq.com/wrepub/epub_621735_2" alt="数据块与Blob对象、定长块、大文件之间的关系"></p>
<h3 id="分布式键值系统"><a href="#分布式键值系统" class="headerlink" title="分布式键值系统"></a>分布式键值系统</h3><p>分布式键值系统用于存储关系简单的半结构化数据,它只提供基于主键的CRUD(Create/Read/Update/Delete)功能。</p>
<p>典型的系统有Amazon Dynamo以及Taobao Tair。从数据结构的角度看,分布式键值系统与传统的哈希表比较类似,不同的是,分布式键值系统支持将数据分布到集群中的多个存储节点。分布式键值系统是分布式表格系统的一种简化实现,一般用作缓存,比如淘宝Tair以及Memcache。一致性哈希是分布式键值系统中常用的数据分布技术,因其被Amazon DynamoDB系统使用而变得相当有名。</p>
<h3 id="分布式表格系统"><a href="#分布式表格系统" class="headerlink" title="分布式表格系统"></a>分布式表格系统</h3><p>分布式表格系统用于存储关系较为复杂的半结构化数据,与分布式键值系统相比,分布式表格系统不仅仅支持简单的CRUD操作,而且支持扫描某个主键范围。分布式表格系统以表格为单位组织数据,每个表格包括很多行,通过主键标示一行,支持根据主键的CRUD功能以及范围查找功能。</p>
<p>分布式表格系统借鉴了很多关系数据库的技术,例如支持某种程度上的事务,比如单行事务,某个实体组(Entity Group,一个用户下的所有数据往往构成实体组)下的多行事务。典型的系统包括Google Bigtable以及Megastore,Microsoft Azure Table Storage,Amazon DynamoDB等。与分布式数据库相比,分布式表格系统主要支持针对单张表格的操作,不支持一些特别复杂的操作,比如多表关联、多表连接、嵌套子查询;另外,在分布式表格系统中,同一个表格的多个数据行也不要求包含相同类型的列,适合半结构化数据。分布式表格系统是一个很好的权衡,这类系统可以做到超大规模,而且支持较多功能,但实现往往比较复杂,而且有一定的使用门槛。</p>
<h3 id="分布式数据库"><a href="#分布式数据库" class="headerlink" title="分布式数据库"></a>分布式数据库</h3><p>分布式数据库一般是从单机关系数据库扩展而来,用于存储结构化数据。分布式数据库采用二维表组织数据,提供SQL关系查询语言,支持多表关联、嵌套子查询等复杂操作,并提供事务以及并发控制。</p>
<p>典型的系统包括MySQL数据分片(MySQL Sharding)集群,Amazon RDS以及Microsoft SQL Azure。分布式数据库支持的功能最为丰富,符合用户使用习惯,但可扩展性往往受到限制。当然,这一点并不是绝对的。Google Spanner系统是一个支持多数据中心的分布式数据库,它不仅支持丰富的关系数据库功能,还能扩展到多个数据中心的成千上万台机器。除此之外,阿里巴巴OceanBase系统也是一个支持自动扩展的分布式关系数据库。</p>
<p>关系数据库是目前为止最为成熟的存储技术,它的功能极其丰富,产生了商业的关系数据库软件(例如Oracle、Microsoft SQL Server、IBM DB2、MySQL)以及上层的工具及应用软件生态链。然而,关系数据库在可以扩展上面临着巨大挑战。传统关系数据库的事务以及二维关系模型很难高效的扩展到多个存储节点上,另外,关系数据库对于要求高并发的应用在性能上优化空间较大。</p>
<h2 id="基础硬件对存储系统的影响"><a href="#基础硬件对存储系统的影响" class="headerlink" title="基础硬件对存储系统的影响"></a>基础硬件对存储系统的影响</h2><p><img src="https://res.weread.qq.com/wrepub/epub_621735_7" alt="常用硬件性能参数"></p>
<h2 id="单机存储引擎"><a href="#单机存储引擎" class="headerlink" title="单机存储引擎"></a>单机存储引擎</h2><p>存储引擎是存储系统的发动机,直接决定了存储系统能够提供的性能和功能。存储系统的基本功能包括:增、删、读、改,其中,读取操作又分为随机读取和顺序扫描。哈希存储引擎是哈希表的持久化实现,支持增、删、改,以及随机读取,但不支持顺序扫描,对应的存储系统为键值(Key-Value)存储系统;B树(B-Tree)存储引擎是B树的持久化实现,不仅支持单条记录的增、删、读、改操作,还支持顺序扫描,对应的存储系统是关系数据库。当然,键值系统也可以通过B树存储引擎实现;LSM树(Log-Structured Merge Tree)存储引擎和B树存储引擎一样,支持增、删、改、随机读取以及顺序扫描。它通过批量转储技术规避磁盘随机写入问题,广泛应用于互联网的后台存储系统,例如Google Bigtable、Google LevelDB以及Facebook开源的Cassandra系统。</p>
<h3 id="哈希存储引擎"><a href="#哈希存储引擎" class="headerlink" title="哈希存储引擎"></a>哈希存储引擎</h3><p>Bitcask是一个基于哈希表结构的键值存储系统,它仅支持追加操作(Appendonly)。在Bitcask系统中,每个文件有一定的大小限制,当文件增加到相应大小是,就会产生一个新的文件,老的文件只读不写。在任意时刻,只有一个文件是可写的,用于数据追加,称为活跃数据文件(active data file)。而其他已经达到大小限制的文件,称为老数据文件(older data file)。</p>
<h4 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h4><p>Bitcask数据文件中的数据是一条一条的写入操作,每一条记录的数据项分别为主键(key)、value内容(value)、主键长度(key_sz)、value长度(value_sz)、时间戳(timestamp)、crc校验值。数据删除操作也不会删除旧的条目,而是将value设定为一个特殊的值用作标识。内存中采用基于哈希表的索引数据结构,哈希表的作用是通过主键快速的定位到value的位置。哈希表结构中的每一项包括了三个用于定位数据的信息,分别是文件编号(file id),value在文件中的位置(value_pos),value长度(value_sz),通过读取file_id对应文件的value_pos开始的value_sz个字节,就得到了最终的value值。写入是首先将Key-Value记录追加到活跃数据文件的末尾,接着更新内存哈希表,接着更新哈希表,因此,每个写操作总共需要进行一次顺序的磁盘写入和一次内存操作。<br><img src="https://res.weread.qq.com/wrepub/epub_621735_10" alt="Bitcask数据结构"></p>
<p>Bitcask在内存中存储了主键和value的索引信息,磁盘文件中存储了主键和value的实际内容。系统基于一个假设,value的长度远大于主键的长度。假如value的平均长度为1KB,每条记录在内存中的索引信息为32字节,那么,磁盘内存比为32:1。这样,32GB内存索引的数据量为32GB*32=1TB。</p>
<h4 id="定期合并"><a href="#定期合并" class="headerlink" title="定期合并"></a>定期合并</h4><p>Bitcask系统中的记录删除或者更新后,原来的记录成为垃圾数据。Bitcask需要定期执行合并(Compaction)操作已实现垃圾回收。</p>
<h4 id="快速恢复"><a href="#快速恢复" class="headerlink" title="快速恢复"></a>快速恢复</h4><p>Bitcask系统中的哈希索引存储在内存中,如果不做额外的工作,服务器断电重启重建哈希表需要扫描一遍数据文件,如果数据文件很大,这是一个非常耗时的过程。Bitcask通过索引文件(hint file)来提高重建哈希表的速度。</p>
<p>简单来说,索引文件就是将内存中的哈希索引表转储到磁盘生成的结果文件。Bitcask对老数据文件进行合并操作时,会产生新的数据文件,这个过程中还会产生一个索引文件,这个索引文件记录每一条记录的哈希索引信息。重建索引表时,仅仅需要将索引文件中的数据一行行读取并重建即可,减少重启后的恢复时间。</p>
<h3 id="B树存储引擎"><a href="#B树存储引擎" class="headerlink" title="B树存储引擎"></a>B树存储引擎</h3><p>相比哈希存储引擎,B树存储引擎不仅支持随机读取,还支持范围扫描。关系数据库中通过索引访问数据,在MySQL InnoDB中,有一个称为聚集索引的特殊索引,行的数据存于其中,组织成B+树数据结构。</p>
<h4 id="数据结构-1"><a href="#数据结构-1" class="headerlink" title="数据结构"></a>数据结构</h4><p>MySQL InnoDB按照页面(Page)来组织数据,每个页面对应B+树的一个节点。其中,叶子节点保存每行的完整数据,非叶子节点保存索引信息。数据在每个节点中有序存储,数据库查询是需要从根节点开始二分查找直到叶子节点,每次读取一个节点,如果对应的页面不在内存中,需要从磁盘中读取并缓存起来。B+树的根节点是常驻内存的,因此,B+树一次检索最多需要h-1次磁盘IO,复杂度为O(h)=O(logdN)(N为元素个数,d为每个节点的出度,h为B+树高度)。修改操作首先需要记录提交日志,接着修改内存中的B+树。如果内存中被修改过的页面超过一定的比例,后台线程会将这些页面刷到磁盘中持久化。<br><img src="https://res.weread.qq.com/wrepub/epub_621735_11" alt="B+树存储引擎"></p>
<h4 id="缓冲区管理"><a href="#缓冲区管理" class="headerlink" title="缓冲区管理"></a>缓冲区管理</h4><p>缓冲区管理器负责将可用的内存划分成缓冲区,缓冲区是与页面同等大小的区域,磁盘块的内容可以传送到缓冲区中。缓冲区管理器的关键在于替换策略,即选择哪些页面淘汰出缓冲池。常见的算法有以下两种。</p>
<h5 id="LRU(Least-Recently-Used)"><a href="#LRU(Least-Recently-Used)" class="headerlink" title="LRU(Least Recently Used)"></a>LRU(Least Recently Used)</h5><p>LRU算法淘汰最近最少使用的块。这种算法要求缓冲区管理器按照页面最后一次被访问的时间组成一个链表,每次淘汰链表尾部的页面。</p>
<p>LRU局限性:假如某一个查询做了一个全表扫描,将导致缓冲池中的大量页面(可能包含很多很快被访问的热点页面)被替换,从而污染缓冲池。</p>
<h5 id="LIRS(Low-Inter-reference-Recency-Set)"><a href="#LIRS(Low-Inter-reference-Recency-Set)" class="headerlink" title="LIRS(Low Inter-reference Recency Set)"></a>LIRS(Low Inter-reference Recency Set)</h5><p>LIRS使用IRR(Inter-Reference Recency)来表示数据块访问历史信息,IRR表示最近连续访问同一个数据块之间访问其他不同数据块非重复个数。</p>
<p>现代数据库一般采用LIRS算法,将缓冲池分为两级,数据首页进入第一级,如果数据在较短的时间内被访问2次或以上,则成为热点数据进入第二级,每一级内部还是采用LRU替换算法。Oracle数据库中的Touch Count算法和MySQL InnoDB中的替换算法都采用了类似的分级思想。以MySQL InnoDB为例,InnoDB内部的LRU链表分为两部分:新子链表(new sublist)和老子链表(old sublist),默认情况下,前者占5/8,后者占3/8。页面首先插入到老子链表,InnoDB要求页面在老子链表停留时间超过一定值,比如1秒,才有可能被转移到新子链表。当出现全表扫描时,InnoDB将数据页面载入到老子链表,由于数据页面在老子链表中的停留时间不够,不会被转移到新子链表中,这就避免了新子链表中的页面被替换出去的情况。</p>
<h3 id="LSM树存储引擎"><a href="#LSM树存储引擎" class="headerlink" title="LSM树存储引擎"></a>LSM树存储引擎</h3><p>LSM树(Log Structured Merge Tree)的思想非常朴素,就是将对数据的修改增量保持在内存中,达到指定大小限制后将这些修改操作批量写入磁盘,读取时需要合并磁盘中的历史数据和内存中最近的修改操作。LSM树的优势在于有效地规避了磁盘随机写入问题,但读取时可能需要访问较多的磁盘文件。下面介绍LevelDB中的LSM树存储引擎。</p>
<h4 id="存储结构"><a href="#存储结构" class="headerlink" title="存储结构"></a>存储结构</h4><p>LevelDB存储引擎主要包括:内存中的MemTable和不可变MemTable(Immutable MemTable,也称为Frozen MemTable)以及磁盘上的几种主要文件:当前(Current)文件、清单(Manifest)文件、操作日志(Commit Log)文件以及SSTable文件。当应用写入一条记录时,LevelDB会首先将修改操作写入到操作日志文件,成功后再将修改操作应用到MemTable,这样就完成了写入操作。<br><img src="https://res.weread.qq.com/wrepub/epub_621735_12" alt="LevelDB存储引擎"></p>
<p>当MemTable占用的内存达到一个上限值后,需要将内存的数据转储到外存文件中。LevelDB会将原先的MemTable冻结成为不可变MemTable,并生成一个新的MemTable。新到来的数据被记入新的操作日志文件和新生成的MemTable中。顾名思义,不可变MemTable的内容是不可更改的,只能读取不能写入或者删除。LevelDB后台线程会将不可变MemTable的数据排序后转储到磁盘,形成一个新的SSTable文件,这个操作称为Compaction。SSTable文件是内存中的数据不断进行Compaction操作后形成的,且SSTable的所有文件是一种层级结构,第0层为Level0,第1层为Level1,以此类推。</p>
<p>SSTable中的文件是按照记录的主键排序的,每个文件有最小的主键和最大的主键。LevelDB的清单文件记录了这些元数据,包括属于哪个层级、文件名称、最小主键和最大主键。当前文件记录了当前使用的清单文件名。在LevelDB的运行过程中,随着Compaction的进行,SSTable文件会发生变化,新的文件会产生,老的文件被废弃,此时往往会生成新的清单文件来记载这种变化,而当前文件则用来指出哪个清单文件才是当前有效的。</p>
<p>直观上,LevelDB每次查询都需要从老到新读取每个层级的SSTable文件以及内存中的MemTable。LevelDB做了一个优化,由于LevelDB对外只支持随机读取单条记录,查询时LevelDB首先会去查看内存中的MemTable,如果MemTable包含记录的主键及其对应的值,则返回记录即可;如果MemTable没有读到该主键,则接下来到同样处于内存中的不可变Memtable中去读取;类似地,如果还是没有读到,只能依次从新到老读取磁盘中的SSTable文件。</p>
<h4 id="合并"><a href="#合并" class="headerlink" title="合并"></a>合并</h4><p>LevelDB写入操作很简单,但是读取操作比较复杂,需要在内存以及各个层级文件中按照从新到老依次查找,代价很高。为了加快读取速度,LevelDB内部会执行Compaction操作来对已有的记录进行整理压缩,从而删除一些不再有效的记录,减少数据规模和文件数量。</p>
<p>LevelDB的Compaction操作分为两种:minor compaction和major compaction。Minor compaction是指当内存中的MemTable大小到了一定值时,将内存数据转储到SSTable文件中。每个层级下有多个SSTable,当某个层级下的SSTable文件数目超过一定设置值后,levelDB会从这个层级中选择SSTable文件,将其和高一层级的SSTable文件合并,这就是major compaction。major compaction相当于执行一次多路归并:按照主键顺序依次迭代出所有SSTable文件中的记录,如果没有保存价值,则直接抛弃;否则,将其写入到新生成的SSTable文件中。</p>
<h2 id="数据模型"><a href="#数据模型" class="headerlink" title="数据模型"></a>数据模型</h2><p>如果说存储引擎相当于存储系统的发动机,那么,数据模型就是存储系统的外壳。存储系统的数据模型主要包括三类:文件、关系以及随着NoSQL技术流行起来的键值模型。传统的文件系统和关系数据库系统分别采用文件和关系模型。关系模型描述能力强,产业链完整,是存储系统的业界标准。然而,随着应用在可扩展性、高并发以及性能上提出越来越高的要求,大而全的关系数据库有时显得力不从心,因此,产生了一些新的数据模型,比如键值模型,关系弱化的表格模型,等等。</p>
<h3 id="文件模型"><a href="#文件模型" class="headerlink" title="文件模型"></a>文件模型</h3><p>文件系统以目录树的形式组织文件,以类UNIX操作系统为例,根目录为/,包含/usr、/bin、/home等子目录,每个子目录又包含其他子目录或者文件。文件系统的操作涉及目录以及文件,例如,打开/关闭文件、读写文件、遍历目录、设置文件属性等。POSIX(Portable Operating System Interface)是应用程序访问文件系统的API标准,它定义了文件系统存储接口及操作集。</p>
<p>POSIX主要接口如下所示。</p>
<ul>
<li>Open/close:打开/关闭一个文件,获取文件描述符;</li>
<li>Read/write:读取一个文件或者往文件中写入数据;</li>
<li>Opendir/closedir:打开或者关闭一个目录;</li>
<li>Readdir:遍历目录。</li>
</ul>
<p>POSIX标准不仅定义了文件操作接口,而且还定义了读写操作语义。例如,POSIX标准要求读写并发时能够保证操作的原子性,即读操作要么读到所有结果,要么什么也读不到;另外,要求读操作能够读到之前所有写操作的结果。POSIX标准适合单机文件系统,在分布式文件系统中,出于性能考虑,一般不会完全遵守这个标准。NFS(Network File System)文件系统允许客户端缓存文件数据,多个客户端并发修改同一个文件时可能出现不一致的情况。</p>
<p>对象模型与文件模型比较类似,用于存储图片、视频、文档等二进制数据块,典型的系统包括AmazonSimple Storage(S3),Taobao File System(TFS)。这些系统弱化了目录树的概念,Amazon S3只支持一级目录,不支持子目录,Taobao TFS甚至不支持目录结构。与文件模型不同的是,对象模型要求对象一次性写入到系统,只能删除整个对象,不允许修改其中某个部分。</p>
<h3 id="关系模型"><a href="#关系模型" class="headerlink" title="关系模型"></a>关系模型</h3><p>每个关系是一个表格,由多个元组(行)构成,而每个元组又包含多个属性(列)。关系名、属性名以及属性类型称作该关系的模式(schema)。例如,Movie关系的模式为Movie(title,year,length),其中,title、year、length是属性,假设它们的类型分别为字符串、整数、整数。数据库语言SQL用于描述查询以及修改操作。数据库修改包含三条命令:INSERT、DELETE以及UPDATE,查询通常通过select-from-where语句来表达,它具有图2-9所示的一般形式。Select查询语句计算过程大致如下(不考虑查询优化):</p>
<ol>
<li>取FROM子句中列出的各个关系的元组的所有可能的组合。</li>
<li>将不符合WHERE子句中给出的条件的元组去掉。</li>
<li>如果有GROUP BY子句,则将剩下的元组按GROUP BY子句中给出的属性的值分组。</li>
<li>如果有HAVING子句,则按照HAVING子句中给出的条件检查每一个组,去掉不符合条件的组。</li>
<li>按照SELECT子句的说明,对于指定的属性和属性上的聚集(例如求和)计算出结果元组。</li>
<li>按照ORDER BY子句中的属性列的值对结果元组进行排序。</li>
</ol>
<p>SQL查询还有一个强大的特性是允许在WHERE、FROM和HAVING子句中使用子查询,子查询又是一个完整的select-from-where语句。</p>
<p>另外,SQL还包括两个重要的特性:索引以及事务。其中,数据库索引用于减少SQL执行时扫描的数据量,提高读取性能;数据库事务则规定了各个数据库操作的语义,保证了多个操作并发执行时的ACID特性。</p>
<h3 id="键值模型"><a href="#键值模型" class="headerlink" title="键值模型"></a>键值模型</h3><p>大量的NoSQL系统采用了键值模型(也称为Key-Value模型),每行记录由主键和值两个部分组成,支持基于主键的如下操作:</p>
<ul>
<li>Put:保存一个Key-Value对。</li>
<li>Get:读取一个Key-Value对。</li>
<li>Delete:删除一个Key-Value对。</li>
</ul>
<p>Key-Value模型过于简单,支持的应用场景有限,NoSQL系统中使用比较广泛的模型是表格模型。表格模型弱化了关系模型中的多表关联,支持基于单表的简单操作,典型的系统是Google Bigtable以及其开源Java实现HBase。表格模型除了支持简单的基于主键的操作,还支持范围扫描,另外,也支持基于列的操作。主要操作如下:</p>
<ul>
<li>Insert:插入一行数据,每行包括若干列;</li>
<li>Delete:删除一行数据;</li>
<li>Update:更新整行或者其中的某些列的数据;</li>
<li>Get:读取整行或者其中某些列数据;</li>
<li>Scan:扫描一段范围的数据,根据主键确定扫描的范围,支持扫描部分列,支持按列过滤、排序、分组等。</li>
</ul>
<p>与关系模型不同的是,表格模型一般不支持多表关联操作,Bigtable这样的系统也不支持二级索引,事务操作支持也比较弱,各个系统支持的功能差异较大,没有统一的标准。另外,表格模型往往还支持无模式(schema-less)特性,也就是说,不需要预先定义每行包括哪些列以及每个列的类型,多行之间允许包含不同列。</p>
<h3 id="SQL与NoSQL"><a href="#SQL与NoSQL" class="headerlink" title="SQL与NoSQL"></a>SQL与NoSQL</h3><p>关系数据库在海量数据场景面临如下挑战:</p>
<ul>
<li><code>事务</code> 关系模型要求多个SQL操作满足ACID特性,所有的SQL操作要么全部成功,要么全部失败。在分布式系统中,如果多个操作属于不同的服务器,保证它们的原子性需要用到两阶段提交协议,而这个协议的性能很低,且不能容忍服务器故障,很难应用在海量数据场景。</li>