-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsample.tex
1420 lines (1105 loc) · 122 KB
/
sample.tex
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
%% 使用 njuthesis 文档类生成南京大学学位论文的示例文档
%%
%% 作者:胡海星,starfish (at) gmail (dot) com
%% 项目主页: http://haixing-hu.github.io/nju-thesis/
%%
%% 本样例文档中用到了吕琦同学的博士论文的提高和部分内容,在此对他表示感谢。
%%
\documentclass[macfonts,master]{njuthesis}
%% njuthesis 文档类的可选参数有:
%% nobackinfo 取消封二页导师签名信息。注意,按照南大的规定,是需要签名页的。
%% phd/master/bachelor 选择博士/硕士/学士论文
% 使用 blindtext 宏包自动生成章节文字
% 这仅仅是用于生成样例文档,正式论文中一般用不到该宏包
\usepackage[math]{blindtext}
\usepackage{setspace}
\usepackage{listings}
% \usepackage[utf8]{inputenc}
% \usepackage[english]{babel}
% \usepackage{minted}
% \usemintedstyle{borland}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
\definecolor{codegreen}{rgb}{0,0.5,0}
\definecolor{luared}{rgb}{0.5,0,0}
\renewcommand{\lstlistingname}{代码}
% Define a custom style
\lstdefinestyle{cppStyle}{
language=C++,
backgroundcolor=\color{white},
commentstyle=\color{codegreen},
keywordstyle=\color{blue},
basicstyle=\ttfamily\footnotesize,
stringstyle=\color{red},
rulecolor=\color{black},
frame=single,
captionpos=b,
breakatwhitespace=false,
breaklines=true,
keepspaces=true,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=4,
}
\lstdefinestyle{golangStyle}{
language=C++,
backgroundcolor=\color{white},
commentstyle=\color{gray},
keywordstyle=\color{blue},
basicstyle=\linespread{1.3}\ttfamily\footnotesize,
stringstyle=\color{codegreen},
rulecolor=\color{black},
morekeywords={go, defer, nil, type, string, int32, len, append, range, func, APIHandler, ResponseWriter, Request},
float=!htbp,
frame=single,
captionpos=b,
breakatwhitespace=false,
breaklines=true,
keepspaces=true,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=4,
}
\lstdefinestyle{luaStyle}{
language=C++,
backgroundcolor=\color{white},
commentstyle=\color{codegreen},
keywordstyle=\color{codegreen},
basicstyle=\linespread{1.2}\ttfamily\footnotesize,
stringstyle=\color{luared},
rulecolor=\color{black},
morekeywords={function, local, pairs, then, end, not, nil, in},
float=!htbp,
frame=single,
captionpos=b,
breakatwhitespace=false,
breaklines=true,
keepspaces=true,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=2,
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 设置《国家图书馆封面》的内容,仅博士论文才需要填写
% 设置论文按照《中国图书资料分类法》的分类编号
\classification{0175.2}
% 论文的密级。需按照GB/T 7156-2003标准进行设置。预定义的值包括:
% - \openlevel,表示公开级:此级别的文献可在国内外发行和交换。
% - \controllevel,表示限制级:此级别的文献内容不涉及国家秘密,但在一定时间内
% 限制其交流和使用范围。
% - \confidentiallevel,表示秘密级:此级别的文献内容涉及一般国家秘密。
% - \clasifiedlevel,表示机密级:此级别的文献内容涉及重要的国家秘密 。
% - \mostconfidentiallevel,表示绝密级:此级别的文献内容涉及最重要的国家秘密。
% 此属性可选,默认为\openlevel,即公开级。
\securitylevel{\openlevel}
% 设置论文按照《国际十进分类法UDC》的分类编号
% 该编号可在下述网址查询:http://www.udcc.org/udcsummary/php/index.php?lang=chi
\udc{004.72}
% 国家图书馆封面上的论文标题第一行,不可换行。此属性可选,默认值为通过\title设置的标题。
\nlctitlea{数据中心}
% 国家图书馆封面上的论文标题第二行,不可换行。此属性可选,默认值为空白。
\nlctitleb{网络模型研究}
% 国家图书馆封面上的论文标题第三行,不可换行。此属性可选,默认值为空白。
\nlctitlec{}
% 导师的单位名称及地址
\supervisorinfo{南京大学计算机科学与技术系~~南京市汉口路22号~~210093}
% 答辩委员会主席
\chairman{张三丰~~教授}
% 第一位评阅人
\reviewera{阳顶天~~教授}
% 第二位评阅人
\reviewerb{张无忌~~副教授}
% 第三位评阅人
\reviewerc{黄裳~~教授}
% 第四位评阅人
\reviewerd{郭靖~~研究员}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 设置论文的中文封面
% 论文标题,不可换行
\title{面向版本一致性的微服务动态更新技术研究}
% 如果论文标题过长,可以分两行,第一行用\titlea{}定义,第二行用\titleb{}定义,将上面的\title{}注释掉
% \titlea{半轻衰变$D^+\to \omega (\phi) e^+\nu_e$的研究}
% \titleb{和弱衰变$J/\psi \to D_s^{ (*) -}e^+\nu_e$的寻找}
% 论文作者姓名
\author{王东宇}
% 论文作者联系电话
\telphone{18362926576}
% 论文作者电子邮件地址
\email{[email protected]}
% 论文作者学生证号
\studentnum{MG1833071}
% 论文作者入学年份(年级)
\grade{2018}
% 导师姓名职称
\supervisor{曹春~~教授}
% 导师的联系电话
\supervisortelphone{18951679203}
% 论文作者的学科与专业方向
\major{计算机科学与技术}
% 论文作者的研究方向
\researchfield{软件方法学}
% 论文作者所在院系的中文名称
\department{计算机科学与技术系}
% 论文作者所在学校或机构的名称。此属性可选,默认值为``南京大学''。
\institute{南京大学}
% 论文的提交日期,需设置年、月、日。
\submitdate{2021年4月15日}
% 论文的答辩日期,需设置年、月、日。
\defenddate{2021年5月25日}
% 论文的定稿日期,需设置年、月、日。此属性可选,默认值为最后一次编译时的日期,精确到日。
%% \date{2013年5月1日}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 设置论文的英文封面
% 论文的英文标题,不可换行
\englishtitle{Research on Microservice Dynamic Update Technology With Version Consistency}
% 论文作者姓名的拼音
\englishauthor{Dongyu Wang}
% 导师姓名职称的英文
\englishsupervisor{Professor Chun Cao}
% 论文作者学科与专业的英文名
\englishmajor{Computer Science and Technology}
% 论文作者所在院系的英文名称
\englishdepartment{Department of Computer Science and Technology}
% 论文作者所在学校或机构的英文名称。此属性可选,默认值为``Nanjing University''。
\englishinstitute{Nanjing University}
% 论文完成日期的英文形式,它将出现在英文封面下方。需设置年、月、日。日期格式使用美国的日期
% 格式,即``Month day, year'',其中``Month''为月份的英文名全称,首字母大写;``day''为
% 该月中日期的阿拉伯数字表示;``year''为年份的四位阿拉伯数字表示。此属性可选,默认值为最后
% 一次编译时的日期。
\englishdate{May 24, 2021}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 设置论文的中文摘要
% 设置中文摘要页面的论文标题及副标题的第一行。
% 此属性可选,其默认值为使用|\title|命令所设置的论文标题
\abstracttitlea{面向版本一致性的微服务动态更新技术研究}
% 设置中文摘要页面的论文标题及副标题的第二行。
% 此属性可选,其默认值为空白
% \abstracttitleb{}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 设置论文的英文摘要
% 设置英文摘要页面的论文标题及副标题的第一行。
% 此属性可选,其默认值为使用|\englishtitle|命令所设置的论文标题
\englishabstracttitlea{Research on Microservice Dynamic Update Technology}
% 设置英文摘要页面的论文标题及副标题的第二行。
% 此属性可选,其默认值为空白
\englishabstracttitleb{With Version Consistency}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{document}
\renewcommand\labelitemi{{\boldmath$\cdot$}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 制作国家图书馆封面(博士学位论文才需要)
% \makenlctitle
% 制作中文封面
\maketitle
% 制作英文封面
\makeenglishtitle
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 开始前言部分
\frontmatter
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 论文的中文摘要
\begin{abstract}
基于微服务架构 (Microservices Architecture) 的开发可以有效地缩短开发周期,满足了软件快速持续集成和持续交付的需求,在工业界得到广泛的应用。已部署的软件通常需要不断地进行更新迭代,以达到漏洞修复、增强功能以及适应外部需求等多方面的目的。然而传统的更新方式通常需要停止系统、部署新版本后再重新启动系统。很多关键领域的系统并不能接受停机更新所导致的系统中断,因此软件动态更新技术显得尤为关键。
当前相关的微服务框架与架构对服务的动态更新支撑不足,没有考虑动态更新过程中系统的一致性。基于此,本文给出了一种面向版本一致性的微服务动态更新方法,并在具体的微服务架构上进行模型的扩展,完成了系统的设计与实现。此外,将本文的方法与实际的应用案例进行整合,完成了此方法和相关更新算法的实验评估对比,验证了其在更新过程中的安全性、及时性和低干扰性,以及对多服务同时进行更新的支持。
具体而言,本文的工作主要包括:
\begin{itemize}
\item 提出一种面向版本一致性的微服务动态更新的实现方法。此方法针对当前主流微服务框架技术在动态更新支撑方面的不足,在事务模型、动态依赖管理和服务状态模型三个方面进行了相应的扩展,为具体的动态更新过程提供运行时支持。
\item 基于上述的实现方法,在开源实现框架Istio之上完成了模型的扩展,添加系统对动态更新的支持。该系统不仅保证动态更新过程的安全性、及时性和低干扰性,而且实现的扩展模块对用户保持透明,与具体的服务业务逻辑解耦开。
\item 利用实际的应用案例,整合上述实现方法并进行实验评估。通过使用不同的更新算法,主要对动态更新过程中各个算法的安全性 (Safety) 、及时性 (Timeliness) 、干扰性 (Disruption) 进行了性能比较,具体实验中同时向多个服务发起动态更新请求,验证了本文实现方法对于多服务同时更新的可行性和有效性。
\end{itemize}
% 中文关键词。关键词之间用中文全角分号隔开,末尾无标点符号。
\keywords{微服务架构;版本一致性;动态更新}
\end{abstract}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 论文的英文摘要
\begin{englishabstract}
Development based on the microservices architecture can effectively shorten the development cycle, meet the needs of rapid continuous integration and continuous delivery of software, and is widely used in the industry. The deployed software often need to be updated continuously to achieve various purposes such as bug fixes, enhancements, and adaptation to external requirements. However, the traditional way to update a software system is to stop the system, replace it with the new version, then restart it. This is not acceptable for some mission critical systems for the interruption caused by downtime updates, so in such situations we need dynamic software update (DSU) .
Existing microservice frameworks and architectures provide limited supports for DSU, and do not take in account the system consistency during the dynamic update process. Based on this, this thesis presents a method for microservices that supports dynamic update with version consistency, extends the models on specific microservices architecture, and completes the design and implementation of the system. We have also integrated the method with the actual application case, completed the experimental evaluation and comparison with the related update algorithm, and the safety, update timeliness and low disruption during the update process , as well as support for simultaneous updates of multiple services are verified.
The work of this paper can be mainly summarized as follows:
\begin{itemize}
\item This thesis presents a method for microservices that supports dynamic update with version consistency. Aiming at the deficiencies of the existing microservice frameworks in dynamic update support, this method extends the transaction model, dynamic dependency management and service state model to provide runtime support for specific dynamic update process.
\item Based on the implementation method, the model is extended on the open source implementation framework Istio and support for dynamic update is added. The system not only ensure the safety, update timeliness and low disruption of the dynamic update process, but also the extension modules remain transparent to users and decoupled from the specific service business logic.
\item We have chosen a practical application case to integrate the implementation method and conduct experimental evaluations. Through the use of various update algorithms, the focus is put on the safety, update timeliness and disruption. In the specific experiments, dynamic update requests are initiated to multiple services at the same time, which verified the feasibility and effectiveness of the method implemented in this paper for multi services simultaneous update.
\end{itemize}
% 英文关键词。关键词之间用英文半角逗号隔开,末尾无符号。
\englishkeywords{Microservices Architecture, Version Consistency, Dynamic Update}
\end{englishabstract}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 论文的前言,应放在目录之前,中英文摘要之后
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 生成论文目次
\tableofcontents
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 生成插图清单。如无需插图清单则可注释掉下述语句。
\listoffigures
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 生成附表清单。如无需附表清单则可注释掉下述语句。
\listoftables
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 开始正文部分
\mainmatter
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 学位论文的正文应以《绪论》作为第一章
\chapter{绪言}\label{chapter_introduction}
\section{研究背景}
\subsection{微服务架构}
在软件技术的发展过程中,诞生了多样化的软件架构和技术。随着软件系统规模的扩大、功能的追加扩展,传统的单体应用 (Monolithic) 将应用程序的所有功能打包成一个独立的单元,最终将成为一个庞然大物,变得越来越复杂,逻辑耦合严重,难以理解。因此为了满足用户对于一定规模的软件的快速开发集成、提高可扩展性等要求,必须将其迁移到微服务架构中来\cite{bucchiarone2018monolithic,taibi2017processes,al2018comparative,马晓星2019软件开发方法发展回顾与展望}。微服务架构 (Microservices Architecture) 是一种基于一组独立部署运行的小型服务来构建应用的架构方法,服务间使用与语言无关的轻量级的通信协议进行通信\cite{Microservices,dragoni2017microservices}。与传统的单体应用相比,微服务架构可以帮助我们对应用进行有效地拆分,缩短软件开发周期,降低软件 维护成本,满足了软件程序对于快速持续集成和持续交付的需求\cite{balalaie2016microservices,taibi2018architectural}。由于前述的相关特性,微服务架构开始在诸多公司内部得到广泛的应用,包括Amazon\footnote{https://gigaom.com/2011/10/12/419-the-biggest-thing-amazon-got-right-the-platform/}、Netflix\footnote{https://www.nginx.com/blog/Microservices-at-netflix-architectural-best-practices/}、Spotify\footnote{https://www.infoq.com/presentations/linkedin-Microservices-urn/}等,成熟的微服务框架有Spring\footnote{https://spring.io/projects}、Dubbo\footnote{https://dubbo.apache.org/zh/}。
微服务架构的基本思想是将应用软件拆分为若干个较小的服务,不同的服务可由不同的团队使用不同的技术栈进行开发,从而达到解耦和降低复杂度的目的\cite{Microservices}。微服务架构的开发流程中的关键步骤在于对服务的拆分。微服务在进行拆分时需要遵循服务独立、数据一致、高内聚低耦合等原则,并且随着需求的迭代,服务的拆分需要持续的进行优化。
从开发过程来看,微服务架构尽量采用去中心化的管理机制,服务主要围绕应用系统的业务能力来进行开发。从软件架构来看,一个微服务架构的系统需要满足一系列的条件和原则,而不仅仅是使用了某个微服务框架即可。因此微服务架构更多地被看成一种架构风格,而不是一种具体的架构。
现有的关于微服务架构的研究较多关注于服务敏捷开发、服务治理等技术,而对于如何保证微服务在运行时正确地进行更新,相关的技术研究较少。
\subsection{软件动态更新技术}
Internet的发展推动了软件技术的快速发展,而随着软件外部环境和用户需求的不断变更,已部署的软件同样需要及时且不时地进行更新迭代,来达到修复bug、增加功能、适应需求变化等多方面的目的。传统的软件更新通常需要停止旧版本软件的运行,然后完成软件的更新,再重新进行新版本软件的部署。显然,这种更新方法会引起一定时间内的系统中断,但某些服务提供商需要为用户提供全天候不间断的服务,对于企业服务,系统的中断将会导致经济效益的损失;而在航空和医疗领域,系统的中断将可能威胁到人的生命安全\cite{neamtiu2011cloud,baresi2014consistent,elliot2014devops}。因此,此类软件系统不能接受传统的更新方式,需要在不停止软件正常处理用户请求的情况下对其进行更新,保证系统服务的持续可用,即所谓的软件动态更新\cite{kramer1990evolving}。然而,实现软件的动态更新并不简单,它在安全性\footnote{本文中的安全性和一致性均指系统在动态更新过程的前后都能正确运行}、及时性、低干扰性等多方面面临着较大的挑战。
从软件动态更新的粒度来看,基于软件的动态更新可大致分为三个层面:
\begin{itemize}
\item \textbf{业务过程层面}$\indent$借助可变过程模型建模和过程迁移技术,可以将过程模型的变化迅速反应到过程的动态执行上。
\item \textbf{程序代码层面}$\indent$在程序代码层面,对新旧版本程序进行差别分析,将旧版本程序的状态动态迁移到新版本中,并按照新版本的程序继续执行。
\item \textbf{软件服务层面}$\indent$在构成软件服务的实例层面,研究面向动态更新的服务容器等,实现软件系统的动态更新和重配置。
\end{itemize}
本文主要关注于从软件服务层面对软件进行动态更新。
\section{研究现状}
目前在工业界较为常见的微服务动态更新部署方式有:蓝绿部署\cite{fowler2010bluegreendeployment}、金丝雀部署\cite{danilo2014canaryupdatestrategies,tarvo2015canaryadvisor}和滚动发布。其中蓝绿部署利用冗余的硬件设备来加载新版本的软件服务,属于基于硬件的动态更新技术,这种方式时间和价格成本较高;另外两种则属于基于软件的动态更新技术,在旧版本实例运行时同时运行新版本服务实例,利用划分用户流量的方式,将用户请求逐渐地转发往新版本服务实例,直至将系统中所有服务实例变为新版本,完成更新。
对于基于微服务的动态更新,文献\cite{gabbrielli2016self}中实现的JRO基于指定的目标体系结构来完成微服务的自动更新部署,但局限于特定的编程语言Jolie\cite{Jolie}。文献\cite{sampaio2017supporting}描述了一个支持微服务演化的模型,该模型涵盖了微服务架构与功能相关内容,确保微服务完成重构的一致性。文献\cite{boyer2018architecture}提出了一种描述相关PaaS站点信息及其内部微服务部署信息的架构模型,运维人员通过指定更新策略来对特定的架构模型进行更新操作,最终反映到具体的服务中。这些方法实现了微服务的更新替换,但都缺乏对更新过程中运行时事务的考虑,无法保证动态更新前后系统运行的安全性。
如何选择正确的更新点来保证动态更新前后系统运行的安全性,是动态更新中至关重要的问题,一致性要求系统中的运行时事务在动态更新前后都不会出现正确性问题\cite{segal1993fly}。在这方面的研究已经存在较多的研究成果。文献\cite{kramer1990evolving,magee1996dynamic}中的CONIC系统,提出了Quiescence更新算法,该算法给出了动态更新过程中对构件的管理包括增加、删除和替换等操作,而且定义了Quiescence状态并将其作为可以执行动态更新的安全时机。但为使系统到达Quiescence状态,算法要求系统中断所有外来的业务处理直至目标更新完成,对系统的干扰极大。因此在文献\cite{vandewoude2007tranquility}中放松了对安全点的定义,提出了Tranquility动态更新算法。但对于某些具体应用,Tranquility算法虽然大大降低了更新对系统造成的干扰,它仅仅保证了事务的局部而非全局一致性,因此导致它所定义的安全点可能并不安全。综合前述两种算法在动态更新过程中的问题,文献\cite{ma2011version,baresi2016efficient}提出了Version Consistency算法,该算法利用运行时动态依赖关系来对安全点进行定义,在保证动态更新过程的安全性的同时较大程度地减少了对系统带来的干扰,在安全性和干扰性等方面取得了较好的平衡。
想要将动态更新应用于实际的微服务系统中,尚存在诸多问题。主要原因在于当前的动态更新算法通常都建立在特定的系统假设和约束条件之上,当系统的具体运行环境泛化时,相关的框架系统缺乏对动态更新的支撑。现有的微服务系统对动态更新支持的不足主要体现为:
\begin{itemize}
\item \textbf{安全性不足}$\indent$无法保证在系统中某个目标服务在动态更新过程中,正在运行时事务和将要发生的事务的一致性。
\item \textbf{及时性不足}$\indent$当收到动态更新请求后,系统需要长时间的操作才能完成,无法快速及时地完成目标服务的动态更新。
\item \textbf{干扰性过大}$\indent$系统在进行动态更新的过程中,需要长时间地中断目标服务,对目标服务造成的干扰性过大。
\end{itemize}
\section{本文工作}
随着微服务开发架构的流行,微服务系统变得多元且复杂,而现有的微服务架构并未对服务的动态更新提供相关的支持。本文针对上述动态更新技术中存在的问题,提出了一种面向版本一致性的微服务动态更新实现方法。该方法针对性在相关模型上完成了扩展与实现,为系统中多个服务的动态更新提供支持,对实际场景中不同类型的微服务系统均具有透明性。整体来看,本文的工作主要包括:
\begin{itemize}
\item 提出一种面向版本一致性的微服务动态更新的实现方法。通过分析当前主流微服务系统框架的基本功能和原理,得出现有框架技术在动态更新支撑方面的不足。针对此问题,本文提出了一种面向版本一致性的微服务动态更新的实现方法,在事务模型、动态依赖管理和服务状态模型三个方面进行了相应的扩展,为具体的动态更新过程提供运行时支持。
\item 基于上述的实现方法,在Service Mesh的开源实现框架Istio\footnote{Istio home page: https://istio.io/}之上完成了模型的扩展,添加系统对动态更新的支持。该系统不仅保证动态更新过程的安全性、及时性和低干扰性,而且实现的扩展模块对用户保持透明,与具体的服务业务逻辑解耦开。
\item 利用实际的应用案例,整合上述实现方法并进行实验评估,使用不同的更新算法,主要对动态更新过程中各个算法的安全性 (Safety) 、及时性 (Timeliness) 、干扰性 (Disruption) 进行了性能比较,具体实验中同时向多个服务发起动态更新请求,验证了本文实现方法对于多服务同时更新的可行性和有效性。
\end{itemize}
\section{本文组织}
本文内容组织如下:
第二章从软件动态更新和已有的微服务框架对动态更新的支持两个方面对相关工作进行介绍。在软件动态更新方面,主要介绍已有的动态更新算法,包括Quiescence、Tranquility和Version Consistency;然后重点介绍相关微服务框架在动态更新方面的支持,以及相关的云平台对微服务架构在动态更新方面的支持。
第三章给出一种面向版本一致性的微服务动态更新实现方法。首先针对相关微服务架构对动态更新支持方面的不足与需求进行了探讨,然后基于此讨论,在事务模型、动态依赖管理和服务状态模型三个方面的扩展进行了详细的阐述,完成模型的扩展。
第四章介绍面向版本一致性的微服务动态更新系统实现。首先,给出系统实现的设计目标和准则。然后结合上述的扩展方案,在具体的开源实现框架Istio之上对相关的模型进行了模块化开发,分别详细介绍了各个扩展模块的内部结构设计与实现,以及系统运行时的模块交互关系。
第五章案例分析与性能评估。结合具体的应用案例,通过实验来对不同的更新算法在安全性、及时性和干扰性三个方面进行性能的比较评估,此外针对实际应用的需求,添加了同时对多个服务进行更新的相关实验,并对实验结果进行分析。最后对应用进行了展示。
第六章对本文的工作进行总结,并对未来的研究工作进行展望。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\chapter{相关工作}\label{chapter_relatedwork}
本文主要研究针对分布式微服务系统的一致性动态更新,与本文的相关工作包括:软件动态更新的相关算法,以及相关微服务架构在动态更新方面的支持。下面将具体从这两个方面展开介绍。
\section{软件动态更新技术}
随着网络环境和用户需求的不断变化,运行时的软件系统无法在设计时就考虑到所有的功能,因此软件系统总是需要不断地进行动态更新演化,以满足多元的功能和性能需求\cite{王怀民2011软件服务的在线演化}。相比较与传统的离线更新方式,动态软件更新 (DSU) \cite{hicks2005dynamic}要求在软件系统不停止服务的状态下,使用新版本的软件来取代目标软件的旧版本,从而使软件系统达到增加功能或修复错误等目的。在动态更新的过程中,软件系统必须要确保程序始终处于运行状态,保证用户的请求不会被阻塞,而且需要满足一定的约束条件。因此软件系统的动态更新变得十分困难,面临诸多挑战\cite{mens2005challenges}:
\begin{itemize}
\item \textbf{安全性 (Safety) }$\indent$动态更新需要选择正确安全的更新时机,保证更新发生的过程中以及更新完成后,系统中运行的请求都不会出现不一致的情况。
\item \textbf{及时性 (Timeliness) }$\indent$系统从收到动态更新请求到更新结束所耗费的时间,要求动态更新操作应尽快完成。
\item \textbf{干扰性 (Disruption) }$\indent$系统在处理动态更新请求的状态下,相较于没有动态更新请求的状态下,对正常处理业务请求所额外造成的干扰程度,即响应时间的增加。要求动态更新算法所额外造成的干扰性应尽量减小。
\end{itemize}
动态更新的基础是保证安全性,关键在于如何确定安全的更新时间点。最基本的要求是,当目标更新服务没有处理请求,同时也不存在于任何一个用户请求的分布式调用中,那么此服务处于一个安全的更新时间点。为了更好地对相关算法进行描述,我们在此引入事务的相关概念。
\begin{definition}[事务]\label{def:transaction}
一个事务表示在某一服务上执行且在有限时间内结束的一系列操作。在某一服务上运行的事务T可以向其它服务发起调用,从而在目标服务生成一个新的事务T',前后两个事务T和T'我们分别称为父事务和子事务,记为$sub (T, T') $。由系统外部调用生成的事务称为根事务。事务T运行所在的节点记为$h_T$。
\end{definition}
\begin{definition}[分布式事务]\label{def:dis_transaction}
根事务及其所有的子事务统称为一个分布式事务,使用对应的根事务符号T进行标识。分布式事务T包括的所有扩展事务集$ext (T) $表示为:$ext (T) = \{x|x=T \vee sub (T,x) \}$
\end{definition}
\begin{definition}[动态更新的安全性]\label{def:safety}
一次动态更新满足安全性,当且仅当不存在一个分布式事务的某两个子事务在同一个微服务的不同版本上运行,即一个分布式事务只能全由一个微服务的旧版本进行处理或对应的新版本进行处理。
\end{definition}
为了解决上述动态更新所面临的挑战,现有工作已有不少的研究。本节主要介绍三种:Krammer和Magee提出了Quiescence\cite{kramer1990evolving}算法。但Quiescence算法对系统造成的干扰极大,Vandewoude等则在其基础上提出了Tranquility\cite{vandewoude2007tranquility}算法。针对Tranquility只能保证局部一致性而不是全局一致性的问题,马晓星等提出了Version Consistency\cite{ma2011version}算法,此算法通过维护服务运行时的动态依赖关系,对安全点提出了新的定义,从而较好地满足了动态更新过程中的对于安全性的要求。
\subsection{软件系统实例}\label{sec:example}
为了更好地阐述说明这些动态更新算法在实际更新时的原理,在具体介绍三种算法之前,本节先引入一个简单但不失一般性的图像拍摄转换系统,静态依赖图如\ref{fig:example}。该系统由四个服务组成,分别是Portal、Camera、Viewer和Convertor。用户可通过Portal发起一次图像拍摄请求,收到请求的Portal转而调用Camera进行具体的拍摄,拍摄所得结果经Convertor转换后返回。然后Portal请求Viewer服务,Viewer服务则进一步请求Convertor,Convertor服务负责将转换后的数据进行解码并返回。为了加快传输转换速度,Convertor负责将来自Camera的数据编码,相应的,将来自Viewer的数据解码。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.8\textwidth]{image/example.png}
\caption{示例系统静态依赖图}
\label{fig:example}
\end{figure}
系统运行的时序图如\ref{fig:timing}。由用户调用在Portal服务上首先开始根事务$T_0$,$T_0$在Camera上发起子事务并得到拍摄数据,为了能够快速传输数据,Camera请求Convertor对数据进行转换,即子事务$T_2$。Camera将得到的内部表示形式的数据返回给Portal。Portal则在Viewer上发起子事务$T_3$,并向其传输数据。Viewer在Convertor上发起新的子事务$T_4$,并将数据进一步传输,最终将解码后的数据结果返回,整个根事务$T_0$结束。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.8\textwidth]{image/timing.png}
\caption{示例系统时序图}
\label{fig:timing}
\end{figure}
在该软件系统实例中,假设希望提高数据的传输转换速度,系统需要对Convertor服务进行动态更新。若动态更新操作发生在时刻\ding{175}和\ding{177}之间,那么动态更新前的事务由旧版本的Convertor进行编码,而更新完成之后的事务由新版本的Convertor进行解码。若新旧版本的编解码算法不可兼容,便会导致系统出现不一致的情形。因此为满足安全性的需求,动态更新算法需要保证,不存在一个分布式事务的某两个子事务在同一个微服务的不同版本上运行。
\subsection{Quiescence算法}
对于一个分布式系统,通常由若干个节点组成,它们之间相互请求调用从而形成相应的依赖关系。此处节点为泛化代称,在具体的应用场景中,节点有具体的表现形态。当我们需要对此分布式系统中的某个节点执行动态更新操作时,包括节点的创建、删除等,便有可能导致系统的静态依赖关系或目标节点的内部状态发生变化,从而进一步地影响运行时事务的正确性。因此,为了保证动态更新过程中系统的安全性,Kramer和Magee\cite{kramer1990evolving}提出了Quiescence动态更新算法,算法中重点为节点定义了Active和Passive状态:
\begin{itemize}
\item \textbf{Active}$\indent$处于该状态的节点可以主动地发起事务、接受事务并为其他事务服务。
\item \textbf{Passive}$\indent$处于该状态的节点不能主动地发起新事务,可以继续接受并为其他事务提供服务,但需要保证: (1) 该节点当前不处于自身发起的事务中; (2) 该节点不会发起新的事务。
\end{itemize}
当某个节点需要执行动态更新操作时,节点的状态可以在Active和Passive之间进行转换,具体的转换关系如图\ref{fig:state_shift}。由前述的Passive状态定义可知,即使节点处于Passive状态下,执行动态更新也不能保证系统的安全性,因为此时节点有可能正在处理由其它节点所发起的事务。因此作者提出了更严格且保证安全性的约束,即Quiescence状态:
\begin{itemize}
\item 该节点当前没有参与到自身发起的事务之中。
\item 该节点不会发起新的事务。
\item 该节点当前未为其他事务提供服务。
\item 该节点无需为其他节点已经发起或者是将来会发起的事务服务。
\end{itemize}
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.6\textwidth]{image/state_shift.png}
\caption{Quiescence节点状态转移图}
\label{fig:state_shift}
\end{figure}
前述对于Quiescence状态的定义中,前两点与Passive状态定义一致,要求当前节点处于Passive状态。而后两点要求本节点不能为其他节点已经发起或者是将来会发起的事务服务,即表示依赖于本节点的其他节点也应处于Passive状态。若某一节点$Q$想要达到Quiescence状态,那么对于集合Passive Set $PS (Q) =\{Q\}\cup\{$直接依赖于$Q$的节点集合$\}$,只有$PS (Q) $中的所有节点均达到Passive状态,节点$Q$才能达到Quiescence状态。
对于$PS (Q) $的定义仅仅考虑了一层依赖关系,更为全面的定义为集合Enlarged Passive Set $EPS (Q) $:
\begin{itemize}
\item $PS (Q) $中的所有节点均属于$EPS (Q) $。
\item 所有依赖于$PS (Q) $的节点。
\end{itemize}
综上,若某一节点$Q$想要达到Quiescence状态,只有当$EPS (Q) $中所有的节点均达到Passive状态时,节点$Q$才能达到Quiescence状态。
在图\ref{fig:example} 的示例中,对应的Convertor服务需要等到Portal、Camera、Viewer服务上运行的事务全部结束后才可达到Quiescence状态。可以发现,此时的Portal、Camera、Viewer服务均处于Passive状态,不能发起事务对外提供服务。因此Quiescence算法虽然可以确保动态更新过程中的安全性,但同时给系统带来了极大的干扰\cite{arnold1996software}。
\subsection{Tranquility算法}
Quiescence算法对于安全点的定义过于严格,给运行中的系统带来了极大的干扰。针对此问题,Vandewoude等在Quiescence的基础上提出了Tranquility算法\cite{vandewoude2007tranquility},并利用该算法在DRACO\cite{vandewoude2003draco}中间件上实现了一个动态更新系统。Tranquility算法中继续沿用了Quiescence中事务、Active状态和Passive状态的相关定义,重新为节点定义了Tranquility状态。节点想要达到Tranquility状态需要满足条件:
\begin{itemize}
\item 该节点当前没有参与到自身发起的事务中。
\item 该节点不会发起新的事务。
\item 该节点当前没有处理任何事务。
\item 该节点的相邻节点不存在过去使用过此节点并且将来还将使用此节点的事务。
\end{itemize}
在Tranquility的定义中,前两点同样与Passive状态定义一致。而后两点定义均弱于Quiescence的定义,因此满足Quiescence状态的节点必然满足Tranquility状态,但反之则未必。不同点在于,Tranquility算法只要求目标节点处于Passive状态,且与其相邻的节点也处于Passive状态即可。它不需要考虑运行于其它更多节点的事务运行情况,只要相邻的节点不存在使用过目标节点且将来还会使用目标节点的事务,目标节点就可以达到Tranquility状态。
在图\ref{fig:timing} 中,当Camera将处理结果返回给Portal后,系统运行到时刻\ding{176},此时Camera、Viewer和Convertor均处于Passive状态,因此Convertor满足Tranquility状态的定义,系统判断可进行更新。但如果此时对Convertor进行版本更新,便有可能因为新版本的Convertor与旧版本存在不可兼容性而出现系统不一致的问题。因此Tranquility算法相比较于Quiescence算法,虽然可以减少对系统造成的干扰,但是无法保证动态更新的过程中系统的安全性问题。
\subsection{Version Consistency算法}
已有的一些动态更新算法或依据静态依赖信息\cite{kramer1990evolving},或依据动态依赖信息\cite{vandewoude2007tranquility,bidan1998dynamic,chen2002component}来保证本地一致性。而马晓星等提出了Version Consistency算法\cite{ma2011version}则对版本一致性的概念进行定义,用于解决了分布式系统下分布式事务的全局一致性问题。
Version Consistency算法定义的版本一致性,要求系统中运行的每一个事务都在旧版本节点上运行或者都在新版本节点上运行。以图\ref{fig:timing} 为示例,系统在时刻\ding{173}之前和时刻\ding{178}之后,对应的$ext (T_0) $中的事务均满足版本一致性的定义,因此都可以进行动态更新。但若系统选择在时刻\ding{176}对Convertor进行动态更新,此时$h_{T_2} = Convertor,h_{T_4} = Convertor^{'}$,且$T_2,T_4 \in ext (T_0) $,其中$Convertor$和$Convertor^{'}$分别表示更新前后的Convertor,显然不满足前述版本一致性的定义,此次动态更新将使得系统出现版本不一致的情况。
版本一致性虽然定义了动态更新的约束条件,但该条件在实际应用中需要全局中心化的信息,很难进行操作和检测判断。因此Version Consistency算法使用节点运行时的动态依赖关系,很好地解决了更新状态条件难以判断的问题。在Version Consistency算法中,动态依赖关系由具体的动态依赖边进行定义表示:其中$C\xrightarrow[T]{future} C^{'}$表示在根事务T中,节点$C$将来可能会用到节点$C^{'}$;$C\xrightarrow[T]{past} C^{'}$表示在根事务T中,节点$C$过去曾经使用过节点$C^{'}$。
系统在运行时,将依据每一个根事务T的运行状态,动态地在节点间执行依赖边的添加和删除操作。基于动态依赖边,Version Consistency算法提出了Freeness更新判断条件,该状态要求,不存在同时指向目标更新节点且被标识为同一个根事务的$future$边和$past$边,表示不存在当前已经调用过目标更新节点且将来还有可能会调用目标更新节点的根事务。
图\ref{fig:dyn_dep} 展示了系统中某个根事务运行时的动态依赖关系,其中$ (a) , (b) , (c) , (d) $分别对应图\ref{fig:timing} 中的时刻\ding{172}、时刻\ding{174}、时刻\ding{176}、时刻\ding{178}。可以发现,Convertor在时刻\ding{172}和时刻{178}满足Freeness,不存在标记为$T_0$的一对$future$和$past$边指向Convertor,可进行动态更新;而在时刻\ding{174}和时刻\ding{176}则同时存在标记为$T_0$的$future$和$past$边指向Convertor,因此Convertor不可以进行动态更新。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 1.0\textwidth]{image/dyn_dep.png}
\caption{系统运行时动态依赖关系示例}
\label{fig:dyn_dep}
\end{figure}
上面介绍的三种具体软件动态更新算法分别对安全更新点给出了自己的定义,即节点分别需要满足Quiescence状态、Tranquility状态和Freeness状态。判断的条件和使用的方式也略有差别,例如Quiescence算法主要利用的是事务和节点间的静态依赖信息,而Version Consistency在其基础上进一步地使用了节点间运行时刻的动态依赖信息,使得对于安全点的判断更为精准。对于前述的动态更新所面临的诸多挑战,如安全性 (Safety) 、及时性 (Timeliness) 和干扰性 (Disruption) ,三种算法的表现也不尽相同。具体对比如表\ref{table:comparison} 所示。
\begin{table}
\centering
\begin{tabular}{ccccp{20mm}}
\toprule
\textbf{更新算法} & \textbf{安全性} & \textbf{及时性} & \textbf{干扰性} \\
\midrule
Quiescence & 安全 & 慢 & 高 \\
Tranquility & 不安全 & 快 & 低 \\
Version Consistency & 安全 & 快 & 低 \\
\bottomrule
\end{tabular}
\caption{动态更新算法比较}
\label{table:comparison}
\end{table}
\section{微服务架构与动态更新}\label{sec:related_microservices}
基于微服务理念进行分布式应用程序的开发,已经成为多数平台的通用指导模式\cite{newman2015building,nadareishvili2016microservice}。应用微服务架构的关键之一在于对复杂的业务系统进行拆分,使得拆分出来的服务能够相互保持逻辑上的独立,从而保证服务可由不同的团队进行负责,快速地完成开发、部署和更新等操作。拆分步骤必然导致系统中的模块数量变多,实际上是将原先业务系统内部维护的复杂度,转换成了模块与模块之间的通信与管理监控的复杂度。
随着微服务架构的流行,相关动态更新的工作也有了较多的关注,目前已经出现了不少的研究成果。
当前工业界中与微服务动态更新相关的部署技术包括蓝绿部署\cite{fowler2010bluegreendeployment}、金丝雀部署\cite{danilo2014canaryupdatestrategies,tarvo2015canaryadvisor}和滚动发布。其中,蓝绿部署不停止老版本的运行,使用冗余的设备部署一套新版本的应用来完成测试等相关工作,完成后将流量直接切换到新版本中。蓝绿部署需要额外的资源部署一套新版本的应用,成本较高。金丝雀部署部署新版本时对用户流量在新旧版本间进行分流,然后依据用户的反馈,逐步扩大新版本的应用范围,最终完成更新。滚动发布更新时取出一个或者多个服务器停止服务执行更新,完成后重新将其投入使用。周而复始,直至集群中的所有实例均更新为最新版本,在金丝雀发布基础上完成优化改进,是一种自动化程度较高的发布方式。后两种更新方式均未对更新过程中事务的运行状态进行考虑,不可避免地会导致系统出现不一致的行为。
在Spring框架\cite{johnson2004spring}中,支持两种热部署 (Hot Deploy) 的方式,热部署主要希望在修改应用代码时,不重启停止应用即可完成代码的更新。此功能类似于动态更新 (动态配置) ,但是在具体的实现上,热部署主要基于Java的动态类加载机制,不能很好地定义安全点,无法保证应用更新过程中的正确性和一致性。例如在Spring框架中,Spring Load是一个用于在JVM运行时重新加载类文件更改的JVM代理,允许用户动态对某个方法字段等进行添加、修改、删除。当使用Spring Load启动程序后,系统会自动监视对应的文件,当目标文件发生改动时,系统便会重新加载类文件,无需重启服务。Devtools的功能类似,原理在于使用两个类加载器:不改变的类包括第三方jar,由base类加载器加载,而目标正在开发的类由restart类加载器加载。当检查到类发现变化时,应用进行重启,restart类加载器直接丢弃重建,而base类加载器则继续留用。由于base类加载器已经可用且已填充,因此本次应用的重启会比普通的重启速度要快得多。这两种热部署的实现方式均考虑的是目标文件的相关变化,但并不关心更新过程中运行时事务的相关状态,因此无法保证更新的正确性。
微服务架构具有耦合度低、独立部署扩展等特性,因此它通常在相关的分布式云服务平台上进行部署运行并使用其底层框架的能力\cite{sampaio2017supporting}。例如Spinnaker\footnote{Spinnaker. https://www.spinnaker.io/}, AWS CodeDeploy\footnote{AWS CodeDeploy. https://aws.amazon.com/codedeploy/}, IBM UrbanCode\footnote{IBM UrbanCode. https://developer.ibm.com/urbancode/}都支持对部署于异构Paas平台上的微服务进行更新部署。更新的执行主要依靠运维人员对底层操作进行编写,并形成一系列的流水线操作,最后以脚本的形式来运行。这种更新方法具有较大的局限性,一方面它要求运维人员撰写出正确且有效的运行脚本,这通常需要耗费较长的调试时间;另一方面,脚本的执行通常不具有幂等性,如果基于脚本的更新过程出现异常的话,往往需要回滚并重启整个更新过程,并小心翼翼地保存相关的状态。push2cloud\footnote{Push2Cloud website. https://push2.cloud/}则运行对更新的目标系统结构进行描述,并支持部署在其上的微服务进行更新。但目前仅支持单站点的微服务部署,更新策略同样需要定义成对应平台的底层流水线操作。
\section{本章小结}
本章主要介绍了软件动态更新的相关算法,以及现有主流的微服务框架、相关微服务架构对动态更新方面的支持。在软件动态更新的相关算法中,本章重点介绍了Quiescence、Tranquility和Version Consistency三种算法,它们分别在各自的算法中对动态更新的安全点进行定义,以及安全点的相关判断条件。同时还对这三种算法在安全性 (Safety) 、及时性 (Timeliness) 和干扰性 (Disruption) 进行了具体的比较。而在现有工业界主流的微服务动态部署技术中,对蓝绿部署、金丝雀部署和滚动发布进行了相关说明。最后介绍了Spring框架对于热部署机制的两种支持方式,以及相关分布式云服务平台对微服务架构在动态更新方面的支持。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\chapter{面向版本一致性的微服务动态更新方法}\label{chapter_3}
微服务动态更新在实际应用场景中有着较大的需求,但当前的微服务架构对更新过程中的安全性考虑不足,缺乏对服务正确且快速更新的支持。本章首先对微服务动态更新进行讨论,分析当前的微服务架构在动态更新支撑方面未解决的关键问题。然后为解决上述问题,给出一种面向版本一致性的微服务动态更新实现方法VCIM (Version Consistency In Microservices) ,针对性地在事务模型、动态依赖管理和服务状态模型三个方面提出了相应的扩展和实现方案。
\section{问题分析}
事务的概念具有多义性,在进行具体问题的分析之前,我们需要进行区分。当前相关的微服务框架所支持的事务主要指传统数据库中的ACID (Atomicity, Consistency, Isolation,
Durability) 事务,如Spring框架中的@Transactional注解,显然此类事务无法满足对于用户请求状态的相关需求,也无法在更新时提供有效的语义支持。而本文所涉及的事务概念,不同于前述的数据库中的事务概念,指的是系统接受外来请求时将在服务上执行的一系列有意义的动作,依据前述定义\ref{def:transaction},这些动作称之为事务,一次用户请求对应于一个特定的分布式事务。本文后续所提及的事务,均遵循此定义。
在微服务系统场景下,服务通过多副本来达到负载均衡、容错等目的,因此微服务系统中的动态更新将主要考虑多版本实例共存的服务更新。为方便说明,这里仍以\ref{sec:example} 节中引入的系统为例进行说明。当系统中的Convertor服务需要进行更新时,应用\ref{sec:related_microservices} 节中所介绍的相关部署技术,用户首先上线相应的新版本服务,然后进行流量的划分,此时的系统状态如图\ref{fig:normal_v1v2} 所示。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.8\textwidth]{image/normal_v1v2.png}
\caption{主流动态更新流程示例}
\label{fig:normal_v1v2}
\end{figure}
由前述的讨论可知,版本一致性指的是微服务系统中的事务版本一致性,它与动态更新的安全性定义\ref{def:safety} 保持一致,要求在一次用户请求所对应的分布式事务中,Camera服务和Viewer服务应调用同一版本的Convertor服务,但当前系统并不能为此提供保证。因此当前主流的微服务架构和相关的动态部署技术无法支持微服务正确地进行动态更新,即无法保证动态更新过程中的版本一致性等关键特征,未解决的问题主要包括:
\begin{itemize}
\item 未对请求的相关状态进行支持:动态更新安全性的定义如前述定义\ref{def:safety},系统更新前后的一致性和用户请求在系统中所调用的服务版本密切相关。在当前的微服务架构中,用户向特定的服务发起请求,微服务架构随即将请求转发往特定的目标服务中,而选择哪一版本的目标服务实例将由相关的负载均衡策略来决定。请求的无标识性使得调用过程具有无状态性,因此无法保证正确地在不同版本间进行请求的转发,从而有可能导致更新过程中系统的不一致性。
\item 未对动态更新的安全点进行考虑:前述的相关动态更新算法对具体的安全更新点进行定义,在对应的时间点可执行相关的更新及撤销操作,并且保证动态更新的安全性。而主流的微服务架构在进行服务的更新时,目标在于服务的快速迭代部署,要求新版本服务尽快完成上线,同时允许多版本服务实例共存并处理外来请求,没有对动态更新的时间点进行严格的定义,无法保证动态更新过程的安全性和及时性等特征。
\end{itemize}
% 因此微服务系统中动态更新的安全点,主要包括了何时在
% $\indent$$\enspace$新旧版本间进行请求流量的切换,以及何时对旧版本服务实例执行撤销
% $\indent$$\enspace$操作。
支持动态更新的微服务架构不仅需要有效地解决上述问题,同时还需尽量保证:
\begin{itemize}
\item 动态更新开始前,用户可以对目标更新服务进行指定,且更新前后需保证系统的安全性 (定义\ref{def:safety}) ,不会出现因更新而导致的不一致现象。
\item 能够尽快地完成动态更新,有较好的及时性,且更新过程不会对整个系统,包括其它非更新服务带来较大的干扰,保持良好的用户体验。
\item 内部的相关模块具有较低的耦合度,从而保证不会对基于此架构开发的应用造成较大的影响,可以适配实际不同应用场景下的微服务系统,具有较好的兼容性。
\end{itemize}
基于上述的讨论和分析,为使当前的微服务系统支撑具体的动态更新算法,我们将为上述问题提出相关的解决方法,并将其整合到相应的微服务架构中。
\section{方法概述}
实际应用场景下,不同的微服务系统将由不同的团队来进行开发,在通信方式、编程语言、运行环境等方面都存在较大的差异,而扩展模块方式 (如流量代理) 的实现,通过将相关的非业务功能抽取出来,与具体的微服务业务逻辑无关,保证了具体的业务逻辑与扩展模块之间的低耦合。因此,我们将基于流量代理的方式,给出一种面向版本一致性的微服务动态更新实现方法VCIM (Version Consistency In Microservices) 。此方法针对前述微服务架构在动态更新支持方面的问题,进行相关模块的扩展实现和整合,在保证版本一致性的基础上,快速有效地完成微服务的动态更新。
\subsection{请求状态支持}
% 因此,VCIM使用流量代理的方式,拦截并接管所有事务请求,同时为微服务系统定义合适的事务模型,对请求状态进行相关支持。
由于事务的相关处理逻辑与微服务实际的业务逻辑无关,因此VCIM为每一个微服务实例插入相应的流量代理,当收到请求时,流量代理负责拦截请求并提取其中的事务上下文信息,若其中不存在相关的分布式事务标识,流量代理将负责生成;否则,沿用指定的分布式事务标识。后续发生远程服务调用时,前述的事务上下文信息将随请求一同发送,被调用方的流量代理将执行相同的逻辑,从而保证了请求在系统中运行时的唯一标识性。
此外VCIM为微服务系统定义合适的事务模型,对请求状态进行相关支持。事务状态的定义如图\ref{fig:transaction_shift} 所示,具体来说,相关的状态包括:初始化、运行、依赖变更、结束。
\begin{itemize}
\item \textbf{初始化}~当服务接收到外来请求时,首先被流量代理所拦截,生成相应的事务,事务进入初始化状态。此时的事务还未被真正的服务实例处理,请求将被进一步地转发。
\item \textbf{运行}~当服务实例开始执行由流量代理转发而来的事务时,事务进入运行状态,表示事务开始执行具体的业务逻辑。
\item \textbf{依赖变更}~事务在运行时有可能对一个或多个依赖服务发起远程调用,并在其之上发起相应的子事务。当远程调用返回时,服务间的动态依赖关系便可能发生变化,记录当前服务已经使用过该依赖服务,以及当前服务在将来是否还会调用该依赖服务。处于依赖变更状态的事务仍处于运行状态,若后续还存在相关的远程调用,则此事务状态保持不变,否则,等待事务执行完成,将转入结束状态。
\item \textbf{结束}~当事务执行完成时,将结果经流量代理返回给具体的调用者。此时事务进入结束状态,表示一次事务完整的执行。
\end{itemize}
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.7\textwidth]{image/transaction_shift.png}
\caption{事务状态转移图}
\label{fig:transaction_shift}
\end{figure}
分布式事务状态的定义与事务状态的定义保持一致,不同之处在于它由其所有的子事务的状态来共同表示。这些子事务的状态在分布式服务实例的本地进行维护,不存在中心化的存储。当系统在需要相关信息时,每个服务可借助于分布式事务标识信息,向依赖于自己的相关服务发起请求,获得全局的分布式事务信息状态。
虽然VCIM从服务启动后就在不断地维护相关的事务信息,但可以看到VCIM对事务模型的定义较为精简,每个服务只需知晓和维护其本地事务的详细信息和对应的分布式事务标识,而无需在服务间进行事务上下文的同步工作,尽可能地减少对全局事务信息的维护,从而避免了服务间通信所带来的额外开销,并且达到了对相关模块减负的目的。
% 事务是服务运行时处理请求的基础,事务的状态一定程度上可以表征当前请求所处的状态,是动态更新算法判断系统状态的重要依据。借助于相关事务的上下文信息,请求在系统中运行时,系统便可以对不同的请求进行区分,通过设置相关路由规则等方式,将请求与特定版本的服务实例进行匹配,保证请求可以在多版本实例共存的情况下完成正确的转发,确保动态更新过程中系统的一致性。
\subsection{安全更新点定义}
由于在微服务系统场景下,多版本服务实例可共同对外提供服务,因此微服务动态更新所定义的安全更新点主要针对的是动态更新过程中对旧版本服务实例的撤销时机。
安全更新点的判断标准是旧版本服务实例何时不再需要处理请求。服务间的静态依赖关系可以保守地对系统运行时进行描述,但是无法保证动态更新的及时性。而基于运行时服务间的动态依赖信息,动态更新算法可以有效地对安全更新点进行判断,在保证动态更新一致性的前提下,及时地完成服务的更新。因此,VCIM通过扩展的动态依赖管理器,将维护服务间的动态依赖信息,为更新过程提供基础保障。这里的动态依赖信息将沿用文献\cite{ma2011version}中提出的动态依赖边概念。
事务运行时状态影响服务间的动态依赖关系,服务间的动态依赖关系也需要具体的事务来进行标识。VCIM扩展的依赖管理器通过对具体的程序进行分析,得到相对应的事务状态自动机,并利用此事务状态机来进行动态依赖信息的获取\cite{su2013automated}。当本地事务运行时,事务的依赖会发生相应的变化,从而反映出服务间依赖关系的变化。该事务状态自动机将分析出在当前状态下,事务曾经调用过的服务集合PAST\_SET,以及将来有可能需要调用的服务集合FUTURE\_SET,依据此信息形成动态依赖关系,并在本地进行管理。
当系统中的目标服务需要进行旧版本的撤销时,收集相关服务的动态依赖信息,同时结合本地维护的局部信息,对安全更新点进行判断:若不存在同时指向目标服务的$past$边和$future$边时,即集合PAST\_SET和集合FUTURE\_SET不存在交集时,可执行旧版本服务实例的撤销操作。下一章中将给出支持动态更新的微服务系统实现,对动态依赖管理器的实现进行详细说明。
\subsection{服务状态模型}
VCIM通过前述扩展的事务模型和动态依赖管理器,为微服务系统的动态更新提供了基础信息。此外,动态更新过程中版本切换算法和旧版本撤销算法的具体实现均需要存在相应的载体,且应该保证具有较好的抽象性,对服务的业务逻辑保持透明。因此VCIM对服务状态模型进行了扩展,一方面将相关的算法在服务状态模型中的相关扩展状态进行具体体现,另一方面很好地满足了动态更新过程中目标服务在不同状态下对于更新的需求。
由于普通的微服务通常具有无状态、即插即用等特性,因此其状态模型较为简单,无法对动态更新中的相关语义进行理解。现有的微服务系统通常仅考虑服务的基本状态,包括初始状态、运行状态、终止状态等。显然单一的运行状态并不能很好地刻画目标服务在整个更新过程中的状态。具体来说,除了相关的基本状态,运行状态的服务从收到更新请求到最后更新结束,还需包括更多拥有特定意义的状态,如更新开始状态、版本切换状态、结束撤销状态等。因此,VCIM需要对服务的运行状态进行更细粒度的扩展,同时定义相关的状态跳转关系,保证更新前后的一致性和持续性。
VCIM允许用户在执行动态更新前指定需要更新的服务,称为目标更新服务,该服务可能对应于多个具体的服务实例。此处服务状态模型的扩展主要是针对服务运行时 (Running) 状态的扩展,这里服务的状态将代表系统对外暴露的服务运行情况,是所有服务实例运行状态的总称,而不是特指某一个服务实例的运行状态。扩展后的服务状态如图\ref{fig:svc_lifecycle} 所示。可以看出,VCIM定义的服务扩展状态及其之间的跳转关系形成了一次完整的更新闭环流程,可为系统提供连续多次更新的能力。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.6\textwidth]{image/svc_lifecycle.png}
\caption{服务状态模型}
\label{fig:svc_lifecycle}
\end{figure}
在前述动态更新的过程中,VCIM仅关注目标更新服务的状态变化情况。在特定状态下,目标更新服务需要向依赖于自身的相关服务发起请求,以获得动态依赖信息,但并不对相关服务的状态进行限定。这意味着使用VCIM方法扩展的服务状态模型具有较好的隔离性,可以将不同服务间的状态解耦开,有效地提高了更新的效率。
下面将结合扩展的服务状态,对实现的版本更新算法和版本撤销算法进行介绍。
\subsubsection{版本更新}
为了保证动态更新过程中的安全性,即满足定义\ref{def:safety} 中的要求,版本更新算法除了对运行时事务进行标识,还需要对运行中的事务所涉及的服务版本进行标识。因此,我们在头信息中引入自定义字段x-version来进行具体的标识。当调用方向服务发起请求并返回时,利用流量代理模块的拦截功能,在其头信息中添加上带有x-version的服务版本信息,作为事务上下文信息进行返回。随后调用方在接收处理结果时,从头信息提取出事务上下文信息并更新本地信息,后续发起远程调用时将利用更新后的信息进行传递,从而完成了运行时事务针对服务版本的标识工作。
当服务初始启动就绪,或者某次更新完成后,服务将进入Normal状态。处于Normal状态下的服务和未扩展前正常运行状态下的服务一致,系统仅存在 (使用) 一个版本的服务实例来处理外来请求。如图\ref{fig:convertor_normal_v1} 所示,表示Convertor服务在初始版本为v1时,系统的正常运行状态。所有向Convertor服务发起调用的请求,针对于目标更新服务Convertor,都将被标识为v1版本。这里将Convertor服务作为目标更新服务,因此仅关注与Convertor服务相关的字段信息,其它非目标更新服务可类推。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.8\textwidth]{image/convertor_normal_v1.png}
\caption{初始状态下的系统}
\label{fig:convertor_normal_v1}
\end{figure}
基于运行时事务针对服务版本的正确标识,我们可进一步实现服务版本的正确更新。当用户想要对某个服务进行动态更新时,首先需要上线相应的新版本服务实例并等待其全部就绪,此时服务进入Deployed状态。此时系统中对应的服务包括新旧多个版本实例,但请求仅由旧版本的服务实例来进行处理。若此时未能确保所有新版本服务实例就绪,则后续随着服务状态的转变,请求有可能被发往未就绪的实例,进而导致异常,返回错误结果。
想要对多版本共存的微服务系统进行版本的切换,VCIM添加基于头信息的相关路由规则,进行更新的准备工作,服务进入Prepared状态。假定对应的新旧版本标识分别为v2、v1,对应系统的运行状态如图\ref{fig:convertor_prepared_v1v2} 所示,虽然系统中的v2版本服务实例已经就绪,但请求全部由v1版本的服务实例进行处理。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.9\textwidth]{image/convertor_prepared_v1v2.png}
\caption{更新准备前的系统}
\label{fig:convertor_prepared_v1v2}
\end{figure}
当前述动态更新的准备工作完成后,VCIM进行流量的切换,将原先路由规则中默认转发往旧版本修改为默认转发往新版本,服务进入Shifted状态。当存在外来请求向目标更新服务发起调用时,若该请求对应的分布式事务曾经在目标更新服务上发起过子事务,那么请求将被转发往旧版本的服务实例;否则,请求将被转发往新版本的服务实例。显然,处于此状态中的服务对于用户请求的处理满足定义\ref{def:safety} 中对于动态更新的安全性要求,对应系统的运行状态如图\ref{fig:convertor_shift_v1v2} 所示。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.9\textwidth]{image/convertor_shift_v1v2.png}
\caption{版本更新中的系统}
\label{fig:convertor_shift_v1v2}
\end{figure}
至此,版本更新算法很好地完成了目标更新服务新旧版本的切换,同时保证了动态更新过程中系统的一致性。
\subsubsection{版本撤销}
在版本更新算法的基础上,为了避免旧版本服务实例对于系统资源持续占有所造成的浪费,用户向目标更新服务发起撤销旧版本的请求,服务进入Freeing状态。
% 当收到用户的旧版本撤销请求时,要求版本撤销算法在保证用户请求不会出错的情况下,尽快完成旧版本实例的撤销,及时释放其占用的系统资源。
在正常状态下,当服务收到请求生成事务时,动态依赖管理器将负责对本地的动态依赖信息进行更新,保存已在本地完成调用的分布式事务标识集合PAST\_SET,以及将来有可能向其它服务发起调用的分布式事务标识集合FUTURE\_SET。如图\ref{fig:normal_past_set} 显示了某一运行时刻,系统中各个服务针对Convertor服务的本地动态依赖信息。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 1.0\textwidth]{image/normal_past_set_2.png}
\caption{维护动态依赖信息}
\label{fig:normal_past_set}
\end{figure}
版本撤销算法的关键在于判断:
\begin{itemize}
\item 旧版本服务实例不会再为未结束的事务提供服务。
\item 旧版本服务实例当前未执行任何事务。
\end{itemize}
% 在此状态下,应用具体的旧版本撤销算法,目标更新服务将向依赖于自己的相关服务发起请求,并获得相关的事务和动态依赖信息。同时结合本地所维护的相关信息,若所有在目标更新服务上发起过子事务的分布式事务均不会再次向目标更新服务发起请求,同时目标更新服务的所有旧版本服务实例当前都没有在执行事务。则目标更新服务满足定义\ref{def:Freeness}中的Freeness条件,旧版本服务实例可以被正确撤销。
应用文献\cite{ma2011version}中所提出的Freeness更新判断条件,利用前述的事务依赖管理器维护的PAST\_SET和FUTURE\_SET集合,实现了对应的版本撤销算法,具体包括如下步骤:
\begin{enumerate}
\item \textbf{阻塞访问}:当收到旧版本撤销请求时,系统首先阻塞所有向旧版本服务实例发起调用的新生成的分布式事务。而对于曾经在旧版本服务实例上发起过的子事务的分布式事务,则允许通过。被阻塞的请求将在调用方执行重试,依据前述版本更新算法中路由规则的定义,本次请求将会被转发往新版本的服务实例中,保证了用户请求依然能够被正常地处理,而且不会再有新的分布式事务标识添加到相关的事务依赖管理器所维护的PAST\_SET集合中,对应的系统状态如图\ref{fig:revoke_1} 所示。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 1.0\textwidth]{image/revoke_1.png}
\caption{阻塞访问}
\label{fig:revoke_1}
\end{figure}
\item \textbf{同步状态}:由前述可知,分布式事务将来是否会调用目标更新服务的状态信息,保存于系统内的各个服务实例中。目标更新服务可利用本地维护的PAST\_SET集合,向依赖于自身的相关服务发起请求,同步相关服务的事务状态信息,便可得到相应的FUTURE\_SET集合。此集合代表所有将来会向目标更新服务发起请求的分布式事务标识集合,同步过程如图\ref{fig:revoke_2} 所示,其中的红色虚线段表示具体请求过程。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 1.0\textwidth]{image/revoke_2.png}
\caption{同步状态}
\label{fig:revoke_2}
\end{figure}
\item \textbf{等待撤销}:基于前一步骤所得到的FUTURE\_SET集合,并结合实例本地事务依赖管理器所维护的相关信息,撤销算法如图\ref{fig:revoke_3} 所示,可执行进一步的判断:若接收到的FUTURE\_SET集合为空,表示针对当前所有调用过旧版本服务实例的分布式事务,均不会再向目标更新服务发起请求,因此只需等待当前旧版本实例上的事务执行结束,即可执行撤销操作,算法执行结束;否则,即表示当前系统中的分布式事务在将来仍会使用到旧版本的服务实例,此时撤销算法流程将进入等待状态,待所有FUTURE\_SET集合中的分布式事务均完成调用后,对PAST\_SET集合进行更新,并重新执行前一步骤,以重新同步相关的分布式事务状态。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 1.0\textwidth]{image/revoke_3.png}
\caption{等待撤销}
\label{fig:revoke_3}
\end{figure}
\end{enumerate}
处于Freeing状态的服务在执行完前述的版本撤销算法后,便可执行旧版本实例的撤销操作,服务进入到Updated状态。在此状态下,VCIM将对相关服务的流量代理模块进行路由规则的重配置和相关事务的标记清理工作。目标更新服务完成更新,回到Normal状态,系统状态如图\ref{fig:convertor_normal_v2} 所示,完成一次完整的动态更新闭环流程。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.8\textwidth]{image/convertor_normal_v2.png}
\caption{更新完成后系统状态}
\label{fig:convertor_normal_v2}
\end{figure}
版本撤销算法利用本地维护的相关事务依赖信息,通过相关的同步请求来收集到具体的分布式事务状态信息,正确判断服务能否被撤销,保证撤销操作的及时性,而且不会阻塞正常的业务请求,对系统的干扰性较小。
\section{本章小结}
本章主要对面向版本一致性的微服务动态更新方法VCIM进行了介绍。首先,对当前微服务架构支持动态更新所面临的关键问题进行了探讨,对其不足进行了具体的分析。然后针对相应的问题,给出对应的实现方法VCIM,有效地在事务模型、动态依赖管理和服务状态模型三个方面提出了具体的扩展方案,有效地为微服务系统提供动态更新。下一章将结合具体的框架技术,对VCIM的系统实现进行详细的阐述和说明。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\chapter{支持动态更新的微服务系统实现}\label{chapter_4}
在上一章中,VCIM具体从事务模型、动态依赖管理和服务状态模型三个方面,对支持动态更新的微服务架构进行了扩展。本章将根据第三章中提出的VCIM方法,介绍如何在开源框架Istio之上进行具体的设计,实现支持动态更新的微服务系统。
\section{系统设计概述}
虽然当前主流的微服务框架本身对用户屏蔽了绝大多数通用功能的实现细节,但开发人员同样需要耗费大量时间与精力去学习掌握框架本身。同时,开发框架通常局限于一种或几种特定的编程语言,业务服务往往得随着框架服务的升级而升级,对支持动态更新的微服务系统实现有较大的局限性。
Mogan于2017年在其文章中提出Service Mesh的概念\cite{morgan2017sa}。Service Mesh考虑使用边车模式 (Sidecar) ,为每一个服务实例部署一个对应的Sidecar实例,开发人员只需关注核心的业务逻辑代码,而由Sidecar实例来负责实现服务发现、负载均衡、认证授权等通用性功能\cite{li2019service,el2019guiding}。Service Mesh的架构图如图\ref{fig:service_mesh} 所示。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.7\textwidth]{image/service_mesh.png}
\caption{Service Mesh架构}
\label{fig:service_mesh}
\end{figure}
使用Service Mesh作为微服务的底层实现架构的优势主要体现在:
\begin{itemize}
\item Service Mesh使用流量代理的方式来对具体的微服务实例进行管理,因此相关的扩展模块可以使用相同的方式来进行实现,在保证透明性的同时,对开发人员的业务代码具有较小的侵入性。
\item Service Mesh允许开发人员选择自身偏好的编码语言,只需保证微服务间的网络可达性即可,可适配于实际应用场景下不同类型的微服务系统。
\item Service Mesh架构可保证微服务与动态更新的管理模块解耦开,两者都可独立地完成升级。
\end{itemize}
当前主流的Service Mesh实现方案是Google、IBM及Lyft合作开发的Istio开源框架\cite{sheikh2018modernize,wang2019developing}。Service Mesh架构为每一个微服务实例均部署特定的网络代理,整个系统引入了大量独立运行的单机代理服务。为了更好地保障系统的整体稳定性,Istio使用集中式的控制面板,提供了统一的管理运维入口,具体架构图如图\ref{fig:istio_arch} 所示,其中:
\begin{itemize}
\item \textbf{控制平面 (Control Plane) }~负责监听底层平台数据源,作为配置中心,将实时的配置信息动态地发往数据平面,控制整个系统的通信等各方面行为。
\item \textbf{数据平面 (Data Plane) }~自动注入的所有服务实例的流量代理总称,负责连接到控制平面并接收相关配置信息,然后对请求按配置进行处理,是配置规则的实际执行者。
\end{itemize}
\begin{figure}[!htbp]
\centering
\includegraphics[width= 1.0\textwidth]{image/istio_arch.png}
\caption{Istio架构图}
\label{fig:istio_arch}
\end{figure}
因此对VCIM的实现将基于具体的开源实现框架Istio,考虑使用与流量代理类似的方式,对事务和依赖管理进行扩展。同时,为了使用户可以方便地对系统执行动态更新等操作,在具体的应用系统之外,定义了额外的服务状态管理器和更新控制模块,为应用系统提供相应的管理功能,实现了模块间的解耦合。
% 概括来说,支持动态更新的微服务系统设计目标和准则包括:\\
% \begin{spacing}{1.0}
% \end{spacing}
% $\bullet$$\indent$兼容当前Service Mesh以及微服务的相关标准。在完成动态更新功能的支
% $\indent$$\enspace$持后,对任何正常开发部署的微服务应用都不应造成影响。
% \begin{spacing}{2.0}
% \end{spacing}
% $\bullet$$\indent$添加的相关模块应保证动态更新过程的安全性、及时性和低干扰性。即
% $\indent$$\enspace$要求动态更新过程可以高效的完成,同时不会对系统中正常业务的请求
% $\indent$$\enspace$造成过大的干扰,破坏系统的一致性。
% \begin{spacing}{2.0}
% \end{spacing}
% $\bullet$$\indent$提供相应的动态更新管理接口,允许用户对系统中服务状态进行查询,
% $\indent$$\enspace$和发起动态更新请求等操作,系统依据服务的相关状态,自动完成动
% $\indent$$\enspace$态更新操作。 \\
% \begin{spacing}{1.0}
% \end{spacing}
\section{系统扩展实现}
本节将基于上一章提出的扩展方案,对相关的扩展模块进行实现,并且为用户提供相关的动态更新管理接口。下面将详细阐述事务依赖管理器模块$TxDepManager$、服务状态管理器模块$SvcStateManager$,以及更新控制模块$UpdateController$的内部结构设计。
\subsection{事务依赖管理器}
运行时事务信息和服务间的动态依赖信息是更新算法判断服务能否被更新的基础依据。动态依赖信息需要同特定的事务相绑定才有具体的表征意义,因此系统实现时定义了事务依赖管理器模块,统一负责对运行时事务和动态依赖进行管理。
为实现VCIM中对于事务与动态依赖管理的扩展模型,我们选择在数据平面对相关模型提供支持。实现时将扩展模型打包成相关的镜像服务,在相关的文件中添加相应的配置,如图\ref{fig:injector} 所示,利用Istio框架提供的自动注入功能,在对微服务实例进行部署时,除了插入流量代理模块,还添加了对应的事务依赖管理器。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.9\textwidth]{image/injector.png}
\caption{注入模块配置}
\label{fig:injector}
\end{figure}
扩展后的某个具体的微服务实例如图\ref{fig:instance_model} 所示,其中包括了:
\begin{itemize}
\item \textbf{流量代理}~拦截并接管所有的外来请求,生成相应的本地事务并添加或继承特定的分布式事务标识。流量代理模块一方面负责将请求分发至相应的业务逻辑模块,另一方面负责对前述的事务上下文信息进行传递。
\item \textbf{事务依赖管理器}~负责事务与依赖的管理,保证在事务依赖发生变更时,对相应的服务集合PAST\_SET和FUTURE\_SET等信息进行更新保存,为VCIM提供运行时事务信息和动态依赖信息。
\item \textbf{业务逻辑}~由服务的开发人员完成编写,从流量代理模块接收请求,执行真正的业务逻辑并返回结果,再由流量代理模块将结果进一步地返回给调用者。
\end{itemize}
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.7\textwidth]{image/instance_model.png}
\caption{微服务实例模型}
\label{fig:instance_model}
\end{figure}
其中,流量代理模块和事务依赖管理器通过UDS\footnote{https://en.wikipedia.org/wiki/Unix\_domain\_socket} (Unix Domain Socket) 进行通信,UDS通过指定同一主机上的socket文件,不需要经过网络协议栈即可完成数据的传输,效率较高。片段\ref{code:uds_client} 展示了为流量代理模块添加的UDS客户端代码片段,片段\ref{code:uds_server} 则展示了事务依赖管理器作为UDS服务端的代码片段,相关的数据总是以单向的形式发送,因此不会对流量代理模块中的请求处理分发流程造成阻塞,保证了对系统具有较低的干扰性。与此同时,流量代理模块对业务逻辑保持透明,为其屏蔽了调用方的相关细节,开发人员只需关注于业务代码,可以很好地与其它模块解耦开。
\begin{lstlisting}[caption={UDS客户端}, label={code:uds_client}, style=cppStyle]
int sock_ = socket (AF_UNIX, SOCK_STREAM, 0) ;
if (sock_ < 0) {
perror ("opening stream socket") ;
exit (1) ;
}
struct sockaddr_un server_;
server_.sun_family = AF_UNIX;
std::strcpy (server_.sun_path, socket_path) ;
int len = std::strlenn (server_.sun_path) + sizeof (server_.sun_family) ;
if (connect (sock_, reinterpret_cast<struct socketaddr *>* (&server_) , len) < 0) {
close (sock_) ;
perror ("connecting stream socket") ;
}
std::string message = "ADD " + traceid + " " + spanid;
if (write (sock_, message.c_str () , std::strlen (message.c_str () ) ) < 0) {
perror ("writinng on stream socket") ;
}
close (sock_) ;
\end{lstlisting}
\begin{lstlisting}[caption={UDS服务端}, label={code:uds_server}, style=golangStyle]
listen, err := net.Listen ("unix", SockAddr)
if err != nil {
log.Fatal ("listen error : ", err)
}
if err = os.Chmod (SockAddr, 0777) ; err != nil {
log.Fatal (err)
}
defer listen.Close ()
for {
con, err := listen.Accept ()
if err != nil {
log.Fatal ("accept error : ", err)
}
go handleServer (con)
}
\end{lstlisting}
% \begin{figure}[!htbp]
% \centering
% \includegraphics[width= 0.9\textwidth]{image/uds_client.png}
% \caption{UDS客户端}
% \label{fig:uds_client}
% \end{figure}
% \begin{figure}[!htbp]
% \centering
% \includegraphics[width= 0.9\textwidth]{image/uds_server.png}
% \caption{UDS服务端}
% \label{fig:uds_server}
% \end{figure}
\subsection{服务状态管理器}
想要对运行于Istio框架之上的服务进行动态更新,需要对原先的服务状态模型进行了扩展,定义了对应的服务状态管理器,提供监听管理服务状态的基本功能。
当前Istio框架的运行环境与容器编排系统Kubernetess\footnote{Kubernetes home page: https://kubernetes.io/}相耦合,服务以容器的形式运行在其之上。因此,服务状态管理器的实现主要利用Kubernetes中提供的自定义资源CRD (Custom Resource Definition) 功能,其实现原理称为控制器模式,如图\ref{fig:reconcile_loop} 所示。用户通过声明式命令对期望状态进行描述,控制器对比期望状态与系统实际的运行状态,如果有出入则通过自定义的调谐逻辑 (Reconcile) 驱使实际状态调整为期望状态。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.8\textwidth]{image/reconcile_loop.png}
\caption{控制器模式}
\label{fig:reconcile_loop}
\end{figure}
通过对系统中服务的概念进行抽象,定义出与服务状态相匹配的自定义资源,称之为托管服务MS (Managed Service) 。托管服务是一个抽象资源,其结构如\ref{code:managed_service} 所示,每个托管服务包含了服务名称、服务的路由版本和当前状态等信息,负责对系统中的某一服务进行管理,对应服务在系统中可能存在多个版本的运行实例。当用户主动发起对托管服务的状态变更请求,如切换新版本、撤销旧版本等请求时,托管服务的控制器将监听到相应的变化,然后将变化反映到系统具体的资源中,驱使服务向目标状态进行跳转;若出现跳转过程出现异常,调谐逻辑将重复执行,直至服务的状态跳转成功。
\begin{lstlisting}[caption={托管服务结构}, label={code:managed_service}, style=golangStyle]
// ManagedServiceSpec defines the desired state of ManagedService
type ManagedServiceSpec struct {
// Name of project
ProjectName string `json:"projectName"`
// Name of service
ServiceName string `json:"serviceName"`
// Destination cluster subset for the traffic to route
RouteSubset string `json:"routeSubset"`
// Ports to be exposed
ExposePorts []int32 `json:"exposePorts,omitempty"`
// Ready to revoke obsolete subset
Phase UpdatePhase `json:"updatePhase,omitempty"`
// Whether the service is served as gateway entrance service
Gateway GatewaySpec `json:"gateway"`
}
\end{lstlisting}
% \begin{figure}[!htbp]
% \centering
% \includegraphics[width= 0.8\textwidth]{image/managed_service.png}
% \caption{托管服务结构}
% \label{fig:managed_service}
% \end{figure}
用户通过声明式命令对托管服务进行描述,托管服务的控制器对相关的资源进行更新,包括路由规则、版本信息等,而更新后的资源将由控制面板进一步分发至对应服务的流量代理中,从而达到管理服务状态的目的。托管服务资源的定义很好地与服务状态的概念完成了匹配,对外屏蔽了内部服务的细节信息,抽象出对应的服务运行状态,对内又可以很好地对各个服务实例的状态进行管理。
由前一章节对服务状态模型的讨论可知,用户对目标更新服务的动态更新操作与服务当前所处的状态存在着密不可分的关系。因此,我们将动态更新过程中的相关更新与撤销算法整合到托管服务的调谐逻辑之中,在完成动态更新中关键的版本更新和撤销操作的同时,驱使服务状态进行相应的跳转。
\subsubsection{版本更新实现}
由前述可知,当作为目标更新服务的Convertor服务处于Normal状态下时,所有向Convertor服务发起调用的请求,针对于目标更新服务Convertor都被标识为v1版本。相关流量被全部转发往v1版本,同时在其返回头信息中添加x-version: convertor-v1字段,规则示例如图\ref{fig:route_v1} 所示。
\begin{figure}[!htbp]
\centering
\includegraphics[width= 0.6\textwidth]{image/route_v1.png}
\caption{Normal状态下的路由规则}
\label{fig:route_v1}
\end{figure}
用户对新版本进行上线部署工作,服务进入Deployed状态。此时,对流量代理模块中的路由规则进行修改,如图\ref{fig:prepared_v1v2} 添加基于头信息的匹配规则,规则将从上之下逐一匹配:若本次请求头信息中的x-version字段包含特定的版本信息,则将该请求转发往特定版本,对应于规则条目\ding{172}与\ding{173};否则,默认转发往v1版本,同时在返回头信息中添加对应字段,对应规则条目\ding{174}。
% \begin{figure}[!htbp]
% \centering
% \includegraphics[width= 0.5\textwidth]{image/prepared_v1v2.png}
% \caption{Prepared状态下的路由规则}
% \label{fig:prepared_v1v2}
% \end{figure}
基于前述的准备工作,只需将路由规则中的第\ding{174}条规则对应修改,将请求默认发往v2版本即可完成版本更新工作,服务进入Shifted状态,修改后的规则如图\ref{fig:shifted_v1v2} 所示。
\begin{figure}
\centering
\begin{minipage}[t]{0.48\textwidth}
\centering
\centerline{\includegraphics[width=1.0\textwidth]{image/prepared_v1v2.png}}
\caption{Prepared状态下的路由规则}
\label{fig:prepared_v1v2}
\end{minipage}
\begin{minipage}[t]{0.48\textwidth}
\centering
\centerline{\includegraphics[width=1.0\textwidth]{image/shifted_v1v2.png}}
\caption{Shifted状态下的路由规则}
\label{fig:shifted_v1v2}
\end{minipage}
\end{figure}
% \begin{figure}[!htbp]
% \centering
% \includegraphics[width= 0.5\textwidth]{image/shifted_v1v2.png}
% \caption{Shifted状态下的路由规则}
% \label{fig:shifted_v1v2}
% \end{figure}
考虑对于任意一个分布式事务T,在规则生效之后,向Convertor服务发起请求,将包括如下几种情况:
\ding{172}若分布式事务T在运行的过程中还未在Convertor服务上发起过子事务,则其请求头信息中将不包含与Convertor服务相关的x-version字段。依据生效后的路由规则,此分布式事务T对于Convertor服务的请求,将被转发往新版本;
\ding{173}若分布式事务T在运行的过程中曾经在Convertor服务上发起过子事务,则由前述规则可知,其请求头信息中必然包含了与Convertor服务相关的x-version字段。依据生效后的路由规则,此分布式事务T对于Convertor服务的请求,将被转发往与x-version字段具有相同标识的版本实例中;
综上,保证了任一分布式事务T在运行过程中,不会在目标更新服务Convertor的不同版本实例上发起子事务,既满足了版本更新过程对于安全性的要求,同时也使得新生成的分布式事务可以较快地使用到新版本的服务。
\subsubsection{版本撤销实现}
依据版本更新算法的最新路由规则,新生成的分布式事务将被转发往新版本的服务实例,而已经调用过Convertor服务的事务会在有限时间内完成,因此旧版本服务实例会在有限时间内达到安全更新点,即不再需要执行事务,因此可被正确地执行撤销操作。具体实现包括:
\begin{enumerate}
\item \textbf{阻塞访问}:当收到旧版本撤销请求时,为旧版本服务实例的流量代理模块添加相应的过滤器,过滤器使用特定的Lua语言进行编写,相应的代码片段展示于\ref{code:lua} 中。此过滤器将放行曾经在旧版本服务实例上发起过的子事务的请求,其头信息中的x-version字段存在特定标识信息;其余向旧版本服务实例发起调用的请求将直接返回错误。调用方将依据返回的错误码执行重试操作,依据最新的路由规则,请求便会被转发往新版本的服务实例中。
\begin{lstlisting}[caption={Lua过滤器}, label={code:lua}, style=luaStyle]
function envoy_on_request (request_handle)
local found = false
local headers = request_handle:headers ()
for key, value in pairs (headers) do
if (key == "x-version" and value == "convertor-v1") then
found = true
break
end
end
if not (found) then
request_handle:respond ({[":status"] = "503"}, nil)
end
end
\end{lstlisting}
% \begin{figure}[!htbp]
% \centering
% \includegraphics[width= 0.9\textwidth]{image/lua.png}
% \caption{过滤器代码片段}
% \label{fig:lua}
% \end{figure}
\item \textbf{同步状态}:为同步相关服务的事务状态信息,目标更新服务将以协程的方式,同时向依赖于自身的相关服务实例发起请求。当返回结果不为空时,利用互斥锁更新相应的FUTURE\_SET集合,确保多个请求同时操作同一集合的正确性,发送请求与锁机制的实现代码片段如\ref{code:collect_future} 所示。此集合代表所有将来会向目标更新服务发起请求的分布式事务标识集合。
\begin{lstlisting}[caption={同步状态实现}, label={code:collect_future}, style=golangStyle]
// Send request
client := &http.Client{}
resp, _ := client.Do (req)
defer resp.Body.Close ()
futureList := []string{}
if err = json.NewDecoder (resp.Body) .Decode (&futrueList) ; err != nil {
log.Printf ("Fail to decode response body : %v\n", err)
errChan<-1
wg.Done ()
return
}
log.Printf ("Receive future list from %s : %v\n", remoteUrl, futureList)
if len (futureList) != 0 {
// Lock
traceIdSet.RWLock.Lock ()
defer traceIdSet.RWLock.Unlock ()