《从 0 开始学微服务》阅读笔记

前一阵子购买了胡忠想老师的《从 0 开始学微服务》极客时间专栏,二月份看完以后做了一些笔记。

服务化拆分

根据我的实际项目经验,一旦单体应用同时进行开发的人员超过 10 人,就会遇到上面的问题,这个时候就该考虑进行服务化拆分了。

服务化拆分的两种姿势:

  • 纵向拆分:按业务维度拆分,关联比较密切的几个业务业务适合拆成微服务;功能相对独立的业务拆成微服务;
  • 横向拆分:从公共且独立功能维度拆分。标准是是否有公共的服务被多个其它服务调用,且依赖的资源独立不与其他业务耦合。

微服务架构

初探微服务架构

微服务架构下,服务调用主要依赖下面几个基本组件:

  • 服务描述
  • 注册中心
  • 服务框架
  • 服务监控
  • 服务追踪
  • 服务治理

服务描述——如何发布和引用微服务

常用的服务描述方式包括 RESTful API、XML 配置以及 IDL 文件三种。

RESTful API 基于 HTTP 协议,因此对调用方(服务消费者)来说几乎不需要任何学习成本即可调用,因此比较适合用作跨平台之间的服务协议。

XML 配置方式的服务发布和引用分三个步骤:

  • 服务提供者定义并实现接口;
  • 服务提供者进程启动时,通过加载 server.xml 配置文件将接口暴露出去;
  • 服务消费者进程启动时,通过加载 client.xml 配置文件来引入要调用的接口。

XML 配置方式的接口变更需要同时更改服务方和调用方的接口文件,在跨部门调用时非常麻烦,所以一般用于私有 RPC 服务。
如果要做变更,尽量新加接口,而不是在现有接口上进行更改。

IDL 是接口描述语言(Interface Description Language)的缩写。将一个使用独立语言(如 protobuf)编写的定义文件编译为其他语言的模块,来实现跨语言的服务通信交流。
常用的 IDL 有两种:Thrift 和 gRPC.
IDL 的优势在于跨平台,但劣势在于它需要对请求和响应格式进行详细定义,如果响应字段很多或格式频繁变化时,服务迭代将会变得很麻烦。

具体采用哪种服务描述方式是根据实际情况决定的,通常情况下,如果只是企业内部之间的服务调用,并且都是 Java 语言的话,选择 XML 配置方式是最简单的。如果企业内部存在多个服务,并且服务采用的是不同语言平台,建议使用 IDL 文件方式进行描述服务。如果还存在对外开放服务调用的情形的话,使用 RESTful API 方式则更加通用。

服务描述方式 使用场景 缺点
RESTful API 跨语言平台,组织内外皆可 使用 HTTP 作为通信协议,相比 TCP 协议,性能较差
XML 配置 Java 平台,一般用于组织内部 不支持跨语言平台
IDL 文件 跨语言平台,组织内外皆可 修改或删除 Protobuf 字段不能向前兼容

注册中心——如何注册和发现微服务

微服务架构下主要有三种角色:服务提供者、服务消费者和服务注册中心。
注册中心负责存储所有可用服务的信息,并将这些信息提供给服务消费者,可以认为是提供者和消费者之间的纽带。

服务注册信息流

注册中心为了及时更新服务状态,需要定期对服务进行健康状态监测,以免将不可用的服务提供给服务消费者。
为例保证高可用性,注册中心一般采用分布式集群存储。
使用注册中心的方式,客户端可以与所有可用的 server 建立连接池,从而在调用端实现请求的负载均衡。

如何实现 RPC 远程服务调用

RPC 调用方与服务提供方建立连接后,双方需要按照某种约定的协议进行网络通信。为了减少数据传输,通常还会对数据进行压缩(序列化)。

服务端处理请求的方式:

  • 同步阻塞(BIO):双方均阻塞
  • 同步非阻塞(NIO):客户端同步调用,服务端通过多路复用进行异步处理
  • 异步非阻塞(AIO):客户端发起调用后返回,服务端处理结束后,客户端会收到结果

序列化方式的选用主要会从三个角度来考虑:支持数据结构类型的复杂度;跨语言支持程度;序列化性能(消息压缩比和序列化速度)

