19、Dubbo实战:Apollo配置中心

说一个人是武林高手:十八般武艺,样样精通!如今,后端技术层出不穷,让人眼花缭乱,如果看官不能达到样样精通,至少拿起方天画戟能耍几下才行,比如削个苹果。言归正传,配置中心属于基础设施,当然必须玩得溜,不论Nacos还是Config,今天我们来耍下Apollo,看好玩否。

准备:

Idea2019.03/Gradle6.0.1/Maven3.6.3/JDK11.0.4/SpringBoot2.3.0RELEASE/Mysql8.0.11/Apollo1.7.0/RHEL8.0/ VMwareWorkstation15Pro

难度: 新手--战士--老兵--大师

目标:

1、 完成Apollo分布式部署和简单应用;

**说明:**为了遇见各种问题,同时保持时效性,我尽量使用最新的软件版本,源码地址,其中的day31:https://github.com/xiexiaobiao/dubbo-project

1 部署图(单服务)

Apollo基础知识,略!请先安装好Maven和JDK环境!Linux内存建议4G以上!

*

注:Apollo自带Eureka组件,可以自动组成集群,当然,如果希望使用外部Eureka组件,需修改配置即可!

2 步骤

我使用最纯粹的源码编译部署方式,其他如Docker、QuickStart,略!

2、 1Gitclone项目源码文件到本地,Window下构建修改文件apollo\scripts\build.bat(Linux下则修改apollo\scripts\build.sh):;

*

2、 2然后直接双击运行build.bat:;

*

2、 3复制\apollo-adminservice,\apollo-configservice,\apollo-portal三个模块的target文件夹下的zip文件到Linux下,我这里分别放三个目录admin-service,config-service,portal-service下:;

*

解压:

[root@server226 config-service]# unzip apollo-configservice-1.7.0-SNAPSHOT-github.zip
[root@server226 admin-service]# unzip apollo-adminservice-1.7.0-SNAPSHOT-github.zip
[root@server226 portal-service]# unzip apollo-portal-1.7.0-SNAPSHOT-github.zip

启动服务,注意顺序:

[root@server226 portal-service]# bash /usr/apollo1.7.0/config-service/scripts/startup.sh
[root@server226 portal-service]# bash /usr/apollo1.7.0/admin-service/scripts/startup.sh
[root@server226 portal-service]# bash /usr/apollo1.7.0/portal-service/scripts/startup.sh

(常见启动异常问题,请见文末部分)

注:如果 ApolloConfigDB.ServerConfig 的 eureka.service.url 只配了当前正在启动的机器的话,在启动apollo-configservice的过程中会在日志中输出eureka注册失败的信息,如com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused。需要注意的是,这个是预期的情况,因为apollo-configservice需要向Meta Server(它自己)注册服务,但是因为在启动过程中,自己还没起来,所以会报这个错。后面会进行重试的动作,所以等自己服务起来后就会注册正常了。

2、 4在启动config-service成功后,即可打开http://192.168.2.226:8080/,可以查看eureka界面:;

*

如果src/main/config/application-github.properties中,开启以下两项:

apollo.eureka.server.enabled=true
apollo.eureka.client.enabled=true

*

2、 5验证是否全部启动成功:http://192.168.2.226:8070/账号/密码:apollo/admin登录:;

*

2、 6优雅关闭服务,注意顺序:;

[root@server226 portal-service]# bash /usr/apollo1.7.0/portal-service/scripts/shutdown.sh
[root@server226 portal-service]# bash /usr/apollo1.7.0/admin-service/scripts/shutdown.sh
[root@server226 portal-service]# bash /usr/apollo1.7.0/config-service/scripts/shutdown.sh

2、 7修改config-service默认端口8080为8085:;

1、 src/main/resources/application.yml中修改server:port:8085;
2、 src/main/resources/configservice.properties中修改server.port=8085;
3、 src/main/scripts/startup.sh中修改SERVER_PORT:=8085;
4、 数据库apolloconfigdb.serverconfig中eureka.service.url字段修改http://192.168.2.226:8085/eureka/;

3 部署图(高可用)

架构部署图:

*

注:这里未画出client,client会先访问metaService,获取configService的列表,再从configService获取配置数据,可以使用SLB(Software Load Balancer)对client访问多个metaService做负载均衡。

同上方法,我修改config-service端口为8086,需修改(共5处) src/main/resources/application.yml,src/main/resources/configservice.properties,src/main/scripts/startup.sh,src/main/resources/configservice.properties, 数据库apolloconfigdb.serverconfig 中eureka.service.url 字段添加http://192.168.2.224:8086/eureka/,并使用同上apollo\scripts\build.bat脚本进行构建打包,放到另一Linux(192.168.2.224)上运行:

然后,可以访问:http://192.168.2.224:8086/

*

访问:http://192.168.2.226:8085/*

由以上两图,可见eureka实例已组成集群,互为备份!

打开:http://192.168.2.226:8070/system_info.html,可以看到 portal 能管理所有的 configService和adminService实例:

*

4 客户端应用

依赖,略!

4、 1API方式:;

配置appID和 metaService, src/main/resources/META-INF/app.properties

*

配置metaService,如果是多environment,通过src/main/resources/apollo-env.properties

*

指定env,Windows文件位置为 C:\opt\settings\server.properties (对于Mac/Linux,文件位置为/opt/settings/server.properties) ,内容为:

