微服务架构组件总结篇
一. 概述
在早期介绍了有关 spring cloud 中的注册中心,配置中心,负载均衡,熔断器等有关内容。借此机会,尝试从设计者的角度来总结这四个组件。
二. 配置中心
2.1 功能
2.1.1 配置管理
既然是配置中心,首当其冲就是对配置内容进行管理。配置管理首要考虑的是数据模型以及存储方式;
数据模型——采用的是 Key-Value 的文本形式。
配置分类——对 Key-Value 在进行分类管理,至于 Value 是什么类型,取决于对配置内容的解析。
在业务系统中的常规配置分类如下;
在配置中心模型如下:
作用域——在分类管理下,出现重复的 Key-Value 场景时,就需要确定其优先级——>全局,局部。
存储方式——有数据库,文件,redis 等形式多样的存储系统来存放配置内容。在 spring-cloud-config 中默认采用的是 git 来管理。
2.1.1.1 多环境管理
由于不同的环境有不同的配置,那么在数据模型中支持多环境处理。如下图:
2.1.1.2 版本管理 &权限管理
版本管理——可以看到配置的历史修改记录,可以在排查问题时用来追溯。
权限管理——可以避免被恶意的修改。
由于 spring-cloud-config-server 并没有提供把配置的维护交给 git 来操作了;所以,git 中已经有版本管理和权限管理等特性,所以 config-server 不需要实现该特性。
2.1.1.3 协议
业务系统与配置中心的交互基于 TCP 交互,在 spring-cloud-config 是采用 http 协议进行交互;
其具体的请求是 GET 方式,格式如下:
响应报文结构如下:
2.1.1.4 小结
在 spring-cloud-config 中,只提供了配置的拉取服务,配置管理默认是交给 git 来维护。当 config-server 会向 git 拉取对应的配置文件放到本地文件系统;
在 config-server 中,有关 git 的管理。config-server 支持多仓库、动态仓库等特性。
多仓库——通过模式匹配到一个动态仓库。
动态仓库——通过占位符的形式替换目标仓库
举个例子来说一下:
当客户端系统的名称是 payment-reconciliation,向服务端请求对应的配置时。服务端匹配到第二个动态仓库,接着对 url 进行替换后,就可以得到 git 真实仓库地址:https://**payment/**/payment-reconciliation/{profiles}/{label}.git。至于其中 profiles 以及 label 也是同样得操作替换。如果没有这些占位符,则不用替换。
当拿到 git 真实仓库地址后,config-server 就会向 git 拉取工程文件到本地文件系统中。工程得层级目录五花八门,那么 config-server 是如何匹配对应得目录以及对应的文件的呢?
这里有这么一个属性 searchPath,通过该属性来匹配到对应的目录,也可以理解为该变量就是表明工程的层级目录结构。其逻辑较为简单,如下:
拿到目录后,就是访问该目录对应的文件,那么对应的文件规则如下:
经过上面的介绍,由此可以得知,config-server 对配置结构的管理,可谓是非常灵活。下面提供一下常规的结构,如下图:
对应的配置,如下:
思考问题:当数据模型如下图时,config-server 该如何配置,客户端才能红色圈出来的配置文件呢?
2.1.2 动态更新
配置内容变更
当配置中心的配置内容发生了变更,对应的业务系统需要有所感知,从而刷新业务系统的本地配置。
在 spring-cloud-config 中,由于配置变更是在 git 上操作的,config-server 是无法感知的。所幸 git 有提供一个特性 webhook,当配置变更时可以回调指定的接口,从而告知 config-server 配置已经发生了变更;
问题由来了,config-client(也就是业务系统)是如何感知的呢?有三种方案,如下:
方案一:业务系统定时去拉取,当发现 config.client.state 值变了,意味着配置已经发生变更。这一块需要定制开发。主要是拉取 git 配置的时候 stage 始终为 null。
方案二:服务端发送请求来告知,意味着服务端需要存放所有业务系统的相关信息。这个方案对于 config-server 架构有点重,而且支持的 MQ 只有 kafka 以及 rabbitmq。不再其考虑范畴内;
方案三:通过 MQ 来监听。如下图。
个人比较倾向方案一。
基于方案一,梳理的交互如下:
刷新配置——借助于 @RefreshScope 注解来实现,其实现原理是基于 spring-context 中的 scope 特性来实现。
2.1.3 加解密
当有一些敏感配置信息,例如密码等,如果直接在 git 上维护明文,容易被泄露。因此,spring-cloud-config 服务端提供了解密环节,也就是我们直接维护加密后的密文到 git 对应的工程,然后服务端进行解密。虽然用处不大,但有一定的防止敏感信息被泄露。解密的操作在 EnvironmentEncryptor 类,然后委托给 CipherEnvironmentEncryptor 进行解密;
2.2 客户端
客户端的相对于较为简单;具体交互图如下:
具体的配置如下:具体可以查看 ConfigClientProperties 类
2.3 服务端
在功能小节说,对服务端介绍了差不多,这里只罗列时序图即可。如下;
2.4 部署架构
在早期文章也介绍了,个人倾向直接的单机部署,适当的定制开发,增加缓存机制。再加上系统运行监控即可。
三. 负载均衡
3.1 集成方式
当下游系统或者中间件有多台服务器时,意味着就需要一个模块,如何选择出合适的目标服务器去处理请求。那么这个模块具体落在哪一测,具体得根据
客户端路由
服务端路由
代理端路由,也可以叫做网关,也可以叫做企业总线
留下一个思考问题:在公司层面做架构规划时,该如何抉择呢?
3.2 负载算法分类
OS 负载
TCP 负载
请求报文负载
IP 负载
3.3 代码
有关很多的细节,在早期的文章已经有所介绍,这里只罗列关键的接口之间的交互,如图所示:
四. 注册中心
4.1 基础功能
服务注册
当业务系统启动时,立即注册或者延迟注册当前实例信息到注册中心,注册中心接收到该请求,会将其保存起来。
心跳检测
当业务系统在运行期间,会不断的告知注册中心,业务系统正常运行着。避免注册中心认为该业务系统宕机,将其踢下线。这里面有两种实现方式,一种是客户端定时发送心跳信息,另外一种是服务端(注册中心)发送心跳请求给到客户端。由于后者方案要实现就必须依赖客户端提供服务接口,这样子对于客户端的侵入性太强,另外服务端的职责边界就有所大,同时也会增加服务端的压力,以及复杂度。所以,个人认为采用客户端主动发起心跳的方案较为友好。
服务发现
通俗的说,也叫拉取服务列表。既然注册中心记录着业务系统的相关服务信息,那么就会对应有拉取服务的接口,提供给客户端(业务系统)拉取服务。拉取服务列表,不会每次拉取全量的信息,这样子会导致网络拥堵。所以注册中心提供了两种方式,一种是全量拉取,另外一种是增量拉取。
4.2 特性
自我保护机制
由于网络的不稳定,大量的业务系统发送心跳的请求会出现短暂的超时,从而导致服务端认为其已经宕机,会将其踢下线。因此服务端为了这种情况,做了自我保护。逻辑如下:
具体有关的阈值计算逻辑如下:
分类管理
在 eureka-server 中提出两个概念,一个 region,另外一个是 zone;region 所指定的范围大于 zone 的。可以通俗的理解为 region 是国家,zone 是省份;在计算领取,可以理解为 region 是地区,例如华东地区,华北地区等;而 zone 是华北地区的北京机房等;
然而服务实例并没有强制与 region,zone 绑定关系,需要额外配置当前实例属于哪个 region,zone 才行。这样子便于在负载时可以优先使用,例如:
4.3 业务系统
4.3.1 服务消费者
消费者在启动时,可以采取立即拉取依赖的服务列表或者延迟拉取。这个是拉取动作是全量拉取。然而在 eureka 框架中,并不是只拉取当前业务系统自己依赖的服务列表,而是拉取注册中心的所有服务实例列表。
启动后,依赖的服务实例状态有可能会变更或者被踢下线,那么就需要后台任务来定期拉取。如果每次拉取全量的列表,无疑会导致网络拥堵,因此提供出增量拉取。
有关拉取远程服务的逻辑,大体如下:
所涉及的配置信息如下:
4.3.2 服务提供者
当作为服务的提供方,那么在服务启动时就需要向注册中心注册当前服务实例,从而被其他上游服务所感知到。
当运行时,需要不停向注册中心,当前服务实例还存活者,可以被调用。具体的流程图:
所涉及的配置信息如下:
4.3.3 网络传输
服务消费者以及服务提供者都需要与注册中心交互,首先就需要配置注册中心的服务地址信息,以及网络交互等其他细节控制,具体配置参数如下:
4.4 注册中心
由于 eureka 采用的去中心化,也就是注册中心即是客户端也是服务端。有关客户端的可以查看上一节的内容;
4.4.1 自我保护
在特性中介绍了其逻辑,有关的配置事项如下:
4.4.2 服务实例剔除
注册中心会定期将过期的服务提供者给剔除出去,逻辑在自我保护中有粗略介绍。这里罗列相关的可配置的参数:
4.4.3 服务列表拉取
提供给服务消费者拉取服务实例列表,这里有全量以及增量。
全量——拉取的逻辑如下图:
增量——大体逻辑跟全量所列的逻辑图差不多。稍微特殊,其数据来源是来自 recentlyChangedQueue。
虽然服务实例信息也是采用内存存放,但获取服务列表时还要经过一层数据转换,所以增加缓存。
如果开启了一级缓存,也就是 readOnlyCacheMap 属性。这一块会起一个定时任务,定期会将二级缓存(readWriteCacheMap 属性)的内容同步到一级缓存中去。
增量的数据来源是 recentlyChangedQueue 集合,这个数据是一直在追加。容易导致信息堆积,这个时候就需要定时去清理这个数据;清理的也较为简单,只要找过指定的时间就将其丢弃调;
这里面所涉及到的可配置的信息如下:
4.4.4 数据同步
为了高可用,需要多台注册中心来配合。这个时候就需要数据同步的操作;
同步到集群中的其他目标节点,是复用业务系统配置的配置信息,如下:
数据同步动作是采用异步的,但其实现方式有两种,如下:
批处理器——每个目标节点默认有 1 个 Acceptor 线程+20 个 Process 线程。具体的逻辑如下:
单处理器——每个目标节点默认有 1 个 Acceptor 线程+1 个 Process 线程。逻辑与批处理有所类似。
之所以需要同时出现这两种,是为了区别对待,也可以理解优先级的问题,优先级高的交给单处理器,优先级低的交给批处理器。也就是人工手动修改服务实例状态(statusUpdate)的,则优先处理,那么其就会交给单处理器来处理。
所涉及的可配置项如下:
目标节点动态更新
如果目标节点列表发生了变更,eureka 支持动态更新。那么就需要一个定时任务来检测并替换操作;具体的代码在 PeerEurekaNodes 类,所涉及可配置项如下:
4.4.5 限流
为了避免注册中心的压力过大,提供出了限流处理。其采用的令牌的实现方案,具体逻辑可以查看 RateLimiter 类
4.4.6 部署架构
在 CAP 模式中,之所以采用 AP 模式,个人感觉是出现短暂的数据不一致性对负载策略的使用并没有造成很大影响,而可用性以及分区容错性影响较低。而且当网络恢复后,数据会最终一致。
五. 熔断服务
当下游系统出现超时或者不可用时,避免出现服务雪崩的问题。很多细节已经在《Spring cloud 之 CircuitBreaker 篇》有所阐述。稍微补充一下有关配置的介绍。
六. 总结
通过再次的总结输出,也纠正了早期的对其的认知,对其四大组件的认知也更加深入。上面介绍的主要是关键特性,并没有对其额外的特性进行介绍,例如注册中心的 aws,DNS 等的使用。随着对代码的深入了解,发现其很多写法不是那么好友,不像 spring framework 那么纯粹,但其思想是具有参考价值的,可以通过对其了解,重复造更加轻量,更加灵活的轮子。一般来讲,中小型企业,直接使用就好,毕竟没有那么多的人力去重复造轮子,而针对大型企业来说,建议是基于 spring framework 框架,参考 spring boot, spring cloud 的思想,创建自己公司层面的技术框架。
版权声明: 本文为 InfoQ 作者【邱学喆】的原创文章。
原文链接:【http://xie.infoq.cn/article/16806e35d1f4f98aad6e744ec】。文章转载请联系作者。
评论