如何监控微服务调用

监控对象可以从上到下分为四个层次:

  • 用户端监控:对功能的直接监控
  • 接口监控:对接口本身的监控
  • 资源监控:对功能依赖资源的监控(如 Redis)
  • 基础监控:对服务器本身的健康状况的监控(CPU,内存,IO 等)

监控指标:

  • QPS
  • 响应时间
  • 错误率

监控维度:

  • 全局维度
  • 分机房维度
  • 单机维度
  • 时间维度
  • 核心维度(根据业务是否为核心业务来进行部署和监控上的隔离)

监控系统主要包括四个环节:数据采集、数据传输、数据处理和数据展示。

如何追踪微服务调用

服务追踪的作用:

  • 优化系统瓶颈
  • 优化链路调用
  • 生成网络拓扑
  • 透明传输数据

服务追踪的核心原理就是调用链:通过一个全局唯一的 ID 将分布在各个服务节点上的同一次请求串联起来,从而还原原有的调用关系。
比较有名的追踪框架有 Twitter 的 Zipkin,阿里的鹰眼,美团的 MTrace ,Opentracing 等。

服务追踪系统的架构

微服务治理的手段

节点管理:

  1. 注册中心主动摘除机制
  2. 服务消费者摘除机制(注册中心照常提供注册信息,服务消费者发现服务提供方不可用时,将它从本地的提供方列表中摘除)

负载均衡算法:

  1. 随机算法:均匀随机;
  2. 轮询算法:按照固定的权重,对可用节点进行轮询。权重可以静态配置,也可以通过某些指标来设置;
  3. 最少活跃调用算法:统计当前消费者和每个节点之间建立的连接数,向连接最少的一方发送请求
  4. 一致性 Hash 算法

服务路由:可用节点的选择除了由负载均衡算法决定,还会由路由规则来决定。

服务容错:对于调用失败的请求,要通过一些手段自动恢复,保证调用成功。

微服务架构实践

微服务发布与引用的实践

服务发布预定义配置:

  1. 服务发布预定义配置
    接口的超时时间和重试次数由服务提供者来定义,而不是服务消费者。
  2. 服务引用定义配置
    接口的超时时间等由服务引用者进行定义。

如何将注册中心落地

注册中心存储服务信息。这些信息处理包含节点信息(IP 和 端口号)以外,还包含其它一些信息,比如请求失败时重试的次数等。
此外,所有服务还会按照某些规则进行分组,如机房、是否核心业务等。

开源服务注册中心选型

当下主流的服务注册与发现的解决方案主要有两种:

  • 应用内注册与发现:应用通过 SDK 与注册中心通信完成服务的注册和发现
  • 应用外注册与发现:应用可以通过其他方式与注册中心交互

两种解决方案的不同之处在于应用场景。对于容器化的云应用来说,一般不适合采用应用内 SDK 的解决方案,因为这样会侵入业务。

应用内注册的典型案例有 Eureka,提供 Java SDK;
应用外最典型的案例是 Consul,它通过 Consul Template 来进行服务注册信息配置(如动态更新 Nginx 配置文件来完成服务请求的路由)

应用中心选型要考虑的两个问题:高可用性、数据一致性。

开源 RPC 框架选型

语言绑定的 RPC 框架:

  • Dubbo:阿里巴巴开源,Java 平台
  • Motan:微博开源,Java 平台
  • Tars:腾讯开源,C++ 平台
  • Spring Cloud:Pivotal 开源,Java 平台,最近几年比较火

跨平台的 RPC 框架:

  • gRPC:Google 开源
  • Thrift:Facebook 开源,Apache 基金会项目

关于 Thrift 和 gRPC 选型:Thrift 历史悠久,支持的平台比 gRPC 多,但 gRPC 在效率和代码程度方面更占优势,所以更推荐使用 gRPC.

如何搭建一个可靠的监控系统

一个监控系统的组成主要涉及四个环节:数据收集、数据传输、数据处理和数据展示。

目前比较流行的开源监控系统实现方案有两种:

  1. 以 ELK 为代表的集中式日志解决方案
  2. 以 Graphite、TICK、Prometheus 等为代表的时序数据库解决方案。

