1 微服务优势与挑战
1.1 微服务的优势
1.1.1 单一职责
微服务架构中的每个节点高度服务化,都是具有业务逻辑的,符合高内聚、低耦合原则以及单一职责原则的单元,包括数据库和数据模型;
不同的服务通过“管道”的方式灵活组合,从而构建出庞大的系统。
1.1.2 轻量级通信
通过REST API模式或者RPC框架,事件流和消息代理的组合相互通信,实现服务间互相协作的轻量级通信机制。
1.1.3 独立性
在微服务架构中,每个服务都是独立的业务单元,与其他服务高度解耦,只需要改变当前服务本身,就可以完成独立的开发、测试、部署、运维。
1.1.4 进程隔离
在微服务架构中,应用程序由多个服务组成,每个服务都是高度自治的独立业务实体,可以运行在独立的进程中,不同的服务能非常容易地部署到不同的主机上,实现高度自治和高度隔离。
进程的隔离,还能保证服务达到动态扩缩容的能力,业务高峰期自动增加服务资源以提升并发能力,业务低谷期则可自动释放服务资源以节省开销。
1.1.5 混合技术栈和混合部署方式
团队可以为不同的服务组件使用不同的技术栈和不同的部署方式(公有云、私有云、混合云)。
1.1.6 简化治理
组件可以彼此独立地进行缩放,从而减少了因必须缩放整个应用程序而产生的浪费和成本,独立的发布、服务治理。
1.1.7 安全可靠,可维护。
从架构上对运维提供友好的支撑,在安全、可维护的基础上规范化发布流程,支持数据存储容灾、业务模块隔离、访问权限控制、编码安全检测等。
1.2 面临的挑战
1.2.1 分布式固有复杂性
微服务架构是基于分布式的系统,而构建分布式系统必然会带来额外的开销。
- 性能: 分布式系统是跨进程、跨网络的调用,受网络延迟和带宽的影响。
- 可靠性: 由于高度依赖于网络状况,任何一次的远程调用都有可能失败,随着服务的增多还会出现更多的潜在故障点。因此,如何提高系统的可靠性、降低因网络引起的故障率,是系统构建的一大挑战。
- 分布式通信: 分布式通信大大增加了功能实现的复杂度,并且伴随着定位难、调试难等问题。
- 数据一致性: 需要保证分布式系统的数据强一致性,即在 C(一致性)A(可用性)P(分区容错性) 三者之间做出权衡。这块可以参考我的这篇《分布式事务》。
1.2.2 服务的依赖管理和测试
在单体应用中,通常使用集成测试来验证依赖是否正常。而在微服务架构中,服务数量众多,每个服务都是独立的业务单元,服务主要通过接口进行交互,如何保证它的正常,是测试面临的主要挑战。
所以单元测试和单个服务链路的可用性非常重要。
1.2.3 有效的配置版本管理
在单体系统中,配置可以写在yaml文件,分布式系统中需要统一进行配置管理,同一个服务在不同的场景下对配置的值要求还可能不一样,所以需要引入配置的版本管理、环境管理。
1.2.4 自动化的部署流程
在微服务架构中,每个服务都独立部署,交付周期短且频率高,人工部署已经无法适应业务的快速变化。有效地构建自动化部署体系,配合服务网格、容器技术,是微服务面临的另一个挑战。
1.2.5 对于DevOps更高的要求
在微服务架构的实施过程中,开发人员和运维人员的角色发生了变化,开发者也将承担起整个服务的生命周期的责任,包括部署、链路追踪、监控;因此,按需调整组织架构、构建全功能的团队,也是一个不小的挑战。
1.2.6 运维成本
运维主要包括配置、部署、监控与告警和日志收集四大方面。微服务架构中,每个服务都需要独立地配置、部署、监控和收集日志,成本呈指数级增长。
服务化粒度越细,运维成本越高。
怎样去解决这些问题,是微服务架构必须面临的挑战。
2 微服务全景架构
3 微服务核心组件
微服务架构核心组件包括:
组件名 |
---|
组件名 |
服务注册与发现 |
API网关服务 |
分布式配置中心 |
服务通信 |
服务治理 |
服务监控 |
分布式服务追踪 |
3.1 服务注册与发现
3.1.1 原理图
服务注册与发现三要素:
- Provider:服务的提供方
- Consumer:调用远程服务的服务消费方
- Registry:服务注册和发现的注册中心
3.1.2 注册中心的原理、流程
1、 Provider(服务提供者)绑定指定端口并启动服务
2、提供者连接注册中心,并发本机 IP、端口、应用信息和服务信息发送至注册中心存储
3、Consumer(消费者),连接注册中心 ,并发送应用信息、所求服务信息至注册中心
4、注册中心根据消费者所求服务信息匹配对应的提供者列表发送至Consumer 应用缓存。
5、Consumer 在发起远程调用时基于缓存的消费者列表择其一发起调用。
6、Provider 状态变更会实时通知注册中心、在由注册中心实时推送至Consumer设计的原因:
Consumer 与 Provider 解偶,双方都可以横向增减节点数。注册中心对本身可做对等集群,可动态增减节点,并且任意一台宕掉后,将自动切换到另一台
7、去中心化,双方不直接依赖注册中心,即使注册中心全部宕机短时间内也不会影响服务的调用(Consumer应用缓存中保留提供者 Provider 列表)
8、服务提供者无状态,任意一台宕掉后,不影响使用
注册中心包含如下功能:注册中心、服务注册和反注册、心跳监测与汇报、服务订阅、服务变更查询、集群部署、服务健康状态检测、服务状态变更通知 等
我们有很多种注册中心的技术,Zookeeper、Etcd、Consul、Eureka 4种比较常用,如下
Zookeeper | Etcd | Consul | Eureka | |
---|---|---|---|---|
CAP模型 | CP | CP | CP | AP |
数据一致性算法 | ZAB | Raft | Raft | |
多数据中心 | ||||
多语言支持 | 客户端 | Http/gRPC | Http/DNS | Http |
Watch | TCP | LongPolling | LongPolling | LongPolling |
KV存储 | ||||
服务健康检查 | 心跳 | 心跳 | 服务状态,内存,硬盘等 | 自定义 |
自身监控 | metrics | metrics | metrics | |
SpringCloud支持 | ||||
自身开发语言 | Java | Go | Go | Java |
分布式系统中CAP模型3者不可兼得。由于网络的原因,分布式系统中P是必备的,意味着只能选择 AP 或者 CP。CP 代表数据一致性是第一位的,AP 代表可用性是第一位的。
Zookeeper、Etcd、Consul 是 CP 型注册中心,牺牲可用性来保证数据强一致性
Eureka 是 AP 型注册中心,牺牲一致性来保证可用性
3.2 API 网关服务
上面是Api网关服务的基本架构:用户的请求经过统一的Api网关来访问微服务里具体的服务颗粒,并且可能产生串联的链路服务调用。
有很多耳熟能详的API网关技术,比如 Zuul、Kong、Tyk等,提供了服务路由在内的很多通用功能,后面会有专门的章节来说这个。
Tyk:Tyk是一个开放源码的API网关,它是快速、可扩展和现代的。Tyk提供了一个API管理平台,其中包括API网关、API分析、开发人员门户和API管理面板。Try 是一个基于Go实现的网关服务。
Kong:Kong是一个可扩展的开放源码API Layer(也称为API网关或API中间件)。Kong 在任何RESTful API的前面运行,通过插件扩展,它提供了超越核心平台的额外功能和服务。
Netflix zuul:Zuul是一种提供动态路由、监视、弹性、安全性等功能的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。
除了路由之外,Api网关服务还包含:认证和授权,重试、熔断、降级,负载均衡,日志、监控、链路追踪,灰度发布,ABTesting 等功能。
3.3 配置中心
上面这个是携程的开源配置中心Apollo系统的架构设计,我们从下往上进行分析:
1、Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
2、Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
3、Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳,支持注册、更新、删除能力
4、在Eureka之上我们架了一层Meta Server用于封装Eureka的服务发现接口
5、Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧会做load balance、错误重试
6、Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance、错误重试
7、为了简化部署,我们实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM进程中
上面的架构体现了如下特点:
•高可用:配置服务为多实例部署,访问层保证 load balance、错误重试
•弱依赖:使用了Eureka来做配置中心的服务注册,如果出现问题或者网络出现问题的时候,服务应该可以依赖于它本身所缓存的配置来提供正常的服务
3.4 服务通信
分布式系统一般是由多个微服务颗粒组成的,微服务与微服务之前存在互相调用,甚至多个链路访问的情况。所以他们之间是需要通信的,通信方式继承于SOA,包含同步与异步两种模式。
3.4.1 同步访问方式
1、RPC 访问模式
Remote Procedure Call Protocol,远程过程调用协议,一般使用在分布式业务或者微服务架构风格中。像调用本地函数一样,去调用一个远端服务。
本质上是请求链的底层,维护同一个端口,进行socket通信。常见的RPC技术包含 gRPC、Dubbo、Thrift 等。
2、REST 访问模式
这个应该大家最常用,可以通过一套统一风格的接口模式,为Web,iOS和Android等提供接口服务。
3.4.2 异步访问方式
消息中间件:RabbitMQ、Kafka、RocketMQ之类,对于实时性要求不那么严格的服务请求和计算。
3.5 服务治理
常见的服务治理手段有如下几种:
3.5.1 节点管理
服务调用失败时可能是服务提供者自身出现,也可能是网络发生故障,我们一般有两种处理手段。
-
注册中心主动摘除机制
这种机制要求服务提供者定时向注册中心汇报心跳,如果超时,就认为服务提供者出现问题,并将节点从服务列表中摘除。 -
服务消费者摘除机制
当服务提供者网络出现异常,服务消费者调用就会失败,如果持续错误就可以将它从服务提供者节点列表中移除。
3.5.2 负载均衡
服务消费者在从服务列表中选取可用节点时,如果能让性能较好的服务机多承担一些流量的话,就能充分利用机器的性能。这就需要对负载均衡算法做一些调整。
常用的负载均衡算法主要包括以下几种:
-
Radom 随机算法
从可用的服务节点中随机选取一个节点。一般情况下,随机算法是均匀的,也就是说后端服务节点无论配置好坏,最终得到的调用量都差不多。 -
Round Robin 轮询算法(加权重)
就是按照固定的权重,对可用服务节点进行轮询。如果所有服务节点的权重都是相同的,则每个节点的调用量也是差不多的。但可以给性能较好的节点的权重调大些,充分发挥其性能优势,提高整体调用的平均性能。 -
Least Conn 最少活跃调用算法
这种算法是在服务消费者这一端的内存里动态维护着同每一个服务节点之间的连接数,选择连接数最小的节点发起调用,也就是选择了调用量最小的服务节点,性能理论上也是最优的。 -
一致性 Hash 算法
指相同参数的请求总是发到同一服务节点。当某一个服务节点出现故障时,原本发往该节点的请求,基于虚拟节点机制,平摊到其他节点上,不会引起剧烈变动。
3.5.3 服务路由
所谓的路由规则,就是通过一定的规则如条件表达式或者正则表达式来限定服务节点的选择范围。
制定路由规则主要有两个原因。
- 业务存在灰度发布、多版本ABTesting的需求
功能逐步开放发布或者灰度测试的场景。
- 多机房就近访问的需求
一般可以通过 IP 段规则来控制访问,在选择服务节点时,优先选择同一 IP 段的节点。这个也是算力靠近的优先原则。
3.5.4 服务容错
在分布式系统中,分区容错性是很重要的一个话题,要知道,服务间的调用调用并不总是成功,服务提供者程序bug、异常退出 或者 消费者与提供者之间的网络故障。而服务调用失败之后,我们需要一些方法来保证调用的正常。
常用的方式有以下几种:
FailOver 失败自动切换。就是服务消费者发现调用失败或者超时后,自动从可用的服务节点列表中选择下一个节点重新发起调用,也可以设置重试的次数。
FailBack 失败通知。就是服务消费者调用失败或者超时后,不再重试,而是根据失败的详细信息,来决定后续的执行策略。
FailCache 失败缓存。就是服务消费者调用失败或者超时后,不立即发起重试,而是隔一段时间后再次尝试发起调用。
FailFast 快速失败。就是服务消费者调用一次失败后,不再重试。
服务治理的手段是从不同角度来确保服务调用的成功率。节点管理是从服务节点健康状态角度来考虑,负载均衡和服务路由是从服务节点访问优先级角度来考虑,而服务容错是从调用的健康状态角度来考虑。
3.6 服务监控
常见的开发监控报警技术有 ELK、InfluxData的TICK、Promethues 等。
在分布式系统中,微服务一般都具有复杂的链路调用,对于链路之间的状态、服务可用性、调用情况的监控,是需要一套完整的服务监控系统去保障的。
如我们上面的那个图所示, 服务系统主要由哪几部分构成:
1、数据采集部分,包含性能指标信息、日志信息(一般是服务埋点日志或者sidecar的inbound、outbound信息)、端到端的Trace信息。
2、采集上来的监控数据通过传输系统,或者使用消息中间件来异步传输,或者调用服务端接口推送监控数据。并把这些数据持久化到我们的数据服务层中。
3、制定一套规则,对于采集到的数据进行清理、计算、分级等,处理好的数据,通过提前设置好的报警策略,来判断它是否触发了这些报警。
4、梳理完的数据可以进行查询展示(有一个日志查询界面)、分级报警、分析趋势报表推送等。
3.7 服务追踪
服务追踪的原理主要包括下面两个关键点。
1、为了实现请求跟踪,当请求发送到分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的跟踪标识,同时在分布式系统内部流转的时候,框架始终保持传递该唯一标识,直到返回给请求方为止,这个唯一标识就是前文中提到的 Trace ID。
通过 Trace ID 的记录,我们就能将所有请求过程的日志关联起来。
2、为了统计各处理单元的时间延迟,当请求到达各个服务组件时,或是处理逻辑到达某个状态时,也通过一个唯一标识来标记它的开始、具体过程以及结束,该标识就是前文中提到的 Span ID。对于每个 Span 来说,它必须有开始和结束两个节点,
通过记录开始 Span 和结束 Span 的时间戳,就能统计出该 Span 的时间延迟,除了时间戳记录之外,它还可以包含一些其他元数据,比如事件名称、请求信息等。
上图显示了Trace ID 和 Spand ID 在链路中的传输过程,它把服务调用的一个时序结构给展现出来了。
常见的服务链路追踪的技术有Zipkin、Pinpoint、SkyWalking 等。后面讲到Service Mesh的时候会详细说下Zipkin的x-b3 header头传递,以及流量染色的使用,非常给力。
4 总结
微服务架构提倡的单一应用程序划分成一组松散耦合的细粒度小型服务,辅助轻量级的协议,互相协调、互相配合,实现高效的应用价值,符合我们应用服务开发的发展趋势。
后续我们围绕它的核心模块:服务注册与发现、API 网关服务、分布式配置中心、服务通信、服务治理、分布式服务追踪与监控等,从原理到实践,一步步展开来研究。