--- title: 微服务基本原理 date: 2020-07-21 15:35:00 order: 10 categories: - 设计 - 架构 - 微服务 tags: - 设计 - 架构 - 微服务 - 分布式 permalink: /pages/214c6d9d/ --- # 微服务基本原理 ## 微服务技术架构 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716195006.png) **第一层:接入层** 外部设备访问的统一接入层。 **第二层:聚合服务层** 对下层的基础服务做一些聚合,剪裁的工作,适配上层不同设备的数据输出。 **第三层:基础服务层** 比较细粒度的微服务层,提供基础的核心服务,公共服务。 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716195117.png) ## 服务通信 通过注册中心,服务消费者和服务提供者就可以感知彼此,但是,要实现交互还必须解决通信问题: - **通信协议**。即服务提供者和服务消费者之间以什么样的 **协议** 进行网络通信,说白了,是要解决客户端和服务端如何建立连接、管理连接以及服务端如何处理请求的问题。是采用四层 TCP、UDP 协议,还是采用七层 HTTP 协议,还是采用其他协议?例如:Dubbo 基于 TCP 通信;而 Spring Cloud 基于 HTTP REST 通信。TCP 通信方式,传输效率更高;但是 HTTP 方式天然可以提供对外服务。 - **传输方式**。即服务提供者和服务消费者之间的数据传输采用哪种方式。是同步还是异步?是在单连接上传输,还是多路复用。 - **序列化和反序列化**。它主要解决客户端和服务端采用哪种数据编解码的问题。常见的序列化方式包括:XML、JSON;二进制类如:[thrift](https://github.com/apache/thrift)、[protobuf](https://github.com/protocolbuffers/protobuf)、[hessian](http://hessian.caucho.com/doc/hessian-overview.xtp)、JDK。 ### 序列化方式 序列化方式的选型,一般基于以下考虑: - 支持数据结构类型的丰富度 - 跨语言支持 - 性能 > 👉 参考:[Java 序列化](https://dunwu.github.io/waterdrop/pages/737e5233/) ### 通信协议 微服务框架对比: | | RPC | REST | | -------- | ------------------------------------------------- | -------------------------------- | | 耦合性 | 强耦合 | 松散耦合 | | 协议 | Tcp | Http、Http2 | | 序列化 | 二进制(Thrift、Protobuf、Hessian、Avro、JDK 等) | Xml、Json | | 性能 | 高 | 低 | | 客户端 | 对编程语言有限制 | 跨语言支持更好(支持 Http 即可) | | 代表技术 | Dubbo、Motan、Tars、gRpc、Thrift | Spring Cloud | ## 服务监控 当服务消费者与服务提供者之间建立了通信,作为管理者需要通过监控手段来观察服务是否正常,调用是否成功。服务监控是很复杂的,在微服务架构下,一次用户调用会因为服务化拆分后,变成多个不同服务之间的相互调用,这也就需要对拆分后的每个服务都监控起来。 ### 监控对象 服务监控一定是通过观察数据来量化分析,所以首先要明确需要监控什么。 一般来说,服务监控数据有以下分类: - **业务监控**:核心指标、登录、登出、下单、支付等。 - **应用监控**:访问接口、访问服务、SQL、内存使用率、响应时间、TPS、QPS 等。 - **系统监控**:CPU、内存、网络、磁盘等。 - **基础监控**:网络流量、丢包数、错包数、连接数等。 - **客户端监控**:性能、返回码、地域、运营商、版本、系统等。 ### 系统监控原理 一旦明确了要监控的对象,接下就是考虑如何监控。 **监控系统主要包括四个环节:数据采集、数据传输、数据处理和数据展示** #### 数据采集 通常有两种数据收集方式: - **服务主动上报**:这种处理方式通过在业务代码或者服务框架里加入数据收集代码逻辑,在每一次服务调用完成后,主动上报服务的调用信息。这种方式在链路跟踪中较为常见,主流的技术方案有:Zipkin。 - **代理收集**:这种处理方式通过服务调用后把调用的详细信息记录到本地日志文件中,然后再通过代理去解析本地日志文件,然后再上报服务的调用信息。主流的技术方案有:ELK、Flume。 #### 数据传输 数据传输最常用的方式有两种: - **UDP 传输**:这种处理方式是数据处理单元提供服务器的请求地址,数据采集后通过 UDP 协议与服务器建立连接,然后把数据发送过去。 - **Kafka 传输**:这种处理方式是数据采集后发送到指定的 Topic,然后数据处理单元再订阅对应的 Topic,就可以从 Kafka 消息队列中读取到对应的数据。由于 Kafka 有非常高的吞吐能力,所以很适合作为大数据量的缓冲池。 #### 数据处理 数据处理是对收集来的原始数据进行聚合并存储。数据聚合通常有两个维度: - **接口维度聚合**:这个维度是把实时收到的数据按照接口名维度实时聚合在一起,这样就可以得到每个接口的每秒请求量、平均耗时、成功率等信息。 - **机器维度聚合**:这个维度是把实时收到的数据按照调用的节点维度聚合在一起,这样就可以从单机维度去查看每个接口的实时请求量、平均耗时等信息。 聚合后的数据需要持久化到数据库中存储,所选用的数据库一般分为两种: - **全文检索数据库**:比如 Elasticsearch,以倒排索引的数据结构存储,需要查询的时候,根据索引来查询。 - **时序数据库**:比如 OpenTSDB,以时序序列数据的方式存储,查询的时候按照时序如 1min、5min 等维度来查询。 #### 数据展示 数据展示是把处理后的数据以 Dashboard 的方式展示给用户。数据展示有多种方式,比如曲线图、饼状图、格子图展示等。 ### 监控技术 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716204432.png) - ELK 的技术栈比较成熟,应用范围也比较广,除了可用作监控系统外,还可以用作日志查询和分析。 - Graphite 是基于时间序列数据库存储的监控系统,并且提供了功能强大的各种聚合函数比如 sum、average、top5 等可用于监控分析,而且对外提供了 API 也可以接入其他图形化监控系统如 Grafana。 - TICK 的核心在于其时间序列数据库 InfluxDB 的存储功能强大,且支持类似 SQL 语言的复杂数据处理操作。 - Prometheus 的独特之处在于它采用了拉数据的方式,对业务影响较小,同时也采用了时间序列数据库存储,而且支持独有的 PromQL 查询语言,功能强大而且简洁。 ## 服务治理 微服务治理平台就是**与服务打交道的统一入口**,无论是开发人员还是运维人员,都能通过这个平台对服务进行各种操作,比如开发人员可以通过这个平台对服务进行降级操作,运维人员可以通过这个平台对服务进行上下线操作,而不需要关心这个操作背后的具体实现。 微服务治理平台关键之处就在于它能够封装对微服务架构内的各个基础设施组件的调用,从而对外提供统一的服务操作 API,而且还提供了可视化的界面,以方便开发人员和运维人员操作。 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716203729.png) 服务治理的常用手段有: - 节点管理 - 注册中心主动摘除机制 - 服务消费者摘除机制 - 负载均衡 - 轮询 - 随机 - 最近最少连接 - 一致性 Hash - 服务路由 - 业务存在灰度发布的需求 - 多机房就近访问的需求 - 服务容错 - FailOver:失败自动切换 - FailBack:失败通知 - FailCache:失败缓存 - FailFast:快速失败 ## API 网关 API 网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API 网关封装了系统内部架构,为每个客户端提供一个定制的 API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。 API 网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供 REST/HTTP 的访问 API。服务端通过 API-GW 注册和管理服务。 ### Zuul ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716201640.png) 在 zuul 中, 整个请求的过程是这样的,首先将请求给 zuulservlet 处理,zuulservlet 中有一个 zuulRunner 对象,该对象中初始化了 RequestContext:作为存储整个请求的一些数据,并被所有的 zuulfilter 共享。zuulRunner 中还有 FilterProcessor,FilterProcessor 作为执行所有的 zuulfilter 的管理器。FilterProcessor 从 filterloader 中获取 zuulfilter,而 zuulfilter 是被 filterFileManager 所加载,并支持 groovy 热加载,采用了轮询的方式热加载。有了这些 filter 之后,zuulservelet 首先执行的 Pre 类型的过滤器,再执行 route 类型的过滤器,最后执行的是 post 类型的过滤器,如果在执行这些过滤器有错误的时候则会执行 error 类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。 ## 负载均衡 > 参考:[负载均衡基本原理](https://dunwu.github.io/waterdrop/pages/6da58e4b/) ## 服务路由 **服务路由就是服务消费者在发起服务调用时,必须根据特定的规则来选择服务节点,从而满足某些特定的需求**。 ### 服务路由的应用场景 - **分组调用**。一般来讲,为了保证服务的高可用性,实现异地多活的需求,一个服务往往不止部署在一个数据中心,而且出于节省成本等考虑,有些业务可能不仅在私有机房部署,还会采用公有云部署,甚至采用多家公有云部署。服务节点也会按照不同的数据中心分成不同的分组,这时对于服务消费者来说,选择哪一个分组调用,就必须有相应的路由规则。 - **灰度发布**。在服务上线发布的过程中,一般需要先在一小部分规模的服务节点上先发布服务,然后验证功能是否正常。如果正常的话就继续扩大发布范围;如果不正常的话,就需要排查问题,解决问题后继续发布。这个过程就叫作灰度发布,也叫金丝雀部署。 - **流量切换**。在业务线上运行过程中,经常会遇到一些不可抗力因素导致业务故障,比如某个机房的光缆被挖断,或者发生着火等事故导致整个机房的服务都不可用。这个时候就需要按照某个指令,能够把原来调用这个机房服务的流量切换到其他正常的机房。 - **读写分离**。对于大多数互联网业务来说都是读多写少,所以在进行服务部署的时候,可以把读写分开部署,所有写接口可以部署在一起,而读接口部署在另外的节点上。 ### 服务路由的规则 服务路由主要有两种规则:一种是条件路由,一种是脚本路由。 #### 条件路由 条件路由是基于条件表达式的路由规则。 ``` condition://0.0.0.0/dubbo.test.interfaces.TestService?category=routers&dynamic=true&priority=2&enabled=true&rule=" + URL.encode(" host = 10.20.153.10=> host = 10.20.153.11") ``` 这里面 `condition://` 代表了这是一段用条件表达式编写的路由规则,具体的规则是 ``` host = 10.20.153.10 => host = 10.20.153.11 ``` 分隔符“=>”前面是服务消费者的匹配条件,后面是服务提供者的过滤条件。当服务消费者节点满足匹配条件时,就对该服务消费者执行后面的过滤规则。那么上面这段表达式表达的意义就是 IP 为“10.20.153.10”的服务消费者都调用 IP 为“10.20.153.11”的服务提供者节点。 如果服务消费者的匹配条件为空,就表示对所有的服务消费者应用,就像下面的表达式一样。 ``` => host != 10.20.153.11 ``` 如果服务提供者的过滤条件为空,就表示禁止服务消费者访问,就像下面的表达式一样。 ``` host = 10.20.153.10=> ``` 下面我举一些 Dubbo 框架中的条件路由,来给你讲解下条件路由的具体应用场景。 - 排除某个服务节点 ``` => host != 172.22.3.91 ``` 一旦这条路由规则被应用到线上,所有的服务消费者都不会访问 IP 为 172.22.3.91 的服务节点,这种路由规则一般应用在线上流量排除预发布机以及摘除某个故障节点的场景。 - 白名单和黑名单功能 ``` host != 10.20.153.10,10.20.153.11 => ``` 这条路由规则意思是除了 IP 为 10.20.153.10 和 10.20.153.11 的服务消费者可以发起服务调用以外,其他服务消费者都不可以,主要用于白名单访问逻辑,比如某个后台服务只允许特定的几台机器才可以访问,这样的话可以机器控制访问权限。 ``` host = 10.20.153.10,10.20.153.11 => ``` 同理,这条路由规则意思是除了 IP 为 10.20.153.10 和 10.20.153.11 的服务消费者不能发起服务调用以外,其他服务消费者都可以,也就是实现了黑名单功能,比如线上经常会遇到某些调用方不管是出于有意还是无意的不合理调用,影响了服务的稳定性,这时候可以通过黑名单功能暂时予以封杀。 - 机房隔离 ``` host = 172.22.3.* => host = 172.22.3.* ``` 这条路由规则意思是 IP 网段为 172.22.3.\* 的服务消费者,才可以访问同网段的服务节点,这种规则一般应用于服务部署在多个 IDC,理论上同一个 IDC 内的调用性能要比跨 IDC 调用性能要好,应用这个规则是为了实现同 IDC 就近访问。 - 读写分离 ``` method = find*,list*,get*,is* => host =172.22.3.94,172.22.3.95 method != find*,list*,get*,is* => host = 172.22.3.97,172.22.3.98 ``` 这条路由规则意思是 find*、get*、is\* 等读方法调用 IP 为 172.22.3.94 和 172.22.3.95 的节点,除此以外的写方法调用 IP 为 172.22.3.97 和 172.22.3.98 的节点。对于大部分互联网业务来说,往往读请求要远远大于写请求,而写请求的重要性往往要远远高于读请求,所以需要把读写请求进行分离,以避免读请求异常影响到写请求,这时候就可以应用这种规则。 #### 脚本路由 脚本路由是基于脚本语言的路由规则,常用的脚本语言比如 JavaScript、Groovy、JRuby 等。 ``` "script://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("(function route(invokers) { ... } (invokers))") ``` 这里面“script://”就代表了这是一段脚本语言编写的路由规则,具体规则定义在脚本语言的 route 方法实现里,比如下面这段用 JavaScript 编写的 route() 方法表达的意思是,只有 IP 为 10.20.153.10 的服务消费者可以发起服务调用。 ``` function route(invokers){ var result = new java.util.ArrayList(invokers.size()); for(i =0; i < invokers.size(); i ++){ if("10.20.153.10".equals(invokers.get(i).getUrl().getHost())){ result.add(invokers.get(i)); } } return result; } (invokers)); ``` ### 服务路由的获取方式 服务路由的获取方式主要有三种: - 本地配置 顾名思义就是路由规则存储在服务消费者本地上。服务消费者发起调用时,从本地固定位置读取路由规则,然后按照路由规则选取一个服务节点发起调用。 - 配置中心管理 这种方式下,所有的服务消费者都从配置中心获取路由规则,由配置中心来统一管理。 - 动态下发 这种方式下,一般是运维人员或者开发人员,通过服务治理平台修改路由规则,服务治理平台调用配置中心接口,把修改后的路由规则持久化到配置中心。因为服务消费者订阅了路由规则的变更,于是就会从配置中心获取最新的路由规则,按照最新的路由规则来执行。 ### 内部服务调用 基础服务之间的调用:结合服务注册中心以及专属的具有负载均衡功能的客户端,如 Eureka+(restTemplate+Ribbon)或者 Eureka+Feign 聚合服务调用:结合服务注册中心以及专属的具有负载均衡功能的客户端,如 Eureka+(restTemplate+Ribbon)或者 Eureka+Feign ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716202409.png) ### 外部服务调用 基于 Netflix 的 zuul,做了简单了解,SpringCloud 与 zuul 集成的方式。这里先对核心流程做个简单了解,后续会有深入的应用、分析。 Spring Cloud 很好的集成了 zuul,并且可以通过注解的形式来进行请求的反向路由以及 API 网关功能 Spring Cloud 集成 zuul,对与 url 映射的处理方式与 SpringMVC 对 url 的请求方式类似,都是通过 RequestMapping 来进行请求绑定的。核心类:ZuulHandlerMapping zuul 的核心是 ZuulServlet,一个请求核心流程:HttpServletRequest –>ZuulHandlerMapping –>ZuulController –> ZuulServlet –> ZuulFilter –> HttpServletResponse ## 配置中心 配置中心的思路就是把服务的各种配置,如代码里配置的各种参数、服务降级的开关甚至依赖的资源等都在一个地方统一进行管理。服务启动时,可以自动从配置中心中拉取所需的配置,并且如果有配置变更的情况,同样可以自动从配置中心拉取最新的配置信息,服务无须重新发布。 配置中心一般包含下面几个功能: - 配置注册功能 - 配置反注册功能 - 配置查看功能 - 配置变更订阅功能 ### Apollo 携程开源的分布式配置中心,支持 Java 和.Net 语言,客户端和配置中心通过 HTTP 长连接实现实时推送,并且有统一的管理界面来实现配置管理。 ![img](https://mmbiz.qpic.cn/mmbiz_png/ELH62gpbFmGdnIjxDT7AOQyZgl2KQnz68zZFSDpHfa80ppne7gbP4ROOLJSuZT7E2uEdf1OTR9zthLNFkIZSLQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) ### Spring Cloud Git Spring Cloud 中使用的配置中心组件,只支持 Java 语言,配置存储在 git 中,变更配置也需要通过 git 操作,如果配置中心有配置变更,需要手动刷新。 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716202911.png) ## 链路追踪 ### 链路追踪的作用 - 优化系统瓶颈 - 优化链路调用 - 生成网络拓扑 - 透明传输数据 ### 链路追踪的原理 理解链路追踪必须先了解以下概念: - **traceId**,用于标识某一次具体的请求 ID。当用户的请求进入系统后,会在 RPC 调用网络的第一层生成一个全局唯一的 traceId,并且会随着每一层的 RPC 调用,不断往后传递,这样的话通过 traceId 就可以把一次用户请求在系统中调用的路径串联起来。 - **spanId**,用于标识一次 RPC 调用在分布式请求中的位置。当用户的请求进入系统后,处在 RPC 调用网络的第一层 A 时 spanId 初始值是 0,进入下一层 RPC 调用 B 的时候 spanId 是 0.1,继续进入下一层 RPC 调用 C 时 spanId 是 0.1.1,而与 B 处在同一层的 RPC 调用 E 的 spanId 是 0.2,这样的话通过 spanId 就可以定位某一次 RPC 请求在系统调用中所处的位置,以及它的上下游依赖分别是谁。 - **annotation**,用于业务自定义埋点数据,可以是业务感兴趣的想上传到后端的数据,比如一次请求的用户 UID。 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716204658.png) ### 链路追踪的实现 一个服务追踪系统一般可以分为三层: - **数据采集**层,负责数据埋点并上报。 - **数据处理**层,负责数据的存储与计算。 - **数据展示**层,负责数据的图形化展示。 #### 数据采集层 一次 RPC 请求可以分为四个阶段。 - CS(Client Send)阶段 : 客户端发起请求,并生成调用的上下文。 - SR(Server Recieve)阶段 : 服务端接收请求,并生成上下文。 - SS(Server Send)阶段 : 服务端返回请求,这个阶段会将服务端上下文数据上报,下面这张图可以说明上报的数据有:traceId=123456,spanId=0.1,appKey=B,method=B.method,start=103,duration=38。 - CR(Client Recieve)阶段 : 客户端接收返回结果,这个阶段会将客户端上下文数据上报,上报的数据有:traceid=123456,spanId=0.1,appKey=A,method=B.method,start=103,duration=38。 #### 数据处理层 数据处理层的作用就是把数据采集层上报的数据按需计算,然后落地存储供查询使用。 - 实时数据处理 针对实时数据处理,一般采用 Storm 或者 Spark Streaming 来对链路数据进行实时聚合加工,存储一般使用 OLTP 数据仓库,比如 HBase,使用 traceId 作为 RowKey,能天然地把一整条调用链聚合在一起,提高查询效率。 - 离线数据处理 针对离线数据处理,一般通过运行 MapReduce 或者 Spark 批处理程序来对链路数据进行离线计算,存储一般使用 Hive。 #### 数据展示层 数据展示层的作用就是将处理后的链路信息以图形化的方式展示给用户。 实际项目中主要用到两种图形展示,一种是调用链路图,一种是调用拓扑图。 ### 链路追踪方案对比 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716205052.png) ## 限流熔断 一般而言,集群故障的产生原因不外乎有两种: 一种是代码 bug 所导致,比如说某一段 Java 代码不断地分配大对象,但没有及时回收导致 JVM OOM 退出; 另一种是突发的流量冲击,超出了系统的最大承载能力,比如“双 11”这种购物活动,电商系统会在零点一瞬间涌入大量流量,超出系统的最大承载能力,一下子就把整个系统给压垮了。 应付集群故障的思路,主要有两种:**限流**和**降级**。 ### 限流 限流就是限制流量。通常情况下,系统能够承载的流量根据集群规模的大小是固定的,可以称之为系统的最大容量。当真实流量超过了系统的最大容量后,就会导致系统响应变慢,服务调用出现大量超时,反映给用户的感觉就是卡顿、无响应。所以,应该根据系统的最大容量,给系统设置一个阈值,超过这个阈值的请求会被自动抛弃,这样的话可以最大限度地保证系统提供的服务正常。 除此之外,通常一个微服务系统会同时提供多个服务,每个服务在同一时刻的请求量也是不同的,很可能出现的一种情况就是,系统中某个服务的请求量突增,占用了系统中大部分资源,导致其他服务没有资源可用。因此,还要针对系统中每个服务的请求量也设置一个阈值,超过这个阈值的请求也要被自动抛弃,这样的话不至于因为一个服务影响了其他所有服务。 在实际项目中,可以用两个指标来衡量服务的请求量,一个是 QPS 即每秒请求量,一个是工作线程数。不过 QPS 因为不同服务的响应快慢不同,所以系统能够承载的 QPS 相差很大,因此一般选择工作线程数来作为限流的指标,给系统设置一个总的最大工作线程数以及单个服务的最大工作线程数,这样的话无论是系统的总请求量过大导致整体工作线程数量达到最大工作线程数,还是某个服务的请求量超过单个服务的最大工作线程数,都会被限流,以起到保护整个系统的作用。 ### 降级 什么是降级呢?在我看来,降级就是通过停止系统中的某些功能,来保证系统整体的可用性。降级可以说是一种被动防御的措施,为什么这么说呢?因为它一般是系统已经出现故障后所采取的一种止损措施。 那么降级一般是如何实现的呢?根据我的实践来看, 一种可行的方案是通过开关来实现。 具体来讲,就是在系统运行的内存中开辟一块区域,专门用于存储开关的状态,也就是开启还是关闭。并且需要监听某个端口,通过这个端口可以向系统下发命令,来改变内存中开关的状态。当开关开启时,业务的某一段逻辑就不再执行,而正常情况下,开关是关闭的状态。 开关一般用在两种地方,一种是新增的业务逻辑,因为新增的业务逻辑相对来说不成熟,往往具备一定的风险,所以需要加开关来控制新业务逻辑是否执行;另一种是依赖的服务或资源,因为依赖的服务或者资源不总是可靠的,所以最好是有开关能够控制是否对依赖服务或资源发起调用,来保证即使依赖出现问题,也能通过降级来避免影响。 在实际业务应用的时候,降级要按照对业务的影响程度进行分级,一般分为三级:一级降级是对业务影响最小的降级,在故障的情况下,首先执行一级降级,所以一级降级也可以设置成自动降级,不需要人为干预;二级降级是对业务有一定影响的降级,在故障的情况下,如果一级降级起不到多大作用的时候,可以人为采取措施,执行二级降级;三级降级是对业务有较大影响的降级,这种降级要么是对商业收入有重大影响,要么是对用户体验有重大影响,所以操作起来要非常谨慎,不在最后时刻一般不予采用。 ## DEVOPS ### 容器和容器平台 Mesos、Marathon、Kubernetes ## RPC 选型 ### 限定语言 RPC 跟语言平台绑定的开源 RPC 框架主要有下面几种。 - Dubbo:国内最早开源的 RPC 框架,由阿里巴巴公司开发并于 2011 年末对外开源,仅支持 Java 语言。 - Motan:微博内部使用的 RPC 框架,于 2016 年对外开源,仅支持 Java 语言。 - Tars:腾讯内部使用的 RPC 框架,于 2017 年对外开源,仅支持 C++ 语言。 - Spring Cloud:国外 Pivotal 公司 2014 年对外开源的 RPC 框架,仅支持 Java 语言,最近几年生态发展得比较好,是比较火的 RPC 框架。 所以很明显,如果你的业务场景仅仅局限于一种语言的话,可以选择跟语言绑定的 RPC 框架中的一种;如果涉及多个语言平台之间的相互调用,就应该选择跨语言平台的 RPC 框架。 仔细分析,可以看出 Spring Cloud 不仅提供了基本的 RPC 框架功能,还提供了服务注册组件、配置中心组件、负载均衡组件、断路器组件、分布式消息追踪组件等一系列组件,也难怪被技术圈的人称之为“Spring Cloud 全家桶”。如果你不想自己实现以上这些功能,那么 Spring Cloud 基本可以满足你的全部需求。而 Dubbo、Motan 基本上只提供了最基础的 RPC 框架的功能,其他微服务组件都需要自己去实现。不过由于 Spring Cloud 的 RPC 通信采用了 HTTP 协议,相比 Dubbo 和 Motan 所采用的私有协议来说,在高并发的通信场景下,性能相对要差一些,所以对性能有苛刻要求的情况下,可以考虑 Dubbo 和 Motan。 ### 跨语言 RPC 而跨语言平台的开源 RPC 框架主要有以下几种。 - gRPC:Google 于 2015 年对外开源的跨语言 RPC 框架,支持常用的 C++、Java、Python、Go、Ruby、PHP、Android Java、Objective-C 等多种语言。 - Thrift:最初是由 Facebook 开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,成为 Apache 开源项目之一,支持常用的 C++、Java、PHP、Python、Ruby、Erlang 等多种语言。 从成熟度上来讲,Thrift 因为诞生的时间要早于 gRPC,所以使用的范围要高于 gRPC,在 HBase、Hadoop、Scribe、Cassandra 等许多开源组件中都得到了广泛地应用。而且 Thrift 支持多达 25 种语言,这要比 gRPC 支持的语言更多,所以如果遇到 gRPC 不支持的语言场景下,选择 Thrift 更合适。 但 gRPC 作为后起之秀,因为采用了 HTTP/2 作为通信协议、ProtoBuf 作为数据序列化格式,在移动端设备的应用以及对传输带宽比较敏感的场景下具有很大的优势,而且开发文档丰富,根据 ProtoBuf 文件生成的代码要比 Thrift 更简洁一些,从使用难易程度上更占优势,所以如果使用的语言平台 gRPC 支持的话,建议还是采用 gRPC 比较好。 ## Service Mesh ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200721154106.png) ### Service Mesh 的实现原理 Service Mesh 实现的关键就在于两点: 一个是上面提到的轻量级的网络代理也叫 SideCar,它的作用就是转发服务之间的调用; 一个是基于 SideCar 的服务治理也被叫作 Control Plane,它的作用是向 SideCar 发送各种指令,以完成各种服务治理功能。下面我就来详细讲解这两点是如何实现的。 ## 参考资料 - [从 0 开始学微服务](https://time.geekbang.org/column/intro/100014401) - [RPC 实战与核心原理](https://time.geekbang.org/column/intro/100046201) - [微服务架构核心 20 讲](https://time.geekbang.org/course/intro/100003901) - [谈谈微服务中的 API 网关(API Gateway)](https://www.cnblogs.com/savorboard/p/api-gateway.html)