ELK:Elasticsearch, Logstash, Kibana

ELK 的数据流向为:Logstash → Elasticsearch → Kibana

  • Logstash 负责数据收集和传输,传输时可以动态地对数据过滤、分析、格式化
  • Elasticsearch 负责数据处理(存储、搜索和分析)
  • Kibana 负责数据展示

Logstash 比较消耗计算资源,所以不太适合在业务服务器上部署,于是引入了 Beats用于在服务器节点收集数据,然后向 Logstash(或 ES)发送数据。
这样一来,数据处理和展示的流水线就变成了 Beats → Logstash → Elasticsearch → Kibana.

Graphite

Graphite 主要包括三部分:Carbon, Whisper, Graphite-Web

  • Carbon: 用于收集指标并持久化到 Whisper 存储文件
  • Whisper:一个简单的时序数据库
  • Graphite-Web:从 Carbon-cache 或 Whisper 读取数据并展示

架构图

TICK:Telegraf、InfluxDB、Chronograf、Kapacitor

架构图

Telegraf 负责数据采集,InfluxDB 负责数据存储,Chronograf 负责数据展示,Kapacitor 负责数据告警。

Prometheus

2015 年正式发布,2016 年加入 CNCF,称为受欢迎程度仅次于 Kubernetes 的项目。

架构图

其它三种方案的数据采集模式都是“推数据”的方式,而 Prometheus 以“拉数据”的方式进行数据采集,所以不需要在服务端部署数据采集代理。

Grafana

开源的展示工具,可以弥补 Graphite、TICK 和 Prometheus 展示功能的薄弱点。
支持以上所有类型的数据源,UI 要比 Kibana 美观一些。

从监控层面考虑,时序数据库的实时性和灵活性都比 ELK 要好,所以监控系统选型更推荐使用时序数据库,尤其是 Prometheus,毕竟 CNCF 亲儿子。
其实 ELK 更适合做日志收集,而不是监控。

如何搭建一套服务追踪系统

服务追踪的实现主要包括三部分:埋点数据收集、实时数据处理、数据链路展示。

OpenZipkin
架构图

Pinpoint
架构图

对比:

  • OpenZipkin 支持很多语言,但 Pinpoint 只支持 Java;
  • Pinpoint 通过 Java 字节码注入来实现追踪,所以对 Java 的支持程度非常高,追踪信息非常丰富;
  • OpenZipkin 只能绘制服务间的链路拓扑图,但 Pinpoint 还可以绘制服务与 DB 之间的链路拓扑图。

如何实现服务存活检测

如果心跳请求失败,注册中心会将服务从可用服务中摘除。但在网络频繁抖动的情况下,可能会导致可用服务列表频繁变化。

解决方案:

  • 心跳开关保护机制:在网络抖动时,启用一个应急设置,限制来自服务消费者的可用服务查询请求,避免注册中心被 DOS. 但这样会导致服务消费者存储的可用服务列表过期。
  • 服务节点摘除保护机制:设定一个比例,如 20%,此时注册中心不能摘除超过总数 20% 的可用节点,保证至少有 80% 的节点存储在列表中。此时,节点列表和节点实际的工作状态无关(很可能大部分节点都是可用的,只是注册中心访问不到它而已)。

静态注册中心

静态注册中心存储所有可用节点列表,但不负责进行存活检测。存活检测将交给服务消费者来进行。服务消费者每次从注册中心中拿到的节点列表是不变的,但消费者在使用服务的过程中可以通过调用的成功情况来动态地维护本地节点列表。这样,注册中心的节点列表就不会受到网络抖动的影响。
从某种角度来讲,这种做法更科学一些,因为实际使用服务的是消费者而不是注册中心。

如何使用负载均衡算法

负载均衡算法的意义:

  • 考虑调用的均匀性,使每个节点接收到的请求更加平均;
  • 考虑调用的性能,哪个节点快用哪个,使得整体响应时间最短。

常用的负载均衡算法:

  1. 随机算法
  2. 轮询算法
  3. 加权轮询算法
  4. 最少活跃连接算法
  5. 一致性 hash 算法:通过某个 hash 函数,把同一个来源的请求都映射到同一个节点上

