This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
<audio id="audio" title="33 | 下一代微服务架构Service Mesh" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7b/35/7b7e85d6e112de5c18d605087e124235.mp3"></audio>
今天我们将进入专栏最后一个模块我会和你聊聊下一代微服务架构Service Mesh。说到Service Mesh在如今的微服务领域可谓是无人不知、无人不晓被很多人定义为下一代的微服务架构。那么究竟什么是Service MeshService Mesh是如何实现的今天我就来给你解答这些疑问。
## 什么是Service Mesh
Service Mesh的概念最早是由Buoyant公司的CEO William Morgan在一篇[文章](https://blog.buoyant.io/2017/04/25/whats-a-service-mesh-and-why-do-i-need-one/)里提出,他给出的服务网格的定义是:
>
A service mesh is a dedicated infrastructure layer for handling service-to-service communication. Its responsible for the reliable delivery of requests through the complex topology of services that comprise a modern, cloud native application. In practice, the service mesh is typically implemented as an array of lightweight network proxies that are deployed alongside application code, without the application needing to be aware.
专栏里我就不解释教条的定义了感兴趣的话你可以点击链接阅读原文这里我来谈谈我对Service Mesh的理解。我认为是Service Mesh是一种新型的用于处理服务与服务之间通信的技术尤其适用以云原生应用形式部署的服务能够保证服务与服务之间调用的可靠性。在实际部署时Service Mesh通常以轻量级的网络代理的方式跟应用的代码部署在一起从而以应用无感知的方式实现服务治理。
从我的理解来看Service Mesh以轻量级的网络代理的方式与应用的代码部署在一起用于保证服务与服务之间调用的可靠性这与传统的微服务架构有着本质的区别在我看来这么做主要是出于两个原因。
1.跨语言服务调用的需要。在大多数公司内通常都存在多个业务团队每个团队业务所采用的开发语言一般都不相同以微博的业务为例移动服务端的业务主要采用的是PHP语言开发API平台的业务主要采用的是Java语言开发移动服务端调用API平台使用的是HTTP请求如果要进行服务化改成RPC调用就需要一种既支持PHP语言又支持支持Java语言的的服务化框架。在专栏第14期我给你讲解了几种开源的服务化框架它们要么与特定的语言绑定比如Dubbo和Spring Cloud只支持Java语言要么是跟语言无关比如gRPC和Thrift得定义个IDL文件然后根据这个IDL文件生成客户端和服务端各自语言的SDK并且服务框架的功能比如超时重试、负载均衡、服务发现等都需要在各个语言的SDK中实现一遍开发成本很高。
2.云原生应用服务治理的需要。在专栏前面我给你讲解了微服务越来越多开始容器化并使用Kubernetes类似的容器平台对服务进行管理逐步朝云原生应用的方向进化。而传统的服务治理要求在业务代码里集成服务框架的SDK这显然与云原生应用的理念相悖因此迫切需要一种对业务代码无侵入的适合云原生应用的服务治理方式。
在这种背景下Buoyant公司开发的第一代Service Mesh产品[L](https://linkerd.io/)[inkerd](https://linkerd.io/)应运而生。从下图中你可以看到服务A要调用服务B经过Linkerd来代理转发服务A和服务B的业务代码不需要关心服务框架功能的实现。为此Linkerd需要具备负载均衡、熔断、超时重试、监控统计以及服务路由等功能。这样的话对于跨语言服务调用来说即使服务消费者和服务提供者采用的语言不同也不需要集成各自语言的SDK。
<img src="https://static001.geekbang.org/resource/image/26/70/269ca9676c98e6b9ed831d38fdd67d70.png" alt=""><br>
(图片来源:[https://linkerd.io/images/what_it_does@2x.png](https://linkerd.io/images/what_it_does@2x.png)
而对于云原生应用来说可以在每个服务部署的实例上都同等的部署一个Linkerd实例。比如下面这张图服务A要想调用服务B首先调用本地的Linkerd实例经过本地的Linked实例转发给服务B所在节点上的Linkerd实例最后再由服务B本地的Linkerd实例把请求转发给服务B。这样的话所有的服务调用都得经过Linkerd进行代理转发所有的Linkerd组合起来就像一个网格一样这也是为什么我们把这项技术称为Service Mesh也就是“服务网格”的原因。
<img src="https://static001.geekbang.org/resource/image/f4/2b/f4b75f2f1c4c72e51f626e331f06cf2b.png" alt=""><br>
(图片来源:[https://buoyant.io/wp-content/uploads/2017/04/linkerd-service-mesh-diagram-1024x587.png](https://buoyant.io/wp-content/uploads/2017/04/linkerd-service-mesh-diagram-1024x587.png)
## Service Mesh的实现原理
根据我的理解Service Mesh实现的关键就在于两点一个是上面提到的轻量级的网络代理也叫SideCar它的作用就是转发服务之间的调用一个是基于SideCar的服务治理也被叫作Control Plane它的作用是向SideCar发送各种指令以完成各种服务治理功能。下面我就来详细讲解这两点是如何实现的。
1.SideCar
我们首先来看一下,在传统的微服务架构下服务调用的原理。你可以看下面这张图,服务消费者这边除了自身的业务逻辑实现外,还需要集成部分服务框架的逻辑,比如服务发现、负载均衡、熔断降级、封装调用等,而服务提供者这边除了实现服务的业务逻辑外,也要集成部分服务框架的逻辑,比如线程池、限流降级、服务注册等。
<img src="https://static001.geekbang.org/resource/image/10/92/1070a53f237a8ef75845f49b71961292.png" alt="">
而在Service Mesh架构中服务框架的功能都集中实现在SideCar里并在每一个服务消费者和服务提供者的本地都部署一个SideCar服务消费者和服务提供者只管自己的业务实现服务消费者向本地的SideCar发起请求本地的SideCar根据请求的路径向注册中心查询得到服务提供者的可用节点列表后再根据负载均衡策略选择一个服务提供者节点并向这个节点上的SideCar转发请求服务提供者节点上的SideCar完成流量统计、限流等功能后再把请求转发给本地部署的服务提供者进程从而完成一次服务请求。整个流程你可以参考下面这张图。
<img src="https://static001.geekbang.org/resource/image/da/6d/daa8dee489e51238f450b51a53c1016d.png" alt="">
我们可以把服务消费者节点上的SideCar叫作正向代理服务提供者节点上的SideCar叫作反向代理那么Service Mesh架构的关键点就在于服务消费者发出的请求如何通过正向代理转发以及服务提供者收到的请求如何通过反向代理转发。从我的经验来看主要有两种实现方案。
- 基于iptables的网络拦截。这种方案请见下图节点A上服务消费者发出的TCP请求都会被拦截然后发送给正向代理监听的端口15001正向代理处理完成后再把请求转发到节点B的端口9080。节点B端口9080上的所有请求都会被拦截发送给反向代理监听的端口15001反向代理处理完后再转发给本机上服务提供者监听的端口9080。
<img src="https://static001.geekbang.org/resource/image/43/3e/438e7dbbd0fe9ba9434834837c712b3e.png" alt="">
- 采用协议转换的方式。这种方案请见下图节点A上的服务消费者请求直接发给正向代理监听的端口15001正向代理处理完成后再把请求转发到节点B上反向代理监听的端口15001反向代理处理完成后再发送给本机上的服务提供者监听的端口9080。
<img src="https://static001.geekbang.org/resource/image/c7/af/c7c5785653da63d2174714f66a7f88af.png" alt="">
可见这两种方案最大的不同之处在于一个是通过iptables网络拦截实现代理转发的一个是靠直接把请求发送给代理来转发的。基于iptables网络拦截的方式理论上会有一定的性能损耗但它的优点是从网络层实现调用拦截能做到完全的业务无感知所以适合云原生应用。而直接把请求发送给代理的方式要求代理层加入业务逻辑才能把请求转发给对应的服务提供者监听的端口。
2.Control Plane
既然SideCar能实现服务之间的调用拦截功能那么服务之间的所有流量都可以通过SideCar来转发这样的话所有的SideCar就组成了一个服务网格再通过一个统一的地方与各个SideCar交互就能控制网格中流量的运转了这个统一的地方就在Sevice Mesh中就被称为Control Plane。如下图所示Control Plane的主要作用包括以下几个方面
<li>
服务发现。服务提供者会通过SideCar注册到Control Plane的注册中心这样的话服务消费者把请求发送给SideCar后SideCar就会查询Control Plane的注册中心来获取服务提供者节点列表。
</li>
<li>
负载均衡。SideCar从Control Plane获取到服务提供者节点列表信息后就需要按照一定的负载均衡算法从可用的节点列表中选取一个节点发起调用可以通过Control Plane动态修改SideCar中的负载均衡配置。
</li>
<li>
请求路由。SideCar从Control Plane获取的服务提供者节点列表也可以通过Control Plane来动态改变比如需要进行A/B测试、灰度发布或者流量切换时就可以动态地改变请求路由。
</li>
<li>
故障处理。服务之间的调用如果出现故障就需要加以控制通常的手段有超时重试、熔断等这些都可以在SideCar转发请求时通过Control Plane动态配置。
</li>
<li>
安全认证。可以通过Control Plane控制一个服务可以被谁访问以及访问哪些信息。
</li>
<li>
监控上报。所有SideCar转发的请求信息都会发送到Control Plane再由Control Plane发送给监控系统比如Prometheus等。
</li>
<li>
日志记录。所有SideCar转发的日志信息也会发送到Control Plane再由Control Plane发送给日志系统比如Stackdriver等。
</li>
<li>
配额控制。可以在Control Plane里给服务的每个调用方配置最大调用次数在SideCar转发请求给某个服务时会审计调用是否超出服务对应的次数限制。
</li>
<img src="https://static001.geekbang.org/resource/image/74/62/746ba12fff2b184157b00ac44ec2e862.png" alt="">
## 总结
今天我给你讲解了什么是Service Mesh以及Service Mesh的实现原理。简单来说Service Mesh思想的孕育而生一方面出于各大公司微服务技术的普及增加了对跨语言服务调用的需求另一方面得益于微服务容器化后采用Kubernetes等云平台部署的云原生应用越来越多服务治理的需求也越来越强烈。Service Mesh通过SideCar代理转发请求把服务框架的相关实现全部集中到SideCar中并通过Control Plane控制SideCar来实现服务治理的各种功能这种业务与框架功能解耦的思想恰好能够解决上面两个问题。
Service Mesh在诞生不到两年的时间里取得令人瞩目的发展在国内外都涌现出一批具有代表性的新产品最著名的莫过于Google、IBM领导的Istio也是Service Mesh技术的代表之作我会在下一期给你详细讲解。而国内在这一方面也不遑多让秉承了Service Mesh的思想也走出了各自的实践之路并且已经开始在线上的核心业务中大规模使用比如微博的Weibo Mesh、华为公有云Service Mesh以及蚂蚁金服的SOFA Mesh等。
## 思考题
Service Mesh中SideCar的部署模式是在每个服务节点的本地都同等部署一个SideCar实例为什么不使用集中式的部署模式让多个服务节点访问一个SideCar实例
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@@ -0,0 +1,289 @@
<audio id="audio" title="34 | IstioService Mesh的代表产品" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a9/8b/a90925145ad679f481b0463647c5958b.mp3"></audio>
专栏上一期我们聊了Service Mesh并以Linkerd为例介绍了Service Mesh的架构。随着技术发展现在来看Linkerd可以说是第一代Service Mesh产品到了今天当我们再谈到Service Mesh时往往第一个想到的是[Istio](https://istio.io/)。为什么我认为Istio可以称得上是Service Mesh的代表产品呢在我看来主要有以下几个原因
<li>
相比LinkerdIstio引入了Control Plane的理念通过Control Plane能带来强大的服务治理能力可以称得上是Linkerd的进化算是第二代的Service Mesh产品。
</li>
<li>
Istio默认的SideCar采用了[Envoy](https://www.envoyproxy.io/)它是用C++语言实现的在性能和资源消耗上要比采用Scala语言实现的Linkerd小这一点对于延迟敏感型和资源敏感型的服务来说尤其重要。
</li>
<li>
有Google和IBM的背书尤其是在微服务容器化的大趋势下云原生应用越来越受欢迎而Google开源的Kubernetes可以说已经成为云原生应用默认采用的容器平台基于此Google可以将Kubernetes与Istio很自然的整合打造成云原生应用默认的服务治理方案。
</li>
现在我们一起走进Istio的架构看看它各部分的实现原理希望能让你有所收获。
## Istio整体架构
如下图所示Istio的架构可以说由两部分组成分别是Proxy和Control Plane。
<li>
Proxy就是前面提到的SideCar与应用程序部署在同一个主机上应用程序之间的调用都通过Proxy来转发目前支持HTTP/1.1、HTTP/2、gRPC以及TCP请求。
</li>
<li>
Control Plane与Proxy通信来实现各种服务治理功能包括三个基本组件Pilot、Mixer以及Citadel。
</li>
<img src="https://static001.geekbang.org/resource/image/00/84/00613758a46fe1341089ce11ef8a0f84.png" alt=""><br>
(图片来源:[https://istio.io/docs/concepts/what-is-istio/arch.svg](https://istio.io/docs/concepts/what-is-istio/arch.svg)
下面我来详细分解Istio架构看看每一个组件的作用和工作原理。
## Proxy
Istio的Proxy采用的是EnvoyEnvoy是跟上一期提到的Linkerd是同一代的产品既要作为服务消费者端的正向代理又要作为服务提供者端的反向代理一般需要具备服务发现、服务注册、负载均衡、限流降级、超时熔断、动态路由、监控上报和日志推送等功能它主要包含以下几个特性
<li>
性能损耗低。因为采用了C++语言实现Envoy能提供极高的吞吐量和极少的长尾延迟而且对系统的CPU和内存资源占用也不大所以跟业务进程部署在一起不会对业务进程造成影响。
</li>
<li>
可扩展性高。Envoy提供了可插拔过滤器的能力用户可以开发定制过滤器以满足自己特定的需求。
</li>
<li>
动态可配置。Envoy对外提供了统一的API包括CDS集群发现服务、RDS路由发现服务、LDS监听器发现服务、EDSEndPoint发现服务、HDS健康检查服务、ADS聚合发现服务等。通过调用这些API可以实现相应配置的动态变更而不需要重启Envoy。
</li>
Envoy是Istio中最基础的组件所有其他组件的功能都是通过调用Envoy提供的API在请求经过Envoy转发时由Envoy执行相关的控制逻辑来实现的。
## Pilot
Pilot的作用是实现流量控制它通过向Envoy下发各种指令来实现流量控制它的架构如下图所示。从架构图里可以看出Pilot主要包含以下几个部分
<li>
Rules API对外封装统一的API供服务的开发者或者运维人员调用可以用于流量控制。
</li>
<li>
Envoy API对内封装统一的API供Envoy调用以获取注册信息、流量控制信息等。
</li>
<li>
抽象模型层,对服务的注册信息、流量控制规则等进行抽象,使其描述与平台无关。
</li>
<li>
平台适配层用于适配各个平台如Kubernetes、Mesos、Cloud Foundry等把平台特定的注册信息、资源信息等转换成抽象模型层定义的平台无关的描述。
</li>
<img src="https://static001.geekbang.org/resource/image/4e/7e/4e78a1b7532df205939f7a4b0f7a047e.png" alt=""><br>
(图片来源:[https://istio.io/docs/concepts/traffic-management/PilotAdapters.svg](https://istio.io/docs/concepts/traffic-management/PilotAdapters.svg)
那么具体来讲Pilot是如何实现流量管理功能的呢
1.服务发现和负载均衡
就像下图所描述的那样服务B也就是服务提供者注册到对应平台的注册中心中去比如Kubernetes集群中的Pod启动时会注册到注册中心etcd中。然后服务A也就是服务消费者在调用服务B时请求会被Proxy拦截然后Proxy会调用Pilot查询可用的服务提供者节点再以某种负载均衡算法选择一个节点发起调用。
除此之外Proxy还会定期检查缓存的服务提供者节点的健康状况当某个节点连续多次健康检查失败就会被从Proxy从缓存的服务提供者节点列表中剔除。
<img src="https://static001.geekbang.org/resource/image/f4/07/f41656a5d6dbe6294adb984f03bffd07.png" alt=""><br>
(图片来源:[https://istio.io/docs/concepts/traffic-management/LoadBalancing.svg](https://istio.io/docs/concepts/traffic-management/LoadBalancing.svg)
2.请求路由
Pilot可以对服务进行版本和环境的细分服务B包含两个版本v1.5和v2.0-alpha其中v1.5是生产环境运行的版本而v2.0-alpha是灰度环境运行的版本。当需要做A/B测试时希望灰度服务B的1%流量运行v2.0-alpha版本就可以通过调用Pilot提供的Rules APIPilot就会向Proxy下发路由规则Proxy在转发请求时就按照给定的路由规则把1%的流量转发给服务B的v2.0-alpha版本99%的流量转发给服务B的v1.5版本。
<img src="https://static001.geekbang.org/resource/image/34/34/34376392a318948e6d6efa9ac61cbe34.png" alt=""><br>
(图片来源:[https://istio.io/docs/concepts/traffic-management/ServiceModel_Versions.svg](https://istio.io/docs/concepts/traffic-management/ServiceModel_Versions.svg)
3.超时重试
缺省状态下Proxy转发HTTP请求时的超时是15s可以通过调用Pilot提供的Rules API来修改路由规则覆盖这个限制。比如下面这段路由规则表达的意思是ratings服务的超时时间是10s。
```
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- route:
- destination:
host: ratings
subset: v1
timeout: 10s
```
除此之外还可以通过修改路由规则来指定某些HTTP请求的超时重试次数比如下面这段路由规则表达的意思就是ratings服务的超时重试次数总共是3次每一次的超时时间是2s。
```
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- route:
- destination:
host: ratings
subset: v1
retries:
attempts: 3
perTryTimeout: 2s
```
4.故障注入
Istio还提供了故障注入的功能能在不杀死服务节点的情况下通过修改路由规则将特定的故障注入到网络中。它的原理是在TCP层制造数据包的延迟或者损坏从而模拟服务超时和调用失败的场景以此来观察应用是否健壮。比如下面这段路由规则的意思是对v1版本的ratings服务流量中的10%注入5s的延迟。
```
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- fault:
delay:
percent: 10
fixedDelay: 5s
route:
- destination:
host: ratings
subset: v1
```
而下面这段路由规则意思是对v1版本的ratings服务流量中的10%注入HTTP 400的错误。
```
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- fault:
abort:
percent: 10
httpStatus: 400
route:
- destination:
host: ratings
subset: v1
```
## Mixer
Mixer的作用是实现策略控制和监控日志收集等功能实现方式是每一次Proxy转发的请求都要调用Mixer它的架构请见下图。而且Mixer的实现是可扩展的通过适配层来适配不同的后端平台这样的话Istio的其他部分就不需要关心各个基础设施比如日志系统、监控系统的实现细节。
<img src="https://static001.geekbang.org/resource/image/13/7c/1340fd4a4f05a977669aff367fc2697c.png" alt=""><br>
(图片来源:[https://istio.io/docs/concepts/policies-and-telemetry/topology-without-cache.svg](https://istio.io/docs/concepts/policies-and-telemetry/topology-without-cache.svg)
理论上每一次的服务调用Proxy都需要调用Mixer一方面检查调用的合法性一方面要上报服务的监控信息和日志信息所以这就要求Mixer必须是高可用和低延迟的那么Mixer是如何做到的呢下图是它的实现原理从图中你可以看到Mixer实现了两级的缓存结构
<li>
Proxy端的本地缓存。为了减少Proxy对Mixer的调用以尽量降低服务调用的延迟在Proxy这一端会有一层本地缓存但由于Proxy作为SideCar与每个服务实例部署在同一个节点上所以不能对服务节点有太多的内存消耗所以就限制了Proxy本地缓存的大小和命中率。
</li>
<li>
Mixer的本地缓存。Mixer是独立运行的所以可以在Mixer这一层使用大容量的本地缓存从而减少对后端基础设施的调用一方面可以减少延迟另一方面也可以最大限度减少后端基础设施故障给服务调用带来的影响。
</li>
<img src="https://static001.geekbang.org/resource/image/c5/72/c5a213195ef50de213bc44b401725772.png" alt=""><br>
(图片来源:[https://istio.io/docs/concepts/policies-and-telemetry/topology-with-cache.svg](https://istio.io/docs/concepts/policies-and-telemetry/topology-with-cache.svg)
那么Mixer是如何实现策略控制和监控日志收集功能呢
1.策略控制
Istio支持两类的策略控制一类是对服务的调用进行速率限制一类是对服务的调用进行访问控制它们都是通过在Mixer中配置规则来实现的。具体来讲速率限制需要配置速率控制的yaml文件每一次Proxy转发请求前都会先调用MixerMixer就会根据这个yaml文件中的配置来对调用进行速率限制。比如下面这段配置表达的意思是服务默认访问的速率限制是每秒5000次除此之外还定义了两个特殊限制第一个是v3版本的reviews服务请求ratings服务的速率限制是每5秒1次第二个是其他服务请求ratings服务的速率限制是每10秒5次。
```
apiVersion: config.istio.io/v1alpha2
kind: memquota
metadata:
name: handler
namespace: istio-system
spec:
quotas:
- name: requestcount.quota.istio-system
maxAmount: 5000
validDuration: 1s
overrides:
- dimensions:
destination: ratings
source: reviews
sourceVersion: v3
maxAmount: 1
validDuration: 5s
- dimensions:
destination: ratings
maxAmount: 5
validDuration: 10s
```
而访问控制需要配置访问控制的yaml文件每一次Proxy转发请求前都会先调用MixerMixer就会根据这个yaml文件中的配置来对调用进行访问控制。比如下面这段配置表达的意思是v3版本的reviews服务调用ratings服务就会被拒绝。
```
apiVersion: &quot;config.istio.io/v1alpha2&quot;
kind: rule
metadata:
name: denyreviewsv3
spec:
match: destination.labels[&quot;app&quot;] == &quot;ratings&quot; &amp;&amp; source.labels[&quot;app&quot;]==&quot;reviews&quot; &amp;&amp; source.labels[&quot;version&quot;] == &quot;v3&quot;
actions:
- handler: denyreviewsv3handler.denier
instances: [ denyreviewsv3request.checknothing ]
```
2.监控和日志收集
跟策略控制的实现原理类似Mixer的监控、日志收集功能也是通过配置监控yaml文件来实现的Proxy发起的每一次服务调用都会先调用Mixer把监控信息发给MixerMixer再根据配置的yaml文件来决定监控信息该发到哪。示例yaml文件可以参考这个[链接](https://istio.io/docs/tasks/telemetry/metrics-logs/)。
## Citadel
Citadel的作用是保证服务之间访问的安全它的工作原理见下图可见实际的安全保障并不是Citadel独立完成的而是需要Proxy、Pilot以及Mixer的配合具体来讲
<li>
Citadel里存储了密钥和证书。
</li>
<li>
通过Pilot把授权策略和安全命名信息分发给Proxy。
</li>
<li>
Proxy与Proxy之间的调用使用双向TLS认证来保证服务调用的安全。
</li>
<li>
最后由Mixer来管理授权和审计。
</li>
<img src="https://static001.geekbang.org/resource/image/bf/44/bfcb6885e5446508e041355665d6c444.png" alt=""><br>
(图片来源:[https://istio.io/docs/concepts/security/architecture.svg](https://istio.io/docs/concepts/security/architecture.svg)
## 总结
今天我给你详细讲解了Istio的架构及其基本组件Proxy、Pilot、Mixer以及Citadel的工作原理从Istio的设计和实现原理可以看出它是采用模块化设计并且各个模块之间高度解耦Proxy专注于负责服务之间的通信Pilot专注于流量控制Mixer专注于策略控制以及监控日志功能而Citadel专注于安全。正是这种高度模块化的设计使得Istio的架构极具扩展性和适配性如果你想加强流量控制方面的功能可以在Pilot模块中定制开发自己的代码而不需要修改其他模块如果你想增加一种监控系统支持可以在Mixer模块中添加对这个监控系统的适配器就能接入Istio。除此之外虽然Istio由Google和IBM主导但也没有完全与Kubernetes平台绑定你也可以在Mesos或者AWS上运行Istio可见它的适配性极强这也是Istio的强大之处以至于它的竞争对手Linkerd也开始支持Istio作为可选的Proxy组件之一。
## 思考题
Mixer的一个功能是实现服务调用的日志收集假如某一个服务调用并发量很高而每一次调用都经过Proxy代理请求Mixer再由Mixer调用后端的日志系统的话整个链路的网络延迟就会对服务调用的性能影响很大你有什么优化建议吗
欢迎你在留言区写下自己的思考,与我一起讨论。
扩展阅读:
<li>
Envoy对外提供统一API的详细作用[https://github.com/envoyproxy/data-plane-api/blob/master/API_OVERVIEW.md](https://github.com/envoyproxy/data-plane-api/blob/master/API_OVERVIEW.md)
</li>
<li>
授权策略:[https://istio.io/docs/concepts/security/#authentication-policies](https://istio.io/docs/concepts/security/#authentication-policies)
</li>
<li>
安全命名信息:[https://istio.io/docs/concepts/security/#secure-naming](https://istio.io/docs/concepts/security/#secure-naming)
</li>
<li>
双向TLS认证[https://istio.io/docs/tasks/security/mtls-migration/](https://istio.io/docs/tasks/security/mtls-migration/)
</li>

View File

@@ -0,0 +1,76 @@
<audio id="audio" title="35 | 微博Service Mesh实践之路" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a0/18/a074e3dee7f793591597a24abdddd418.mp3"></audio>
专栏上一期我们聊了Service Mesh的代表作Istio由于Istio的设计理念非常新并且它诞生在微服务容器化和Kubernetes云平台火爆之后所以从设计和实现上Istio都天生对云原生应用更友好。
但是现实是不是也是那么美好呢对于一个已经上线运行多年的业务系统来说要想从经典的微服务架构走上Istio这条看似完美的道路并不容易各种内部基础设施的定制化以及业务稳定性优先准则等因素都注定了大多数公司要走出一条自己的Service Mesh实践之路。今天我就来带你回顾下微博是如何一步步走向Service Mesh的。
## 跨语言服务调用的需求
我在前面讲过微博的服务化框架采用的是自研的MotanMotan诞生于2013年出于微博平台业务单体化架构拆分为微服务改造的需求在结合当时的开源服务化框架和自身实际的需求选择了采用自研的方式。而且由于微博平台的业务采用的是Java语言开发所以Motan早期只支持Java语言。后期随着微博业务的高速发展越来越多的PHP业务开始登上舞台于是在微博的流量体系中主要是三股服务之间的相互调用一个是Java与Java语言一个是PHP和Java语言一个是PHP和PHP语言。Java应用之间的调用采用的是Motan协议而Java应用与PHP、PHP与PHP应用之间采用的都是HTTP协议。我回忆了一下当时一次PHP与Java之间的HTTP调用过程大致需要经过DNS解析、四层LVS负载均衡、七层Nginx负载均衡最后才能调用Java应用本身。
<img src="https://static001.geekbang.org/resource/image/8c/04/8cafb3bc4c314e5df40a80f8aee3cc04.png" alt="">
从上面这张图可以看出一次HTTP调用的链路相当长从我的实践来看经常会遇到好几个问题。
**第一个问题:中间链路损耗大**。由于一次HTTP调用要经过DNS、LVS、Nginx这三个基础设施每一层都会带来相应的损耗。我曾经在线上就碰到过因为DNS解析延迟、LVS带宽打满引起的网络延迟以及Nginx本地磁盘写满引起的转发延迟等各种情况造成接口响应在中间链路的损耗甚至超过了接口本身业务逻辑执行的时间。
**第二个问题:全链路扩容难**。由于微博业务经常要面临突发热点事件带来的流量冲击,所以需要能够随时随地动态扩缩容。其实在应用本身这一层扩容并不是难点,比较麻烦的是四七层负载均衡设备的动态扩缩容,它涉及如何评估容量、如何动态申请节点并及时修改生效等,要完成一次全链路扩容的话,复杂度非常高,所以最后往往采取的办法是给四七层负载均衡设备预备足够的冗余度,在峰值流量到来时,只扩容应用本身。
**第三个问题:混合云部署难**。专栏前面我讲过微博的业务目前采用的是混合云部署也就是在内网私有云和公有云上都有业务部署同样也需要部署四七层负载均衡设备并且要支持公有云上的请求经过DNS解析后要能够转发到公有云上的负载均衡设备上去避免跨专线访问带来不必要的网络延迟和专线带宽占用。
因此迫切需要一种支持跨语言调用的服务化框架使得跨语言应用之间的调用能够像Java应用之间的调用一样不需要经过其他中间链路转发做到直接交互就像下图描述的那样。
<img src="https://static001.geekbang.org/resource/image/6b/fe/6b54bcf17039645c3034dce373a053fe.png" alt="">
## Yar协议的初步尝试
为此微博最开始考虑基于Motan框架进行扩展使其支持PHP语言的Yar协议下面是扩展后的架构图。这个架构的思路是PHP客户端的服务发现通过Nginx来支持经过Nginx把PHP的Yar协议请求转发给服务端由于Motan框架中了适配Yar协议服务端会把PHP的Yar协议请求转换成Motan请求来处理处理完后再转成Yar协议的返回值经过Nginx返回给客户端。
<img src="https://static001.geekbang.org/resource/image/d7/3c/d7d21afa6d37bf5f55a831a25fdef83c.png" alt="">
但这种架构主要存在两个问题。
**第一个问题**Motan协议与Yar协议在基本数据结构和序列化方式的支持有所不同需要经过复杂的协议转换。
**第二个问题**服务调用还必须依赖Nginx所以调用链路多了一层在应用部署和扩容时都要考虑Nginx。
## gRPC会是救命稻草吗
时间往后推演gRPC横空出世它良好的跨语言特性以及高效的序列化格式的特性吸引了我们于是便开始考虑在Motan中集成gRPC来作为跨语言通信的协议。当时设计了下图的架构这个架构的思路是利用gRPC来生成PHP语言的Client然后在Motan框架中加入对gRPC协议的支持这样的话PHP语言的Client就可以通过gRPC请求来调用Java服务。
<img src="https://static001.geekbang.org/resource/image/02/2a/02de374239ba3b0ea10cc9192821552a.png" alt="">
但在我们的实际测试中发现微博的业务场景并不适合gRPC协议因为gRPC协议高度依赖PB序列化而PHP对PB的兼容性不是很好在微博的业务场景下一个接口返回值有可能超过几十KB此时在PHP Client端PB数据结构解析成JSON对象的耗时甚至达到几十毫秒这对业务来说是不可接受的。而且gRPC当时还不支持PHP作为Server对外提供服务也不满足微博这部分业务场景的需要。
## 代理才是出路
考虑到PHP语言本身没有常驻内存控制的能力在实现服务注册和发现以及其他各种服务框架功能时仅靠PHP-FPM进程本身难以实现因此需要一个统一常驻内存的进程来帮助完成服务框架的各种功能。一开始我们考虑过使用本地守护进程和OpenResty的Timer来实现服务发现但其他服务框架的功能不太好实现比如专栏前面提到的各种复杂的负载均衡策略、双发、熔断等。为此我们希望通过一个Agent也就是代理来帮助PHP进程来完成服务框架的各种功能PHP进程本身只需要负责运行业务逻辑的代码以及最简单的Motan协议解析。基于这个思路当时我们设计了下面这个架构它的思路就是在PHP进程的本地也部署一个AgentPHP进程发出去的请求都经过Agent进行处理后再发给对应的Java应用。
<img src="https://static001.geekbang.org/resource/image/a7/5c/a75e0cd4b3e9aa355a6caec951e5845c.png" alt="">
## 向Service Mesh迈进
2017年就在我们开始采用Agent方案对业务进行改造以支持PHP应用调用Java应用服务化的时候Service Mesh的概念突然火热起来并随着Istio的发布风靡业界。相信经过我前面对Service Mesh的讲解你一定会发现这里的Agent不恰恰就是Service Mesh中的SideCar吗没错我们跨语言调用的解决方案竟然与Service Mesh的理念不谋而合。借鉴Service Mesh的思想我们也对Agent方案进一步演化不仅客户端的调用需要经过本地的Agent处理后再转发给服务端服务端在处理前也需要经过本地的Agent最后再由服务端业务逻辑处理下面是它的架构图。如此一来业务只需要进行集成最简单的Motan协议解析而不需要关心其他服务框架功能可以理解为业务只需要集成一个轻量级的Client用于Motan协议解析而繁杂的服务框架功能全都由Agent来实现从而实现业务与框架功能的解耦。
<img src="https://static001.geekbang.org/resource/image/78/91/78c53f81b72dc818d90400160b573d91.png" alt="">
从上面的图中你可以看出这个架构与上一期我们聊的Istio大体思路相同但是区别还是很明显的可以概括为以下几点
<li>
都通过SideCar方式部署的代理来实现流量转发Istio里使用的是Envoy而Weibo Mesh采用的是自研的Motan-go Agent。这里有一个很明显的区别是Weibo Mesh中业务代码还需要集成一个轻量级的Client所以对业务有一定的倾入性而Istio采用的是iptables技术拦截网络请求给Envoy所以业务无需做任何变更更适合云原生应用。在微博的业务场景下由于大部分业务并不是云原生应用都是部署在物理机或者虚拟机集群之中的所以需要根据自己的业务特点来决定SideCar的部署方式。而且Weibo Mesh中的轻量级Client除了实现基本的Motan协议的解析功能之外还添加了一些业务需要的特性比如为了防止Agent不可用在本地保存了一份服务节点的本地快照必要时Client可以访问本地快照获得节点的地址直接向服务节点Server发起调用而不需要经过Agent转发处理只不过这个时候就丧失了Agent的服务治理功能。
</li>
<li>
Weibo Mesh和Istio都具备服务治理功能只不过Istio是通过Control Plane来控制Proxy来实现并且Control Plane包括三个组件Pilot、Mixer以及Citedar三者各司其职。而Weibo Mesh是通过统一的服务治理中心来控制Agent从而实现服务治理的。这是因为微博本身的各种基础设施大部分是自研的比如注册和配置中心是自研的Vintage、监控系统是自己基于Graphite改造的、容器平台DCP以及负责容量评估的Diviner也是自研的为此需要一个统一的地方把这些基础设施串联起来。而Istio好像就为开源而生设计之初就要考虑如何更好地集成并支持各类开源方案比如专门抽象出Mixer组件来对接各种监控和日志系统。
</li>
## 总结
今天我给你讲解了微博是如何一步步走向Service Mesh之路的从这个过程你可以看出微博的Weibo Mesh并不是一开始就是设计成这样的它是随着业务的发展对跨语言服务调用的需求日趋强烈才开始探索如何使得原有仅支持Java语言的服务化框架Motan支持多语言在这个过程中又不断尝试了各种解决方案之后才笃定了走Agent代理这条路并实际应用到线上。而随着Service Mesh概念的兴起微博所采用的Agent代理的解决方案与Service Mesh理念不谋而合于是在Agent代理的方案中吸纳Service Mesh的思想进一步演变成如今的Weibo Mesh。所以说一个可靠的架构从来都不是设计出来的是逐步演进而来的。
## 思考题
如果要支持更多不同语言应用之间的相互调用你觉得Weibo Mesh中的轻量级的Client需要做哪些工作
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@@ -0,0 +1,95 @@
<audio id="audio" title="36 | 微博Service Mesh实践之路" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1d/57/1dfb89108c616e019b488a29e43eb457.mp3"></audio>
专栏上一期我们聊到了微博的服务化是如何一步步走向Service Mesh之路的可以说正是由于微博自身业务对跨语言服务调用的需求日趋强烈才促使了Weibo Mesh的诞生也因此乘上了Service Mesh的东风。我在前面讲过Service Mesh主要由两部分组成一部分是SideCar负责服务之间请求的转发一部分是Control Plane负责具体的服务治理。从Weibo Mesh的实现方案来看对应的SideCar采用的是自研的Motan-go Agent服务治理则是通过统一服务治理中心来实现这里面的一些思路还是和Control Plane有很大区别的。
今天我们就来聊聊Weibo Mesh实现的技术细节看看它给业务带来了哪些收益最后再谈谈Weibo Mesh下一步的发展方向。
## Motan-go Agent
通过上一期的学习我们知道Weibo Mesh中使用的SideCar就是Motan-go Agent考虑到Motan-go Agent要与PHP进程部署在一起为了减少对本机资源的占用这里Motan-go Agent采用了Go语言来实现它包含的功能模块请看下图。
<img src="https://static001.geekbang.org/resource/image/79/2b/79b6ebf400d8d6eb4b390ffc3de6bf2b.png" alt="">
我们拆解一下图中Motan-go Agent主要的模块看看它们的作用是什么。
**Filter Chain模块**是以请求处理链的组合方式来实现AccessLog请求日志记录、Metric监控统计、CircuitBreaker熔断、Switcher降级、Tracing服务追踪、Mock单元测试、ActiveLimit限流等功能。
<img src="https://static001.geekbang.org/resource/image/84/03/8464472dced2bf74304f08963205cb03.png" alt="">
**High Available模块**是用来保证高可用性默认集成了Failover、Backup Request等故障处理手段。
**Load Balance模块**负载均衡默认集成了Random、Roundrobin等负载均衡算法。
**EndPoint模块**的作用是封装请求来调用远程的Server端默认可以封装Motan请求和gRPC请求。
**Serialize模块**负责实现不同类型的序列化方式默认支持Simple序列化。
**Server模块**实现不同类型的Server要么是采用Motan协议实现要么是采用gRPC协议。
Motan-go Agent每个模块都是功能可扩展的你可以在Filter Chain模块加上自己实现的Trace功能这样请求在经过Filter Chain处理时就会自动加载你加上的Trace功能。当然你也可以在High Available模块添加自己实现的故障处理手段在Load Balance模块里实现自己的负载均衡算法在EndPoint模块封装HTTP协议的请求在Serialize模块添加PB序列化在Server模块实现HTTP协议等。
另外Motan-go Agent之间的通信采用的是自定义的Motan2协议它把请求中的Meta信息与请求参数信息进行了分离更适合对请求进行代理转发并且默认使用了Simple序列化来对不同语言的数据进行编码以实现跨语言服务通信。
更多关于Motan2协议和Simple序列化的介绍你可以点击[这里](https://github.com/weibocom/motan-go/wiki/zh_userguide#%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D)查看。
## 统一服务治理中心
专栏上一期我给你讲过在Weibo Mesh中是通过统一服务治理平台与Motan-go Agent交互来实现服务治理功能的。对着下面这张Weibo Mesh的架构图我们一起看一下统一服务治理平台SGCenter具体是如何与Motan-go Agent交互来实现服务治理的各项功能的。
<img src="https://static001.geekbang.org/resource/image/e3/c8/e3ea24873a543747c96fe988c18b3ac8.png" alt="">
1.动态服务注册与发现
首先来看下统一服务治理平台是如何实现服务注册与发现的。如下图所示在Motan-go Agent中实现了具体的服务注册与发现的逻辑Server端进程启动时会通过Motan-go Agent向Vintage注册中心发起注册请求把服务注册到Vintage中。Client端发起服务调用时会经过Motan-go Agent转发Motan-go Agent会调用Vintage查询该服务在Vintage中的注册信息获取到服务节点列表后按照某一种负载均衡算法选择一个服务节点向这个服务节点发起调用。可以通过统一服务治理平台SGCenter调用Vintage的管理接口执行添加或者删除服务节点等操作Motan-go Agent会感知到服务节点的变化获取最新的服务节点。一般在业务开发或者运维人员需要手工扩容或者缩容一批服务节点时才会执行这个操作。
<img src="https://static001.geekbang.org/resource/image/51/1f/512d93a6858cbe58c38e2efe34f0c21f.png" alt="">
2.监控上报
再看下面这张图Client端发起的请求经过Motan-go Agent转发时Motan-go Agent就会在内存中统计每一次调用的耗时、成功率等信息并且每隔固定的时间间隔将这段时间内各个服务调用的QPS、平均耗时、成功率以及P999等metric信息发送给Graphite监控系统。这样的话通过SGCenter调用Graphite的Web API就可以获取到服务调用的信息了。
<img src="https://static001.geekbang.org/resource/image/5d/6d/5d8369d5db67ca3742e0a923e29ce96d.png" alt="">
3.动态流量切换与降级
动态流量切换与降级的过程请看下面这张图。Motan-go Agent在查询Vintage中某个服务节点信息的同时也会订阅该服务的变更这样的话就可以通过SGCenter向Vintage下发服务的切流量或者降级指令订阅了这个服务的Motan-go Agent就会收到变更通知如果是切流量指令比如把调用永丰机房服务的流量都切换到土城机房那么Motan-go Agent就会把原本发给永丰机房的请求都发给土城机房如果是降级指令Motan-go Agent就会停止调用这个服务。
<img src="https://static001.geekbang.org/resource/image/67/d5/6702ff187f9d7e5741d391962d5493d5.png" alt="">
4.自动扩缩容
服务调用时Motan-go Agent会把Server端服务调用的监控信息上报给Graphite监控系统同时Diviner容量评估系统会实时调用Graphite以获取服务在不同区间的QPS信息以计算服务池的水位线然后SGCenter会每隔一段时间调用Diviner来获取各个服务池的冗余度以决定是否需要扩容。假如此时服务池的冗余度不足的话SGCenter就会调用DCP容器运维平台给服务池进行扩容DCP完成扩容后新的服务节点就会注册到Vintage当中这样的话订阅了该服务的Motan-go Agent就会感知到服务节点的变化从Vintage中获取最新的服务节点信息这就是一个服务自动扩缩容的整个流程你可以参考下面这张图。
<img src="https://static001.geekbang.org/resource/image/5b/f5/5b4ffd777e4eaeea813df753de8bcaf5.png" alt="">
## Weibo Mesh的收益
经过前面的讲解相信你已经对Weibo Mesh的实现方案有了一定的了解。Weibo Mesh是在微博的业务场景下一步步进化到今天这个架构的它给微博的业务带来的巨大的收益总结起来主要有以下几点
<li>
**跨语言服务化调用的能力**。Weibo Mesh发展之初最首要的目的就是想让微博内部的Motan服务化框架能够支持PHP应用与Java应用之间调用因而开发了Motan-go Agent并在此基础上演变成今天的Weibo Mesh。支持多种语言之间的服务化调用有助于统一公司内部业务不同语言所采用的服务化框架达到统一技术体系的目的。
</li>
<li>
**统一服务治理能力**。以微博应对突发热点事件带来的峰值流量冲击为例为了确保首页信息流业务的稳定性我们有针对性的研发了自动扩缩容系统。而随着微博的不断发展不断涌现出新的业务线比如热门微博和热搜也同样面临着突发热点事件带来的流量冲击压力。而开发一套稳定可用的自动扩缩容系统并非一朝一夕之事如何能够把信息流业务研发的自动扩缩容系统推广到各个业务线是个比较棘手的问题。因为信息流业务的后端主要采用了Java语言实现而热门微博和热搜主要采用的是PHP语言无法直接接入自动扩缩容系统。而Weibo Mesh可以支持多种语言将热门微博和热搜业务进行服务化改造就可以统一接入到自动扩缩容系统实现了公司级的统一服务治理能力。
</li>
<li>
**业务无感知的持续功能更新能力**。采用Motan或者Dubbo类似的传统服务化框架一旦服务框架功能有升级就需要业务同步进行代码升级这对大部分业务来说都是一种不愿承受的负担。而采用Weibo Mesh添加新功能只需要升级Motan-go Agent即可业务代码不需要做任何变更对于业务开发人员更友好。尤其是作为公司级的服务化框架时服务框架的升级如果跟业务系统升级绑定在一起从我的实践经验来看将是一件耗时费力的工作需要协调各个业务方配合才能完成。而Weibo Mesh可以看作是服务器上部署的基础组件它的升级与维护不需要各个业务方的参与这样才能具备作为公司级的服务化框架推广到各个业务线的前提。
</li>
## Weibo Mesh的发展规划
在微博的业务场景下存在大量服务对缓存、数据库以及消息队列等资源的调用如果把资源也看作是一种服务那么Weibo Mesh不仅可以管理服务与服务之间的调用还可以管理服务与资源之间的调用这样的话Weibo Mesh强大的服务治理能力也能延伸到对资源的治理上对业务来说又将解决资源治理这一大难题。另一方面随着Weibo Mesh治理的服务越来越多收集的数据也越来越多利用这些数据可以挖掘一些更深层次的东西也是Weibo Mesh未来的发展方向之一。比如引入机器学习算法对采集的数据进行分析进行监控报警的优化等。
<img src="https://static001.geekbang.org/resource/image/d8/4e/d85ff6120d7650fa80cae9acba8f814e.png" alt="">
## 总结
今天我从Motan-go Agent和统一服务治理中心的具体实现这两个方面给你讲解了Weibo Mesh的技术细节你可以看到很多都是微博基于自身业务特点定制化的解决方案。对于大部分中小团队来说除非从一开始就采用了云原生应用的部署方式否则Istio等开源方案并不能直接拿来就用都需要从自身的业务特征和既有技术体系出发选择一条适合自己的Service Mesh实践之路。Weibo Mesh也因为其紧贴业务并没有脱离实际去设计所以才能够在微博的业务中落地生根被证明是行之有效的架构实践使得微博服务化体系的统一成为可能也坚定了我们在Weibo Mesh这条路上继续走下去。
## 思考题
Service Mesh中业务与服务框架解耦的优秀设计思想除了能用于服务与服务之间相互调用的场景你认为还能应用于哪些业务场景中
欢迎你在留言区写下自己的思考,与我一起讨论。