简单的整理了一下配置中心的基本概念,主要是为了方便记住,没啥营养。
配置中心将配置从应用中剥离出来,统一管理,优雅的解决了配置的动态变更、持久化、运维成本等问题。应用自身既不需要去添加管理配置接口,也不需要自己去实现配置的持久化,更不需要引入“定时任务”以便降低运维成本。总得来说,配置中心就是一种统一管理各种应用配置的基础服务组件。
从配置中心角度来看,性能方面Nacos的读写性能最高,Apollo次之,Spring Cloud Config依赖Git场景不适合开放的大规模自动化运维API。功能方面Apollo最为完善,nacos具有Apollo大部分配置管理功能,而Spring CloudConfig不带运维管理界面,需要自行开发。Nacos的一大优势是整合了注册中心、配置中心功能,部署和操作相比Apollo都要直观简单,因此它简化了架构复杂度,并减轻运维及部署工作。
Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
上图简要描述了Apollo的总体设计,我们可以从下往上看:
Apollo提供的客户端程序,为应用提供配置获取、实时更新等功能
通过Meta Server获取Config Service服务列表(IP+Port),通过IP+Port访问服务
在Client侧做load balance、错误重试
为什么我们采用Eureka作为服务注册中心,而不是使用传统的zk、etcd呢?我大致总结了一下,有以下几方面的原因:
在配置中心中,一个重要的功能就是配置发布后实时推送到客户端。下面我们简要看一下这块是怎么设计实现的。
上图简要描述了配置发布的大致过程:
Admin Service在配置发布后,需要通知所有的Config Service有配置发布,从而Config Service可以通知对应的客户端来拉取最新的配置。
从概念上来看,这是一个典型的消息使用场景,Admin Service作为producer发出消息,各个Config Service作为consumer消费消息。通过一个消息组件(Message Queue)就能很好的实现Admin Service和Config Service的解耦。
在实现上,考虑到Apollo的实际使用场景,以及为了尽可能减少外部依赖,我们没有采用外部的消息中间件,而是通过数据库实现了一个简单的消息队列。
实现方式如下:
上一节中简要描述了NotificationControllerV2是如何得知有配置发布的,那NotificationControllerV2在得知有配置发布后是如何通知到客户端的呢?
实现方式如下:
notifications/v2
接口,也就是NotificationControllerV2,参见RemoteConfigLongPollServiceApollo客户端的实现原理:
apollo.refreshInterval
来覆盖,单位为分钟。Nacos 的关键特性包括:
服务发现和服务健康监测
Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDK、OpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODO 或HTTP&API查找和发现服务。
Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。
动态配置服务
动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。
动态 DNS 服务
动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。
服务及其元数据管理
Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。
Nacos的配置中心可以分为获取配置,监听配置,发布配置,删除配置,其中配置的获取删除发布相对简单,堆服务端来说,无非就是怎么存储配置。是否要持久化,对客户端来说,就是通过接口从服务器端查询相应的数据,然后返回即可。
而监听的高压,常见的交互方式就是pull和push。Nacos采用的就是pull模式,是一种长轮询机制,它结合Push和pull两者的优点。客户端采用长轮询的方式定时发起pull请求,去检查服务端配置信息是否发生了变化,如果变化,就会获取最新的配置。如果没有变化,服务端就会“Hold”住这个请求,直到这段时间内配置发生变化。其实就是设置一个定时任务,延迟29.5s执行,并且把当前的客户端长轮询连接加入allSubs队列,这样降低了服务端的压力。
现在我们了解了 Nacos 的配置管理的功能了,但是有一个问题我们需要弄明白,那就是 Nacos 客户端是怎么实时获取到 Nacos 服务端的最新数据的。
其实客户端和服务端之间的数据交互,无外乎两种情况:
那到底是推还是拉呢,从 Nacos 客户端通过 Listener 来接收最新数据的这个做法来看,感觉像是服务端推的数据,但是不能想当然,要想知道答案,最快最准确的方法就是从源码中去寻找。
从我们的 demo 中可以知道,首先是创建了一个 ConfigService。而 ConfigService 是通过 ConfigFactory 类创建的,如下图所示:
可以看到实际是通过反射调用了 NacosConfigService 的构造方法来创建 ConfigService 的,而且是有一个 Properties 参数的构造方法。
需要注意的是,这里并没有通过单例或者缓存技术,也就是说每次调用都会重新创建一个 ConfigService的实例。
现在我们来看下 NacosConfigService 的构造方法,看看 ConfigService 是怎么实例化的,如下图所示:
实例化时主要是初始化了两个对象,他们分别是:
其中 agent 是通过装饰着模式实现的,ServerHttpAgent 是实际工作的类,MetricsHttpAgent 在内部也是调用了 ServerHttpAgent 的方法,另外加上了一些统计操作,所以我们只需要关心 ServerHttpAgent 的功能就可以了。
agent 实际是在 ClientWorker 中发挥能力的,下面我们来看下 ClientWorker 类。
以下是 ClientWorker 的构造方法,如下图所示:
可以看到 ClientWorker 除了将 HttpAgent 维持在自己内部,还创建了两个线程池:
第一个线程池是只拥有一个线程用来执行定时任务的 executor,executor 每隔 10ms 就会执行一次 checkConfigInfo() 方法,从方法名上可以知道是每 10 ms 检查一次配置信息。
第二个线程池是一个普通的线程池,从 ThreadFactory 的名称可以看到这个线程池是做长轮询的。
现在让我们来看下 executor 每 10ms 执行的方法到底是干什么的,如下图所示:
可以看到,checkConfigInfo 方法是取出了一批任务,然后提交给 executorService 线程池去执行,执行的任务就是 LongPollingRunnable,每个任务都有一个 taskId。
现在我们来看看 LongPollingRunnable 做了什么,主要分为两部分,第一部分是检查本地的配置信息,第二部分是获取服务端的配置信息然后更新到本地。
1.本地检查
首先取出与该 taskId 相关的 CacheData,然后对 CacheData 进行检查,包括本地配置检查和监听器的 md5 检查,本地检查主要是做一个故障容错,当服务端挂掉后,Nacos 客户端可以从本地的文件系统中获取相关的配置信息,如下图所示:
通过跟踪 checkLocalConfig 方法,可以看到 Nacos 将配置信息保存在了~/nacos/config/fixed-{address}_8848_nacos/snapshot/DEFAULT_GROUP/{dataId}
这个文件中,我们看下这个文件中保存的内容,如下图所示:
2.服务端检查
然后通过 checkUpdateDataIds() 方法从服务端获取那些值发生了变化的 dataId 列表,
通过 getServerConfig 方法,根据 dataId 到服务端获取最新的配置信息,接着将最新的配置信息保存到 CacheData 中。
最后调用 CacheData 的 checkListenerMd5 方法,可以看到该方法在第一部分也被调用过,我们需要重点关注一下。
可以看到,在该任务的最后,也就是在 finally 中又重新通过 executorService 提交了本任务。
好了现在我们可以为 ConfigService 来添加一个 Listener 了,最终是调用了 ClientWorker 的 addTenantListeners 方法,如下图所示:
该方法分为两个部分,首先根据 dataId,group 和当前的场景获取一个 CacheData 对象,然后将当前要添加的 listener 对象添加到 CacheData 中去。
也就是说 listener 最终是被这里的 CacheData 所持有了,那 listener 的回调方法 receiveConfigInfo 就应该是在 CacheData 中触发的。
我们发现 CacheData 是出现频率非常高的一个类,在 LongPollingRunnable 的任务中,几乎所有的方法都围绕着 CacheData 类,现在添加 Listener 的时候,实际上该 Listener 也被委托给了 CacheData,那我们要重点关注下 CacheData 类了。
首先让我们来看一下 CacheData 中的成员变量,如下图所示:
可以看到除了 dataId,group,content,taskId 这些跟配置相关的属性,还有两个比较重要的属性:listeners、md5。
listeners 是该 CacheData 所关联的所有 listener,不过不是保存的原始的 Listener 对象,而是包装后的 ManagerListenerWrap 对象,该对象除了持有 Listener 对象,还持有了一个 lastCallMd5 属性。
另外一个属性 md5 就是根据当前对象的 content 计算出来的 md5 值。
现在我们对 ConfigService 有了大致的了解了,现在剩下最后一个重要的问题还没有答案,那就是 ConfigService 的 Listener 是在什么时候触发回调方法 receiveConfigInfo 的。
现在让我们回过头来想一下,在 ClientWorker 中的定时任务中,启动了一个长轮询的任务:LongPollingRunnable,该任务多次执行了 cacheData.checkListenerMd5() 方法,那现在就让我们来看下这个方法到底做了些什么,如下图所示:
到这里应该就比较清晰了,该方法会检查 CacheData 当前的 md5 与 CacheData 持有的所有 Listener 中保存的 md5 的值是否一致,如果不一致,就执行一个安全的监听器的通知方法:safeNotifyListener,通知什么呢?我们可以大胆的猜一下,应该是通知 Listener 的使用者,该 Listener 所关注的配置信息已经发生改变了。现在让我们来看一下 safeNotifyListener 方法,如下图所示:
可以看到在 safeNotifyListener 方法中,重点关注下红框中的三行代码:获取最新的配置信息,调用 Listener 的回调方法,将最新的配置信息作为参数传入,这样 Listener 的使用者就能接收到变更后的配置信息了,最后更新 ListenerWrap 的 md5 值。和我们猜测的一样, Listener 的回调方法就是在该方法中触发的。
那 CacheData 的 md5 值是何时发生改变的呢?我们可以回想一下,在上面的 LongPollingRunnable 所执行的任务中,在获取服务端发生变更的配置信息时,将最新的 content 数据写入了 CacheData 中,我们可以看下该方法如下:
可以看到是在长轮询的任务中,当服务端配置信息发生变更时,客户端将最新的数据获取下来之后,保存在了 CacheData 中,同时更新了该 CacheData 的 md5 值,所以当下次执行 checkListenerMd5 方法时,就会发现当前 listener 所持有的 md5 值已经和 CacheData 的 md5 值不一样了,也就意味着服务端的配置信息发生改变了,这时就需要将最新的数据通知给 Listener 的持有者。
至此配置中心的完整流程已经分析完毕了,可以发现,Nacos 并不是通过推的方式将服务端最新的配置信息发送给客户端的,而是客户端维护了一个长轮询的任务,定时去拉取发生变更的配置信息,然后将最新的数据推送给 Listener 的持有者。
客户端拉取服务端的数据与服务端推送数据给客户端相比,优势在哪呢,为什么 Nacos 不设计成主动推送数据,而是要客户端去拉取呢?如果用推的方式,服务端需要维持与客户端的长连接,这样的话需要耗费大量的资源,并且还需要考虑连接的有效性,例如需要通过心跳来维持两者之间的连接。而用拉的方式,客户端只需要通过一个无状态的 http 请求即可获取到服务端的数据。
Nacos 服务端创建了相关的配置项后,客户端就可以进行监听了。
客户端是通过一个定时任务来检查自己监听的配置项的数据的,一旦服务端的数据发生变化时,客户端将会获取到最新的数据,并将最新的数据保存在一个 CacheData 对象中,然后会重新计算 CacheData 的 md5 属性的值,此时就会对该 CacheData 所绑定的 Listener 触发 receiveConfigInfo 回调。
考虑到服务端故障的问题,客户端将最新数据获取后会保存在本地的 snapshot 文件中,以后会优先从文件中获取配置信息的值。
这边参考:https://www.jianshu.com/p/38b5452c9fec,大部分和Spring Cloud Alibaba中一致。
Spring Cloud Config项目是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并依据此数据初始化自己的应用。在微服务架构中,通常会使用轻量级的消息代理来构建一个共用的消息主题来连接各个微服务实例,它广播的消息会被所有在注册中心的微服务实例监听和消费,也称消息总线。SpringCloud中也有对应的解决方案,SpringCloud Bus 将分布式的节点用轻量的消息代理连接起来,可以很容易搭建消息总线,配合SpringCloud config 实现微服务应用配置信息的动态更新。
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持。使用Config Server,您可以为所有环境中的应用程序管理其外部属性。它非常适合spring应用,也可以使用在其他语言的应用上。随着应用程序通过从开发到测试和生产的部署流程,您可以管理这些环境之间的配置,并确定应用程序具有迁移时需要运行的一切。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。
Spring Cloud Config服务端特性:
原文:https://www.cnblogs.com/dalianpai/p/14311014.html