在服务端性能差异较大的情况下,如果能预先定义权重,则可以使用加权轮询算法,否则最好使用最少活跃连接算法。

如果场景比较复杂,可以考虑自适应选择最优算法。通过“二八原则”动态调整权重。

如何使用服务路由

服务路由是在服务消费者发起服务调用时,必须根据特定的规则来选择服务节点,从而满足某些特定的需求。

服务路由的应用场景:

  • 分组调用
  • 灰度发布
  • 流量切换
  • 读写分离

服务路由主要有两种规则:条件路由和脚本路由。

服务路由的获取方式:

  • 本地配置:路由规则存在消费者的本地;
  • 配置中心管理:路由规则存在配置中心,消费者从配置中心获取规则;
  • 动态下发:运维人员通过服务治理平台修改路由规则,服务治理平台将规则持久化到配置中心,消费者订阅配置中心的变更。

服务端故障时该如何应对

微服务系统中的故障可能出现三种:

  • 集群故障(该服务的所有实例都出现了故障)
  • 单 IDC 故障(由于光缆被挖断导致某 IDC 脱网)
  • 单机故障

应对集群故障的思路主要有两种:限流(限制请求数量)和降级(停止系统中的某些功能,保证系统整体的可用性);
降级时可以在内存中存储一些开关,出现故障的时候打开某些开关,从而在代码中绕过一些处理逻辑。

应对单 IDC 故障的方式有两种:基于 DNS 解析的流量切换、基于 RPC 分组的流量切换(配置路由规则)。

在业务量大的服务中,单机故障发生的概率最高。所以最好设计一套系统来自动处理单机故障,而不是依靠人力处理。
处理单机故障的一个有效的办法就是自动重启,但重启条件的判定和服务重启比例需要确定,否则可能会出现大规模重启导致服务不可用。

服务调用失败时的处理手段

调用服务时需要设置超时时间,以避免依赖的服务迟迟没有返回结果,把服务消费者拖死。
具体超时时间设定可以以服务提供者在线上真实的服务水平为准,比如取 99.99% 分位的响应时间。

有些调用失败(或超时)后可以考虑重试,通过多次尝试调用以降低整体故障率。

除了重试外,还可以考虑双发,即同时发起两次请求。但这种方式会给服务提供者带来压力,所以一般情况下双发是不可取的。

如果服务提供者大规模故障导致所有客户端同时重试,则会给服务提供者带来更大的压力,从而加剧故障。所以这种情况下要使用熔断机制:如果发现服务提供者发生故障,则短时间内停止所有请求,以给服务提供者恢复时间来恢复。

如何管理服务配置

  • 本地配置:将配置写在代码中,随代码一同发布
  • 配置中心:将配置写在配置中心中,服务启动时从配置中心拉取所需配置;如果配置发生变更,服务还可以收到通知并自动更新本地配置

配置中心一般是 KV 实现,通常包含如下功能:注册、反注册、查看、变更订阅。
配置中心的典型应用场景:资源服务化、业务动态降级、分组流量切换。
开源的配置中心:Spring Cloud Config, Disconf, Apollo.
同时,Consul、Zookeeper 和 Etcd 等注册中心在小规模服务中也可以作为配置中心使用。

如何搭建微服务治理平台

微服务治理平台的主要功能:

对于扇贝来说,服务治理平台的选型为:

  • 服务管理(服务上下线):Kubernetes
  • 服务治理(限流降级等):Ratelimit
  • 服务监控:Prometheus & Grafana,服务依赖拓扑的整体监控暂无
  • 问题定位:Prometheus(宏观层面),Sentry(围观层面)
  • 日志查询:ELK
  • 服务运维(发布、扩缩容):Kubernetes, GitLab CI/CD

微服务架构要如何落地

  1. 技术团队中需要求架构师,也要包含懂业务的开发人员;
  2. 首先要在一个小的业务上进行试点,在架构方案成熟后再继续推进;
  3. 做好技术取舍,技术选型时要考虑目前团队的实际掌控能力,对新技术方案的引入要尤其慎重;
  4. 采用 DevOps,进行一站式开发、测试、上线和运维;
  5. 统一微服务治理平台