env=DEV

通过portal UI创建一个配置,最后效果如下:

*

测试代码(java):

public*class*ApiMain*{

****private*static*final*Logger*logger*=*LoggerFactory.getLogger(ApiMain.class);
****private*String*DEFAULT_VALUE*=*"undefined";
****//*config*instance*is*singleton*for*each*namespace*and*is*never*null
****private*Config*config;

****ConfigChangeListener*changeListener*=*configChangeEvent*->*{
********for*(String*key:*configChangeEvent.changedKeys()
*************)*{
************ConfigChange*change*=*configChangeEvent.getChange(key);
************logger.info("Config*Change*>>>>>*key:*{},*oldValue:*{},*newValue:*{},*changeType:*{}",
********************change.getPropertyName(),*change.getOldValue(),*change.getNewValue(),
********************change.getChangeType());
********}
****};

****public*ApiMain()*{
********this.config*=*ConfigService.getAppConfig();
********this.config.addChangeListener(changeListener);
****}

****public*static*void*main(String[]*args)*throws*IOException*{
********ApiMain*apiMain*=*new*ApiMain();
********//*getConfig("key");*key为配置数据中的key
********apiMain.getConfig("name");
****}

****private*String*getConfig(String*key){
********String*result*=*config.getProperty(key,DEFAULT_VALUE);
********logger.info(String.format("Loading*key*>>>>*%s*with*value:*%s",*key,*result));
********return*result;
****}
}

以上代码解析:通过 Config config 成员变量注入,并添加一个 ConfigChangeListener 做配置变化的监听器,可以在配置变化时得到通知,如数据库连接串变化后需要重建连接等。

运行输出:

*

读取到的配置信息:

*

在portal界面,进行“修改”->“发布”,监听到的配置变化:

*

4、 2Springboot方式:修改src/main/resources/config/application.yml:;

*

注:多namespace的情况下,应用会默认读取非‘application’的配置;

写一个测试用bean,使用SPEL表达式就可以实现自动属性注入:

public*class*ConfigBean*{

****private*String*school;

****@Value("${timeValue:100}")
****private*String*timeValue;

****public*String*getSchool()*{
********return*school;
****}

****@Value("${school:200}")
****public*void*setSchool(String*school)*{
********this.school*=*school;
****}
...
}

另外我还写了一个注解方式的:

@ConfigurationProperties(prefix*=*"redis.cache")
public*class*SampleRedisConfig*{
****private*int*expireSeconds;
****private*int*commandTimeout;
...
}

配合spring机制实现bean注入:

@Configuration
//@EnableApolloConfig(value*=*{"FX.apollo",*"application.yml"},*order*=*1)
public*class*SomeConfig*{

****@Bean
****public*ConfigBean*getConfigBean(){
********return*new*ConfigBean();
****}

****@Bean
****public*SampleRedisConfig*sampleRedisConfig()*{
********return*new*SampleRedisConfig();
****}
}

最后给个应用的入口:

@SpringBootApplication
@RestController
public*class*AppMain*{
****private*ConfigBean*configBean;
****private*SampleRedisConfig*redisConfig;

****@Autowired
****public*AppMain(ConfigBean*configBean,SampleRedisConfig*redisConfig){
********this.configBean*=*configBean;
********this.redisConfig*=*redisConfig;
****}

****public*static*void*main(String[]*args)*{
********SpringApplication.run(AppMain.class,args);
********System.out.println("AppMain*app*started*>>>>>>>>>>>>");
****}
****@RequestMapping("/config")
****public*String*apollo(){
********System.out.println("config*1*>>>>*"*+*configBean.getSchool()*+"/"+*configBean.getTime());
********System.out.println("config*2*>>>>*"*+*redisConfig.getCommandTimeout()+"/"+*redisConfig.getExpireSeconds());
********return*"config*>>>>*"*+*configBean.getSchool()*+"/"+*configBean.getTime();
****}
}

在portal界面配置KV值:

*

运行应用并URL访问:

*

控制台输出:

*

4 常见问题

4、 1查看config-service启动日志,发现mysql连接异常:;

[root@server226 admin-service]# less /opt/logs/100003171/apollo-configservice.log

*

另一种查看mysql连接是否成功方法是启动apollo服务前后分别运行,比较连接数:

*

问题解决,注意window机器防火墙规则,可以直接关闭,或添加出入规则。

4、 2查看config-service启动日志,发现端口冲突异常:;

*

解决问题,先查看端口占用情况:

[root@server226 config-service]# lsof -i:8080

*

直接kill 命令关闭,或者找到对应的应用进行关闭即可!

4、 3查看config-service启动日志,发现Eureka连接异常:;

*

问题解决:Linux使用了hostname,导致localhost解析异常,修改 src/main/resources/bootstrap.yml :

*

注意需同步修改 apollo\apollo-adminservice 模块下的 src/main/resources/bootstrap.yml,内容同上。

遗留问题

1、 "访问密钥"开启的情况下,一直访问失败;;

2、 使用@ConfigurationProperties如果需要在Apollo配置变化时自动更新注入的值,需要配合使用EnvironmentChangeEventRefreshScope相关代码实现,可以参考官方apollo-use-cases项目;;

后记:在以上各个部署的每个步骤中,几乎都有多种实现方式,我只是使用了其中的1-2种,其他可参考官方说明!

全文完!