微服务系列之配置中心 Apollo
1.背景与介绍
随着微服务架构的发展,企业级项目由无数的服务组成,这时候急需用到集中管理、治理的配置的组件,来统一管理各个服务的开关、配置参数、数据库地址、服务器等等,然而这还不够,还要对这个管理配置的组件有着修改后实时发布、多环境、灰度发布、权限控制、审核等等机制,由此配置中心出现了,而由携程开源的apollo(阿波罗)人气最高、高可用性、各种功能非常完善,当然最关键选择apollo的一点是,文档真的非常非常完善,==本篇文章主要介绍基于docker版本的apollo搭建,apollo的一些核心概念、架构设计以及中间遇到的一些问题解决方案。
2.apollo核心概念
- application 就是字面意思应用,比如在apollo上新建一个配置项目,就是这个应用名称,设置一个appid做为唯一标识。
- environment 环境,apollo支持多环境配置管理,比如dev、fat、uat、pro,每个环境部署的代码,从对应的环境中读取配置。
- cluster 集群,apollo支持,同一个application应用+环境,按照不同集群来读取不同的配置,这个我没有实战中试过,感兴趣的可以去试试,根据官方文档来操作。
- namespace 命名空间,在apollo上建立项目(应用)时候,会默认在application命名空间下,我们的配置中,有许多项目都共同使用,比如数据库连接、服务发现KEY/VALUE等等各种公共的配置项,这时候就可以放到共享命名空间下,开放给所有客户端去拉取这个命名空间下的配置。
3.apollo架构设计
这是apollo官方提供的架构图,由图可见,这是典型的微服务架构,如果做过微服务的同学看起来比较容易,这里面涉及的服务、中间件拆分细节如下:
- ConfigService 配置服务,依赖configDb数据库,提供配置获取接口,为客户端推送配置更新通知,客户端直连或者通过slb地址连接。
- AdminService 后端服务,依赖configDb数据库,提供配置管理接口,提供配置增删改查等等接口,Portal管理界面使用该服务。
- Client 就是我们的API服务,依赖ConfigService,通过configService获取应用配置,或者长连接实时更新;
- Portal 阿波罗的管理界面,依赖Portal数据库,依赖AdminService;
- Euraka 一个服务注册与发现的中间件,ConfigService和AdminService都注册在这,apollo默认架构是这个组件与ConfigService部署在一起,也可以独立部署出去。
- MetaServer 这个相当于,Euraka的一个反向代理
- NginxLB 负载均衡器,如果是分布式部署的话,ConfigService与AdminService同过这个负载地址拉拉取对应的服务发现的反向代理MetaServer地址;
configService和AdminService都是无状态的,因此可以横向无线扩展,于是携程就引入只支持java的服务发现中间件Euraka引入进来,当服务启动时候,自动注册服务,Portal与java客户端(client)通过Euraka的SLB地址来发现服务列表,后来呢,因为要支持其他语言,又引入MetaServer做为Euraka的反向代理中间件,而MetaServer也做成集群,暴漏SLB地址,这样把Euraka的SLB换成MetaServer的SLB地址,所有语言都可以轻易使用Apollo。
如图:
写到这的时候,突发一个思考,由于目前主流微服务架构,单服务大概率都是基于docker部署的且多pod形式存在,本身该服务这些pod会有一个负载IP,但是通过服务注册发现后,有两种注册方式:
- pod实例本身ip地址注册,这种的话,服务发现拉取服务列表,是会把所有pod都拉回来,我们代码本身要进行轮询负载算法进行选择;
- pod负载IP地址注册,这样的话,服务发现拉取回来就是负载地址,不需要我们写轮询算法;
4.配置发布的实时通知设计
客户在portal操作发布后,内部调用adminService接口进行对配置数据库的操作,然后发送ReleaseMessage给ConfigService,收到消息后,通知Client。
推送ReleaseMessage的实现原理如下,本来想通过消息队列的形式,但人家做为开源框架,不想使用多的外部依赖,所以通过数据库消息表形式来完成简单的消息队列,具体流程如下:
- AdminService发布后,会往ReleaseMessage表里插入一条配置的APPID+Cluster+NameSpace的记录;
- ConsigService有一个线程每秒扫描一次,如果有新纪录话,就会通知到所有消息监听器,官方给的叫ReleaseMessageListener,具体不做详细了解,感兴趣的去看;
- 监听器得到消息后,会降配置内容,通知给所有客户端;
- 客户端发起个HTTP请求到ConfigService的 监听器接口,但是这个监听器接口会把这个请求挂起,因为客户端在配置apollo的时候会根据appid和namespace来获取关心的配置,所以挂起请求的同时,60秒内,如果这个监听器没有接收到其关心的配置消息,就返回304,相反如果有其关心的配置消息,接口会立即返回给客户端其关心的nameSpace信息,然后客户端立即再发起请求ConfigService获取这些namespace下的配置内容。
5.客户端实现原理
- 通过上述通知设计可知,客户端通过Long Http Polling与configService保持一个长连接,来第一时间获取最新的NameSpace更新信息;
- 为了防止长连接推送失败,客户端每5分钟也会去请求一下ConfigService,通过对比版本信息,来获取关心的最新配置;
- 客户端拿到最新配置后,会缓存到本地一份,这样遇到apollo服务不可用或者网络不通时候,可以从本地恢复配置;
6.apollo的高可用性
直接贴出官方给出的表格
7.apollo部署
说真的,携程这个开源的配置中心,文档真的非常非常细,大赞!从单体部署到分布式部署以及各种形式部署,都非常详细,文档地址:部署文档,这里由于小霸王服务器问题,我们只部署一个环境的非分布式部署来搭建apollo,真正的生产环境上,公司的运维会扛起分布式部署大旗,我们后端辅助即可哈哈。
本文部署使用基于docker方式:
-
先安装mysql,每安装的去安装,这里不多说;
-
搭建所依赖的两个数据库ApolloPortalDB和ApolloConfigDB,我这选择从人家github上直接下载脚本,去执行,数据脚本传送,下载完后,在navcat上直接执行,会得到两个数据库
-
拉取configService镜像
docker pull apolloconfig/apollo-configservice:1.8.0
- 运行configService容器
docker run -p 8080:8080 \
-e SPRING_DATASOURCE_URL="jdbc:mysql://你的mysql地址:3306/ApolloConfigDB?characterEncoding=utf8" \
-e SPRING_DATASOURCE_USERNAME=数据库用户 -e SPRING_DATASOURCE_PASSWORD=数据库密码 \
-d -v /tmp/logs:/opt/logs --name apollo-configservice apolloconfig/apollo-configservice:1.8.0
- 拉取adminService镜像
docker pull apolloconfig/apollo-adminservice:1.8.0
- 运行adminService容器
docker run -p 8090:8090 \
-e SPRING_DATASOURCE_URL="jdbc:mysql://你的mysql地址:3306/ApolloConfigDB?characterEncoding=utf8" \
-e SPRING_DATASOURCE_USERNAME=数据库用户 -e SPRING_DATASOURCE_PASSWORD=数据库密码 \
-d -v /tmp/logs:/opt/logs --name apollo-adminservice apolloconfig/apollo-adminservice:1.8.0
- 拉取portal镜像
docker pull apolloconfig/apollo-portal:1.8.0
- 运行portal容器
docker run -p 8070:8070 \
-e SPRING_DATASOURCE_URL="jdbc:mysql://你的mysql地址:3306/ApolloPortalDB?characterEncoding=utf8" \
-e SPRING_DATASOURCE_USERNAME=数据库用户 -e SPRING_DATASOURCE_PASSWORD=数据库密码 \
-e APOLLO_PORTAL_ENVS=dev,pro \ (注意这里,你需要什么环境,就填写哪些,运行起来会自动更新数据库ApolloPortalDB中的apollo.portal.envs,这测试只写dev)
-e DEV_META=http://你的metaService:8080 (注意,configService与euraka正常启动在同一进程)
-d -v /tmp/logs:/opt/logs --name apollo-portal apolloconfig/apollo-portal:1.8.0
全部运行起来后,查看docker运行情况如下图:
8.apollo管理界面使用
1.运行portal地址,默认账号密码是apollo/admin,登录
2.什么是创建项目?独立管理你微服务中一个或一类的私有配置集合,比如我有个订单服务,我这里就建一个OrderApi项目,正常情况下,有1个货几个公共项目,其他都是业务类项目。
3.修改部门,在数据库ApolloPortalDB.表ServerConfig.organizations修改。下面具体怎么使用,也没什么可说的,实在不会就照着文档来官方使用说明
4.我新建了一个docker-api的项目,和一个share公共项目,并分别新增和发布了配置,注意下,命名空间,公有私有的属性问题;
9..net core使用apollo
1.新建一个.net core3.1项目,nuget包安装Com.Ctrip.Framework.Apollo.Configuration;
2.启动类构造函数
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
//输出debug日志在控制台,方便查找问题
Com.Ctrip.Framework.Apollo.Logging.LogManager.UseConsoleLogging(Com.Ctrip.Framework.Apollo.Logging.LogLevel.Debug);
var builder = new ConfigurationBuilder()
.AddApollo(configuration.GetSection("apollo"))
.AddNamespace("yf.Share")
.AddDefault();
Configuration = builder.Build();
}
3.appsetting.json
{
"apollo": {
"AppId": "docker-api1",
"MetaServer": "http://xxxxxxx:8080",
"ConfigServer": [ "http://xxxxxxxx:8080/" ]
}
}
4.Api
[HttpGet("get/configs")]
public async Task<IActionResult> GetConfigs()
{
var res = "";
//获取公共命名空间下的配置
var publicConfig = configuration["env"];
//获取该私有项目下的配置
var privateConfig = configuration["myConfig1"];
res = $"公共:{publicConfig},私有:{privateConfig}";
return Ok(res);
}
5.运行
10.常见问题
按照官方文档搭建部署的话,基本会碰到如下问题,为避免新人踩坑浪费时间,现总结如下:
1.当容器全部启动成功后,新建完项目,点击进去,右上角弹窗报错“请联系管理员”,我大概记得这么个问题,查看容器日志发现,apollo的ApolloConfigDB数据库中的ServerConfig中的eureka.service.url配置的默认是本地URL,由于docker的隔离性,所以请求失败,我们去这里给改成docker间可通信地址即可。
2.都配置完后,管理界面也都正常发布,但是代码里死活取不到数据,这个问题查了好久,终于最后在github上提问中找到篇这个问题的帖子,查看问题,就是startup中给debug日志打开,可以看到在注册apollo的时候的控制台日志,原因就客户端在连接configService服务列表时,通过metaserver(eureka反向代理地址)时候,默认取的是内网容器IP,在我们客户端没法访问,所以解决方案,就是appsetting中增加ConfigServer配置,来指定即可解决。