第2章 Apollo源码剖析
能力目标
- 能够基于Git导入Apollo源码
- 能够基于IDEA实现DEBUG分析APP创建
- 掌握Namespace创建过程
- 掌握Item创建过程
- 掌握灰度发布创建过程
1:namespace创建、灰度发布配置、Item创建作为自学
2:客户端剖析
通信->Http、轮询机制
配置文件优先级、缓存、关联关系
刷新机制【注解解析】
1 Apollo源码搭建
在上一章我们已经学习了Apollo项目实战,为了更进一步学习Apollo、掌握Apollo工作原理,我们开始学习Apollo源码,所以我们先搭建Apollo源码环境。
1.1 源码下载
我们从github上 https://github.com/ctripcorp/apollo 下载源码,下载后的源码如下:
版本切换至v1.7.1(课程中使用的是1.7.0),如下操作:
1.2 导入数据库
在项目根路径下有scripts/sql
目录,下面有2个sql脚本,我们将该脚本导入到数据库中。
如下图,在本地mysql上执行这两个脚本:
1.3 apollo-assembly启动服务
我们启动Apollo服务,需要同时启动configservice、adminservice,如果手动启动比较慢,Apollo帮我们封装了一个工程apollo-assembly
,可以基于该工程同时启动 apollo-adminservice
和 apollo-configservice
项目。
修改apollo-configservice
的核心配置文件bootstrap.yml
添加Eureka不注册Eureka数据也不获取Eureka数据,配置如下:
完整代码如下:
eureka:
instance:
hostname: ${hostname:localhost}
preferIpAddress: true
status-page-url-path: /info
health-check-url-path: /health
server:
peerEurekaNodesUpdateIntervalMs: 60000
enableSelfPreservation: false
client:
serviceUrl:
# This setting will be overridden by eureka.service.url setting from ApolloConfigDB.ServerConfig or System Property
# see com.ctrip.framework.apollo.biz.eureka.ApolloEurekaClientConfig
defaultZone: http://${eureka.instance.hostname}:8080/eureka/
healthcheck:
enabled: true
eurekaServiceUrlPollIntervalSeconds: 60
fetch-registry: false
register-with-eureka: false
我们先配置该工程,如下图:
这里的VM optins:
-Dapollo_profile=github
-Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloConfigDB?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
-Dspring.datasource.username=root
-Dspring.datasource.password=123456
-Dlogging.file=D:/project/xc-apollo/apollo-assembly.log
参数Program arguments
中的两个参数分别表示启动configservice
和adminservice
服务。
启动完成后,我们请求Eureka http://localhost:8080/
PortalService启动
apollo-portal工程需要单独启动,启动的时候我们也需要配置密码和日志输出文件,如下图:
VM options配置如下:
-Dapollo_profile=github,auth
-Ddev_meta=http://localhost:8080/
-Dserver.port=8070
-Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloPortalDB?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
-Dspring.datasource.username=root
-Dspring.datasource.password=123456
-Dlogging.file=D:/project/xc-apollo/apollo-portal.log
启动完成后,我们接下来访问控制台 http://localhost:8070 效果如下:
1.4 服务测试
我们可以先创建一个项目并且app.id=100004458,如下图:
在该项目的
application.properties
中添加一个username
参数,如下图:
Apollo
提供了内置的测试服务,该服务会访问Apollo
服务app.id=100004458
的项目,我们可以在该工程启动时配置VM options
参数指定Apollo
注册中心地址,如下图:
VM options参数配置如下:
-Denv=dev
-Ddev_meta=http://localhost:8080
启动程序,我们输入username回车,可以看到对应数据,如下输出结果:
Apollo Config Demo. Please input key to get the value. Input quit to exit.
> username
> [apollo-demo][main] INFO [com.ctrip.framework.apollo.demo.api.SimpleApolloConfigDemo] Loading key : username with value: 张三
2 Portal创建APP
Apollo创建App的过程如果基于控制台操作是很简单的,但是Apollo是如何实现的呢,我们接下来进行相关源码剖析。
创建APP的流程如上图:
1:用户在后台执行创建app,会将请求发送到Portal Service
2:Portal Service将数据保存到Portal DB中
3:Portal Service同时将数据同步到Admin Service中,这个过程是异步的
4:Admin Service将数据保存到Config DB中
2.1 创建APP
创建APP由Portal Service执行,我们从它的JavaBean、Controller、Service、Dao一步一步分析。
2.1.1 实体Bean
1)Table
APP对应的表结构如下:
CREATE TABLE `App` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`AppId` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'AppID',
`Name` varchar(500) NOT NULL DEFAULT 'default' COMMENT '应用名',
`OrgId` varchar(32) NOT NULL DEFAULT 'default' COMMENT '部门Id',
`OrgName` varchar(64) NOT NULL DEFAULT 'default' COMMENT '部门名字',
`OwnerName` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'ownerName',
`OwnerEmail` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'ownerEmail',
`IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
`DataChange_CreatedBy` varchar(32) NOT NULL DEFAULT 'default' COMMENT '创建人邮箱前缀',
`DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`DataChange_LastModifiedBy` varchar(32) DEFAULT '' COMMENT '最后修改人邮箱前缀',
`DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
PRIMARY KEY (`Id`),
KEY `AppId` (`AppId`(191)),
KEY `DataChange_LastTime` (`DataChange_LastTime`),
KEY `IX_Name` (`Name`(191))
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='应用表';
2)App(Bean)
在 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 {
/**
* App名字
*/
@NotBlank(message = "Name cannot be blank")
@Column(name = "Name", nullable = false)
private String name;
/**
* App.id
*/
@NotBlank(message = "AppId cannot be blank")
@Pattern(
regexp = InputValidator.CLUSTER_NAMESPACE_VALIDATOR,
message = InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE
)
@Column(name = "AppId", nullable = false)
private String appId;
/**
* 部门编号
*/
@Column(name = "OrgId", nullable = false)
private String orgId;
/**
* 部门名
*/
@Column(name = "OrgName", nullable = false)
private String orgName;
/***
* 拥有人名
* 例如在 Portal 系统中,使用系统的管理员账号,即 UserPO.username 字段
*/
@NotBlank(message = "OwnerName cannot be blank")
@Column(name = "OwnerName", nullable = false)
private String ownerName;
/***
* 拥有人邮箱
*/
@NotBlank(message = "OwnerEmail cannot be blank")
@Column(name = "OwnerEmail", nullable = false)
private String ownerEmail;
//...get set 略
}
- ORM 选用 Hibernate 框架。
@SQLDelete(...)
+@Where(...)
注解,配合BaseEntity.extends
字段,实现 App 的逻辑删除。- 字段比较简单。
3)BaseEntity(Bean)
com.ctrip.framework.apollo.common.entity.BaseEntity
,是基础实体抽象类。代码如下:
@MappedSuperclass
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity {
/**
* 编号
*/
@