1. 如何发布和引用服务
服务描述:服务调用首先解决的问题就是服务如何对外描述。 常用的服务描述方式包括 RESTful API、XML 配置以及 IDL 文件三种。
RESTful API
主要被用作 HTTP 或者 HTTPS 协议的接口定义,即使在非微服务架构体系下,也被广泛采用
- 优势:
HTTP 协议本身是一个公开的协议,对于服务消费者来说几乎没有学习成本,所以比较适合用作跨业务平台之间的服务协议。
- 劣势: -性能相对比较低
XML 配置
一般是私有 RPC 框架会选择 XML 配置这种方式来描述接口,因为私有 RPC 协议的性能比 HTTP 协议高,所以在对性能要求比较高的场景下,采用 XML 配置比较合适。这种方式的服务发布和引用主要分三个步骤:
- 服务提供者定义接口,并实现接口
- 服务提供者进程启动时,通过加载 server.xml 配置文件将接口暴露出去。
- 服务消费者进程启动时,通过加载 client.xml 配置文件引入要调用的接口。
优势:
- 私有 RPC 协议的性能比 HTTP 协议高,所以在对性能要求比较高的场景下,采用 XML 配置方式比较合适 劣势:
- 对业务代码侵入性比较高
- XML 配置有变更的时候,服务消费者和服务提供者都要更新(建议:公司内部联系比较紧密的业务之间采用)
IDL 文件
IDL 就是接口描述语言(interface description language)的缩写,通过一种中立的方式来描接口,使得在不同的平台上运行的对象和不同语言编写的程序可以相互通信交流。常用的 IDL:一个是 Facebook 开源的 Thrift 协议,另一个是 Google 开源的 gRPC 协议。无论是 Thrift 协议还是 gRPC 协议,他们的工作原来都是类似的。
- 优势:
用作跨语言平台的服务之间的调用
- 劣势:
在描述接口定义时,IDL 文件需要对接口返回值进行详细定义。如果接口返回值的字段比较多,并且经常变化时,采用 IDL 文件方式的接口定义就不太合适了。
一方面会造成 IDL 文件过大难以维护
另一方面只要 IDL 文件中定义的接口返回值有变更,都需要同步所有的服务消费者都更新,管理成本太高了。
总结
具体采用哪种服务描述方式是根据实际情况决定,通常情况下, 如果只是企业内部之间的服务调用,并且都是 Java 语言的话,选择 XML 配置方式是最简单的。如果企业内部存在多个服务,并且服务采用的是不同语言平台,建议使用 IDL 文件方式进行描述服务。如果还存在对外开放服务调用的情形的话,使用 RESTful API 方式则更加通用。
2. 如何注册和发现服务
注册中心原理
在微服务架构下, 主要有三种角色:服务提供者(RPC Server)、服务消费者(RPC Client)和服务注册中心(Registry),三者的交互关系如图
- RPC Server 提供服务,在启动时,根据服务发布文件 server.xml 中配置的信息,向 Registry 订阅服务,把 Registry 返回的服务节点列表缓存在本地内存中,并于 RPC Server 建立连接。
- RPC Client 调用服务,在启动时,根据服务引用文件 client.xml 中配置的信息,向 Registry 订阅服务,把 Registry 返回的服务节点列表缓存在本地内存中,并于 RPC Client 建立连接。
- 当 RPC Server 节点发生变更时,Registry 会同步变更,RPC Client 感知后会刷新本地内存中缓存的服务节点列表。
- RPC Client 从本地缓存的服务节点列表中,基于负载均衡算法选择一台 RPC Server 发起调用。
注册中心实现方式
注册中心API
- 服务注册接口:服务提供者通过调用注册接口来完成服务注册
- 服务反注册接口:服务提供者通过调用服务反注册接口来完成服务注销
- 心跳汇报接口:服务提供者通过调用心跳汇报接口完成节点存货状态上报
- 服务订阅接口:服务消费者调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表
- 服务变更查询接口:服务消费者通过调用服务变更查询接口,获取***的可用服务节点列表
- 服务查询接口:查询注册中心当前住了哪些服务信息
- 服务修改接口:修改注册中心某一服务的信息
集群部署
注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。
- Zookeeper 的工作原理:
每个 Server 在内存中存储了一份数据,Client 的读请求可以请求任意一个 Server
Zookeeper 启动时,将从实例中选举一个 leader(Paxos 协议)
Leader 负责处理数据更新等操作(ZAB 协议)
一个更新操作方式,Zookeeper 保证了高可用性以及数据一致性
目录存储
- ZooKeeper作为注册中心存储服务信息一般采用层次化的目录结构:
每个目录在 ZooKeeper 中叫作 znode,并且其有一个唯一的路径标识
znode 可以包含数据和子 znode。
znode 中的数据可以有多个版本,比如某一个 znode 下存有多个数据版本,那么查询这个路径下的数据需带上版本信息。
服务健康状态检测
- 注册中心除了要支持最基本的服务注册和服务订阅功能以外,还必须具备对服务提供者节点的健康状态检测功能,这样才能保证注册中心里保存的服务节点都是可用的。
- 基于 ZooKeeper 客户端和服务端的长连接和会话超时控制机制,来实现服务健康状态检测的。
- 在 ZooKeeper 中,客户端和服务端建立连接后,会话也也随之建立,并生成一个全局唯一的 Session ID。服务端和客户端维持的是一个长连接,在 SESSION_TIMEOUT 周期内,服务端会检测与客户端的链路是否正常,具体方式是通过客户端定时向服务端发送心跳消息(ping 消息),服务器重置下次 SESSION_TIMEOUT 时间。如果超过 Session 就已经结束了,ZooKeeper 就会认为这个 Session 就已经结束了,ZooKeeper 就会认为这个服务节点已经不可用,将会从注册中心中删除其信息。
服务状态变更通知
- 一旦注册中心探测到有服务器提供者节点新加入或者被剔除,就必须立刻通知所有订阅该服务的服务消费者,刷新本地缓存的服务节点信息,确保服务调用不会请求不可用的服务提供者节点。
- 基于 Zookeeper 的 Watcher 机制,来实现服务状态变更通知给服务消费者的。服务消费者在调用 Zookeeper 的 getData 方式订阅服务时,还可以通过监听器 Watcher 的 process 方法获取服务的变更,然后调用 getData 方法来获取变更后的数据,刷新本地混存的服务节点信息。
白名单机制
- 注册中心可以提供一个白名单机制,只有添加到注册中心白名单内的 RPC Server,才能够调用注册中心的注册接口,这样的话可以避免测试环境中的节点意外跑到线上环境中去。
总结
注册中心可以说是实现服务话的关键,因为服务话之后,服务提供者和服务消费者不在同一个进程中运行,实现了解耦,这就需要一个纽带去连接服务提供者和服务消费者,而注册中心就正好承担了这一角色。此外,服务提供者可以任意伸缩即增加节点或者减少节点,通过服务健康状态检测,注册中心可以保持***的服务节点信息,并将变化通知给订阅服务的服务消费者。
注册中心一般采用分布式集群部署,来保证高可用性,并且为了实现异地多活,有的注册中心还采用多 IDC 部署,这就对数据一致性产生了很高的要求,这些都是注册中心在实现时必须要解决的问题。
3. 如何实现 RPC 远程服务调用
客户端和服务端如何建立网络连接
HTTP 通信
HTTP 通信是基于应用层HTTP 协议的,而 HTTP 协议又是基于传输层 TCP 协议的。一次 HTTP 通信过程就是发起一次 HTTP 调用,而一次 HTTP 调用就会建立一个 TCP 连接,经历一次下图所示的 “三次握手”的过程来建立连接。
完成请求后,再经历一次“四次挥手”的过程来断开连接。
Socket 通信
Socket 通信是基于 TCP/IP 协议的封装,建立一次Socket 连接至少需要一对套接字,其中一个运行于客户端,称为 ClientSocket ;另一个运行于服务器端,称为 ServerSocket 。
- 服务器监听:ServerSocket 通过点用 bind() 函数绑定某个具体端口,然后调用 listen() 函数实时监控网络状态,等待客户端的连接请求。
- 客户端请求:ClientSocket 调用 connect() 函数向 ServerSocket 绑定的地址和端口发起连接请求。
- 服务端连接确认:当 ServerSocket 监听都或者接收到 ClientSocket 的连接请求时,调用 accept() 函数响应 ClientSocket 的请求,同客户端建立连接。
- 数据传输:当 ClientSocket 和 ServerSocket 建立连接后,ClientSocket 调用 send() 函数,ServerSocket 调用 receive() 函数,ServerSocket 处理完请求后,调用 send() 函数,ClientSocket 调用 receive() 函数,就可以得到返回结果。
当客户端和服务端建立网络连接后,就可以起发起请求了。但网络不一定总是可靠的,经常会遇到网络闪断、连接超时、服务端宕机等各种异常,通常的处理手段有两种:
- 链路存活检测:客户端需要定时地发送心跳检测小心(一般通过 ping 请求) 给服务端,如果服务端连续 n 次心跳检测或者超过规定的时间没有回复消息,则认为此时链路已经失效,这个时候客户端就需要重新与服务端建立连接。
- 断连重试:通常有多种情况会导致连接断开,比如客户端主动关闭、服务端宕机或者网络故障等。这个时候客户端就需要与服务端重新建立连接,但一般不能立刻完成重连,而是要等待固定的间隔后再发起重连,避免服务端的连接回收不及时,而客户端瞬间重连的请求太多而把服务端的连接数占满。
服务端如何处理请求
同步阻塞方式(BIO)
- 客户端每发一次请求,服务端就生成一个线程去处理。当客户端同时发起的请求很多事,服务端需要创建很多的线程去处理每一个请求,如果达到了系统***的线程数瓶颈,新来的请求就没法处理了。
- BIO 适用于连接数比较小的业务场景,这样的话不至于系统中没有可用线程去处理请求。这种方式写的程序也比较简单直观,易于理解。
同步非阻塞(NIO)
- 客户端每发一次请求,服务端并不是每次都创建一个新线程来处理,而是通过 I/O 多路复用技术进行处理。就是把国歌 I/O 的阻塞复用到听一个 select 的阻塞上,从而使系统在单线程的情况下可以同时处理多个客户端请求。这种方式的优势是开销小,不用为每个请求创建一个线程,可以节省系统开销。
- NIO 适用于连接数比较多并且请求消耗比较轻的业务场景,比如聊天服务器。这种方式相比 BIO,相对来说编程比较复杂。
异步非阻塞(AIO)
- 客户端只需要发起一个 I/O 操作然后立即返回,等 I/O 操作真正完成以后,客户端会得到 I/O 操作完成的通知,此时客户端只需要对数据进行处理就好了,不需要进行实际的 I/O 读写操作,因为真正的 I/O 读取或者写入操作已经由内核完成了。这种方式的优势是客户端无需等待,不存在阻塞等待问题。
- AIO 适用于连接数比较多而且请求消耗比较重的业务场景,比如涉及 I/O 操作的相册服务器。这种方式相比另外两种,编程难难度***,程序也不易于理解。
建议
最为稳妥的方式是使用成熟的开源方案,比如 Netty、MINA 等,它们都是经过业界大规模应用后,被充分论证是很可靠的方案。
数据传输采用什么协议
无论是开放的还是私有的协议,都必须定义一个“契约”,以便服务消费和服务提供者之间能够达成共识。服务消费者按照契约,对传输的数据进行编码,然后通过网络传输过去;服务提供者从网络上接收到数据后,按照契约,对传输的数据进行解码,然后处理请求,再把处理后的结果进行编码,通过网络传输返回给服务消费者;服务消费者再对返回的结果进行解码,最终得到服务提供者处理后的返回值。
HTTP 协议
- 消息头
Server 代表是服务端服务器类型
Content-Length 代表返回数据的长度
Content-Type 代表返回数据的类型
- 消息体
具体的返回结果
数据该如何序列化和反序列化
一般数据在网络中进行传输,都要先在发送方一段对数据进行编码,经过网络传输到达另一段后,再对数据进行解码,这个过程就是序列化和反序列化
常用的序列化方式分为两类:文本类如 XML/JSON 等,二进制类如 PB/Thrift 等,而具体采用哪种序列化方式,主要取决于三个方面的因素。
- 支持数据结构类型的丰富度。数据结构种类支持的越多越好,这样的话对于使用者来说在编程时更加友好,有些序列化框架如 Hessian 2.0 还支持复杂的数据结构比如 Map、List等。
- 跨语言支持。
- 性能。主要看两点,一个是序列化后的压缩比,一个是序列化的速度。以常用的 PB 序列化和 JSON 序列化协议为例来对比分析,PB 序列化的压缩比和速度都要比 JSON 序列化高很多,所以对性能和存储空间要求比较高的系统选用 PB 序列化更合;而 JSON 序列化虽然性能要差一些,但可读性更好,所以对性能和存储空间要求比较高的系统选用 PB 序列化更合适对外部提供服务。
总结
- 通信框架:它主要解决客户端和服务端如何建立连接、管理连接以及服务端如何处理请求的问题。
- 通信协议:它主要解决客户端和服务端采用哪些数据传输协议的问题。
- 序列化和反序列化:它主要解决客户端和服务端采用哪种数据编码的问题。
这三部分就组成了一个完成的RPC 调用框架,通信框架提供了基础的通信能力,通信协议描述了通信契约,而序列化和反序列化则用于数据的编/解码。一个通信框架可以适配多种通信协议,也可以采用多种序列化和反序列化的格式,比如服务话框架 不仅支持 Dubbo 协议,还支持 RMI 协议、HTTP 协议等,而且还支持多种序列化和反序列化格式,比如 JSON、Hession 2.0 以及 Java 序列化等。
4. 如何监控微服务调用
在谈论监控微服务监控调用前,首先要搞清楚三个问题:监控的对象是什么?具体监控哪些指标?从哪些维度进行监控?
监控对象
- 用户端监控:通常是指业务直接对用户提供的功能的监控。
- 接口监控:通常是指业务提供的功能所以来的具体 RPC 接口监控。
- 资源监控:通常是指某个接口依赖的资源的监控。(eg:Redis 来存储关注列表,对 Redis 的监控就属于资源监控。)
- 基础监控:通常是指对服务器本身的健康状况的监控。(eg: CPU、MEM、I/O、网卡带宽等)
监控指标
- 请求量
实时请求量(QPS Queries Per Second):即每秒查询次数来衡量,反映了服务调用的实时变化情况
统计请求量(PV Page View):即一段时间内用户的访问量来衡量,eg:一天的 PV 代表了服务一天的请求量,通常用来统计报表
- 响应时间:大多数情况下,可以用一段时间内所有调用的平均耗时来反应请求的响应时间。但它只代表了请求的平均快慢情况,有时候我们更关心慢请求的数量。为此需要把响应时间划分为多个区间,比如0~10ms、10ms~50ms、50ms~100ms、100ms~500ms、500ms 以上这五个区间,其中 500ms 以上这个区间内的请求数就代表了慢请求量,正常情况下,这个区间内的请求数应该接近于 0;在出现问题时,这个区间内的请求数应该接近于 0;在出现问题时,这个区间内的请求数会大幅增加,可能平均耗时并不能反映出这一变化。除此之外,还可以从P90、P95、P99、P999 角度来监控请求的响应时间在 500ms 以内,它代表了请求的服务质量,即 SLA。
- 错误率:通常用一段时间内调用失败的次数占调用总次数的比率来衡量,比如对于接口的错误率一般用接口返回错误码为 503 的比率来表示。
监控维度
- 全局维度:从整体角度监控对象的请求量、平均耗时以及错误率,全局维度的监控一般是为了让你对监控对象的调用情况有个整体了解。
- 分机房维度:为了业务高可用,服务部署不止一个机房,因为不同机房地域的不同,同一个监控对象的各种指标可能会相差很大。
- 单机维度:同一个机房内部,可能由于采购年份和批次不的不同,各种指标也不一样。
- 时间维度:同一个监控对象,在每天的同一时刻各种指标通常也不会一样,这种差异要么是由业务变更导致,要么是运营活动导致。为了了解监控对象各种指标的变化,通常需要与一天前、一周前、一个月前,甚至三个月前比较。
- 核心维度:业务上一般会依据重要性成都对监控对象进行分级,最简单的是分成核心业务和非核心业务。核心业务和非核心业务在部署上必须隔离,分开监控,这样才能对核心业务做重点保障。
对于一个微服务来说,必须要明确监控哪些对象、哪些指标,并且还要从不同的维度进行监控,才能掌握微服务的调用情况。
监控系统原理
- 数据采集:收集到每一次调用的详细信息,包括调用的响应时间、调用是否成功、调用的发起者和接收者分别是谁,这个过程叫做数据采集。
- 数据传输:采集到数据之后,要把数据通过一定的方式传输给数据处理中心进行处理,这个过程叫做数据出传输。
- 数据处理:数据传输过来后,数据处理中心再按照服务的维度进行聚合,计算出不同服务的请求量、响应时间以及错误率等信息并存储起来,这个过程叫做数据处理。
- 数据展示:通过接口或者 DashBoard 的形式对外展示服务的调用情况,这个过程叫做数据展示。
数据采集
- 服务主动上报
- 代理收集:这种处理方式通过服务调用后把调用的详细信息记录到本地日志文件中,然后再通过代理去解析本地日志文件,然后再上报服务的调用信息。
不管是哪种方式,首先要考虑的问题就是采样率,也就是采集数据的频率。一般来说,采样率越高,监控的实时性就越高,精确度也越高。但采样对系统本身的性能也会有一定的影响,尤其是采集后的数据需要写到本地磁盘的时候,过高的采样率会导致系统写入的 I/O 过高,进而会影响到正常的服务调用。所以合理的采样率是数据采集的关键,***是可以动态控制采样率,在系统比较空闲的时候加大采样率,追求监控的实时性与精确度;在系统负载比较高的时候减少采样率,追求监控的可用性与系统的稳定性。
数据传输
- UDP传输:这种处理方式是数据处理单元提供服务器的请求地址,数据采集后通过 UDP 协议与服务器建立连接,然后把数据发送过去。
- Kafka传输:这种处理方式是数据采集后发送都指定的 Topic,然后数据处理单元再订阅对应的 Topic,就可以从 Kafka 消息队列中读取对应的数据。
无论哪种传输方式,数据格式十分重要,尤其是对带宽敏感以及解析性能要求比较高的场景,一般数据传输时采用的数据格式有两种:
- 二进制协议,最常用的就是 PB 对象
- 文本协议,最常用的就是 JSON 字符串
数据处理
- 接口维度聚合:把实时收到的数据按照调用的节点维度聚合在一起,这样就可以得到每个接口的实时请求、平均耗时等信息。
- 机器维度聚合:把实时收到的数据按照调用的节点维度聚合在一起,这样就可以从单机维度去查看每个接口的实时请求量、平均耗时等信息。
聚合后的数据需要持久化到数据库中存储,所选用的数据库一般分为两种:
- 索引数据库:比如 Elasticsearcher,以倒排索引的数据结构存书,需要查询的时候,根据索引来查询。
- 时序数据库:比如 OpenTSDB,以时序序列数据的方式存储,查询的时候按照时序如 1min、5min 等维度查询
数据展示
- 曲线图:监控变化趋势。
- 饼状图:监控占比分布。
- 格子图:主要坐一些细粒度的监控。
总结
- 服务监控子啊微服务改造过程中的重要性不言而喻,没有强大的监控能力,改造成微服务架构后,就无法掌控各个不同服务的情况,在遇到调用失败时,如果不能快速发现系统的问题,对于业务来说就是一场灾难。
- 搭建一个服务监控系统,设计数据采集、数据传输、数据处理、数据展示等多个环节,每个环节都需要根据自己的业务特点选择合适的解决方案
5. 如何追踪微服务调用
跟踪记录一次用户请求都发起了哪些调用,经过哪些服务处理,并且记录每一次调用所涉及的详细信息,这时候如果发生调用失败,就可以通过这个日志快速定位是在哪个环节出了问题。
服务追踪的作用
优化系统瓶颈
- 通过记录调用经过的每一条链路上的耗时,可以快速定位整个系统的瓶颈点在哪里。可能出现的原因如下:
运营商网络延迟
网关系统异常
某个服务异常
缓存或者数据库异常
- 通过服务追踪,可以从全局视角上去观察,找出整个系统的瓶颈点所在,然后做出针对性的优化
优化链路调用
- 通过服务追踪可以分析调用所经过的路径,然后评估是否合理
- 一般业务都会在多个数据中心都部署服务,以实现异地容灾,这个时候经常会出现一种状况就是服务 A 调用了另外一个数据中心的服务 B,而没有调用同处于一个数据中心的舒服务 B。跨数据中心的调用视距离远近都会有一定的网络延迟,像北京和广州这种几千公里距离的网络延迟可能达到了30ms以上,这对于有些业务几乎是不可接受的。通过对调用链路进行分析,可以找出跨数据中的服务调用,从而进行优化,尽量规避这总情况出现。
生成网络拓扑
- 通过服务追踪系统中记录的链路信息,可以生成一张系统的网络调用拓扑图,它可以反映系统都依赖了哪些服务,以及服务之间的调用关系是什么样的,可以一目了然。除此之外,在网络拓扑图上还可以把服务调用的详细信息也标出来,也能起到服务监控的作用。
透明传输数据
- 除了服务追踪,业务上经常有一种需求,期望能把一些用户数据,从调用的开始一直往下传递,以便系统中的各个服务都能获取到这个信息。比如业务想做一些 A/B 测试,这时候就想通过服务追踪系统,把 A/B 测试的开关逻辑一直往下传递,经过的每一层服务都能获取到这个开关值,就能够统一进行 A/B 测试。
服务追踪原理
服务追踪鼻祖:Google 发布的一篇的论文Dapper, [a Large-Scale Distributed Systems Tracing Infrastructure
- 核心理念:通过一个全局唯一的 ID 将分布在各个服务节点上的同一次请求串联起来,从而还原原有的调用关系,可以追踪系统问题、分析调用数据并统计各种系统指标
- 可以说后面的诞生各种服务追踪系统都是基于 Dapper 衍生出来的,比较有名的有 Twitter 的Zipkin、阿里的鹰眼、美团的MTrace等。
讲解下服务追踪系统中几个最基本概念
- traceId:用于标识某一次具体的请求ID。
- spanId:用于标识一次 RPC 调用在分布式请求中的位置。
- annotation:用于业务自定义埋点数据,可以是业务感兴趣的上上传到后端的数据,比如一次请求的用户 UID。
traceId 是用于串联某一次请求在系统中经过的所有路径,spanId 是用于区分系统不同服务之间调用的先后关系,而annotation 是用于业务自定义一些自己感兴趣的数据,在上传 traceId 和 spanId 这些基本信息之外,添加一些自己感兴趣的信息。
服务追踪系统实现
上面是服务追踪系统架构图,一个服务追踪系统可以分三层:
- 数据采集层:负责数据埋点并上报
- 数据处理层:负责数据的存储与计算
- 数据展示层:负责数据的图形化展示
数据采集层
作用:在系统的各个不同的模块中尽心埋点,采集数据并上报给数据处理层进行处理。
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 或者 Spack Streaming 来对链路数据进行实时聚合加工,存储一拜是用 OLTP 数据仓库,比如 HBase,使用 traceId 作为 RowKey,能天然地把一条调用链聚合在一起,提高查询效率。
- 离线数据处理:要求计算效率相对没那么高,一般能在小时级别完成链路数据的聚合计算即可,一般用作汇总统计。
针对离线数据处理,一般通过运行 MapReduce 或者 Spark 批处理程序来对链路数据进行离线计算,存储一般使用 Hive
数据展示
作用:将处理后的链路信息以图形化的方式展示给用户和做故障定位
- 调用链路图(eg:Zipkin)
服务整体情况:服务总耗时、服务调用的网络深度、每一层经过的系统,以及多少次调用。下图展示的一次调用,总耗时 209.323ms,经过了 5 个不同系统模块,调用深度为 7 层,共发生了 2
- 调用拓扑图(Pinpoint)
调用拓扑图是一种全局视野,在实际项目中,主要用作全局监控,用户发现系统异常的点,从而快速做出决策。比如,某一个服务突然出现异常,那么在调用链路拓扑图中可以看出对这个服务的调用耗时都变高了,可以用红色的图样标出来,用作监控报警。
总结
- 服务追踪能够帮助查询一次用户请求在系统中的具体执行路径,以及每一条路径下的上下游的详细情况,对于追查问题十分有用。
- 实现一个服务追踪系统,设计数据采集、数据处理和数据展示三个流程,有多种实现方式,具体采取某一种要根据自己的业务情况来选择。
6. 微服务治理的手段有哪些
一次服务调用,服务提供者、注册中心、网络这三者都可能会有问题,此时服务消费者应该如何处理才能确保调用成功呢?这就是服务治理要解决的问题。
节点管理
- 服务调用失败一般是由两类原因引起的
服务提供者自身出现问题,比如服务器宕机、进程意外退出等
网络问题,如服务提供者、注册中心、服务消费者这三者任意两者之间的网络问题
无论是服务哪种原因,都有两种节点管理手段:
注册中心主动摘除机制
这种机制要求服务提供者定时的主动向注册中心汇报心跳,注册中心根据服务提供者节点最近一次汇报心跳的时间与上一次汇报心跳时间做比较,如果超出一定时间,就认为服务提供者出现问题,继而把节点从服务列表中摘除,并把最近的可用服务节点列表推送给服务消费者。
服务消费者摘除机制
虽然注册中心主动摘除机制可以解决服务提供者节点异常的问题,但如果是因为注册中心与服务提供者之间的网络出现异常,最坏的情况是注册中心会把服务节点全部摘除,导致服务消费者没有可能的服务节点调用,但其实这时候提供者本身是正常的。所以,将存活探测机制用在服务消费者这一端更合理,如果服务消费者调用服务提供者节点失败,就将这个节点从内存保存的可用夫提供者节点列表一处。
负载均衡算法
常用的负载均衡算法主要包括以下几种:
- 随机算法(均匀)
- 轮询算法(按照固定的权重,对可用服务节点进行轮询)
- 最少活跃调用算法(性能理论***)
- 一致性 Hash 算法(相同参数的请求总是发到同一服务节点)
服务路由
- 对于服务消费者而言,在内存中的可用服务节点列表中选择哪个节点不仅由负载均衡算法决定,还由路由规则决定。
- 所谓的路由规则,就是通过一定的规则如条件表达式或者正则表达式来限定服务节点的选择范围。
为什么要指定路由规则呢?主要有两个原因:
- 业务存在灰度发布的需求
比如,服务提供者做了功能变更,但希望先只让部分人群使用,然后根据这部分人群的使用反馈,再来决定是否全量发布。
- 多机房就近访问的需求
跨数据中心的调用视距离远近都会有一定的网络延迟,像北京和广州这种几千公里距离的网络延迟可能达到了30ms以上,这对于有些业务几乎是不可接受的,所以就要一次服务调用尽量选择同一个 IDC 内部节点,从而减少网络耗时开销,提高性能。这时一般可以通过 IP 段规则来控制访问,在选择服务节点时,优先选择同一 IP 段的节点。
那么路由规则该如何配置?
- 静态配置:服务消费者本地存放调用的路由规则,如果改变,需重新上线才能生效
- 动态配置:路由规则存放在配置中心,服务消费者定期去请求注册中心来保持同步,要想改变消费者的路由配置,可以通过修改注册中心的配置,服务消费者在下一个同步周期之后,就会请求注册中心更新配置,从而实现动态变更
服务容错
常用的手段主要有以下几种:
- FailOver:失败自动切换(调用失败或者超时,可以设置重试次数)
- FailBack:失败通知(调用失败或者超时,不立即发起重试,而是根据失败的详细信息,来决定后续的执行策略)
- FailCache:失败缓存(调用失败或者超时,不立即发起重试,而是隔一段时间后再次尝试发起调用)
- FailFirst:快速失败(调用一次失败后,不再充实,一般非核心业务的调用,会采取快速失败策略,调用失败后一般就记录下失败日志就返回了)
一般对于幂等的调用可以选择 FailOver 或者 FailCache,非幂等的调用可以选择 Failback 或者 FailFast
总结
- 节点管理是从服务节点健康状态角度来考虑,负载均衡和服务路由是从服务节点访问优先级角度来考虑,而服务容错是从调用的健康状态来考虑,可谓殊途同归。
- 在实际的微服务架构中,上面的服务治理手段一般都会在服务框架中默认即成,比如 阿里的 Dubbo、微博开源的服务架构 Motan等。