-
Notifications
You must be signed in to change notification settings - Fork 5
/
apollo.txt
2338 lines (1882 loc) · 106 KB
/
apollo.txt
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
《Apollo 源码解析 —— 调试环境搭建》
《Apollo 源码解析 —— Portal 创建 App》
《Apollo 源码解析 —— Portal 创建 Cluster》
《Apollo 源码解析 —— Portal 创建 Namespace》
《Apollo 源码解析 —— Portal 关联 Namespace》
《Apollo 源码解析 —— Portal 创建 Item》
《Apollo 源码解析 —— Portal 批量变更 Item》
《Apollo 源码解析 —— Admin Service 锁定 Namespace》
《Apollo 源码解析 —— Portal 发布配置》
《Apollo 源码解析 —— Admin Service 发送 ReleaseMessage》
《Apollo 源码解析 —— Config Service 通知配置变化》
《Apollo 源码解析 —— Config Service 配置读取接口》
《Apollo 源码解析 —— Client 轮询配置》
《Apollo 源码解析 —— Config Service 记录 Instance》
《Apollo 源码解析 —— Portal 创建灰度》
《Apollo 源码解析 —— Portal 配置灰度规则》
《Apollo 源码解析 —— Portal 灰度发布》
《Apollo 源码解析 —— Portal 灰度全量发布》
《Apollo 源码解析 —— 服务自身配置 ServerConfig》
《Apollo 源码解析 —— Config Service 操作审计日志 Audit》
《Apollo 源码解析 —— Portal 认证与授权(一)之认证》
《Apollo 源码解析 —— Portal 认证与授权(二)之授权》
《Apollo 源码解析 —— OpenAPI 认证与授权(一)之认证》
《Apollo 源码解析 —— OpenAPI 认证与授权(二)之授权》
《Apollo 源码解析 —— 服务的注册与发现》
《Apollo 源码解析 —— 客户端 API 配置(一)之一览》
《Apollo 源码解析 —— 客户端 API 配置(二)之 Config》
《Apollo 源码解析 —— 客户端 API 配置(三)之 ConfigFile》
《Apollo 源码解析 —— 客户端 API 配置(四)之 ConfigRepository》
《Apollo 源码解析 —— 客户端配置 Spring 集成(一)之 XML 配置》
《Apollo 源码解析 —— 客户端配置 Spring 集成(二)之注解配置》
《Apollo 源码解析 —— 客户端配置 Spring 集成(三)之外部化配置》
1. 概述
2. App
2.1 BaseEntity
2.2 为什么需要同步
3. Portal 侧
3.1 AppController
3.2 AppService
3.3 AppRepository
3.4 AppCreationEvent
3.5 AdminServiceAPI
4. Admin Service 侧
4.1 AppController
4.2 AdminService
4.3 AppService
4.4 AppRepository
666. 彩蛋
本文分享 Portal 创建 App 的流程,整个过程涉及 Portal、Admin Service ,如下图所示:
下面,我们先来看看 App 的实体结构
老艿艿:因为 Portal 是管理后台,所以从代码实现上,和业务系统非常相像。也因此,本文会略显啰嗦。
2. App
在 apollo-common 项目中, com.ctrip.framework.apollo.common.entity.App ,继承 BaseEntity 抽象类,应用信息实体。代码如下:
@Entity
@Table(name = "App")
@SQLDelete(sql = "Update App set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class App extends BaseEntity {
@Column(name = "Name", nullable = false)
private String name;
@Column(name = "AppId", nullable = false)
private String appId;
@Column(name = "OrgId", nullable = false)
private String orgId;
@Column(name = "OrgName", nullable = false)
private String orgName;
@Column(name = "OwnerName", nullable = false)
private String ownerName;
@Column(name = "OwnerEmail", nullable = false)
private String ownerEmail;
}
ORM 选用 Hibernate 框架。
@SQLDelete(...) + @Where(...) 注解,配合 BaseEntity.extends 字段,实现 App 的逻辑删除。
字段比较简单,胖友看下注释。
2.1 BaseEntity
com.ctrip.framework.apollo.common.entity.BaseEntity ,基础实体抽象类。代码如下:
@MappedSuperclass
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity {
@Id
@GeneratedValue
@Column(name = "Id")
private long id;
@Column(name = "IsDeleted", columnDefinition = "Bit default '0'")
protected boolean isDeleted = false;
@Column(name = "DataChange_CreatedBy", nullable = false)
private String dataChangeCreatedBy;
@Column(name = "DataChange_CreatedTime", nullable = false)
private Date dataChangeCreatedTime;
@Column(name = "DataChange_LastModifiedBy")
private String dataChangeLastModifiedBy;
@Column(name = "DataChange_LastTime")
private Date dataChangeLastModifiedTime;
@PrePersist
protected void prePersist() {
if (this.dataChangeCreatedTime == null) dataChangeCreatedTime = new Date();
if (this.dataChangeLastModifiedTime == null) dataChangeLastModifiedTime = new Date();
}
@PreUpdate
protected void preUpdate() {
this.dataChangeLastModifiedTime = new Date();
}
@PreRemove
protected void preRemove() {
this.dataChangeLastModifiedTime = new Date();
}
// ... 省略 setting / getting 方法
}
@MappedSuperclass 注解,见 《Hibernate 中 @MappedSuperclass 注解的使用说明》 文章。
@Inheritance(...) 注解,见 《Hibernate(11)映射继承关系二之每个类对应一张表(@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)》 文章。
id 字段,编号,Long 型,全局自增。
isDeleted 字段,是否删除,用于逻辑删除的功能。
dataChangeCreatedBy 和 dataChangeCreatedTime 字段,实现数据的创建人和时间的记录,方便追踪。
dataChangeLastModifiedBy 和 dataChangeLastModifiedTime 字段,实现数据的更新人和时间的记录,方便追踪。
@PrePersist、@PreUpdate、@PreRemove 注解,CRD 操作前,设置对应的时间字段。
在 Apollo 中,所有实体都会继承 BaseEntity ,实现公用字段的统一定义。这种设计值得借鉴,特别是创建时间和更新时间这两个字段,特别适合线上追踪问题和数据同步。
2.2 为什么需要同步
在文初的流程图中,我们看到 App 创建时,在 Portal Service 存储完成后,会异步同步到 Admin Service 中,这是为什么呢?
在 Apollo 的架构中,一个环境( Env ) 对应一套 Admin Service 和 Config Service 。
而 Portal Service 会管理所有环境( Env ) 。因此,每次创建 App 后,需要进行同步。
或者说,App 在 Portal Service 中,表示需要管理的 App 。而在 Admin Service 和 Config Service 中,表示存在的 App 。
3. Portal 侧
3.1 AppController
在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.controller.AppController ,提供 App 的 API 。
在创建项目的界面中,点击【提交】按钮,调用创建 App 的 API 。
创建项目
代码如下:
1: @RestController
2: @RequestMapping("/apps")
3: public class AppController {
4:
5: @Autowired
6: private UserInfoHolder userInfoHolder;
7: @Autowired
8: private AppService appService;
9:
12: @Autowired
13: private ApplicationEventPublisher publisher;
14: @Autowired
15: private RolePermissionService rolePermissionService;
16:
17:
23: @RequestMapping(value = "", method = RequestMethod.POST)
24: public App create(@RequestBody AppModel appModel) {
25: // 将 AppModel 转换成 App 对象
26: App app = transformToApp(appModel);
27: // 保存 App 对象到数据库
28: App createdApp = appService.createAppInLocal(app);
29: // 发布 AppCreationEvent 创建事件
30: publisher.publishEvent(new AppCreationEvent(createdApp));
31: // 授予 App 管理员的角色
32: Set<String> admins = appModel.getAdmins();
33: if (!CollectionUtils.isEmpty(admins)) {
34: rolePermissionService.assignRoleToUsers(RoleUtils.buildAppMasterRoleName(createdApp.getAppId()),
35: admins, userInfoHolder.getUser().getUserId());
36: }
37: // 返回 App 对象
38: return createdApp;
39: }
40:
41: // ... 省略其他接口和属性
42: }
POST apps 接口,Request Body 传递 JSON 对象。
com.ctrip.framework.apollo.portal.entity.model.AppModel ,App Model 。在 com.ctrip.framework.apollo.portal.entity.model 包下,负责接收来自 Portal 界面的复杂请求对象。例如,AppModel 一方面带有创建 App 对象需要的属性,另外也带有需要授权管理员的编号集合 admins ,即存在跨模块的情况。
第 26 行:调用 #transformToApp(AppModel) 方法,将 AppModel 转换成 App 对象。🙂 转换方法很简单,点击方法,直接查看。
第 28 行:调用 AppService#createAppInLocal(App) 方法,保存 App 对象到 Portal DB 数据库。在 「3.2 AppService」 中,详细解析。
第 30 行:调用 ApplicationEventPublisher#publishEvent(AppCreationEvent) 方法,发布 com.ctrip.framework.apollo.portal.listener.AppCreationEvent 事件。
第 31 至 36 行:授予 App 管理员的角色。详细解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。
第 38 行:返回创建的 App 对象。
3.2 AppService
在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.service.AppService ,提供 App 的 Service 逻辑。
#createAppInLocal(App) 方法,保存 App 对象到 Portal DB 数据库。代码如下:
1: @Autowired
2: private UserInfoHolder userInfoHolder;
3: @Autowired
4: private AppRepository appRepository;
5: @Autowired
6: private AppNamespaceService appNamespaceService;
7: @Autowired
8: private RoleInitializationService roleInitializationService;
9: @Autowired
10: private UserService userService;
11:
12: @Transactional
13: public App createAppInLocal(App app) {
14: String appId = app.getAppId();
15: // 判断 `appId` 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。
16: App managedApp = appRepository.findByAppId(appId);
17: if (managedApp != null) {
18: throw new BadRequestException(String.format("App already exists. AppId = %s", appId));
19: }
20: // 获得 UserInfo 对象。若不存在,抛出 BadRequestException 异常
21: UserInfo owner = userService.findByUserId(app.getOwnerName());
22: if (owner == null) {
23: throw new BadRequestException("Application's owner not exist.");
24: }
25: app.setOwnerEmail(owner.getEmail()); // Email
26: // 设置 App 的创建和修改人
27: String operator = userInfoHolder.getUser().getUserId();
28: app.setDataChangeCreatedBy(operator);
29: app.setDataChangeLastModifiedBy(operator);
30: // 保存 App 对象到数据库
31: App createdApp = appRepository.save(app);
32: // 创建 App 的默认命名空间 "application"
33: appNamespaceService.createDefaultAppNamespace(appId);
34: // 初始化 App 角色
35: roleInitializationService.initAppRoles(createdApp);
36: // 【TODO 6001】Tracer 日志
37: Tracer.logEvent(TracerEventType.CREATE_APP, appId);
38: return createdApp;
39: }
第 15 至 19 行:调用 AppRepository#findByAppId(appId) 方法,判断 appId 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。
第 20 至 25 行:调用 UserService#findByUserId(userId) 方法,获得 com.ctrip.framework.apollo.portal.entity.bo.UserInfo 对象。com.ctrip.framework.apollo.portal.entity.bo 包下,负责返回 Service 的业务对象。例如,UserInfo 只包含 com.ctrip.framework.apollo.portal.entity.po.UserPO 的部分属性:userId、username、email 。
第 27 至 29 行:调用 UserInfoHolder#getUser()#getUserId() 方法,获得当前登录用户,并设置为 App 的创建和修改人。关于 UserInfoHolder ,后续文章,详细分享。
第 31 行:调用 AppRepository#save(App) 方法,保存 App 对象到数据库中。
第 33 行:调用 AppNameSpaceService#createDefaultAppNamespace(appId) 方法,创建 App 的默认 Namespace (命名空间) "application" 。对于每个 App ,都会有一个默认 Namespace 。具体的代码实现,我们在 《Apollo 源码解析 —— Portal 创建 Namespace》
第 35 行:初始化 App 角色。详解解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。
第 37 行:【TODO 6001】Tracer 日志
3.3 AppRepository
在 apollo-portal 项目中,com.ctrip.framework.apollo.common.entity.App.AppRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 App 的数据访问,即 DAO 。
代码如下:
public interface AppRepository extends PagingAndSortingRepository<App, Long> {
App findByAppId(String appId);
List<App> findByOwnerName(String ownerName, Pageable page);
List<App> findByAppIdIn(Set<String> appIds);
}
基于 Spring Data JPA 框架,使用 Hibernate 实现。详细参见 《Spring Data JPA、Hibernate、JPA 三者之间的关系》 文章。
🙂 不熟悉 Spring Data JPA 的胖友,可以看下 《Spring Data JPA 介绍和使用》 文章。
3.4 AppCreationEvent
com.ctrip.framework.apollo.portal.listener.AppCreationEvent ,实现 org.springframework.context.ApplicationEvent 抽象类,App 创建事件。
代码如下:
public class AppCreationEvent extends ApplicationEvent {
public AppCreationEvent(Object source) {
super(source);
}
public App getApp() {
Preconditions.checkState(source != null);
return (App) this.source;
}
}
构造方法,将 App 对象作为方法参数传入。
#getApp() 方法,获得事件对应的 App 对象。
3.4.1 CreationListener
com.ctrip.framework.apollo.portal.listener.CreationListener ,对象创建监听器,目前监听 AppCreationEvent 和 AppNamespaceCreationEvent 事件。
我们以 AppCreationEvent 举例子,代码如下:
1: @Autowired
2: private PortalSettings portalSettings;
3: @Autowired
4: private AdminServiceAPI.AppAPI appAPI;
5:
6: @EventListener
7: public void onAppCreationEvent(AppCreationEvent event) {
8: // 将 App 转成 AppDTO 对象
9: AppDTO appDTO = BeanUtils.transfrom(AppDTO.class, event.getApp());
10: // 获得有效的 Env 数组
11: List<Env> envs = portalSettings.getActiveEnvs();
12: // 循环 Env 数组,调用对应的 Admin Service 的 API ,创建 App 对象。
13: for (Env env : envs) {
14: try {
15: appAPI.createApp(env, appDTO);
16: } catch (Throwable e) {
17: logger.error("Create app failed. appId = {}, env = {})", appDTO.getAppId(), env, e);
18: Tracer.logError(String.format("Create app failed. appId = %s, env = %s", appDTO.getAppId(), env), e);
19: }
20: }
21: }
@EventListener 注解 + 方法参数,表示 #onAppCreationEvent(...) 方法,监听 AppCreationEvent 事件。不了解的胖友,可以看下 《Spring 4.2框架中注释驱动的事件监听器详解》 文章。
第 9 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将 App 转换成 com.ctrip.framework.apollo.common.dto.AppDTO 对象。com.ctrip.framework.apollo.common.dto 包下,提供 Controller 和 Service 层的数据传输。😈 笔者思考了下,Apollo 中,Model 和 DTO 对象很类似,差异点在 Model 更侧重 UI 界面提交“复杂”业务请求。另外 Apollo 中,还有 VO 对象,侧重 UI 界面返回复杂业务响应。整理如下图:各种 Entity 整理
老艿艿认为,PO 对象,可以考虑不暴露给 Controller 层,只在 Service 和 Repository 之间传递和返回。
和彩笔老徐交流了下,实际项目可以简化,使用 VO + DTO + PO 。
第 11 行:调用 PortalSettings#getActiveEnvs() 方法,获得有效的 Env 数组,例如 PROD UAT 等。后续文章,详细分享该方法。
第 12 至 20 行:循环 Env 数组,调用 AppAPI#createApp(Env, AppDTO) 方法,调用对应的 Admin Service 的 API ,创建 App 对象,从而同步 App 到 Config DB。
3.5 AdminServiceAPI
com.ctrip.framework.apollo.portal.api.AdminServiceAPI ,Admin Service API 集合,包含 Admin Service 所有模块 API 的调用封装。简化代码如下:
代码
3.5.1 API
com.ctrip.framework.apollo.portal.api.API ,API 抽象类。代码如下:
public abstract class API {
@Autowired
protected RetryableRestTemplate restTemplate;
}
提供统一的 restTemplate 的属性注入。对于 RetryableRestTemplate 的源码实现,我们放到后续文章分享。
3.5.2 AppAPI
com.ctrip.framework.apollo.portal.api.AdminServiceAPI.AppAPI ,实现 API 抽象类,封装对 Admin Service 的 App 模块的 API 调用。代码如下:
@Service
public static class AppAPI extends API {
public AppDTO loadApp(Env env, String appId) {
return restTemplate.get(env, "apps/{appId}", AppDTO.class, appId);
}
public AppDTO createApp(Env env, AppDTO app) {
return restTemplate.post(env, "apps", app, AppDTO.class);
}
public void updateApp(Env env, AppDTO app) {
restTemplate.put(env, "apps/{appId}", app, app.getAppId());
}
}
使用 restTemplate ,调用对应的 API 接口。
4. Admin Service 侧
4.1 AppController
在 apollo-adminservice 项目中, com.ctrip.framework.apollo.adminservice.controller.AppController ,提供 App 的 API 。
#create(AppDTO) 方法,创建 App 。代码如下:
1: @RestController
2: public class AppController {
3:
4: @Autowired
5: private AppService appService;
6: @Autowired
7: private AdminService adminService;
8:
9:
15: @RequestMapping(path = "/apps", method = RequestMethod.POST)
16: public AppDTO create(@RequestBody AppDTO dto) {
17: // 校验 appId 格式。若不合法,抛出 BadRequestException 异常
18: if (!InputValidator.isValidClusterNamespace(dto.getAppId())) {
19: throw new BadRequestException(String.format("AppId格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
20: }
21: // 将 AppDTO 转换成 App 对象
22: App entity = BeanUtils.transfrom(App.class, dto);
23: // 判断 `appId` 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。
24: App managedEntity = appService.findOne(entity.getAppId());
25: if (managedEntity != null) {
26: throw new BadRequestException("app already exist.");
27: }
28: // 保存 App 对象到数据库
29: entity = adminService.createNewApp(entity);
30: // 将保存的 App 对象,转换成 AppDTO 返回
31: dto = BeanUtils.transfrom(AppDTO.class, entity);
32: return dto;
33: }
34:
35: // ... 省略其他接口和属性
36: }
POST apps 接口,Request Body 传递 JSON 对象。
第 17 至 20 行:调用 InputValidator#isValidClusterNamespace(appId) 方法,校验 appId 是否满足 "[0-9a-zA-Z_.-]+" 格式。若不合法,抛出 BadRequestException 异常。
第 22 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将 AppDTO 转换成 App对象。
第 24 至 27 行:调用 AppService#findOne(appId) 方法,判断 appId 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。
第 29 行:调用 AdminService#createNewApp(App) 方法,保存 App 对象到数据库。
第 30 至 32 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将保存的 App 对象,转换成 AppDTO 返回。
4.2 AdminService
com.ctrip.framework.apollo.biz.service.AdminService ,😈 无法定义是什么模块的 Service ,目前仅有 #createNewApp(App) 方法,代码如下:
1: @Service
2: public class AdminService {
3:
4: @Autowired
5: private AppService appService;
6: @Autowired
7: private AppNamespaceService appNamespaceService;
8: @Autowired
9: private ClusterService clusterService;
10: @Autowired
11: private NamespaceService namespaceService;
12:
13: @Transactional
14: public App createNewApp(App app) {
15: // 保存 App 对象到数据库
16: String createBy = app.getDataChangeCreatedBy();
17: App createdApp = appService.save(app);
18: String appId = createdApp.getAppId();
19: // 创建 App 的默认命名空间 "application"
20: appNamespaceService.createDefaultAppNamespace(appId, createBy);
21: // 创建 App 的默认集群 "default"
22: clusterService.createDefaultCluster(appId, createBy);
23: // 创建 Cluster 的默认命名空间
24: namespaceService.instanceOfAppNamespaces(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, createBy);
25: return app;
26: }
27:
28: }
第 15 至 18 行:调用 AppService#save(App) 方法,保存 App 对象到数据库中。
第 20 行:调用 AppNamespaceService#createDefaultAppNamespace(appId, createBy) 方法,创建 App 的默认 Namespace (命名空间) "application" 。具体的代码实现,我们在 《Apollo 源码解析 —— Portal 创建 Namespace》 详细解析。
========== 如下部分,是 Admin Service 独有 ==========
App 下有哪些 Cluster ,在 Portal 中是不进行保存,通过 Admin Service API 读取获得。
【AppNamespace】第 22 行:调用 ClusterService#createDefaultCluster(appId, createBy) 方法,创建 App 的默认 Cluster "default" 。后续文章,详细分享。
【Namespace】第 24 行:调用 NamespaceService#instanceOfAppNamespaces(appId, createBy) 方法,创建 Cluster 的默认命名空间。
4.3 AppService
在 apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.AppService ,提供 App 的 Service 逻辑给 Admin Service 和 Config Service 。
#save(App) 方法,保存 App 对象到数据库中。代码如下:
1: @Autowired
2: private AppRepository appRepository;
3: @Autowired
4: private AuditService auditService;
5:
6: @Transactional
7: public App save(App entity) {
8: // 判断是否已经存在。若是,抛出 ServiceException 异常。
9: if (!isAppIdUnique(entity.getAppId())) {
10: throw new ServiceException("appId not unique");
11: }
12: // 保护代码,避免 App 对象中,已经有 id 属性。
13: entity.setId(0); // protection
14: App app = appRepository.save(entity);
15: // 记录 Audit 到数据库中
16: auditService.audit(App.class.getSimpleName(), app.getId(), Audit.OP.INSERT, app.getDataChangeCreatedBy());
17: return app;
18: }
第 8 至 11 行:调用 #isAppIdUnique(appId) 方法,判断是否已经存在。若是,抛出 ServiceException 异常。代码如下:
public boolean isAppIdUnique(String appId) {
Objects.requireNonNull(appId, "AppId must not be null");
return Objects.isNull(appRepository.findByAppId(appId));
}
第 13 行:置“空” App 对象,防御性编程,避免 App 对象中,已经有 id 属性。
第 14 行:调用 AppRepository#save(App) 方法,保存 App 对象到数据库中。
第 16 行:记录 Audit 到数据库中。
4.4 AppRepository
com.ctrip.framework.apollo.biz.repository.AppRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 App 的数据访问 给 Admin Service 和 Config Service 。代码如下:
public interface AppRepository extends PagingAndSortingRepository<App, Long> {
@Query("SELECT a from App a WHERE a.name LIKE %:name%")
List<App> findByName(@Param("name") String name);
App findByAppId(String appId);
}
666. 彩蛋
我们知道,但凡涉及跨系统的同步,无可避免会有事务的问题,对于 App 创建也会碰到这样的问题,例如:
Portal 在同步 App 到 Admin Service 时,发生网络异常,同步失败。那么此时会出现该 App 存在于 Portal ,却不存在于 Admin Service 中。
新增了一套环境( Env ) ,也会导致 Portal 和 Admin Service 不一致的情况。
那么 Apollo 是怎么解决这个问题的呢?😈 感兴趣的胖友,可以先自己翻翻源码。嘿嘿。
1. 概述
2. Cluster
3. Portal 侧
3.1 ClusterController
3.2 ClusterService
3.3 ClusterAPI
4. Admin Service 侧
4.1 ClusterController
4.2 ClusterService
4.3 ClusterRepository
666. 彩蛋
您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
新的源码解析文章实时收到通知。每周更新一篇左右。
认真的源码交流微信群
---------------------
本文分享 Portal 创建 Cluster 的流程,整个过程涉及 Portal、Admin Service ,如下图所示:
流程
下面,我们先来看看 Cluster 的实体结构
老艿艿:因为 Portal 是管理后台,所以从代码实现上,和业务系统非常相像。也因此,本文会略显啰嗦。
2. Cluster
com.ctrip.framework.apollo.biz.entity.Cluster ,继承 BaseEntity 抽象类,Cluster 实体。代码如下:
@Entity
@Table(name = "Cluster")
@SQLDelete(sql = "Update Cluster set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Cluster extends BaseEntity implements Comparable<Cluster> {
@Column(name = "Name", nullable = false)
private String name;
@Column(name = "AppId", nullable = false)
private String appId;
@Column(name = "ParentClusterId", nullable = false)
private long parentClusterId;
}
appId 字段,App 编号,指向对应的 App 。App : Cluster = 1 : N 。
parentClusterId 字段,父 App 编号。用于灰度发布,在 《Apollo 源码解析 —— Portal 创建灰度》 有详细解析。
3. Portal 侧
3.1 ClusterController
在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.controller.ClusterController ,提供 Cluster 的 API 。
在创建 Cluster的界面中,点击【提交】按钮,调用创建 Cluster 的 API 。
创建 Cluster
代码如下:
1: @RestController
2: public class ClusterController {
3:
4: @Autowired
5: private ClusterService clusterService;
6: @Autowired
7: private UserInfoHolder userInfoHolder;
8:
9: @PreAuthorize(value = "@permissionValidator.hasCreateClusterPermission(#appId)")
10: @RequestMapping(value = "apps/{appId}/envs/{env}/clusters", method = RequestMethod.POST)
11: public ClusterDTO createCluster(@PathVariable String appId, @PathVariable String env,
12: @RequestBody ClusterDTO cluster) {
13: // 校验 ClusterDTO 非空
14: checkModel(Objects.nonNull(cluster));
15: // 校验 ClusterDTO 的 `appId` 和 `name` 非空。
16: RequestPrecondition.checkArgumentsNotEmpty(cluster.getAppId(), cluster.getName());
17: // 校验 ClusterDTO 的 `name` 格式正确。
18: if (!InputValidator.isValidClusterNamespace(cluster.getName())) {
19: throw new BadRequestException(String.format("Cluster格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
20: }
21: // 设置 ClusterDTO 的创建和修改人为当前管理员
22: String operator = userInfoHolder.getUser().getUserId();
23: cluster.setDataChangeLastModifiedBy(operator);
24: cluster.setDataChangeCreatedBy(operator);
25: // 创建 Cluster 到 Admin Service
26: return clusterService.createCluster(Env.valueOf(env), cluster);
27: }
28:
29: // ... 省略 deleteCluster 接口
30: }
POST apps/{appId}/envs/{env}/cluster 接口,Request Body 传递 JSON 对象。
@PreAuthorize(...) 注解,调用 PermissionValidator#hasCreateClusterPermission(appId,) 方法,校验是否有创建 Cluster 的权限。后续文章,详细分享。
第 14 行:校验 ClusterDTO 非空。注意,此处使用的接收请求参数是 ClusterDTO 。
第 16 行:调用 RequestPrecondition#checkArgumentsNotEmpty(String... args) 方法,校验 ClusterDTO 的 appId 和 name 非空。
第 16 至 21 行:调用 InputValidator#isValidClusterNamespace(name) 方法,校验 ClusterDTO 的 name 格式正确,符合 [0-9a-zA-Z_.-]+" 格式。
第 21 至 24 行:设置 ClusterDTO 的创建和修改人为当前管理员。
第 26 行:调用 ClusterService#createCluster(Env, ClusterDTO) 方法,创建并保存 Cluster 到 Admin Service 。在 「3.2 ClusterService」 中,详细解析。
3.2 ClusterService
在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.service.ClusterService ,提供 Cluster 的 Service 逻辑。
#createCluster(Env, ClusterDTO) 方法,创建并保存 Cluster 到 Admin Service 。代码如下:
1: @Autowired
2: private AdminServiceAPI.ClusterAPI clusterAPI;
3:
4: public ClusterDTO createCluster(Env env, ClusterDTO cluster) {
5: // 根据 `appId` 和 `name` 校验 Cluster 的唯一性
6: if (!clusterAPI.isClusterUnique(cluster.getAppId(), env, cluster.getName())) {
7: throw new BadRequestException(String.format("cluster %s already exists.", cluster.getName()));
8: }
9: // 创建 Cluster 到 Admin Service
10: ClusterDTO clusterDTO = clusterAPI.create(env, cluster);
11: // 【TODO 6001】Tracer 日志
12: Tracer.logEvent(TracerEventType.CREATE_CLUSTER, cluster.getAppId(), "0", cluster.getName());
13: return clusterDTO;
14: }
第 5 至 8 行:调用 ClusterAPI#isClusterUnique(appId, Env, clusterName) 方法,根据 appId 和 name 校验 Cluster 的唯一性。注意,此处是远程调用 Admin Service 的 API 。
第 10 行:调用 ClusterAPI#create(Env, ClusterDTO) 方法,创建 Cluster 到 Admin Service 。
第 12 行:【TODO 6001】Tracer 日志
3.3 ClusterAPI
com.ctrip.framework.apollo.portal.api.ClusterAPI ,实现 API 抽象类,封装对 Admin Service 的 Cluster 模块的 API 调用。代码如下:
ClusterAPI
4. Admin Service 侧
4.1 ClusterController
在 apollo-adminservice 项目中, com.ctrip.framework.apollo.adminservice.controller.ClusterController ,提供 Cluster 的 API 。
#create(appId, autoCreatePrivateNamespace, ClusterDTO) 方法,创建 Cluster 。代码如下:
@RestController
1: public class ClusterController {
2:
3: @Autowired
4: private ClusterService clusterService;
5:
6: @RequestMapping(path = "/apps/{appId}/clusters", method = RequestMethod.POST)
7: public ClusterDTO create(@PathVariable("appId") String appId,
8: @RequestParam(value = "autoCreatePrivateNamespace", defaultValue = "true") boolean autoCreatePrivateNamespace,
9: @RequestBody ClusterDTO dto) {
10: // 校验 ClusterDTO 的 `name` 格式正确。
11: if (!InputValidator.isValidClusterNamespace(dto.getName())) {
12: throw new BadRequestException(String.format("Cluster格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
13: }
14: // 将 ClusterDTO 转换成 Cluster 对象
15: Cluster entity = BeanUtils.transfrom(Cluster.class, dto);
16: // 判断 `name` 在 App 下是否已经存在对应的 Cluster 对象。若已经存在,抛出 BadRequestException 异常。
17: Cluster managedEntity = clusterService.findOne(appId, entity.getName());
18: if (managedEntity != null) {
19: throw new BadRequestException("cluster already exist.");
20: }
21: // 保存 Cluster 对象,并创建其 Namespace
22: if (autoCreatePrivateNamespace) {
23: entity = clusterService.saveWithInstanceOfAppNamespaces(entity);
24: // 保存 Cluster 对象,不创建其 Namespace
25: } else {
26: entity = clusterService.saveWithoutInstanceOfAppNamespaces(entity);
27: }
28: // 将保存的 Cluster 对象转换成 ClusterDTO
29: dto = BeanUtils.transfrom(ClusterDTO.class, entity);
30: return dto;
31: }
32:
33: // ... 省略其他接口和属性
34: }
POST /apps/{appId}/clusters 接口,Request Body 传递 JSON 对象。
第 10 至 13 行:调用 InputValidator#isValidClusterNamespace(name) 方法,校验 ClusterDTO 的 name 格式正确,符合 [0-9a-zA-Z_.-]+" 格式。
第 15 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将 ClusterDTO 转换成 Cluster 对象。
第 16 至 20 行:调用 ClusterService#findOne(appId, name) 方法,校验 name 在 App 下,是否已经存在对应的 Cluster 对象。若已经存在,抛出 BadRequestException 异常。
第 21 至 23 行:若 autoCreatePrivateNamespace = true 时,调用 ClusterService#saveWithInstanceOfAppNamespaces(Cluster) 方法,保存 Cluster 对象,并创建其 Namespace 。
第 24 至 27 行:若 autoCreatePrivateNamespace = false 时,调用 ClusterService#saveWithoutInstanceOfAppNamespaces(Cluster) 方法,保存 Cluster 对象,不创建其 Namespace 。
第 29 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将 Cluster 转换成 ClusterDTO 对象。
4.2 ClusterService
在 apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.ClusterService ,提供 Cluster 的 Service 逻辑给 Admin Service 和 Config Service 。
#saveWithInstanceOfAppNamespaces(Cluster) 方法,保存 Cluster 对象,并创建其 Namespace 。代码如下:
1: @Autowired
2: private ClusterRepository clusterRepository;
3: @Autowired
4: private AuditService auditService;
5: @Autowired
6: private NamespaceService namespaceService;
7:
8: @Transactional
9: public Cluster saveWithInstanceOfAppNamespaces(Cluster entity) {
10: // 保存 Cluster 对象
11: Cluster savedCluster = saveWithoutInstanceOfAppNamespaces(entity);
12: // 创建 Cluster 的 Namespace 们
13: namespaceService.instanceOfAppNamespaces(savedCluster.getAppId(), savedCluster.getName(), savedCluster.getDataChangeCreatedBy());
14: return savedCluster;
15: }
第 11 行:调用 #saveWithoutInstanceOfAppNamespaces(Cluster) 方法,保存 Cluster 对象。
第 13 行:调用 NamespaceService#instanceOfAppNamespaces(appId, clusterName, createBy) 方法,创建 Cluster 的 Namespace 们。在 《Apollo 源码解析 —— Portal 创建 Namespace》 中,有详细解析。
#saveWithoutInstanceOfAppNamespaces(Cluster) 方法,保存 Cluster 对象。代码如下:
@Transactional
public Cluster saveWithoutInstanceOfAppNamespaces(Cluster entity) {
// 判断 `name` 在 App 下是否已经存在对应的 Cluster 对象。若已经存在,抛出 BadRequestException 异常。
if (!isClusterNameUnique(entity.getAppId(), entity.getName())) {
throw new BadRequestException("cluster not unique");
}
// 保存 Cluster 对象到数据库
entity.setId(0);//protection
Cluster cluster = clusterRepository.save(entity);
// 【TODO 6001】Tracer 日志
auditService.audit(Cluster.class.getSimpleName(), cluster.getId(), Audit.OP.INSERT, cluster.getDataChangeCreatedBy());
return cluster;
}
4.3 ClusterRepository
com.ctrip.framework.apollo.biz.repository.ClusterRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 Cluster 的数据访问 给 Admin Service 和 Config Service 。代码如下:
public interface ClusterRepository extends PagingAndSortingRepository<Cluster, Long> {
List<Cluster> findByAppIdAndParentClusterId(String appId, Long parentClusterId);
List<Cluster> findByAppId(String appId);
Cluster findByAppIdAndName(String appId, String name);
List<Cluster> findByParentClusterId(Long parentClusterId);
}
1. 概述
2. ReleaseService
2.1 publishBranchNamespace
2.2 mergeFromMasterAndPublishBranch
3. 加载灰度配置
3.1 GrayReleaseRulesHolder
3.2 GrayReleaseRuleCache
666. 彩蛋
🙂🙂🙂关注微信公众号:【芋道源码】有福利:
RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
新的源码解析文章实时收到通知。每周更新一篇左右。
认真的源码交流微信群。
1. 概述
老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 ,特别是 《Apollo 官方 wiki 文档 —— 灰度发布使用指南》。
灰度发布,实际上是子 Namespace ( 分支 Namespace )发布 Release 。所以,调用的接口和 《Apollo 源码解析 —— Portal 发布配置》 是一样的。
差异点,在于 apollo-biz 项目中,ReleaseService#publish(...) 方法中,多了一个处理灰度发布的分支逻辑。
2. ReleaseService
2.1 publishBranchNamespace
#publishBranchNamespace(...) 方法,子 Namespace 发布 Release 。子 Namespace 会自动继承 父 Namespace 已经发布的配置。若有相同的配置项,使用 子 Namespace 的。配置处理的逻辑上,和关联 Namespace 是一致的。代码如下:
1: private Release publishBranchNamespace(Namespace parentNamespace, Namespace childNamespace,
2: Map<String, String> childNamespaceItems,
3: String releaseName, String releaseComment,
4: String operator, boolean isEmergencyPublish) {
5: // 获得父 Namespace 的最后有效 Release 对象
6: Release parentLatestRelease = findLatestActiveRelease(parentNamespace);
7: // 获得父 Namespace 的配置项
8: Map<String, String> parentConfigurations = parentLatestRelease != null ? gson.fromJson(parentLatestRelease.getConfigurations(), GsonType.CONFIG) : new HashMap<>();
9: // 获得父 Namespace 的 releaseId 属性
10: long baseReleaseId = parentLatestRelease == null ? 0 : parentLatestRelease.getId();
11: // 合并配置项
12: Map<String, String> childNamespaceToPublishConfigs = mergeConfiguration(parentConfigurations, childNamespaceItems);
13: // 发布子 Namespace 的 Release
14: return branchRelease(parentNamespace, childNamespace, releaseName, releaseComment,
15: childNamespaceToPublishConfigs, baseReleaseId, operator,
16: ReleaseOperation.GRAY_RELEASE, isEmergencyPublish);
17:
18: }
第 5 至 12 行:获得最终的配置 Map 。
第 6 行:调用 #findLatestActiveRelease(parentNamespace) 方法,获得父 Namespace 的最后有效 Release 对象。
第 8 行:获得父 Namespace 的配置 Map 。
第 10 行:获得父 Namespace 的 releaseId 属性。
第 12 行:调用 #mergeConfiguration(parentConfigurations, childNamespaceItems) 方法,合并父子 Namespace 的配置 Map 。代码如下:
private Map<String, String> mergeConfiguration(Map<String, String> baseConfigurations, Map<String, String> coverConfigurations) {
Map<String, String> result = new HashMap<>();
// copy base configuration
// 父 Namespace 的配置项
for (Map.Entry<String, String> entry : baseConfigurations.entrySet()) {
result.put(entry.getKey(), entry.getValue());
}
// update and publish
// 子 Namespace 的配置项
for (Map.Entry<String, String> entry : coverConfigurations.entrySet()) {
result.put(entry.getKey(), entry.getValue());
}
// 返回合并后的配置项
return result;
}
x
第 14 行:调用 #branchRelease(...) 方法,发布子 Namespace 的 Release 。代码如下:
1: private Release branchRelease(Namespace parentNamespace, Namespace childNamespace,
2: String releaseName, String releaseComment,
3: Map<String, String> configurations, long baseReleaseId,
4: String operator, int releaseOperation, boolean isEmergencyPublish) {
5: // 获得父 Namespace 最后有效的 Release 对象
6: Release previousRelease = findLatestActiveRelease(childNamespace.getAppId(), childNamespace.getClusterName(), childNamespace.getNamespaceName());
7: // 获得父 Namespace 最后有效的 Release 对象的编号
8: long previousReleaseId = previousRelease == null ? 0 : previousRelease.getId();
9:
10: // 创建 Map ,用于 ReleaseHistory 对象的 `operationContext` 属性。
11: Map<String, Object> releaseOperationContext = Maps.newHashMap();
12: releaseOperationContext.put(ReleaseOperationContext.BASE_RELEASE_ID, baseReleaseId);
13: releaseOperationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish);
14:
15: // 创建子 Namespace 的 Release 对象,并保存
16: Release release = createRelease(childNamespace, releaseName, releaseComment, configurations, operator);
17:
18: // 更新 GrayReleaseRule 的 releaseId 属性
19: // update gray release rules
20: GrayReleaseRule grayReleaseRule = namespaceBranchService.updateRulesReleaseId(childNamespace.getAppId(),
21: parentNamespace.getClusterName(),
22: childNamespace.getNamespaceName(),
23: childNamespace.getClusterName(),
24: release.getId(), operator);
25:
26: // 创建 ReleaseHistory 对象,并保存
27: if (grayReleaseRule != null) {
28: releaseOperationContext.put(ReleaseOperationContext.RULES, GrayReleaseRuleItemTransformer.batchTransformFromJSON(grayReleaseRule.getRules()));
29: }
30: releaseHistoryService.createReleaseHistory(parentNamespace.getAppId(), parentNamespace.getClusterName(),
31: parentNamespace.getNamespaceName(), childNamespace.getClusterName(),
32: release.getId(),
33: previousReleaseId, releaseOperation, releaseOperationContext, operator);
34: return release;
35: }
第 6 行:获得父 Namespace 最后有效的 Release 对象。
第 8 行:获得父 Namespace 的 releaseId 属性。
第 10 至 13 行:创建 Map ,用于 ReleaseHistory 对象的 operationContext 属性。
第 16 行:调用 #createRelease(...) 方法,创建子 Namespace 的 Release 对象,并保存到数据库中。
第 18 至 24 行:更新 GrayReleaseRule 的 releaseId 属性到数据库中。代码如下:
@Transactional
public GrayReleaseRule updateRulesReleaseId(String appId, String clusterName, String namespaceName, String branchName, long latestReleaseId, String operator) {
// 获得老的 GrayReleaseRule 对象
GrayReleaseRule oldRules = grayReleaseRuleRepository.findTopByAppIdAndClusterNameAndNamespaceNameAndBranchNameOrderByIdDesc(appId, clusterName, namespaceName, branchName);
if (oldRules == null) {
return null;
}
// 创建新的 GrayReleaseRule 对象
GrayReleaseRule newRules = new GrayReleaseRule();
newRules.setBranchStatus(NamespaceBranchStatus.ACTIVE);
newRules.setReleaseId(latestReleaseId); // update
newRules.setRules(oldRules.getRules());
newRules.setAppId(oldRules.getAppId());
newRules.setClusterName(oldRules.getClusterName());
newRules.setNamespaceName(oldRules.getNamespaceName());
newRules.setBranchName(oldRules.getBranchName());
newRules.setDataChangeCreatedBy(operator); // update
newRules.setDataChangeLastModifiedBy(operator); // update
// 保存新的 GrayReleaseRule 对象
grayReleaseRuleRepository.save(newRules);
// 删除老的 GrayReleaseRule 对象
grayReleaseRuleRepository.delete(oldRules);
return newRules;
}
删除老的 GrayReleaseRule 对象。
保存新的 GrayReleaseRule 对象。
第 26 至 33 行:调用 ReleaseHistoryService#createReleaseHistory(...) 方法,创建 ReleaseHistory 对象,并保存到数据库中。
2.2 mergeFromMasterAndPublishBranch
本小节不属于本文,考虑到和灰度发布相关,所以放在此处。
在父 Namespace 发布 Release 后,会调用 #mergeFromMasterAndPublishBranch(...) 方法,自动将 父 Namespace (主干) 合并到子 Namespace (分支),并进行一次子 Namespace 的发布。代码如下:
1: private void mergeFromMasterAndPublishBranch(Namespace parentNamespace, Namespace childNamespace,
2: Map<String, String> parentNamespaceItems,
3: String releaseName, String releaseComment,
4: String operator, Release masterPreviousRelease,
5: Release parentRelease, boolean isEmergencyPublish) {
6: // 获得子 Namespace 的配置 Map
7: // create release for child namespace
8: Map<String, String> childReleaseConfiguration = getNamespaceReleaseConfiguration(childNamespace);
9: // 获得父 Namespace 的配置 Map
10: Map<String, String> parentNamespaceOldConfiguration = masterPreviousRelease == null ? null : gson.fromJson(masterPreviousRelease.getConfigurations(), GsonType.CONFIG);
11:
12: // 计算合并最新父 Namespace 的配置 Map 后的子 Namespace 的配置 Map
13: Map<String, String> childNamespaceToPublishConfigs = calculateChildNamespaceToPublishConfiguration(parentNamespaceOldConfiguration,
14: parentNamespaceItems, childNamespace);
15:
16: // compare
17: // 若发生了变化,则进行一次子 Namespace 的发布
18: if (!childNamespaceToPublishConfigs.equals(childReleaseConfiguration)) {
19: branchRelease(parentNamespace, childNamespace, releaseName, releaseComment,
20: childNamespaceToPublishConfigs, parentRelease.getId(), operator,
21: ReleaseOperation.MASTER_NORMAL_RELEASE_MERGE_TO_GRAY, isEmergencyPublish);
22: }
23: }
第 8 行:调用 #getNamespaceReleaseConfiguration(childNamespace) 方法,获得子 Namespace 的最新且有效的 Release 的配置 Map 。代码如下:
private Map<String, String> getNamespaceReleaseConfiguration(Namespace namespace) {
// 获得最后有效的 Release 对象
Release release = findLatestActiveRelease(namespace);
Map<String, String> configuration = new HashMap<>();
// 获得配置 Map
if (release != null) {
configuration = new Gson().fromJson(release.getConfigurations(), GsonType.CONFIG);
}
return configuration;
}
第 10 行:获得父 Namespace 的配置 Map 。
第 12 至 14 行:计算合并最新父 Namespace 的配置 Map 后,子 Namespace 的配置 Map 。代码如下:
// 计算合并最新父 Namespace 的配置 Map 后的子 Namespace 的配置 Map
private Map<String, String> calculateChildNamespaceToPublishConfiguration(
Map<String, String> parentNamespaceOldConfiguration, Map<String, String> parentNamespaceNewConfiguration,
Namespace childNamespace) {
// 获得子 Namespace 的最后有效的 Release 对象
// first. calculate child namespace modified configs
Release childNamespaceLatestActiveRelease = findLatestActiveRelease(childNamespace);
// 获得子 Namespace 的配置 Map
Map<String, String> childNamespaceLatestActiveConfiguration = childNamespaceLatestActiveRelease == null ? null :
gson.fromJson(childNamespaceLatestActiveRelease.getConfigurations(), GsonType.CONFIG);
// 以子 Namespace 的配置 Map 为基础,计算出差异的 Map
Map<String, String> childNamespaceModifiedConfiguration = calculateBranchModifiedItemsAccordingToRelease(parentNamespaceOldConfiguration,
childNamespaceLatestActiveConfiguration);
// second. append child namespace modified configs to parent namespace new latest configuration
return mergeConfiguration(parentNamespaceNewConfiguration, childNamespaceModifiedConfiguration);
}
// 以子 Namespace 的配置 Map 为基础,计算出差异的 Map
private Map<String, String> calculateBranchModifiedItemsAccordingToRelease(
Map<String, String> masterReleaseConfigs,
Map<String, String> branchReleaseConfigs) {
// 差异 Map
Map<String, String> modifiedConfigs = new HashMap<>();
// 若子 Namespace 的配置 Map 为空,直接返回空 Map
if (CollectionUtils.isEmpty(branchReleaseConfigs)) {
return modifiedConfigs;
}
// 若父 Namespace 的配置 Map 为空,直接返回子 Namespace 的配置 Map
if (CollectionUtils.isEmpty(masterReleaseConfigs)) {
return branchReleaseConfigs;
}
// 以子 Namespace 的配置 Map 为基础,计算出差异的 Map
for (Map.Entry<String, String> entry : branchReleaseConfigs.entrySet()) {
if (!Objects.equals(entry.getValue(), masterReleaseConfigs.get(entry.getKey()))) { // 对比
modifiedConfigs.put(entry.getKey(), entry.getValue());
}
}
return modifiedConfigs;
}
【第一步】逻辑看起来比较冗长和“绕”。简单的说,子 Namespace 的配置 Map 是包含老的父 Namespace 的配置 Map ,所以需要剔除。但是呢,剔除的过程中,又需要保留子 Namespace 的自定义的配置项。这就是第二个方法,#calculateBranchModifiedItemsAccordingToRelease(...) 的逻辑。
【第二步】做完上面的步骤后,就可以调用 #mergeConfiguration(...) 方法,合并新的父 Namespace 的配置 Map 。
胖友好好理解下。
第 17 至 22 行:若发生了变化,则调用 #branchRelease(...) 方法,进行一次子 Namespace 的发布。这块就和 「2.1 publishBranchNamespace」 一致了。
什么情况下会未发生变化呢?例如,父 Namespace 修改配置项 timeout: 2000=> 3000 ,而恰好子 Namespace 修改配置项 timeout: 2000=> 3000 并且已经灰度发布。
3. 加载灰度配置
在 《Apollo 源码解析 —— Config Service 配置读取接口》 中,我们看到 AbstractConfigService#findRelease(...) 方法中,会读取根据客户端的情况,匹配是否有灰度 Release ,代码如下:
1:
14: private Release findRelease(String clientAppId, String clientIp, String configAppId, String configClusterName,
15: String configNamespace, ApolloNotificationMessages clientMessages) {
16: // 读取灰度发布编号
17: Long grayReleaseId = grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(clientAppId, clientIp, configAppId, configClusterName, configNamespace);
18: // 读取灰度 Release 对象
19: Release release = null;
20: if (grayReleaseId != null) {
21: release = findActiveOne(grayReleaseId, clientMessages);
22: }
23: // 非灰度,获得最新的,并且有效的 Release 对象
24: if (release == null) {
25: release = findLatestActiveRelease(configAppId, configClusterName, configNamespace, clientMessages);
26: }
27: return release;
28: }
第 17 行:调用 GrayReleaseRulesHolder#findReleaseIdFromGrayReleaseRule(...) 方法,读取灰度发布编号,即 GrayReleaseRule.releaseId 属性。详细解析,在 「3.1 GrayReleaseRulesHolder」 中。
第 18 至 22 行:调用 #findActiveOne(grayReleaseId, clientMessages) 方法,读取灰度 Release 对象。
3.1 GrayReleaseRulesHolder
com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder ,实现 InitializingBean 和 ReleaseMessageListener 接口,GrayReleaseRule 缓存 Holder ,用于提高对 GrayReleaseRule 的读取速度。
3.1.1 构造方法
private static final Logger logger = LoggerFactory.getLogger(GrayReleaseRulesHolder.class);
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
private static final Splitter STRING_SPLITTER = Splitter.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).omitEmptyStrings();
@Autowired
private GrayReleaseRuleRepository grayReleaseRuleRepository;
@Autowired
private BizConfig bizConfig;
private int databaseScanInterval;
private ScheduledExecutorService executorService;
//store configAppId+configCluster+configNamespace -> GrayReleaseRuleCache map
private Multimap<String, GrayReleaseRuleCache> grayReleaseRuleCache;
//store clientAppId+clientNamespace+ip -> ruleId map
private Multimap<String, Long> reversedGrayReleaseRuleCache;
// an auto increment version to indicate the age of rules