架构知识整理

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
架构设计三原则
架构即决策。架构需要面向业务在各种 资源约束条件下做权衡、取舍。而决策就会存在不确定性,采用一些高屋建瓴的设计原则有助于去消除不确定,去逼近解决问题的最优解
合适原则: 合适优于业界领先
不合适的选择超前架构,容易导致失败。
原因:
没那么多人,却想干那么多活
没那么多积累,却想一步登天
没有卓越的业务场景,却幻想灵光一闪成为天才
简单原则: 简单优于复杂
软件领域,复杂代表的可能是"问题"
软件领域复杂性体现在两个方面:
1.结构的复杂性
组件越多,越有可能其中某个组件出现问题
某个组件改动,会影响关联的所有组件
定位一个复杂系统的问题总是比简单系统更困难
2.逻辑的复杂性
意识到结构复杂性后,我们第一反应就是"降低组件数量",毕竟组件数量越少,系统结构越简单。最简单的结构当然整个系统只有一个组件,不幸的是这样是行不通的,原因在于除了结构的复杂性,还有逻辑的复杂性,如果某个组件逻辑太复杂,一样会带来各种问题。
为什么复杂的电路意味着更强大的功能,而复杂的架构却有很多问题呢?更笨原因在于电路设计好后不会再变,而软件系统投入使用后,还会有远远不断的需求要实现,因此要不断修改系统,复杂性在整个生命周期中都有很大影响。
演化原则: 演化优于一步到位
对于建筑来说,永恒是主题;而对于软件来说,变化才是主题。
架构设计流程:识别复杂度
正确的做法是将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题
架构流程设计: 设计备选方案
误区
1.设计最优秀的方案。不要面向简历进行架构设计,要遵循架构设计三原则
2.只做一个方案。一个方案容易陷入思考问题片面、自我坚持的认知陷阱
架构设计流程:评估和选择备选方案
架构设计流程: 详细方案设计
通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度。方案复杂度越高,某个细节推翻整个方案的可能性就越高。
高性能架构模式
高性能数据库集群
第一种方式: 读写分离,本质是将访问压力分散到集群多个节点,但是没有分散存储压力
第二种方式: 分库分表。即可分散访问压力,又可以分散存储压力
读写分离
读写分离实现逻辑不复杂,但两个细节将引入设计复杂度: 主从复制延迟和分配机制
复制延迟
解决主从复制延迟几种常见的方法:
1.写操作后的读操作直接发给主服务器
2.读从失败后再读一次主机,即“二次读取”。不足之处在于如果有很多二次读取,将大大增加主机读压力。
3.关键业务读写操作全部主库,非关键业务读写分离。
分配机制
将读写操作区分开,然后访问不同的数据库服务器,一般有两种方式:程序代码封装和中间件封装
1.程序代码封装
指在代码中抽象一个数据访问层,实现读写操作分离和数据库服务器连接管理
特点:
1.实现简单,而且可以根据业务做比较多定制化的功能
2.每个编程语言都需要自己实现一遍,无法通用
3.故障情况下,如果主从发生切换,可能需要所有系统都修改配置并重启
2.中间件封装
中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。中间件对业务服务器提供SQL兼容的协议,业务服务器无须自己进行读写分离。对于业务服务器来说,访问中间件和访问数据库没有区别,事实上在业务服务器来看,中间件就是一个数据库服务器。
特点:
1.能够支持多种编程语言,因为数据库中间件对业务服务器提供的是标准的SQL接口
2.数据库中间件要支持完整的SQL语法和数据库服务器协议,实现比较复杂,细节特别多,容器出现bug
3.数据库中间件自己不执行真正的读写,但所有数据库操作都要经过中间件,中间件性能要求也很高
4.数据库主从切换对业务服务器无感知,数据库中间件可以探测数据库服务器的主从状态
官方推荐MySQL Router。360开源了自己的Atlas,基于Mysql Proxy实现
分库分表
分库分表包括分库和分表两大类
业务分库
业务分库指的是按照业务模块将数据分散到不同的数据库服务器。比如电商网站包括用户、商品、订单三个业务模块,我们可以将用户数据、商品数据、订单数据分开放到三台不同的数据库服务器上
分库能够分散存储和访问压力,但同时也带来了新的问题
1.join操作问题
比如查询购买了化妆品的用户中女性用户的列表这个功能,订单数据中有用户的id信息,用户的性别数据在用户数据库中,如果在同一个库中,简单的join查询就能完成;现在数据分散在两个不同的数据库中,无法做join查询,只能采用先从订单数据中查询购买了化妆品的用户id列表,然后再到用户数据库中查询这批用户id中的女性用户列表,这样实现就比简单的join查询复杂一些。
2.事务问题
原本在同一个数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。需要业务程序自己来模拟实现事务的功能。例如先扣商品库存,扣成功后生成订单,如果因为订单数据库异常导致生成订单失败,业务程序有需要将商品库存加上;而如果因为业务程序自己异常导致生成订单失败,则商品库存就无法恢复了,需要人工通过日志等方式来手工修复库存异常。
3.成本问题
业务分库同时带来了成本的代价,本来一台服务器搞定的事情,现在要多台。
基于以上原因,小公司不建议一开始就这样拆分
分表
单表数据拆分有两种方式:垂直分表和水平分表。
分表能够有效分散存储压力和带来性能提升,但和分库一样,也会引入各种复杂性
1.垂直分表
垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。
垂直分表引入的复杂性主要体现在表操作的数量要增加。
2.水平分表
水平分表适合表行数特别大的表,有的公司要求单标行数超过5000W就必须进行分表,这个数字可以参考,但并不是绝对标准,关键还是要看表的访问性能。水平分表相比垂直分表,会引入更多复杂性,主要表现在以下几个方面:
a.路由
水平分表后,某条数据具体属于哪个切分后的子表,需要增加路由算法计算,这个算法会引入一定复杂性
常见算法有
i.范围路由。1~999999放到数据库1的表中,1000000~1999999放到数据库2的表中。
范围路由设计的复杂点主要体现在分段大小的选取上,一般建议分段大小在100W~2000W之间
范围路由的优点是可以随着数据增长平滑扩充新的表。
范围路由的缺点是分布不均匀,加入按照1000W来进行分表,有可能某个分段实际存储的数据量只有1000条,,而另一个分段实际存储量有900W条
ii.Hash路由。选取某个列的值进行Hash运算,然后根据Hash结果分散到不同的数据库表中。
Hash路由设计的复杂点主要体现在初始表数量的选取上,表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。
Hash路由的优点是分布比较均匀,缺点是扩充很麻烦,所有数据都要重分布。(使用2^n次方扩容,可以减少迁移工作量,只迁移一部分数据)
iii.配置路由。配置路由就是路由表,用一张独立的表来记录路由信息。同样以用户ID为例,我们新增一张user_router表,这个表包含user_id和table_id两列,根据user_id就可以查询对应的table_id
配置路由设计简单,使用非常灵活,尤其是扩充表的时候,只需要迁移指定的数据,然后修改路由表就可以了。
配置路由的缺点是必须多查询一次,会影响整体性能;而路由表本身如果太大,性能同样会成为瓶颈,如果我们再次将路由表分库分表,则又面临一个死循环式的路由算法选择问题。
b.join操作
水平分表后,数据分散在多个表中,如果需要与其他表进行join查询,需要在业务代码或数据库中间件中进行多次join查询,然后将结果合并
c.count()操作
常见处理方式有以下两种:
count()相加:具体是业务代码或中间件中对每个表进行count操作,然后将结果相加。
这种做法实现简单,缺点是性能比较低
记录数表:具体做法是新建一张表,每次插入或删除字表数据成功后,都更新记录数表
d.order by操作
水平分表后,数据分散到多个字表中,排序操作无法在数据库中完成,只能由业务代码或数据库中间件分别查询字表中的数据,然后汇总进行排序
实现方法
和数据库读写分离类似,实现范式也是"程序代码封装"和“中间件封装”
高性能NoSQL
关系数据库存在如下缺点:
1.关系数据库存储的是行记录,无法存储数据结构
2.关系数据库的schema扩展很不方便
3.关系数据库在大数据场景下I/O较高
4.关系数据库的全文搜索功能比较弱
针对上述问题,分别诞生了不同的NoSql解决方案。NoSql方案带来的优势,本质上是牺牲ACID中的某个或某几个特性,我们不能盲目迷信NoSql是银弹,而应该将NoSql作为SQL的一个有力补充
常见NoSQL分为4类
1.K-V存储: 解决关系数据库无法存储数据结构的问题,以Redis为代表
2.文档数据库: 解决关系数据库强schema约束的问题,以MongoDB为代表
3.列式数据库: 解决关系数据库大数据场景下的I/O问题,以HBase为代表
4.全文索引引擎: 解决关系数据库的全文搜索性能问题,以ES为代表
K-V存储
Redis是K-V存储的典型代表,Redis的Value是具体的数据结构,所以常常被称为数据结构服务器
Redis的缺点主要体现在并不支持完整的ACID事务,Redis虽然提供事务功能,但Redis的事务和关系数据库的事务不可同日而语,Redis的事务只能保证隔离性和一致性,无法保证原子性和持久性。
文档数据库
为解决关系数据库schema带来的问题,文档数据库应运而生。文档数据库最大的特点就是no-schema,可以存储和读取任意的数据。目前绝大部分文档数据库存储的数据格式是JSON(或者叫BSON),因为JSON是自描述的,无需在使用前定义字段,读取一个JSON中不存在的字段也不会导致SQL那样的语法错误
文档数据库no-schema的特性给业务开发带来几个明显的优势
1.新增字段简单
业务新增字段,无需再像关系数据库一样要先执行DDL语句修改表结构,程序代码直接读取即可
2.历史数据不会出错
对于历史数据,即使没有新增的字段,也不会导致错误,只会返回空值,此时代码进行兼容处理即可
3.可以很容易存储复杂数据
JSON能够描述复杂的数据结构
文档数据库的这个特点,特别适合电商和游戏这类的业务场景,以电商为例,不同商品的属性差异很大。
文档数据库no-schema的特性带来的代价是不支持事务。例如使用MongoDB存储库存,系统创建订单的时候首先要减扣库存,然后再创建订单。如果用MongoDB来实现,就无法做到事务性,异常情况下可能出现库存被扣减,但订单没有创建的情况,因此对事务要求严格的业务场景是不能使用文档数据库的。
文档数据库另一个缺点就是无法实现关系数据库的join操作,文档数据库是无法进行join查询的,如果要查询"购买了苹果笔记本用户中的女性用户",Mongodb需要查询两次:一次查询订单中购买了苹果笔记本的用户,然后再查询这些用户哪些是女性用户
列式数据库
顾名思义,列式数据库就是按照列来存储数据的数据库,与之对应的传统关系数据库被称为"行式数据库",因为关系数据库是按照行来存储数据的。
关系数据库按照行式来存储数据,主要以下几个优势:
1.业务同时读取多个列时效率高。因为这些列都是按列存储在一起的,一次磁盘操作就能把一行数据库的各个列都读取到内存中
2.能够一次性完成对一行中的多个列的写操作,保证了针对列数据写操作的原子性和一致性;
缺点:
如果采用列存储,可能出现某次写成功,有的列成功,有的列失败,导致数据不一致
我们可以看到,列式存储的优势是在特定业务场景下才能体现,如果不存在这样的业务场景,那么列式存储优势将不复存在,典型的场景就是海量数据进行统计。例如计算某个城市体重超重的人员数据,实际只需读取每个人的体重这一列进行统计即可,而行式存储即使最终只使用一列,也会将所有行数据都读取出来。如果单行用户信息有1KB,其中体重只有4个字节,行式存储还是会将整个1KB数据全部读取到内存中,这是明显的浪费。而如果采用列式存储,每个用户只需读取4字节的体重数据即可,I/O将大大减少。
除了节省I/O,列式存储具有更高的存储压缩比,能够节省更多的存储空间,,普通的行式数据库压缩率在3:1到5:1左右,而列式数据库的压缩率一般在8:1到30:1左右,因为单个列的数据相似度相比行来说更高,能够达到更高的压缩率。
同样,如果场景发生变化,列式存储的优势又会变成劣势,典型的场景是需要频繁地更新多个列,因为列式存储将不同列存储在磁盘上不连续的空间,导致更新多个列时磁盘是随机写操作;而行式存储时同一行多个列都存储在连续的空间,一次磁盘写操作就可以完成,列式存储的随机写效率要远远低于行式存储的写效率。此外,列式存储高压缩率在更新场景下也会成为劣势,因为更新时需要将压缩数据解压后更新,然后再压缩,最后写入磁盘
基于以上列式存储优缺点,一般将列式存储应用在离线的大数据分析和统计场景中,因为这种场景主要是针对部分列单列进行操作,且数据写入后就无需再更新删除
全文搜索引擎
全文搜索基本原理
全文搜索引擎的技术原理被称为"倒排索引"(inverted index),也常被称为反向索引,是一种索引方法,其基本原理是建立单词到文档的索引。之所以被吃称为倒排,是和正排相对的,正排索引的基本原理是建立文档到单词的索引
正排索引示例:
![201 90615156056908271751.png](http://pic.aipp.vip/20190615156056908271751.png)
倒排索引示例:
![2019061515605691043958.png](http://pic.aipp.vip/2019061515605691043958.png)
倒排索引适用于根据关键词来查询文档内容。例如,用户只是想看"设计"相关的文章,网站需要将文章内容中包含“设计”一词的文章都搜索出来展示给用户
全文索引的使用方法
全文索引的索引对象是单词和文档,而关系数据库的索引对象是键和行,两者属于差异很大,不能简单等同起来。因此为了让全文索引引擎支持关系数据的全文搜索,需要做一些转换,即将关系型数据转换为文档数据
目前常用的转换方式是将关系型数据按照对象的形式转换为json文档,然后将json文档数据输入全文搜索引擎进行索引。
网友补充:
关于NoSQL,看过一张图,挺形象:“1970,We have no SQL”->“1980,Know SQL”->“2000,NoSQL”->“2005,Not only SQL”->“2015,No,SQL”。目前,一些新型数据库,同时具备了NoSQL的扩展性和关系型数据库的很多特性。
关系型和NoSQL数据库的选型。考虑几个指标,数据量、并发量、实时性、一致性要求、读写分布和类型、安全性、运维性等。根据这些指标,软件系统可分成几类。
1.管理型系统,如运营类系统,首选关系型。
2.大流量系统,如电商单品页的某个服务,后台选关系型,前台选内存型。
3.日志型系统,原始数据选列式,日志搜索选倒排索引。
4.搜索型系统,指站内搜索,非通用搜索,如商品搜索,后台选关系型,前台选倒排索引。
5.事务型系统,如库存、交易、记账,选关系型+缓存+一致性协议,或新型关系数据库。
6.离线计算,如大量数据分析,首选列式,关系型也可以。
7.实时计算,如实时监控,可以选时序数据库,或列式数据库。
高性能缓存架构
缓存的架构设计要点
缓存穿透
缓存穿透指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统系统需要再次去存储系统查询数据,通常有以下两种情况
1.存储数据不存在
这种情况的解决方法比较简单,如果查询系统的数据没有找到,则直接设置一个默认值,可以是空值也可以是具体的值到缓存中,这样第二次读取缓存时就会获取到默认值,而不会继续访问存储系统
2.缓存数据生成耗费大量时间或资源
这种情况是存储系统中存在数据,但是缓存数据需要耗费较长时间或耗费大量资源,如果刚好在业务访问时缓存失效率,那么也会出现缓存没有发挥作用,访问压力全部集中在存储系统上的情况。
比如电商的商品分页,比如我们在手机这个类别查看,由于数据巨大,不能把所有数据都缓存起来,只能按照分页来进行缓存,由于难以预测用户到底会方案哪些分页,因此业务上最简单的就是每次点击分页时按分页计算和生成缓存。通常情况下这样实现能满足基本要求,但是如果被竞争对手用爬虫来遍历的时候,系统性能就可能出现问题。
比如通常用户不会从第一页到最后一页都看完,一般集中访问在前10页,因此第10页以后的缓存过期失效的可能性很大
竞争对手每周爬取数据,爬虫会将所有分类的所有数据全部遍历,从第一页到最后一页全部都会读取,此时很多分页缓存可能都失效了。
由于很多分页都没有缓存数据,从数据库中生成缓存数据又非常耗费性能,因此爬虫会将整个数据库全部拖慢
这种情况没有调好的解决方案,通常的应对方案就是识别爬虫然后禁止访问,但是这可能会影响SEO和推广;要么就是做好监控,发现问题及时处理,因为爬虫不是攻击,不会进行暴力破坏,对系统的影响是逐步的,监控发现问题后有时间进行处理。
缓存雪崩
缓存雪崩是指当缓存失效(过期)后引起新系统性能急剧下降的情况。
缓存雪崩常见解决方法有两种: 更新锁机制和后台更新机制
1.更新锁
对缓存更新操作进行加锁保护,保证只有一个线程能够进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或默认值。
对于采用分布式集群的业务系统,由于存在即是上百台服务器,即使单台服务器只有一个线程更新缓存,但几十上百台服务器一起算下来也会有几十上百个线程同时来更新缓存,同样存在雪崩的问题。因此分布式集群的业务系统要实现更新锁机制,需要用到分布式锁,如zookeeper
2.后台更新
由后台线程来更新缓存,而不是业务线程来更新缓存
后台定时机制需要考虑一种特殊的场景,当缓存系统内存不够时,会踢掉一些缓存数据,从缓存被踢掉到下次定时更新缓存的这段时间内,业务线程读取缓存返回空值,而业务本身又不会去更新缓存,因此业务上看到的线上就是数据丢了,解决方式由两种:
a.后台线程除了定时更新缓存,还要频繁去读取缓存(比如1秒或100毫秒读取一次),如果发现缓存被提了就立刻更新缓存
b.业务线程发现缓存失效后,通过队列发送一条消息通知后台线程更新缓存。可能会出现多个业务线程都发送了缓存更新消息,但是其实对后台线程没有影响,后台线程收到消息后更新缓存前可以判断是否存在,存在就不执行更新操作。这种方式依赖消息队列,复杂度会高一些,但存储更新更及时,用户体验更好
后台更新既适应单机多线程的场景,也适合分布式集群的场景,相比更新锁机制要简单一些。
后台更新机制还适合业务刚上线的时候进行缓存预热,缓存预热指系统上线后,将相关的缓存数据直接加载到缓存系统中,而不是等待用户访问才来触发缓存加载。
3.多key机制
比如双key,要换粗你的key过期时间是t,key1没有过期时间,每次缓存读取不到key时就会回key1的内容,然后出发一个事件。这个事件会同时更新key和key1
缓存热点
虽然缓存系统本身性能比较高,但对于一些特别热点的数据,如果大部分甚至所有业务请求都命中同一份缓存数据,这份数据所在的缓存服务器压力也很大。比如某明星微博发布"我们"恋爱了,短时间内上千万用户都会来围观
缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。以微博为例,对于粉丝数超过100W的明星,每条微博都可以生成100份缓存,缓存的数据是一样的,通过在缓存的key里加编号进行区分,每次读取缓存时随机读取某分缓存
缓存副本设计有一个细节需要注意,就是不同的缓存副本不要设置统一的过期时间,否则会出现所有缓存副本同时生效或同时失效的情况,从而引发缓存雪崩。正确的做法是设定一个过期时间范围,不同的缓存副本的过期时间是指定范围内的随机值。
实现方式
由于缓存的各种访问策略和存储的访问策略是相关的,因此上面的各种缓存设计方案通常情况下都是集成在存储访问方案中,可以采用"程序代码实现"的中间层方式,也可以采用独立的中间件来实现
单服务器高性能模式:PPC和TPC
高性能架构设计主要集中在两方面
1.尽量提升单服务器的性能,将单服务器的性能发挥到极致
2.如果单服务器无法支撑性能,设计服务器集群方案
单服务器高性能的关键之一就是服务器采用的并发模型,并发模型有如下两个关键设计点
1.服务器如何管理连接
2.服务器如何处理请求
以上两个设计点最终都和操作系统的I/O模型及进程模型相关
I/O模型: 阻塞、非阻塞、同步、异步
进程模型: 单进程、多进程、多线程
PPC
PPC是Process Per Connection的缩写,其含义是指每次新的连接就新建一个进程去专门处理这个连接的请求,这是传统的UNIX网络服务器所采用的模型。
![2019061515605723492.png](http://pic.aipp.vip/2019061515605723492.png)
注意,图中有个小细节,父进程"fork"子进程后,直接调用了close,看起来好像是关闭了连接,其实只是将连接的文件描述符引用计数减一,真正的关闭连接是等子进程也调用close后,连接对应的文件描述符引用计数变为0后,操作系统才会真正的关闭连接。
prefork
TPC
![20190616156066477569526.png](http://pic.aipp.vip/20190616156066477569526.png)
prethread
单服务器高性能模式: Reactor与Proactor
Reactor
PPC模式最主要的问题就是每个连接都要创建进程,连接结束后进程就销毁了,这样做是很大的浪费。为解决这个问题,自然的想法就是资源复用,即不再为每个连接创建进程,而是创建一个进程池,将连接分配给进程,一个进程可以处理多个连接的业务。
引入资源池处理后引入一个新的问题:进程如何才能高效地处理多个连接的业务?当一个连接一个进程时,进程可以采用"read->业务处理->write"的处理流程,如果当前连接没有数据可以读,则进程就阻塞在read操作上。这种阻塞的方式在一个连接一个进程的场景下没有问题,但如果一个进程处理多个连接,进程阻塞在某个连接的read操作上,此时即使其他连接有数据可读,进程也无法去处理,很显然这样是无法做到高性能的。
解决这个问题最简单的方式是将read操作改为非阻塞,然后进程不断地轮训多个连接。这种方式能够解决阻塞的问题,但解决的方式并不优雅。首先,轮训是要消耗CPU的;其次,如果一个进程处理几千几万的连接,则轮训的效率是很低的。
为了能够更好解决上述问题,很容易想到,只有当连接上有数据的时候进程才去处理,这就是I/O多路复用技术的来源
I/O多路复用技术归纳起来有两个关键实现点:
1.当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无需再轮训所有连接,常见的方式由select、epoll、kqueue等
2.当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理。
I/O多路复用结合线程池,完美解决PPC和TPC的问题,我们给它骑了一个很牛的名字Reactor,Reactor是"事件反应"的意思,通俗理解就是来了一个事件我有相应的反应。Reactor模式也叫Dispather模式。
Reactor模式核心包括Reactor和处理资源池,其中Reactor负责监听和分配事件,处理资源池负责处理事件
单Reactor单进程/线程
![20190616156068618257510.png](http://pic.aipp.vip/20190616156068618257510.png)
优点简单,无进程间通信,无进程竞争
缺点:
1.只有一个进程,无法发挥多核性能
2.handler在处理某个连接上的业务时,整个进程无法处理其他连接的时间,很容易造成性能瓶颈
只适用于业务处理非常快速的场景,比如著名的开源软件Redis
单Reactor多线程
![20190616156068618996130.png](http://pic.aipp.vip/20190616156068618996130.png)
优点是能够充分利用多核能力
缺点:
1.多线程数据共享和访问比较复杂
2.Reacotor承担所有事件的监听和响应,只在主进程中运行,瞬间高并发时会成为性能瓶颈
多Reactor多线程/进程
![20190616156068619725564.png](http://pic.aipp.vip/20190616156068619725564.png)
* 父进程中 mainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给某个子进程。
* 子进程的 subReactor 将 mainReactor 分配的连接加入连接队列进行监听,并创建一个 Handler 用于处理连接的各种事件。
* 当有新的事件发生时,subReactor 会调用连接对应的 Handler(即第 2 步中创建的 Handler)来进行响应。
* Handler 完成 read→业务处理→send 的完整业务流程。
Proactor
Reactor是非阻塞同步网络模型,因为真正的read和send操作都需要用户进程同步操作,这里的"同步"指的是用户进程在执行read和send这类I/O操作的时候是同步的,如果把I/O操作改为异步就能够进一步提升性能,这就是异步网络模型Proactor
多Reactor多进程/线程看起来比单Reactor多线程要复杂,但实际时反而更加简单,原因在于:
1.父进程和子进程的职责非常明确,父进程只负责接收新连接,子进程负责完成后续的业务处理
2.父进程和子进程的交互很简单,父进程只需要把新连接传给子进程,子进程无需返回数据
3.子进程之间是互相独立的,无需同步共享之类的处理
目前著名的Nginx采用的是多Reactor多进程,Memcache和Netty采用的是多Reactor多线程
高性能负载均衡:分类及架构
高性能集群的复杂性主要体现在需要增加一个任务分配器,以及为任务选择一个合适的任务分配算法。对于任务分配器,现在更流行的叫法是"负载均衡器",但这个名字有一定误导性,让人潜意识里认为考虑分配的目的就是要保持各个计算单元的负载达到负载均衡,而实际上任务分配并不只是考虑计算单元的负载均衡,不同的任务分配算法目标是不一样的,有的基于负载考虑,有的基于性能(吞吐量、响应时间)考虑,有的基于业务考虑。负载均衡不止是为了计算单元的负载达到均衡状态。
负载均衡分类
DNS负载均衡
DNS负载均衡优点是:
1.简单、成本低
2.就近访问,提升访问速度:DNS解析可以根据请求来源IP,解析成距离用户最近的服务器地址,可以加快访问速度,改善性能。
缺点是:
1.更新不及时:DNS缓存时间较长
2.扩展性差,DNS负载均衡控制权在域名商哪里,无法根据业务特地那做更多定制化功能
3.分配策略比较简单: DNS负载均衡支持算法少,不能区分服务器的差异(不能根据系统和服务的状态来判断负载),也无法感知后端服务器的状态
对于DNS负载一些缺点,对于时延和故障敏感的业务,有些公司实现了HTTP-DNS的功能,即用HTTP协议实现一个私有的DNS系统,这样的方案和通用的DNS优缺点正好相反
硬件负载均衡
F5和A10
优点:
性能强大
功能强大
稳定性高
缺点:
价格昂贵
扩展能力差
软件负载均衡
常见的有Nginx和LVS,其中Nginx是软件的7层负载均衡,LVS是Linux内核的4层负载均衡。4层和7层的区别就在于协议和灵活性,Nginx支持HTTP、E-mail协议;而LVS是4层负载均衡,和协议无关,几乎所有应用都可以做,例如聊天、数据库等
优点:
简单
便宜
灵活
缺点
性能一般
功能没有硬件负载均衡那么强大
一般不具备防火墙和防DDoS等安全功能
负载均衡典型架构
对于上述3种常见的负载均衡机制,通常情况下会基于它们组合使用,基本原则是:DNS负载均衡用于实现地理级别的负载均衡;硬件负载均衡用于实现集群级别的负载均衡;软件负载均衡用户实现机器级别的负载均衡
![20190616156068851176843.png](http://pic.aipp.vip/20190616156068851176843.png)
练习:
假如要设计一个日活跃用户1000W的论坛的负载均衡集群,你的方案是什么?设计理由是什么?
1、首先,流量评估。
1000万DAU,换算成秒级,平均约等于116。
考虑每个用户操作次数,假定10,换算成平均QPS=1160。
考虑峰值是均值倍数,假定10,换算成峰值QPS=11600。
考虑静态资源、图片资源、服务拆分等,流量放大效应,假定10,QPS*10=116000
2、其次,容量规划。
考虑高可用、异地多活,QPS*2=232000。
考虑未来半年增长,QPS*1.5=348000。
3、最后,方案设计。
三级导流
第一级,DNS,确定机房,以目前量级,可以不考虑。
第二级,确定集群,扩展优先,则选Haproxy/LVS,稳定优先则选F5。
第三级,Nginx+KeepAlived,确定实例。
TPS: transaction 代表写请求
QPS: query 代表读请求
高性能负载均衡: 算法
负载均衡算法很多,根据算法期望达到的目的,大体分为以下几类
1.任务平分类: 负载均衡系统将受到的任务平均分配给服务器进行处理,这里的平均可以是绝对数量的平均,也可以是比例或者权重上的平均
2.负载均衡类: 根据服务器的负载进行分配,这里的负载并不一定是通常意义上我们所说的CPU负载,而是系统当前的压力,可以用CPU负载来衡量,也可以用连接数、I/O使用率、网卡吞吐量等来衡量系统的压力
3.性能最优类: 负载均衡系统根据服务器的响应时间来进行任务分配,优先将新任务分配给响应最快的服务器
4.HASH类: 根据某系关键信息进行HASH运算,将相同HASH值的请求分配到同一台服务器上,常见的有源地址HASH、目标地址HASH、session id HASH、用户ID HASH等
下面介绍一下负载均衡算法及他们的优缺点
轮训
轮训是最简单的策略,无需关注服务器本身的状态
只要服务器在运行,运行状态是不关注的
加权轮训
加权轮训是轮训的一种特殊形式,目的是为了解决不同服务器处理能力有差异的问题,比如有的服务器是32核的,老机器是16核的
负载最低优先
将任务分配给当前负载最低的服务器,这里的负载根据不同的任务类型和业务场景,可以用不同的指标来衡量。例如
* LVS这种4层网络负载均衡设备,可以以连接数来判断服务器的状态,服务器连接数越大,说明服务器压力越大
* Nginx这种7层网络负载系统,可以以HTTP请求数来判断服务器状态(Nginx内置负载均衡算法不支持这种算法,需要进行扩展)
* 如果我们自己开发负载均衡系统,可以根据业务特点来选择指标衡量系统压力。如果是CPU密集型,可以以”CPU负载“来衡量系统压力;如果是I/O密集型,可以以I/O负载来衡量系统压力
负载最低优先算法解决了轮训算法中无法感知服务器状态的问题,由此的代价是复杂度增加了很多,例如
* 最少连接数优先的算法要求负载均衡系统统计每个系统当前建立的连接,其应用场景仅限于负载均衡接收的任何连接都会转发给服务器进行处理,否则如果负载均衡系统和服务器之间是固定的连接池方式,就不适合采用这种算法
* CPU负载最低的算法要求负载均衡系统以某种方式📱每个服务器的CPU负载,而要确定是以1分钟的负载为标准,还是以15分钟的负载为标准,不存在1分钟肯定比15分钟要好或差
负载最低优先算法基本能够比较完美解决轮训算法的缺点,其代价是复杂度大幅上升,通俗来讲轮训可能是5行就能实现的算法,负载最低优先算法要1000行才能实现。
性能最优
与负载最低类似,性能最优优先算法本质上也是感知了服务器的状态,只是通过响应时间这个外部标准来衡量服务器状态而已,因此存在的问题也类似,复杂度都很高
Hash类
比如
源地址Hash
将来源同一个源IP地址的任务分配给同一个服务器进行处理,适用于存在事务、会话的业务。
ID Hash
将某个ID标识的业务分配到同一个服务器进行处理,这里的ID一般是临时性数据的ID,比如(SESSION id)
思考:
微信抢红包的高并发架构,应该采用怎么样的负载均衡算法?谈谈你的分析和理解
具体到微信红包的算法选择上,由于并发量特别高,需要有一个简单高效的算法,因而性能优先类算法可以不做考虑。对于微信这种级别的机房,其容器化技术必然是炉火纯青,每一台vm的配置是可以完全相同的,因而也就无需采用负载均衡类算法和权重轮询算法,剩下来的就是hash类算法和简单轮询算法。对于红包业务,最主要的操作是发红包和抢红包:不管是发个人红包还是发群红包整体业务相差不大,可以采用简单轮询算法,到任何一台服务器均可。但抢个人红包和抢群红包是不同的,抢群红包是有先后顺序,当有多个人抢同一个群红包时最好是由同一个服务器进行处理,这台服务器在收到抢红包的请求后将请求放到一个队列中,而后按照先进先出的方式进行消费,可以保证先到的人能抢到红包,后到的人后抢到红包。因而对于抢红包业务最好按照红包id进行hash负载。
如果是只选择一个负载算法的话,就是hash负载,发红包按照userid进行hash负载,抢红包按照红包id进行hash负载
如何应对接口级高性能挑战
高性能主要体现方式在QPS以及RT上,单机承载QPS越高、RT越低说明接口性能越好
1.串行改并行,有效降低依赖服务调用时间。对于依赖服务改为并行调用,例如php中用curl_multi模拟并行多用
2.同步变异步,如果后端服务写入操作耗时过长,可以利用消息队列解耦,同样可以改善性能
3.使用OR等更高效能语言及架构,提升服务整体性能
高可用架构模式
想成为架构师,你必须知道CAP理论
CAP
在一个分布式系统(指相互连接并共享数据的节点的集合)中,当设计到读写操作时,执行能保证一致性、可用性、分区容错性三者中的两个,另一个必须被牺牲
C一致性
对某个指定的客户端来说,读操作保证能够返回最新的写操作结果
A可用性
非故障的结点在合理的时间内返回合理的响应(不是错误或超时的响应)
P分区容错
当出现网络分区后,系统能够继续"履行职责"
CAP应用
分布式环境下来思考,我们会发现必须选择P要素,所以只剩AP或CP
CP
![20190617156070290095213.png](http://pic.aipp.vip/20190617156070290095213.png)
为保证一致性,当发生分区现象后,N1节点上你的数据已经更新到y,但由于N1和N2之间的复制通道中断,数据y无法同步到N2,N2节点上的数据还是x。这时客户端C访问N2时,N2需要返回error,提示客户端C系统现在发生了错误,这种处理方式违背了可用性的要求,因此CAP三者只能满足CP
AP
![20190617156070291111935.png](http://pic.aipp.vip/20190617156070291111935.png)
为了保证可用性,当发生分区现象后,N1节点上的数据已经更新到Y,但由于N1/N2之间的复制通道中断,数据y无法同步到N2,N2上的数据还是X,这时客户端C访问N2时,N2将当前自己拥有的数据x返回给客户端C了,而实际当前最新的数据已经是y了,这就不满足一致性要求了
网友理解
设计分布式系统的两大初衷:横向扩展(scalability)和高可用性(availability)。“横向扩展”是为了解决单点瓶颈问题,进而保证高并发量下的「可用性」;“高可用性”是为了解决单点故障(SPOF)问题,进而保证部分节点故障时的「可用性」。由此可以看出,分布式系统的核心诉求就是「可用性」。这个「可用性」正是 CAP 中的 A:用户访问系统时,可以在合理的时间内得到合理的响应。
为了保证「可用性」,一个分布式系统通常由多个节点组成。这些节点各自维护一份数据,但是不管用户访问到哪个节点,原则上都应该读取到相同的数据。为了达到这个效果,一个节点收到写入请求更新自己的数据后,必须将数据同步到其他节点,以保证各个节点的数据「一致性」。这个「一致性」正是 CAP 中的 C:用户访问系统时,可以读取到最近写入的数据。
需要注意的是:CAP 并没有考虑数据同步的耗时,所以现实中的分布式系统,理论上无法保证任何时刻的绝对「一致性」;不同业务系统对上述耗时的敏感度不同。
分布式系统中,节点之间的数据同步是基于网络的。由于网络本身固有的不可靠属性,极端情况下会出现网络不可用的情况,进而将网络两端的节点孤立开来,这就是所谓的“网络分区”现象。“网络分区”理论上是无法避免的,虽然实际发生的概率较低、时长较短。没有发生“网络分区”时,系统可以做到同时保证「一致性」和「可用性」。
发生“网络分区”时,系统中多个节点的数据一定是不一致的,但是可以选择对用户表现出「一致性」,代价是牺牲「可用性」:将未能同步得到新数据的部分节点置为“不可用状态”,访问到这些节点的用户显然感知到系统是不可用的。发生“网络分区”时,系统也可以选择「可用性」,此时系统中各个节点都是可用的,只是返回给用户的数据是不一致的。这里的选择,就是 CAP 中的 P。
分布式系统理论上一定会存在 P,所以理论上只能做到 CP 或 AP。如果套用 CAP 中离散的 C/A/P 的概念,理论上没有 P 的只可能是单点(子)系统,所以理论上可以做到 CA。但是单点(子)系统并不是分布式系统,所以其实并不在 CAP 理论的描述范围内。
思考
1.基于Paxos算法构建的分布式系统,属于CAP架构中的哪一种?谈谈你的分析和理解
2.假如你来设计电商你网站的高可用系统,按照CAP理论的要求,你会如何设计?
一个电商网站核心模块有会员,订单,商品,支付,促销管理等。
对于会员模块,包括登录,个人设置,个人订单,购物车,收藏夹等,这些模块保证AP,数据短时间不一致不影响使用。
订单模块的下单付款扣减库存操作是整个系统的核心,我觉得CA都需要保证,在极端情况下牺牲P是可以的。
商品模块的商品上下架和库存管理保证CP,搜索功能因为本身就不是实时性非常高的模块,所以保证AP就可以了。
促销是短时间的数据不一致,结果就是优惠信息看不到,但是已有的优惠要保证可用,而且优惠可以提前预计算,所以可以保证AP
现在大部分的电商网站对于支付这一块是独立的系统,或者使用第三方的支付宝,微信。其实CAP是由第三方来保证的,支付系统是一个对CAP要求极高的系统,C是必须要保证的,AP中A相对更重要,不能因为分区,导致所有人都不能支付
想成为架构师,你必须知道CAP细节
FMEA方法,排除架构可用性隐患的利器
高可用相较于高性能更复杂在于其异常的场景很多,只要有一个场景遗漏,架构设计就存在可用性隐患,而根据墨菲定律"可能出错的事情最终都会出错",架构隐患总有一天会导致系统故障。因此我们在进行架构设计的时候必须全面分析系统的可用性,那么如何才能”全面“呢?
这里要介绍的FMEA方法,就是保证我们做到全面分析的一个非常简单但非常高效的方法。
FMEA
FMEA(Failure mode and effects analysis,故障模式与影响分析)又称为失效模式与后果分析等是一种在各行各业都有广泛应用的可用性分析方法,通过对系统范围内潜在的故障模式加以分析,并按照严重程度进行分类,以确定失效对于系统的最终影响。
FMEA方法
具体方法:
1.给出初始的架构设计图
2.假设架构中的某个部件发生故障
3.分析此故障对系统功能造成的影响
4.根据分析结果,判断架构是否需要进行优化
FMEA分析方法其实很简单,就是一个FMEA分析表,常见的FMEA分析表格包含下面部分
1.功能点
这里的功能点是从用户角度来看的,而不是从系统各个模块功能点划分的。比如对于用户管理系统,”登录“/”注册“是功能点,而数据库存储、Redis缓存等不能作为FMEA分析的功能点
2.故障模式
故障模式指的是系统会出现什么样的故障,包括故障点和故障形式。
故障模式描述要尽可能精确,多用量化描述,避免泛化描述
例如: Mysql无法访问
3.故障影响
当发生故障模式中描述的故障时,功能点具体会受到什么影响
例如: "20%用户无法登陆"
4.严重程度
严重程度是指从业务角度故障的影响程度,一般分为”致命/高/中/低/无“五个档次
5.故障原因
6.故障概率
高/中/低
7.风险程度
严重程度x故障概率
8.已有措施
检测告警
容错
自恢复
9.规避措施
技术手段: 为避免引入新的MongoDB丢失数据,在Mysql中冗余一份
管理手段: 为降低磁盘坏道的概率,强制统一更换服务时间超过2年的磁盘
10.解决措施
解决措施指为了能够解决问题而做的一些事情,一般都是技术手段
例如为了解决密码暴力破解,增加密码重试次数限制
11.后续计划
实战分析
![20190617156073774097828.png](http://pic.aipp.vip/20190617156073774097828.png)
![2019061715607377521672.png](http://pic.aipp.vip/2019061715607377521672.png)
最终得到以下几条需要改进的措施
1.Mysql增加备机
2.MC从单机扩展为集群
3.Mysql双网卡连接
![20190617156073781464693.png](http://pic.aipp.vip/20190617156073781464693.png)
高可用存储架构:双机架构
存储高可用方案的本质是冗余,其复杂性主要体现在如何应对复制和中断导致的数据不一致问题。因此对于任何一个高可用存储方案,我们需要从以下几个方面去进行思考和分析:
1.数据如何复制?
2.各个结点的职责是什么
3.如何应对复制延迟
4.如何应对复制中断
常见高可用存储架构有主备、主从、主主、集群、分区,其中常见的双机高可用架构有:主备、主从、主备/主从切换和主主
主备
![20190617156075468394310.png](http://pic.aipp.vip/20190617156075468394310.png)`
主从
![20190617156075473765526.png](http://pic.aipp.vip/20190617156075473765526.png)
主从架构缺点:
1.客户端需要感知主从关系,将不同的操作发送给不同的机器进行处理,复杂度比主备要高
2.主从复制架构中,从机提供读业务,如果主从复制延迟比较大,业务会因为数据不一致出现问题
3.故障时需要人工干预
一般情况下,读多写少的业务使用主从复制存储架构比较多
主备/主从切换
主备与主从复制方案存在两个共性的问题
1.主机故障后,无法进行写操作
2.如果主机无法恢复,需要人工指定新的主机角色
双机切换就是为解决这个问题而出现的,包括主备切花内核主从切换两种方案,简单来说就是在原有主从或主备基础上增加了切换功能
要实现一个完善的切换方案,必须考虑以下几个关键点:
1.主备间状态判断
2.切换决策
切换时机
切换策略
自动程度
3.数据冲突解决
常见架构
互联式
中介式
中介式在主备两者之外引入第三方中介,主备机之间不直接连接,而都去连接中介,并通过中介来传递状态信息,架构如下
![20190617156075511453373.png](http://pic.aipp.vip/20190617156075511453373.png)
单从架构上来看似乎比互联式更复杂了,首先要引入中介,然后要各自上报状态。然而事实上,中介式架构在状态传递和决策上却更加简单了,这是为什么呢?
1.连接管理更简单
主备机无需再建立和管理多种类型的状态传递连接通道,只要连接到中介即可,实际是降低了主备机的连接管理复杂度
2.状态决策更简单
主备机的状态决策简单了,无需考虑多种类型的连接通道获取的状态信息如何决策的问题,只需要按照下面简单算法即可完成状态决策
中介式架构在状态传递和状态决策上更加简单但不意味着这种优点是没有代价的,其代价就在于如何实现中间本身的高可用,如果中介自己宕机了,整个系统就进入双备的在状态。
MongoDB的Replica Set采用的就是这种方式,其基本架构如下
![20190617156075562269014.png](http://pic.aipp.vip/20190617156075562269014.png)
MongoDB(M)为主节点,S表示备节点,A表示仲裁节点。主备节点存储数据,仲裁节点不存储数据。客户端同时连接主节点和备节点,不连接仲裁节点
幸运的是,开源方案已经偶比较成熟的中介式解决方案,例如zookeeper和keepalived。zookeeper本身已经实现了高可用集群架构,因此帮我们解决了中介本身的可靠性问题,在工程实践中推荐基于zookeeper搭建中介式切换架构
主主
两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台进行读写操作。
![20190617156075583484458.png](http://pic.aipp.vip/20190617156075583484458.png)
特点:
1.两台都是主机,不存在切换的概念
2.客户端无需区分不同角色的主机,随便将读写操作发送给哪台主机都可以
主主架构有其独特的复杂性,具体表现在:如果采用主主复制架构,必须保证其能够双向复制,而很多数据是不能双向复制的,比如用户注册后生成的用户id或是库存信息
因此主主复制对数据的设计有严格的要求,一般适用于那些临时性、可丢失、可覆盖的数据场景,比如用户登录产生的session数据、用户想行为的日志数据、论坛的草稿数据等
思考
如果你来设计一个政府信息公开网站的信息存储系统,你会采用哪种架构?
主从
高可用存储架构:集群和分区
数据集群
1.数据集中集群
数据集中集群与主备、主从类似,也可以乘数据集中集群为1主多备货1主多从。数据集中集群数据只能往主机中写
![20190617156075782537105.png](http://pic.aipp.vip/20190617156075782537105.png)
2.数据分散集群
数据分散集群指多个服务器组成一个集群,每个服务器会负责存储的一部分;同时,为了提升硬件利用率,每台服务器又会备份一部分数据
1.均衡性
算法需要保证服务器上你的数据分区基本是均衡的
2.容错性
当出现部分服务器故障时,算法需要将原本分配给故障服务器的数据分区分配给其他服务器
3.可伸缩性
当集群容量不够,扩充新服务器后,算法能够自动将部分数据分区迁移到新服务器,并保证扩容后所有服务器的均衡性。
数据分散集群和数据集中集群的不通点在于,数据分散集群中的每台服务器都可以处理读写请求,因此不存在数据集中集群中负责写的主机那样的角色。但数据分散集群中,必须有一个角色来负责执行数据分配算法,这个角色可以是一台独立的服务器,也可以是集群自己选举出的一台服务器。
例如ES集群通过选举一台服务器来做数据分区的分配,叫做master node,其数据分区挂你架构是
![20190617156075809310030.png](http://pic.aipp.vip/20190617156075809310030.png)
数据集中集群架构中,客户端只能将数据写到主机;数据分散集群架构中,客户端可以向任意服务器中读写数据,正式因为这个关键的差异,决定了两种集群的应用场景不同。一般来说,数据集中集群适合数据量不大,集群机器不多的场景;而数据分散集群,由于其良好的可伸缩性,适合业务数据量巨大、集群机器数量庞大的业务场景。
数据分区
前面讨论的存储高可用架构都是基于硬件故障的场景去考虑和设计的,但对于一些非常大的灾难来说,有可能所有的硬件全部故障。这样就需要我们基于地理级别的故障来设计高可用架构,这就是数据分区架构产生的北京
数据分区指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障锁造成的巨大影响,受影响的也只是一部分数据,而不是全部数据不可用
分区规则
根据业务不同可以按照洲际、国家、城市分区
复制规则
常见的分区复制规则有三种: 集中式、互备式和独立式
集中式
集中式备份指存在一个总的备份中心,所有的分区都将数据备份到备份中心
![20190617156075855752600.png](http://pic.aipp.vip/20190617156075855752600.png)
独立式
指每个分区自己有独立的备份中心
![20190617156075859266639.png](http://pic.aipp.vip/20190617156075859266639.png)
业务高可用的保障: 异地多活架构
常见三种异地多活架构
1.同城异区
关键在于搭建高速网络将两个机房连接起来,达到锦泗一个本地机房的效果。
2.跨城异地
3.跨国异地
异地多活4大技巧
1.保证核心业务异地多活
2.保证核心数据最终一致性
尽量减少异地多活机房的距离,搭建高速网络
尽量减少数据同步,只同步核心业务相关的数据
3.多种手段同步数据
不要仅仅使用数据库方式同步数据,我们还有如下几种方式同步数据:
1.消息队列
对于账号数据,由于账号只会创建,不会修改和删除(假如不提供),我们可以将账号数据通过队列同步到其他业务中心
2.二次读取
有些请跨国拿下可能消息队列也延迟就,比如用户在A注册,然后访问B业务,B中心本地拿不到用户账号数据。为解决这个问题,B中心在读取本地失败时,可以根据路由规则,再去A中心访问一次
3.存储系统同步方式
4.只保证绝大部分用户的异地多活
如何应对接口级的故障
解决接口故障核心思想: 优先保证核心业务和优先保证绝大部分用户
降级
常见降级方式
1.系统后门降级
2.独立降级系统
熔断
熔断和降级是两个比较容易混淆的概念,但其内在含义是不同的,原因在于降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障
熔断机制实现的关键是需要有一个统一的API调用层,由API调用层来进行采样或统计,如果接口调用散落在代码各处就没法进行统一处理了
熔断机制实现的另一个关键是阈值的设计,例如1分钟内30%的请求响应时间超过1秒就熔断等等
限流
降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障。限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃
限流一般都是系统内实现的,常见分为两类
1.基于请求限流
基于请求限流指从外部访问的请求角度考虑限流,常见有:限制总量、限制时间量
2.基于资源限流
基于资源限流是从系统内部考虑的,常见的内部资源有: 连接数、文件句柄、线程数、请求队列等
基于资源限流相比基于请求限流更能有效反应当前系统的压力
排队
排队实际是限流的一个变种,排队是让用户等待一段时间,一般情况下排队需要用独立的系统去实现,例如使用kafka这类消息队列来缓存用户请求
![20190617156076087193299.png](http://pic.aipp.vip/20190617156076087193299.png)
可扩展架构模式
可扩展架构基本思想和模式
可扩展架构设计,基本思想是:拆
按照不同思路来拆分软件系统,就会得到不同的架构,常见拆分思路有如下三种:
1.面向流程拆分: 将整个业务流程拆分为几个阶段,每个阶段作为一部分
2.面向服务拆分: 将系统提供的服务拆分,每个服务作为一部分
3.面向功能拆分: 将系统提供的功能拆分,每个功能作为一部分
PS: 面向流程指的是数据移动的流程,而不是业务流程。分层架构的本质,就是固定的内核,移动的数据
以一个简单的学生管理系统为例
1.面向流程拆分
![20190617156076260095508.png](http://pic.aipp.vip/20190617156076260095508.png)
2.面向服务拆分
![20190617156076261918240.png](http://pic.aipp.vip/20190617156076261918240.png)
3.面向功能拆分
每个服务都可以拆分出更多细粒度的功能
![20190617156076269640754.png](http://pic.aipp.vip/20190617156076269640754.png)
不同的拆分方式,本质上决定了系统的扩展扩展方式
可扩展方式
下面是不同拆分方式对应扩展的优势
1.面向流程扩展
扩展时大部分情况只需要修改某一层,少部分可能修改关联的两层,不会出现所有层都需要修改
2.面向服务拆分
对某个服务扩展,或要增加新的服务时,只要扩展相关服务即可,无需修改所有的服务。以学生管理系统为例,如果我们要在注册服务中增加一种”学号注册“功能,则只需要修改”注册服务“和”登录服务“即可,其他服务无需修改
3.面向功能拆分
对某个功能扩展,或增加新的功能时,无需修改所有服务。
不同拆分方式,将得到不同的系统架构,典型的可扩展系统架构有
1.面向流程拆分: 分层架构
2.面向服务拆分: SOA、微服务
3.面向功能拆分: 微内核架构
传统的可扩展架构模式: 分层架构和SOA
分层架构
分层架构是很常见的架构欧式,它也叫N层架构,通常情况下,N至少是2层。例如C/S架构、B/S架构。常见的是3层架构(MVC/MVP架构)
按照分层架构进行设计时,根据不同的划分维度和对象,可以得到多种不同的分层架构
1.C/S架构、B/S架构
划分对象是整个业务系统,划分的维度是用户交互,即将和用户交互的部分独立为一层,支撑用户交互的后台作为另外一层。例如,下面是C/S架构结构图
2.MVC架构、MVP架构
划分对象是单个业务子系统,划分的维度是职责,将不同的职责划分到独立层,但各层的依赖关系比较灵活。例如,MVC架构中各层之间是亮亮交互的
![20190617156076435244041.png](http://pic.aipp.vip/20190617156076435244041.png)
3.逻辑分层架构
划分的对象可以是单个业务子系统,也可以是整个业务系统,划分的维度也是职责。虽然都是基于职责划分,但逻辑分层架构和MVC架构、MVP架构的不通电在于,逻辑分层架构中的层是自定向下依赖的。典型的有操作系统内核架构、TCP/IP架构。例如,下面是Android操作系统架构图
![20190617156076454833251.png](http://pic.aipp.vip/20190617156076454833251.png)
针对整个业务系统进程逻辑分层的架构图:
![20190617156076459862822.png](http://pic.aipp.vip/20190617156076459862822.png)
无论采用何种分层维度,分层架构核心一点是需要保证各层之间的差异足够清晰,边界足够明显,让人看到架构图后就能看懂整个架构,这个也是分层不能分太多层的原因。
分层架构之所以能够较好的支撑系统扩展,本质在于**隔离关键点**,即每个层中的组件只会处理本层的逻辑。比如,展现层只需要处理展现逻辑,业务层只需要处理业务逻辑,这样我们在扩展某层时,其他层是不受影响的,通过这种方式可以支撑系统在某块上快速扩展。例如,Linux内核如果要增加一个新的文件系统,则只需要修改文件存储即可,其他核心层无需变动
当然,并不是简单地分层就一定能够实现隔离关注点从而支撑快速扩展,分层式要保证层与层之间的依赖是稳定的,才能真正支撑快速扩展。例如,Linux内核为了支撑不同的文件系统格式,抽象了VFS文件系统接口,架构如下
![20190617156076483079761.png](http://pic.aipp.vip/20190617156076483079761.png)
如果没有VFS,只是简单的将ext2,ext3等文件系统划分为”文件系统层“,那么这个分层是达不到支撑可扩展的目的的。因为增加一个新的文件系统后,所有基于文件系统的功能都要适配新的文件系统接口;而有了VFS后,只需要VFS适配新的文件系统接口,其他基于文件系统的功能是依赖VFS的,不会受到影响
分层结构的另一个特点就是层层传递,也就是一旦分层确定,整个业务流程是按照层进行依次传递的,不能在层之间进行跳跃。最简单的C/S结构,用户必须先使用C层,然后C层再传递到S层,用户是不能直接访问S层的。传统的J2EE 4层架构,收到请求后,必须按照下面的方式传递请求
![20190617156076514896646.png](http://pic.aipp.vip/20190617156076514896646.png)
分层结构的这种约束,好处在于强制将分层依赖限定为两两依赖,降低整体系统复杂度。
例如Business Layer被Presentation Layer依赖,自己只依赖Persistence Layer。但分层架构的代价就是冗余,也就是说,不管这个业务有多么简单,每层都必须参与处理,甚至可能每层都写了一个简单的包装函数。我们以用户管理系统最简单的一个功能"查看头像"为例。查看头像功能实现很简单,只是显示一张图片而已,但是按照分层分册架构来实现,每层都要写一个简单的函数
可以看到Bufsiness Layer的AvatarBizz类的getAvatarUrl方法和Persistence Layer的AvatarDao类的getAvatarUrl方法,名称和惨呼都一模一样
既然如此,我们是否应自由选择绕过分层的束缚呢?例如”查看头像“示例中,直接让AvatarView类访问AvatarDao类,不就可以减少AvatarBizz的冗余实现了吗?
答案是不建议这么做,分层架构的优势就是通过分层强制约束两两依赖,一旦自由选择绕过分层,时间一长,架构就会变得混乱。
分层架构另一个典型的缺点就是性能,因为每一次业务请求都需要穿越所有的架构分层,有一些事情是多余的,多少都会有一些性能的浪费。现在硬件和网络的性能有了质的飞跃,其实分层模式理论上的这点性能损失,在实际应用场景中,绝大部分场景下都可以忽略
SOA
SOA全称是Service Oriented Architecture,中文是"面向服务的架构"
SOA出现的背景是企业内部的IT系统重复建设切效率地下,主要体现在:
1.企业各部门有独立的IT系统,比如人力资源系统、财务系统、销售系统,这些系统可能都设计人员管理,各IT系统都需要重复开发人员管理的功能。例如,某个员工离职后,需要分别到上述三个系统中删除员工的权限
2.各个独立的IT系统可能采购于不同的供应商,实现技术不同,企业自己也不太可能基于这些系统进行重构
3.随着业务的发展,复杂度越来越高,更多的流程和业务需要多个IT系统合作完成。由于各个独立的IT系统没有标准的实现方式,每次开发新的流程和业务,都需要协调大量的IT系统,同时定制开发,效率很低
为应对传统IT系统存在的问题,SOA提出了3个关键概念
1.服务
所有业务功能都是一项服务,服务就意味着要对外提供开放的能力,当其他系统需要使用这项功能时,无需定制化开发
服务可大可笑,例如人力资源管理可以是一项服务,包括人员基本信息、请假管理、组织管理等功能。到底是划分粗粒度的服务,还是划分为细粒度的服务,需要根据企业的实际情况进行判断
2.ESB
ESB全称是Enterprise Service Bus,中文翻译是"企业服务总线"。从名字可以看出ESB参考了计算机总线的概念。计算机中总线将各个不同的设备连接在一起,ESB将企业中各个不同的服务连接在一起。因为各个独立的服务是异构的,如果没有统一的标准,则各个异构系统对外提供的接口是各种各样的。SOA使用ESB来屏蔽异构系统对外提供各种不同的接口方式,以此达到服务间高效的互联互通。
3.松耦合
松耦合目的是减少各个服务间的依赖和互相影响
![20190617156077058950571.png](http://pic.aipp.vip/20190617156077058950571.png)
SOA架构是比较高层级的架构设计理念,一般我们可以说某个企业采用了SOA的架构来构建IT系统,但不会说某个独立的系统采用了SOA架构。
SOA解决了传统IT系统重复建设和扩展效率低的问题,但其本身也引入了更多的复杂性。SOA最广为人诟病的就是ESB,ESB需要实现与各个系统间的协议转换、数据转换、透明的动态路由等功能。例如,下图中将JSON转换为java对象
![20190617156077073342278.png](http://pic.aipp.vip/20190617156077073342278.png)
下图中ESB将REST协议转换成RMI和AMQP两个不同的协议:
![20190617156077075949987.png](http://pic.aipp.vip/20190617156077075949987.png)
ESB虽然强大,但现实中协议有很多种,如WS、HTTP、RPC等,数据格式也有很多种XML、JSON、二进制等。ESB要完成这么多协议和数据格式的相互转换,工作量和复杂度都很大,而且这种转换是需要耗费大量计算性能的,当ESB承载的消息太多时,ESB本身会成为系统的性能瓶颈
当然,SOA的ESB设计也是无奈之举,回想一下SOA的提出背景就可以发现,企业在应用SOA时
思考
为什么互联网企业很少采用SOA架构
SOA是集成的思想,是解决服务孤岛打通链条,是无奈之举。ESB集中化的管理带来了性能不佳,厚重等问题,也无法快速扩展,不适合互联网业务特点
SOA是把多个系统整合,而微服务是把单个系统拆开来,方向正好相反。
深入理解微服务架构: 银弹 or 焦油坑?
微服务是近几年非常火热的架构设计理念,微服务理念中包含了服务,SOA中也有服务的概念,微服务与SOA有什么关系?有什么区别?为何有了SOA还要提微服务?
微服务与SOA的关系
1.服务粒度
整体来说,SOA的服务粒度要粗一些,而微服务的服务粒度要细一些。例如,对一个大型企业来说,”员工管理系统“就是一个SOA架构中的服务;而如果采用微服务架构,则”员工管理系统“会被拆分为更多的服务,比如"员工信息管理",”员工考勤管理“等更多服务
2.服务通信
SOA采用ESB作为服务通信的关键,负责服务定义、服务路由、消息转换、消息传递,总体上是总量级的实现。微服务推荐使用统一的协议和格式,例如RESTFUL协议、RPC协议,无需ESB这样的重量级实现。
3.服务交付
SOA对服务的交付没有特殊要求,因为SOA更多考虑的是兼容已有的系统;微服务的架构理念要求”快速交付“,响应地要求才去自动化测试、持续集成、自动化部署等敏捷开发相关的最佳实践。
4.应用场景
SOA更加适合于庞大、复杂、异构的企业级系统。
微服务更加适合于快速、轻量级、基于web的互联网系统,这类系统业务变化快,需要快速尝试、快速交付
![20190617156077404662553.png](http://pic.aipp.vip/20190617156077404662553.png)
因此,SOA和微服务本质上是两种不同的架构设计理念,只是在"服务"这个点上有交集而已,因此两者的关系应该是上面第三种观点
微服务的陷阱
淡出你从对比来看,似乎微服务大大优于SOA,我们来看看微服务有哪些坑:
1.服务划分过细,服务间关系复杂
服务划分过细,单个服务的复杂度确实下降了,但是整个系统的复杂度却上升了,因为微服务将系统内的复杂度转移为系统间的复杂度了
![20190617156077430285519.png](http://pic.aipp.vip/20190617156077430285519.png)
2.服务数量太多,团队效率急剧下降
微服务的"微"字本身就是一个陷阱,很多团队看到微字后就想必须将服务拆分的很细,有的团队5~6个人,然而却拆分出30多个微服务,平均每个人妖维护5个以上的微服务。
无论是设计、开发、测试、部署都要工程师不停在不同服务间切换
3.调用链太长,性能下降
微服务间大多使用HTTP或RPC滴啊用,每次调用必须经过网络,一般线上接口之间的调用,平均响应时间大约50毫秒,如果用户一次请求需要经过6次微服务调用,则性能消耗就是300毫秒
4.调用链太长,问题定位困难
系统拆分为微服务后,一次用户请求需要多个微服务协同处理,任意微服务的故障都将导致整个业务失败。然而由于微服务数量较多,且故障存在扩散现象,快速定位到是哪个微服务故障是一间很复杂的事情
5.没有自动化支撑,就无法快速交付
没有自动化系统支撑,那么微服务不但达不到快速交付的目的,甚至还不如一个大而全的系统效率搞
* 没有自动化测试支撑,每次测试时都需要测试大量接口
* 没有自动化部署支撑,每次部署6~7个服务,几十台机器,运维人员敲shell命令驻台部署,手都要敲麻
* 没有自动化监控,每次故障定位都需要人工查几十台机器几百个微服务的各种状态和各种日志文件
6.没有服务治理,微服务数量多了后管理混乱
服务路由:假设某个微服务有60个结点,部署在20台机器上,那么其他依赖的微服务如何知道这个部署情况呢?
服务故障隔离: 假如60个节点中有5个发生故障了,依赖的微服务如何处理这种情况呢?
服务注册和发现: 同样是上述的例子,现在我们决定从60个节点扩容到80个节点,或者将60个结点缩减成40个节点,新增或者减少的结点如何让依赖的服务知道呢?
如果以上场景都依赖人工管理,整个系统将陷入一片混乱,最终的解决方案必须依赖自动化的服务管理系统,这时就会发现,微服务所推崇的"lightweight",最终也发展成和ESB几乎一样的复杂程度
思考
你的业务有采用微服务吗?
微服务架构最佳实践-方法篇
服务粒度
三个火枪手原则,即一个微服务三个人负责开发。当我们实施微服务架构时,根据团队规模来划分微服务数量,如果业务规模继续发展,团队规模扩大,我们再将已有微服务进行拆分。比如最初有6个人,那么业务可以划分为2个微服务,随着业务发展团队扩展到12个人,我们可以将已有的2个微服务进行拆分,变成4个微服务。
为什么是3个人,不是4个人,也不是2个呢?
首先从系统规模上来讲,3个人负责开发一个系统,系统的复杂度刚好达到每个人都能全面理解整个系统,又能进行分工的粒度。
其次,从团队管理上来说,3个人可以形成一个稳定的备份,即使1个人休假或调配到其他系统,剩余2个人还可以支撑;如果是2个人,抽调一个后剩余的1个人压力很大;如果是1个人,这就是单点了,团队没有备份,某些情况下是很危险的,加入这个人休假了,系统出问题了怎么办?
最后,从技术提升的角度来讲,3个人的技术小组既能够形成有效的套路你,又能够快速达成一致意见,如果是2个人可能会出现互相坚持自己的意见,如果是1个人,很可能陷入思维忙去导致重大问题;
"三个火枪手"原则主要应用于微服务设计和开发阶段,如果微服务经过一段时间发展后已经比较稳定,处于维护期,无需太多的开发,那么平均1个人维护1个微服务甚至几个微服务都可以。当然考虑到人员备份问题,每个微服务最好安排2个人维护,每个人都可以维护多个微服务
拆分方法
1.基于业务拆分
这是最常见的一种拆分方式,将系统中的业务模块按照职责范围识别出来,每个单独的业务模块拆分为一个独立的服务
2.基于可扩展拆分
将系统中的业务模块按照稳定性排序,将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务
这样拆分主要是为了提升项目快速迭代的效率,避免在开发的时候,不小心影响已有成熟功能导致线上问题
3.基于可靠性拆分
将系统中的业务模块按照优先级排序,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。这样拆分有以下好处:
1.避免非核心服务故障影响核心服务
2.核心服务高可用方案可以更简单
3.能够降低高可用成本
4.基于性能拆分
将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务。常见的拆分方式和具体的性能瓶颈有关,可以拆分web服务、数据库、缓存等。例如电商的抢购,性能压力最大的是入口的排队功能,可以将排队功能独立为一个服务
以上哪几种拆分方式不是多选一,而是可以根据实际情况自由组合排列,例如基于可靠性拆分出服务A,基于性能拆分出服务B,基于可靠占星拆分出CDF三个服务,加上原有服务X,一共拆分出6个服务
基础设施
大部分人主要关注的是微服务的"small"和”lightweight"特性,但实际上真正决定微服务成败的,洽洽是大部分人忽略的"automated"
![20190618156078963459951.png](http://pic.aipp.vip/20190618156078963459951.png)
微服务并不是很多人认为的那样简单有轻量级,要做好微服务,这些基础设施都是必不可少的,否则微服务就会变成一个焦油坑,让业务和团队在里面不断挣扎且无法自拔。因此也可以说,微服务并没有减少复杂度,只是将复杂度从ESB转移到了基础设施。
虽然完善微服务基础设施是一项庞大的工程,但是也不用太过回信,因为已经有开源的微服务基础设施全家桶了,比如spring cloud项目,涵盖了服务发现、服务路由、网管、配置中心等等功能;第二个如果微服务数量不是很多的话,并不是每个基础设施都是必须的,通常情况下,建议按一下优先级搭建基础设施:
1.服务发现、服务路由、服务容错: 这是最基本的微服务基础设施
2.接口框架、API网关: 主要是为了提升开发效率,接口框架是提升内部服务的开发效率,API网关是为了提升与外部服务对接的效率
3.自动化部署、自动化测试、配置中心: 主要是为了提升测试和运维效率
4.服务监控、服务跟踪、服务安全: 主要是为了进一步提升运维效率
微服务架构最佳实践-基础设施篇
自动化测试
如果每次更新都要靠人工回归整个系统,则工作量大,效率地下,达不到"快速交付"的目的,因此必须通过自动化测试系统来完成绝大部分测试回归的工作
自动化测试涵盖的范围包括代码级的单元测试,单个系统级的集成测试、系统间的接口测试,理想请求是每类测试都自动化,如果因团队规模和人力原因无法全面覆盖,至少要做到接口测试自动化
自动化部署
自动化部署系统包括版本控制、资源管理(例如机器管理、虚拟机管理)、部署操作、回退操作等功能
配置中心
配置中心包括配置版本管理(例如同样的微服务,有10个结点是给移动用户服务的,有20个结点是给联通用户服务的,配置项都一样,配置值不一样)、增删改查配置、结点管理、配置同步、配置推送等
接口框架
微服务一般采用HTTP/REST或RPC方式统一接口协议
接口框架不是一个可运行的系统,一般以库或包的形式提供给所有微服务调用。例如针对JSON样例,可以由基础技术团队提供多种不同语言的解析包(java包、Python包、C包)
API网关
系统拆分为微服务后,内部微服务之间是互联互通的,互相之间的访问都是点对点的。如果外部系统想调用系统的某个功能,也采用点对点的方式,则外部系统会非常"头大"因为外部系统看来,它不需要也没办法理解这么多微服务的职责划分和边界,它只关注它需要的能力,而不关注这个能力应该由哪个微服务提供
此外,外部系统访问系统还涉及安全和权限相关的限制,如果外部系统直接访问某个微服务,则意味着每个微服务都要自己实现安全和权限的功能
综上分析,微服务需要一个统一的API网关,负责外部系统的访问操作
API网关是外部系统访问的接口,所有外部系统接入系统都需要通过API网关,主要包括接入鉴权(是否允许接入)、权限控制(可以访问哪些功能)、传输加密、请求路由、流量控制等
服务发现
我们系统节点的变化能够及时同步到所有其他依赖的微服务,因此需要一套服务发现系统来支撑微服务的自动注册和发现
1.自理式
![20190618156087333499285.png](http://pic.aipp.vip/20190618156087333499285.png)
自理式结构就是指每个微服务自己能完成服务发现。例如,图中SERVICE INSTANCE A访问SERVICE REGISTRY获取服务注册信息,然后直接访问SERVICE INSTANCE B
自理式服务发现实现比较简单,因为这部分的功能一般通过统一的程序或程序包提供给各个微服务调用,而不会每个微服务自己来重复实现一遍;并且由于每个微服务都承担了服务发现的功能,访问压力分散到了各个微服务节点,性能和可用性不存在明显的压力和风险
例如motan中客户端发现服务后自己选择具体访问的机器节点
2.代理式
![20190618156087351322215.png](http://pic.aipp.vip/20190618156087351322215.png)
代理式结构就是指微服务之间有一个负载均衡系统,由负载均衡系统来完成微服务之间的服务发现
代理式看似很清晰,但实际是存在风险的。第一是可用性风险,一旦load balance系统故障,就会影响所有微服务间调用;第二个风险是性能风险,所有调用流量都要经过load balancer系统,这里会成为性能瓶颈
不管哪种实现,服务发现的核心就是服务注册表,注册表记录了所有的服务节点的配置和状态,每个微服务启动后都需要将自己的信息注册到服务注册表,然后由微服务或load balancer系统到服务注册表查询可用服务。
服务路由
有了服务发现后,微服务之间能够方便的获取相关配置信息。但是具体进行某次调用请求时,我们还需要从所有符合条件的可用微服务节点中挑选出一个具体的节点发起请求,这就是服务路由需要完成的工作。
服务路由通常情况下是和服务发现放一起实现的。对于自理式服务发现,服务路由是微服务内部实现的;对于代理式服务发现,服务路由是由load balancer系统实现的。无论放在那里,服务路由核心的功能就是路由算法。常见的路由算法有: 随机路由、轮训路由、最小压力路由、最小连接数路由等
服务容错
微服务具有故障扩散的特点,如果不及时处理故障,故障扩散起来就会导致看起来系统中很多服务节点都故障了,因此微服务要能够自动应对这种出错场景,及时进行处理。
常见的服务容错包括请求重试、流控和服务隔离。通常情况下,服务容错会继承在服务发现和服务路由系统中。
服务监控
服务监控作用有:
1.实时搜集信息并分析,避免故障来后在分析,减少处理时间
2.服务监控可以在实时分析的基础上进行预警,在问题萌芽的阶段发觉并预警,降低了问题影响的范围和时间
通常情况下,服务监控需要搜集并分析大量的数据,因此建议做成独立的系统,而不要继承到服务发现、API网关等系统中
服务跟踪
服务监控和服务跟踪的区别简单概括为宏观和微观的区别。例如,A服务通过HTTP协议请求B服务10次,B通过HTTP返回JSON对象,服务监控会记录请求次数、响应时间平均值、响应时间最高值、错误码分布这些信息;而服务跟踪会记录其中某次请求的发出时间、响应时间、响应错误码、请求参数、返回的JSON对象等信息
目前绝大部分请求跟踪实现技术都基于Google的Dapper论文《Dapper,a Large-Scale Distributed Sytems Tracing Infrastructure》
服务安全
系统拆分为微服务后,数据分散在各微服务节点上。从系统连接的角度来说,任意微服务都可以访问所有其他微服务节点,但从业务的家都来说,部分敏感数据或操作,只能部分微服务可以访问,而不是所有微服务都可以访问,因此需要设计服务安全机制来保障业务和数据的安全性
服务安全主要分为三部分: 接入安全、数据安全、传输安全。通常情况下,服务安全可以集成到配置中心系统中进行实现,即配置中心配置微服务的接入安全策略和数据安全策略,微服务节点从配置中心获取这些配置信息,然后在处理具体微服务调用请求时根据安全策略进行处理
![20190618156087277355487.png](http://pic.aipp.vip/20190618156087277355487.png)
微内核架构详解
微内核架构也被称为插件化架构(plugin architecture),指一种面向功能进行拆分的可扩展性架构,通常用于实现基于产品的应用,例如Eclipse这类IDE软件、UNIX这类操作系统等。
基本架构
微内核架构包含两类组件:核心系统和插件模块
核心系统负责和具体业务功能无关的通用功能,例如加载、模块间通信等;插件模块负责实现具体的业务逻辑,例如专栏前经常提到的"学生信息管理"系统中的”手机号注册“功能
微内核架构示意图如下:
![2019061915608745626233.png](http://pic.aipp.vip/2019061915608745626233.png)
这张图中core system功能稳定,不会因为业务功能而不断修改,插件模块可以根据业务功能的需要不断扩展。微内核的架构本质是将变化部分封装在插件里,从而达到快速灵活扩展的目的
设计要点
微内核核心系统设计关键技术有: 插件管理、插件连接和插件通信
1.插件管理
核心系统需要知道当前有哪些插件可用,如何加载这些插件,什么时候加载插件。常见的实现方法是插件注册表机制
核心系统提供插件注册表(可以是配置文件,也可以是代码,还可以是数据库),插件注册表含有每个插件模块的信息,包括它的名字、位置、加载时机(启动就加载还是按需加载)等
2.插件连接
插件连接指插件如何连接到核心系统。通常来说,核心系统必须定制插件和核心系统的连接规范,然后插件按照规范实现,核心系统按照规范加载即可
常见的连接机制有OSGi(Eclipse使用)、消息模式、依赖注入(Spring使用),甚至使用分布式协议也是可以的,比如RPC或HTTP WEB的方式
3.插件通信
插件通信指插件间的通信。虽然设计的时候插件间是完全解耦的,但实际业务运行过程中,必然会出现某个业务流程需要多个插件协作,这就要求两个插件间进行通信。由于插件之间没有直接联系,通信必须通过核心系统,因此核心系统必须要提供插件通信机制。这种情况和计算机类似,计算机CPU、硬盘、内存、网卡是独立设计的配件,但计算机运行过程中,CPU和内存、内存和硬盘肯定是有通信的,计算机通过主板上的总线提供了这些组件之间的通信功能。微内核的核心系统必须提供类似的通信机制,各个插件之间才能进行正常的通信
架构实战
架构师应该如何判断技术演进的方向?
互联网技术演进模式
互联网业务发展一般分为几个时期: 初创期、发展期、竞争期、成熟期
不同时期差别主要体现在两个方面: 复杂性、用户规模
业务复杂性
互联网业务发展第一个主要方向就是"业务越来越复杂"
1.初创期
初创期业务对技术要求就一个字:快
2.发展期
业务快速发展期主要目的是将原来不完善的业务逐渐完善,对绝大部分技术团队来说,这个阶段技术的核心工作就是快速实现各种需求,如何做到快,一般会经理下面几个阶段
* 堆功能期
* 优化期
随着功能越来越多,系统开始越来越复杂,后面继续堆功能会感到越来越吃力,速度越来越慢。一种典型场景是做好一个需求要改好多地方,一不小心就该出问题,直到某一天,技术团队或产品人员再也受不了这种慢速的方式,终于下定决心要解决这个问题
如何解决这个问题,一般有两派:一是优化派,二是架构派
优化派核心思想是将现有的系统优化。例如重构、分层、优化某个MySQL查询语句等。优化派可以比较快速实施;缺点是可能过不了多久,系统又撑不住了
架构拍的核心思想是调整系统架构,主要是将原来的大系统拆分为多个互相配合的小系统。例如将购物系统拆分为登录认证子系统、订单系统、查询系统、分析系统等。架构派的优势是一次调整可以支撑比较长期的业务发展,缺点是动作比较大、耗时较长、对业务的发展影响也比较大
相信在很多公司都遇到过这种情况,大部分情况都是"优化派"会赢,主要还是因为"优化"是最快的方式。至于说”优化派“支撑不了多久这个问题,其他也不用考虑太多,因为业务能够发展到哪个阶段还是个未知数
* 架构期
经过优化后,如果业务能够继续发展,慢慢就会发现优化也顶不住了。
架构期可以采用的手段很多,归根接地一个字"拆"
拆功能
拆数据库
拆服务器
3.竞争期
业务形成规模后,一定会有竞争对手开始加入行业来竞争,竞争对手加入后大家互相学习和模仿,业务更加完善,也不断有新的业务创新出来,而由于竞争的压力,对技术的要求是更上一层楼了。
新业务的创新给技术带来的典型压力就是新的系统会更多,同时,原有系统也会拆的越来越多。
系统数量的量变带来了技术工作的质变,主要体现在下面几个方面:
* 重复造轮子
* 系统交互一团乱麻
针对这个时期业务变化带来的问题,技术工作主要的解决手段有:
a.平台化
目的在于解决"重复造轮子"的问题
b.服务化
目的在于解决"系统交互"问题,常见做法是通过消息队列还完成系统间的异步通知,通过服务框架来完成系统间的同步调用
消息队列: kafka activeMQ等
服务框架: thrift、Dubbo、HSF等
4.成熟期
成熟期业务创新机会已经不大,竞争压力也没有那么吉列,此时求快求新已经没有很大空间,业务上开始转向"求精",我们的响应时间是否比竞争对手快?我们的用户体验是否比竞争对手好?我们的成本是否比竞争对手低
此时技术上也基本进入了成熟期,更多的是优化,例如将用户响应时间从200ms降低到50ms等等
用户规模
互联网发展第二个主要方向就是"用户量越来越大",其对技术的影响主要体现在两方面:性能要求越来越高,可用性要求越来越高
![20190619156095853384906.png](http://pic.aipp.vip/20190619156095853384906.png)
互联网架构模板:"存储层"技术
![20190619156095863796897.png](http://pic.aipp.vip/20190619156095863796897.png)
架构重构内功心法:
1.有的放矢
2.合纵连横
3.运筹帷幄
优先级排序
问题分类
先易后难
循序渐进
再谈开源项目: 如何选择、使用以及二次开发?
DRY: don't repeat yourself
不要重复发明轮子,但要找到合适的轮子,如何选择一个开源项目呢?
1.聚焦是否满足项目,而不是关注开源项目是否优秀
2.聚焦是否成熟
3.聚焦运维能力
再谈APP架构的演进
架构设计理念关键点:
1.架构是系统的顶层结构
2.架构设计的主要目的是解决软甲那系统复杂度带来的问题
3.架构设计需要遵循三个主要原则: 合适原则、简单原则、演化原则
4.架构设计首先要掌握业界已经成熟的各种架构模式,然后再进行优化、调整、创新
架构实战: 架构设计文档
https://time.geekbang.org/column/article/13419
架构师或技术经理
深入学习代码运行环境
深入学习

接口层

逻辑层

持久层

架构分类

业务架构——根据业务需求设计业务模块及其关系
系统架构——设计系统和子系统的模块
技术架构——决定采用的技术及框架

架构图

功能模块图、逻辑架构图、物理架构图

中间件

所有的中间件都是为了解耦