微服务容器化

微服务为什么要容器化

容器化的优势:

  • 可以使代码运行环境多样化
  • 开发测试生产可以使用同一套环境

微服务容器化实践:充分利用 Docker 镜像的分层机制,将基础环境、运行时环境、业务代码分层注入到镜像中。

容器化运维

容器化运维和传统运维不同:每个服务都是容器,可能没有固定的 IP. 所以需要容器运维平台来辅助运维工作。
一个容器运维平台通常包含以下几个组成部分:镜像仓库、资源调度、容器调度和服务编排。

镜像仓库:权限控制、镜像同步(多节点负载均衡)、高可用性。

资源调度:通过一个统一的层来对接不同集群(实体机、私有云、公有云),进行统一的资源管理。

容器调度:在物理机中对服务容器进行调度。常见的调度系统有 Swarm, Mesos 和 Kubernetes.
容器调度需要解决的问题:主机过滤、调度策略。

服务编排:服务依赖(Docker Compose),服务发现(基于 Nginx 或基于注册中心),自动扩缩容。

微服务如何实现 DevOps

DevOps 是一种新型的业务研发流程,业务的开发人员不仅需要负责业务代码的开发,还需要负责业务的测试以及上线发布等全生命周期,真正做到掌握服务全流程。
实现 DevOps,就必须要实现 CI 和 CD 流程。
CD 可以是持续交付(只需要让代码达到线上发布要求就行),但也可以是持续部署(Continuous Deploy),持续地把代码部署到线上。部署过程可以自动,但更推荐手动控制,因为这样更加安全。
容器化的出现解决了代码环境的可移植性问题,使得 DevOps 取得了突飞猛进的发展,并成为业界推崇的开发模式。

微博的 DevOps 实践:使用 GitLab CI 来实现 DevOps
微博的 CICD 流程

业务量大了以后,并不需要要求自动化的持续部署,方便在分阶段部署的时候及时观察服务状态并干涉部署过程。

如何做好微服务容量规划

微服务的处理能力差异很大,且很多微服务之间具有依赖关系,所以微服务容量规划相当困难。

容量规划系统的作用是根据各个微服务部署集群的最大容量和线上实际运行的负荷,来决定各个微服务是否需要弹性扩缩容,以及需要扩缩容多少台机器
实施容量规划系统的关键在于两点:做好容量评估;做好调度决策。
容量评估需要靠压测,压测需要选出合适的压测指标。调度决策主要靠设定水位线:当(一个或多个)实际指标高于水位线一段时间后,则需要扩容。

Service Mesh

概念

Service Mesh 的核心概念:将轻量级的网络代理和应用代码部署在一起,从而以应用无感知的方式实现服务治理。
Service Mesh 用轻量级网络代理的方式与应用代码部署在一起,以保证服务之间调用的可靠性,这与传统的微服务架构有本质区别。这么做主要有两个原因:

  1. 跨语言服务调用需要
  2. 云原生应用服务治理需要

Service Mesh 的核心组件:

  1. Sidecar:轻量级代理
  2. Control Plane:服务治理主控,向 sidecar 发送指令完成服务治理功能

Control Plane 的作用主要包括:服务发现、负载均衡、请求路由、故障处理、安全认证、监控上报、日志记录、配额控制

第一代 Service Mesh 的产品是 Linkerd,第二代是 Istio.

Istio

Istio 是新一代 Service Mesh 产品,具有强大的功能以及适配性,可以在 Kubernetes, Mesos 等多个平台上运行。

Istio 采用 Envoy 作为默认的 Sidecar,Control Plane 包含三个基本组件:Pilot、Mixer 以及 Citadel.

Envoy 是 Istio 中最基础的组件,所有其它组件的功能都是通过调用 Envoy 的 API,在请求经过 Envoy 转发时,由 Envoy 执行相关的控制逻辑来实现的。
Pilot 通过向 Envoy 下发指令来实现流量控制,如负载均衡、请求路由、故障注入等。
Mixer 提供了策略控制和监控日志收集等功能。
Citadel 的作用是保证服务之间访问的安全。