这是小卷对分布式系统架构学习的第7篇文章,前面已经讲了很多理论知识,今天结合具体的中间件来讲分布式配置中心
1.面试官提问
面试官:假设你是公司的基础架构部门,现在需要设计内部的配置中心中间件,你要怎么设计?
我:设计客户端和服务端,客户端集成到业务项目中,项目启动时从服务端pull配置加载到本地,并且定时check服务端和本地配置是否一致,服务端如有更新,再pull到本地
面试官:那如果有几万台服务器,都是这样定时去check,服务端压力岂不是很大,要怎么解决呢?
我:那改成用服务端push的方式???
面试官:......
面试官:那今天就到这里吧,你回去等通知吧......
2.为什么需要分布式配置中心
不了解底层原理的小卷只好回家后苦心专研分布式配置中心的原理,一定要弄清楚底层逻辑,下次要吊打面试官。
先来简单理解为什么需要配置中心?
我们开发的服务都是单体架构时,配置文件就和代码放在一起,如springboot的application.yml文件,对配置的修改只需要修改这一个文件就行。到分布式服务中,一个服务会有多台机器,不可能每个机器都单独修改配置文件,然后重新部署的。
这就要用到配置中心了,以nacos为例,下图是配置修改时和服务器间的操作:
3.开源框架
这里列举4种分布式配置中心的中间件,我们直接从一个中间件的原理来学习配置中心。
工作这么多年,应该得了解一些开源组件,大大小小的都行:
1、Apollo
2016年5月,携程开源的配置管理中心,具备规范的权限、流程治理等特性。
2、spring cloud config
2014年9月开源,Spring Cloud 生态组件,可以和Spring Cloud体系无缝整合。
3、Nacos
2018年6月,阿里开源的配置中心,也可以做DNS和RPC的服务发现。
4、Diamond
Diamond 出自淘宝,开源地址 【https://github.com/takeseem/diamond】 ,阿里集团内部的配置中心仍然用的diamond,只是开源版本不再维护
面试时可能会问到为什么选择Apollo作为配置中心?不用其他的配置中心呢?
很多人用的时候就是看别人也这么用,或者大家都这么用,就选择了这个中间件。这里如果遇到了的话,就可以提到开源社区的活跃性,因为Apollo 的社区生态活跃,且使用的公司特别多,常见的坑基本都被踩完了,所以选用Apollo。
4. Apollo工作原理
4.1基础模型
Apollo文档:Apollo配置中心设计,工作原理比较简单:
- 用户在配置中心对配置进行修改并发布
- 配置中心通知Apollo客户端有配置更新
- Apollo客户端从配置中心拉取最新配置,更新本地配置并通知到应用
4.2架构模块
解释下各个模块的功能:
- Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
- Admin Service提供配置的修改、发布功能,服务对象是Apollo Portal(管理界面)
- Config Service和Admin Service都需要注册到Eureka并保持心跳;
- Meta Server是对Eureka做了一层封装,封装的是服务发现接口;
- Client通过域名访问Meta Server获取Config Service服务列表,即获取IP+端口,然后通过IP+端口访问服务,同时Client端自己做负载均衡,错误重试;
- Portal访问Meta Server获取Admin Service服务列表,也是获取IP+端口,然后访问服务,Portal侧也做负载均衡;
5. 使用Apollo
官方有提供快速部署使用文档:Quick Start
具体操作步骤可以自行查看官方文档,这里我们主要通过简单使用Apollo来理解配置中心。部署完成后,登陆Apollo的管理界面,然后创建个应用,发布后再创建个配置,接着再次发布,如下图:
这里我是在本地启动的,访问http://localhost:8080/可以查看已注册的实例
然后创建一个Springboot应用连接到Apollo配置中心,这里不写那么具体了,可以自行参考官方的Java客户端使用指南
Mac电脑需先在本地的/opt/settings/server.properties
文件中配置环境env=DEV
,然后在application.properties
文件中配置Apollo相关的内容如下:
# 接入Apollo配置
app.id=multi_function
apollo.meta=http://localhost:8080
# Apollo本地缓存路径
apollo.cache-dir=/Users/longbig/log
# 指定Apollo配置文件的环境
env=DEV
# 配置访问秘钥
apollo.accesskey.secret=4c61a00512ad4cc09ef8a0e1ee672d89
apollo.bootstrap.enabled=true
为了测试客户端接收到配置中心配置变更的事件,我们参考官方文档的代码写个监听器的代码如下:
@Configuration
@Slf4j
public class ApolloConfig {
@Bean
public void init() {
Config config = ConfigService.getAppConfig();
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
log.info("Changes for namespace " + changeEvent.getNamespace());
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
log.info(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()));
}
}
});
}
}
最后测试验证,在管理界面增加一个配置,然后对配置修改发布,可以看到客户端已经接收到配置变更的事件了,并且打印出日志信息了
6. 配置发布后实时生效设计
从上面简单使用中可以看到,配置发布后实时推送到客户端。下面我们简要看一下这块是怎么设计实现的
配置发布的大致过程:
- 用户在Portal操作配置发布
- Portal调用Admin Service的接口操作发布
- Admin Service发布配置后,发送ReleaseMessage给各个Config Service
- Config Service收到ReleaseMessage后,通知对应的客户端
6.1发送ReleaseMessage的实现方式
从上图看,应该是用MQ的方式比较合适,但是Apollo没有用外部消息中间件,而是通过数据库来实现这个简单的消息队列的。具体如下:
- Admin Service在配置发布后会往ReleaseMessage表插入一条消息记录,消息内容就是配置发布的AppId+Cluster+Namespace
- Config Service有一个线程会每秒扫描一次ReleaseMessage表,看看是否有新的消息记录
- Config Service如果发现有新的消息记录,那么就会通知到所有的消息监听器(ReleaseMessageListener)
- 消息监听器得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端
我们查看数据库的ReleaseMessage
和ReleaseHistory
表,可以查看到当前消息和历史消息
6.2 Config Service通知客户端的实现方式
这里能解释说明开头的面试题,客户端更新配置是Pull还是Push的方式?
具体实现方式如下:
- 客户端会发起一个Http请求到Config Service的
notifications/v2
接口,也就是NotificationControllerV2 - NotificationControllerV2不会立即返回结果,而是通过Spring DeferredResult把请求挂起
- 如果在60秒内没有该客户端关心的配置发布,那么会返回Http状态码304给客户端
- 如果有该客户端关心的配置发布,NotificationControllerV2会调用DeferredResult的setResult方法,传入有配置变化的namespace信息,同时该请求会立即返回。客户端从返回的结果中获取到配置变化的namespace后,会立即请求Config Service获取该namespace的最新配置。
7. 客户端的工作原理
接着讲讲Apollo客户端的工作原理:
- 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。(通过Http Long Polling实现)
- 客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。
- 这是一个fallback机制,为了防止推送机制失效导致配置不更新
- 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
- 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property:
apollo.refreshInterval
来覆盖,单位为分钟。
- 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
- 客户端会把从服务端获取到的配置在本地文件系统缓存一份
- 在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
- 应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知
8.题外话
之前在第一家公司工作过程中,遇到个问题是:对应用某个配置的变更如何通知到生产环境的所有机器?
当时的场景是前端发起HTTP请求,调用后端接口修改配置,因为负载均衡的缘故,请求只会打到1台机器上,只有1台机器的内存配置被更新,其他机器的内存配置还是旧的,当时小组一起讨论解决办法,可能认知有限,只想到MQ等等方式,没想到配置中心的原理
后来去了阿里之后,参与过写配置中心配置变更监听器,实现了全量机器的内存配置更新功能
现在回想起来,当时没解决的原因还是认知不够,现在学了配置中心的原理又想到了这件事,分享给大家学习参考~
相信通过学习Apollo配置中心的原理,你在面试过程中如果遇到开头的题目,应该也能说上一二了。