作者 | 姜桥
出品 | CSDN云计算(ID:CSDNcloud)
前面的文章<<干货|如何步入Service Mesh微服务架构时代>>实战演练了Service Mesh微服务架构的具体玩法,该案例中通过Istio+Kubernetes的组合,一组以Spring Boot框架开发的服务,在自身没有实现任何服务注册发现逻辑的情况下,被部署到Kubernetes后便能通过服务名直接完成服务接口调用,并且还能对调用进行限流、熔断及负载均衡等一系列服务治理操作。
这一切是怎么发生的呢?在今天介绍Service Mesh微服务架构有关服务注册发现原理的文章中将为大家揭晓答案。
传统微服务注册中心实现机制
在阐述Service Mesh服务注册发现机制前,先简单回顾下在以Spring Cloud为代表的传统微服务中是如何实现服务注册与发现的。
传统微服务体系中,注册中心无疑是整个微服务体系最重要的组成部分,没有服务注册中心提供的统一服务注册发现功能,微服务本身就无从谈起。不过相较于Service Mesh架构中服务注册发现,很大程度上是依赖于基础设施(例如数据面[envoy]、控制面[istio]以及Kubernetes集群),而无需微服务亲力亲为。在Spring Cloud传统架构中,服务注册、发现及健康性检查等服务治理逻辑则都需要微服务自己上下打点。
虽然不用重复造轮子,都有现成的服务治理组件及框架,但从应用运行形态上说,与服务注册发现相关的逻辑都是微服务直接与注册中心产生的交互。从系统架构上看,这种方式显然是将服务治理逻辑与业务应用耦合了,其运行逻辑如下图所示:
从上图可以看到,微服务启动时会通过集成的服务发现SDK,向注册中心(应用配置的注册中心地址)发送注册信息,注册中心在收到服务注册请求后将存储服务的基本信息;之后,如有服务消费者要调用该服务,那么调用方通过服务发现组件(例如Ribbon)就可以向注册中心查询目标微服务的地址列表,并通过获取的服务地址列表,以某种负载策略向目标微服务发起调用了。
而当服务节点发现变更时,新的节点会被重新注册,而下线的节点基于服务探活机制也会及时从服务注册中心被踢除,基于这样的服务注册/发现机制,微服务之间的通信顺畅就能得到保证。从这个角度看,注册中心与服务之间的健康性检查就显得很重要,如果注册中心不能及时将下线或故障的节点从可用服务器地址列表剔除,那么就很可能会造成微服务某些调用的失败。
那么一般怎样进行服务健康性检查呢?从方式上看,主流的健康性检查方式,主要有以下3种:
1、微服务主动探活
服务主动探活就是微服务定期向注册中心发送租约信息以表面自己的存活。在实际场景中主动探活是我们使用注册中心时用得最多的一种方式,如果服务规模不大,或者使用了类似于Eureka这样的最终一致性注册中心,那么主动探活就是一种最佳选择,它可以较大程度地避免服务部署在Kubernetes集群后,因为Pod IP重用而导致的旧有服务节点依然存活的问题,毕竟续约信息都是带着服务基础信息上报到注册中心的。
但这种方式也有明显的弊端,它会造成注册中心写操作压力变大。如果大量的服务同时发布,节点产生较大的变动,那么就可能产生大量的通知事件,从而对整个微服务体系的稳定产生较大影响。
此外,主动续约,也并不完全说明服务是健康的,因为某些特殊情况下可能会存在虽然服务无法对外提供服务,但还能正常向注册中心发出租约信息的问题。
2、注册中心发起健康性检查
前面分析了微服务主动探活方式的优缺点,而如果由注册中心发起健康性检查会怎么样呢?
这种方式是指微服务在注册时,同时向注册中心暴露自己的健康检查端点(如/actuator/health),注册中心通过定期访问来探测服务节点是否存活。
不过这种方式也不是完全没有问题,例如前面提到的Pod IP重用问题,如果其他微服务重用了之前节点的IP,那么就会发生失效节点被激活的假象。当然也有相应的解决方案,例如后面会讲到Istio微服务注册发现时Envoy会对服务名称进行二次Check的方式。
3、调用方负载均衡器进行健康性检查
第3种方案比较极端,注册中心不进行任何服务探活,全部由微服务调用方所在的负载均衡器进行探活。这种方案常见的一种场景就是gRPC的失效节点自动摘除功能。但这种方案依然具有IP被重用问题,所以此种方式在实际的场景中并不是很受欢迎。
前面讲了微服务注册发现常见的三种服务探活方式,其实三种方案各有利弊。以Spring Cloud微服务体系中,使用最广泛的几种注册中心为例,如Eureka、Consul、Nacos,它们的对比如下表所示:
从表格中可以看到,除了Eureka主要采用服务主动探活方式外,Consul及Nacos都采用了多种服务探活方式,从而尽量规避不同方式的弊端,这也是为什么目前大部分实践逐步抛弃Eureka而采用Consul或Nacos的原因。
除了健康性检查外,上面表格还分别列出了这几种注册中心的一致性协议。这里顺带普及下CAP理论。CAP是分布式系统中重要的一种概念,它分别由一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)三部分组成。在CAP理论中,一个分布式系统不可能三种都满足,一般只能同时满足其中的两种,例如Eureka和Nacos只能满足可用性和分区容错性,而无法满足一致性;Consul则只能满足一致性和分区容错性,而无法完全满足可用性。
在注册中心的场景中,一致性一般要求并不高,只要能达到最终一致性即可。毕竟在微服务架构中,涉及节点的注册和反注册,注册中心和客户端之间的通信需要一定时间,一致性本身也很难达到。所以在注册中心的选型中,一般会优先选择AP的系统,这也是目前还在以Spring Cloud构建微服务的实践中,除了自研外,开源技术中会优先选择Nacos作为服务注册中心的原因。
Kubernetes服务注册与自动发现
前面以一定篇幅回顾了Spring Cloud传统微服务体系中,注册中心有关的基本逻辑及常见开源技术。在具体讲述Service Mesh架构中服务注册发现的逻辑前,有必要先了解下Kubernetes容器编排中,与Service服务资源有关的概念。
因为最流行的Service Mesh方案(如Istio),大都选择了与Kubernetes集群相结合的方案,而其服务注册逻辑也主要是利用了Kubernetes的内部服务发现机制。例如Istio就是通过监听Kubernetes Pod的变化来实现服务发现的。当然这并不是说在Service Mesh中不能选择传统的注册中心方案,只不过实施起来可能需要改造或自研注册中心才能满足需求(兼容新旧微服务体系时,可能会这么考虑)。如果是全新设计的Service Mesh微服务架构,最佳的方案还是选择像Istio这样直接利用Kubernetes自身功能实现服务发现。
在Kubernetes中能够为Service Mesh微服务提供内部服务注册发现的基础就是Service类型的资源。Service是Kubernetes抽象出来的服务概念,一组Pod的集合就是Kubernetes的Service。在Kubernetes中Pod是最小的容器编排单元,一个Pod编排资源中可以定义多个容器,Pod内的容器可以通过本地访问在Pod中通信,一组容器共享一个Pod IP,这就是Kubernetes之所以被称为容器编排平台最核心的体现。
但Pod是有生命周期的,也就是说Pod的IP地址并不固定,它会随着Pod生命周期的变化而变化,所以如果以Pod为服务调用对象,那么IP地址的经常变化会导致调用方服务发现逻辑不稳定,从而使系统变得复杂。为了解决这个问题,Kubernetes中就抽象出了Service资源类型,虽然Pod的IP地址会变化,但是抽象的Service名称却是固定的,所以Kubernetes集群中通过Service名称就能访问这些后端IP,调用方则不用再直接感知这些后端Pod IP的变化了。具体Pod与Service之间的映射关系,则完全由Kubernetes集群本身来实现。它们之间的关系如下图所示:
如上图所示,现在要负载均衡地访问一组相同的服务副本——订单,通过Service资源类型的定义,就可以将其对外表示成一个进程或服务资源对象,Kubernetes会为其分配集群内的固定IP地址,之后通过请求Service(集群内用名称、集群外可通过Ingress或NodePort方式暴露),Service自己就会负载均衡地将请求转发给相应的Pod节点。
Service资源与Pod编排对象之间的关联,虽说从实现上是由Kubernetes集群自己管理的,但在服务发布时则是需要我们自己通过资源定义进行关联。以一段Kuberntes发布代码为例:
apiVersion: v1
kind: Service
metadata:
name: micro-order
labels:
app: micro-order
service: micro-order
spec:
type: ClusterIP
ports:
- name: http
#此处设置80端口的原因在于改造的Mock FeignClient代码默认是基于80端口进行服务调用
port: 80
targetPort: 9091
selector:
app: micro-order
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: micro-order-v1
labels:
app: micro-order
version: v1
spec:
replicas: 2
selector:
matchLabels:
app: micro-order
version: v1
template:
metadata:
labels:
app: micro-order
version: v1
spec:
containers:
- name: micro-order
image: 10.211.55.2:8080/micro-service/micro-order:1.0-SNAPSHOT
imagePullPolicy: Always
tty: true
ports:
- name: http
protocol: TCP
containerPort: 19091
#环境参数设置(设置微服务返回gRPC服务端的地址+端口)
env:
- name: GRPC_SERVER_HOST
value: micro-pay
- name: GRPC_SERVER_PORT
value: "18888"
如上述Kubernetes发布文件中高亮的代码,在定义Pod编排对象时通过metadata标签定义了这一组Service的label,然后通过selector标签指定响应的标签,这样Service就可以访问带有这组标签定义的Pod集合了。
以上就是Kubernetes实现服务注册发现基本原理,其中涉及的逻辑将被利用在Service Mesh微服务平台Istio的设计实现中。
Istio服务注册发现
接下来我们以Istio(基于istio1.9架构)为代表的Service Mesh架构为例,看看它是如何实现服务注册发现的。经过前面的铺垫,相信你多少已经了解了一点Istio实现服务注册发现的基本原理:“通过监听Kubernetes节点变化及Service资源类型实现”。
接下来我们将进一步细化它,从运行逻辑的视角来分析下在Istio中控制面与数据面是如何配合实现微服务注册发现的。具体如下图所示:
上图所描述的就是Istio基于Kubernetes实现微服务注册发现的核心逻辑。Kubernetes集群环境安装Istio后,Kubernetes在创建Pod时,就会通过Kube-APIserver调用控制面组件的Sidecar-Injector服务、自动修改应用程序的描述信息并将其注入SideCar,之后在创建业务容器的Pod中同时创建SideCar代理容器。SideCar则会通过xDS协议与Istio控制面各组件连接,这里我们着重介绍Pilot控制面组件实现服务发现的内容。
首先明确的是在新版的Istio中,服务发现的逻辑已经上移至控制面组件Pilot,Pilot会监听Kube-APIServer中关于Service、Endpoint、Pod等资源的变化,并实时下发给各微服务SideCar中的Pilot Agent代理组件。这个过程是通过xDS标准协议下发的,而下发的基本逻辑是Envoy启动时Pilot会把所有服务实例信息推送给Envoy,后续则是有更新就推送更新数据至所有Envoy,这样各个微服务的Envoy就能实时感知微服务实例节点的变化了。
这种方式保证各个微服务的Envoy代理能随时感知到Kubernetes集群中服务节点的变化,也天然实现了微服务的健康性检测逻辑,当然为了防止Pod IP重用的问题,Envoy在接收Pilot推送的变动实例信息时也会对服务名称进行二次Check,如果发现IP所对应的Service名称与之前不一致,则及时更新本地数据信息,防止调用出错。
通过上述服务发现系统的支持,服务消费方在调用目标微服务时,流量会被本Pod中的Envoy代理自动劫持,此时Envoy就会根据自身存储的服务实例映射信息,及控制面配置下发的服务治理规则,对调用执行负载均衡、熔断、限流等服务治理规则。
这就是以Istio为代表的Service Mesh微服务架构实现服务注册发现的基本逻辑,可以看到Envoy数据面与Pilot控制面组件的配合自动实现了服务发现逻辑,而这一切对微服务本身来说都是无感知的!
后记
本篇文章简单总结和概述了从传统微服务架构到Service Mesh有关服务注册发现逻辑的变化,也进一步从侧面也感受到了以Istio为代表的Service Mesh微服务架构,在架构设计理念上的进步。希望本篇文章的内容能够对你有所启发,并进一步打开你进入Service Mesh微服务架构时代的窗户,在后面的文章中我们还会进一步分享与Service Mesh原理和实践相关的知识,敬请期待!
参考文档:
#Istio 1.9流量管理官方文档(英文版)
https://istio.io/latest/docs/concepts/traffic-management/#introducing-istio-traffic-management
#Istio 1.9流量管理官方文档(中文版)
https://istio.io/latest/zh/docs/concepts/traffic-management/
#Sidecar资源隔离配置参考文档
https://istio.io/latest/docs/reference/config/networking/sidecar
作者简介:
姜桥 毕业于长春工业大学,曾就职于摩拜单车,设计研发日订单千万级支付系统;现任职于马蜂窝旅行网,负责业务系统的微服务架构转型及中台系统。
今晚(2021.4.16)19:30,软考工程师备考上分攻略,提升你的复习战斗技巧!
立即扫码进群,预约直播,拿超级福利大礼包!
更多精彩推荐
☞5G、射频、奥特曼,这仨有联系吗?☞继云计算巨头失火后,微软决定送数据中心去“泡澡”!☞有没有不用加班的程序员?如何衡量程序员的工作量?
点分享点收藏点点赞点在看