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

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

到底什么是微服务?

微服务定义

微服务是由单一应用程序构成的小服务,拥有自己的进程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 HTTP API 通讯。同时,服务会使用最小规模的集中管理 (例如 Docker)技术,服务可以用不同的编程语言与数据库等。

——Martin Fowler 和 James Lewis

单体应用的问题

  • 部署效率低
  • 团队协作开发成本高
  • 单点故障问题
  • 线上发布变慢

服务化:本地方法调用 转为 远程方法调用(RPC)

微服务和服务化的差异:

  • 服务拆分粒度更细
  • 服务独立部署、维护
  • 服务治理要求高

从单体应用走向服务化

什么时候进行服务化拆分?

经验:开发人员超过 10 人(沟通成本变高),就可以考虑服务化拆分

服务化拆分的两种姿势

纵向拆分,从业务维度进行拆分。标准是按照业务的关联程度来决定,关联比较密切的业务适合拆分为一个微服务,而功能相对比较独立的业务适合单独拆分为一个微服务。

横向拆分,从公共且独立功能维度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源独立不与其他业务耦合。

服务化拆分的前置条件

  • 服务如何定义。通过接口来约定。
  • 服务如何发布和订阅。通过服务注册和发现。
  • 服务如何监控故障如何定位。服务化需要链路监控。
  • 服务如何治理。超时和重试、流量控制。

初探微服务架构

微服务通过注册中心,实现发布订阅模式。

服务调用主要依赖几个基本组件:

  • 服务描述:常用的服务描述方式包括 RESTful API、XML 配置以及 IDL 文件三种。
    • RESTful API 代表:Swagger
    • XML 代表:Dubbo
    • IDL 代表:Thrift、gRPC
  • 注册中心
    • 服务提供者在启动时,根据服务发布文件中配置的发布信息向注册中心注册自己的服务。
    • 服务消费者在启动时,根据消费者配置文件中配置的服务信息向注册中心订阅自己所需要的服务。
    • 注册中心返回服务提供者地址列表给服务消费者。
    • 当服务提供者发生变化,比如有节点新增或者销毁,注册中心将变更通知给服务消费者。
  • 服务框架
    • 通信协议:选择 TCP、UDP、HTTP,还是其他?
    • 数据传输方式:同步、异步、多路复用?
    • 序列化方式:JDK 序列化、Json、二进制(Protobuf、Thrift)?
  • 服务监控
    • 数据采集
    • 数据处理
    • 数据展示
  • 服务追踪
  • 工作原理:通过 requestId、spanId 分别表示一次请求、请求中的某一环节
  • 服务治理:
    • 超时、重试
    • 负载均衡
    • 故障转移
    • 流量控制

如何发布和引用服务?

RESTful API:主要被用作 HTTP 或者 HTTPS 协议的接口定义。代表:Eureka

XML 配置:代表:Dubbo。工作步骤:

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

IDL 文件:IDL 就是接口描述语言(interface description language)的缩写。主要用作跨语言平台的服务之间的调用。有两种最常用的 IDL:Thrift、gRPC。

如何注册和发现服务?

微服务架构下,主要有三种角色:

  • 服务提供者(RPC Server)
  • 服务消费者(RPC Client)
  • 服务注册中心(Registry)

注册中心实现方式

注册中心必须提供以下最基本的 API,例如:

  • 服务注册接口

  • 服务注销接口

  • 心跳汇报接口

  • 服务订阅接口:服务消费者通过调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表。

  • 服务变更查询接口

  • 服务查询接口

  • 服务修改接口

集群部署

注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。

以 ZooKeeper 的工作原理为例:

  • 每个 Server 在内存中存储了一份数据,Client 的读请求可以请求任意一个 Server。
  • ZooKeeper 启动时,将从实例中选举一个 leader(Paxos 协议)。
  • Leader 负责处理数据更新等操作(ZAB 协议)。
  • 一个更新操作成功,当且仅当大多数 Server 在内存中成功修改 。

目录存储

注册中心存储服务信息一般采用层次化的目录结构:

  • 每个目录在 ZooKeeper 中叫作 znode,并且其有一个唯一的路径标识。
  • znode 可以包含数据和子 znode。
  • znode 中的数据可以有多个版本,比如某一个 znode 下存有多个数据版本,那么查询这个路径下的数据需带上版本信息。

服务健康状态检测

ZooKeeper 客户端和服务端维持的是一个长连接。连接成功后,会生成一个全局唯一的 Session ID,客户端定期发送心跳消息,服务端收到后重置会话超时时间。如果超时,则认为连接结束。

如果一个服务将 ZooKeeper 作为服务注册中心,一旦连接超时,ZooKeeper 会认为这个服务节点已经不可用,就会将其信息删除。

服务状态变更通知

ZooKeeper 支持 Watch 机制。服务消费者可以监听服务提供者的节点信息。一旦服务提供者的节点信息哟变化,就可以获取到变更状态。

白名单机制

通常注册中心会有多套环境,区分开发、测试、线上等环境。如果弄错了,会出现意想不到的后果,为此需要引入白名单保护机制。只有添加到注册中心白名单内的 RPC Server,才能够调用注册中心的注册接口,这样的话可以避免测试环境中的节点意外跑到线上环境中去。

如何实现 RPC 远程服务调用?

客户端和服务端如何建立网络连接?

  • HTTP 通信:三次握手建立连接;四次挥手断开连接
  • Socket 通信
    • 服务器监听
    • 客户端请求
    • 连接确认
    • 数据传输

服务端如何处理请求?

  • BIO
  • NIO
  • AIO

数据传输采用什么协议?

  • Http
  • Dubbo

数据该如何序列化和反序列化?

  • JDK
  • JSON
  • 二进制(PB、Thrift 等)

如何监控微服务调用?

监控对象

  • 客户端监控
  • 接口监控
  • 资源监控
  • 基础监控

监控指标

  • 请求量
  • 响应时间
  • 错误率

监控维度

  • 全局维度
  • 机房维度
  • 单机维度
  • 时间维度
  • 重要性维度

监控关键点

  • 数据采集
    • 主动上报
    • 代理收集
  • 数据传输
    • UDP
    • Kafka
  • 数据处理
    • 全文检索:如 Elasticsearch
    • 时序数据库:如 InfluxDB、OpenTSDB
    • 流计算:如 Spark、Storm、Flink
  • 数据展示

如何追踪微服务调用?

服务追踪的作用

  • 定位整个系统的瓶颈点
  • 优化链路调用
  • 生成网络拓扑
  • 透明传输数据

服务追踪系统原理

经典论文:Dapper, a Large-Scale Distributed Systems Tracing Infrastructure

  • 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。

服务追踪系统实现

服务追踪系统可以分为三层。

  • 数据采集层,负责数据埋点并上报。
  • 数据处理层,负责数据的存储与计算。
  • 数据展示层,负责数据的图形化展示。

微服务治理的手段有哪些?

服务调用失败原因:

  • 服务提供者自身问题,如宕机、进程退出等;
  • 网络问题

节点管理

  • 注册中心主动摘除机制:服务提供者定时发送心跳,如果超时,注册中心把节点从服务列表中删除
  • 服务消费者摘除机制:如果服务消费者调用服务提供者节点失败,就将这个节点从内存中保存的可用服务提供者节点列表中移除。

负载均衡

  • 随机算法
  • 轮询算法
  • 最少活跃调用算法
  • 一致性 Hash 算法

服务路由

为什么要制定路由规则呢?

  • 业务存在灰度发布的需求
  • 多机房就近访问的需求

如何配置路由规则

  • 静态配置:修改服务消费者本地配置,上线后生效
  • 动态配置:修改注册中心的配置,服务消费者在下一个同步周期之后,就会动态更新

服务容错

  • FailOver:失败自动切换。
  • FailBack:失败通知。
  • FailCache:失败缓存。
  • FailFast:快速失败。

一般情况下对于幂等的调用,可以选择 FailOver 或者 FailCache,非幂等的调用可以选择 FailBack 或者 FailFast。

Dubbo 框架里的微服务组件

服务发布和引用的实践

XML 配置方式的服务发布和引用流程

  • 服务提供者定义接口
  • 服务提供者发布接口
  • 服务消费者引用接口

服务发布和引用的那些坑

如何将注册中心落地?

注册中心如何存储服务信息

服务一般会分成多个不同的分组

  • 核心与非核心,从业务的核心程度来分。
  • 机房,从机房的维度来分。
  • 线上环境与测试环境,从业务场景维度来区分。

所以注册中心存储的服务信息一般包含三部分内容:分组服务名以及节点信息,节点信息又包括节点地址和节点其他信息。

注册中心工作流程

  • 服务提供者注册流程。
  • 服务提供者反注册流程。
  • 服务消费者查询流程。
  • 服务消费者订阅变更流程。

如何注册节点

  • 首先查看要注册的节点是否在白名单内?如果不在就抛出异常,在的话继续下一步。
  • 其次要查看注册的 Cluster(服务的接口名)是否存在?如果不存在就抛出异常,存在的话继续下一步。
  • 然后要检查 Service(服务的分组)是否存在?如果不存在则抛出异常,存在的话继续下一步。
  • 最后将节点信息添加到对应的 Service 和 Cluster 下面的存储中。

如何反注册

  • 查看 Service(服务的分组)是否存在,不存在就抛出异常,存在就继续下一步。
  • 查看 Cluster(服务的接口名)是否存在,不存在就抛出异常,存在就继续下一步。
  • 删除存储中 Service 和 Cluster 下对应的节点信息。
  • 更新 Cluster 的 sign 值。

如何查询节点信息

首先从 localcache(本机内存)中查找,如果没有就继续下一步。

接着从 snapshot(本地快照)中查找,如果没有就继续下一步。

如何订阅服务变更

  • 服务消费者从注册中心获取了服务的信息后,就订阅了服务的变化,会在本地保留 Cluster 的 sign 值。
  • 服务消费者每隔一段时间,调用 getSign() 函数,从注册中心获取服务端该 Cluster 的 sign 值,并与本地保留的 sign 值做对比,如果不一致,就从服务端拉取新的节点信息,并更新 localcache 和 snapshot。

注册与发现的几个问题

  • 多注册中心

  • 并行订阅服务

  • 批量反注册服务

  • 服务变更信息增量更新

开源服务注册中心如何选型?

  • 应用内注册与发现:注册中心提供服务端和客户端的 SDK,业务应用通过引入注册中心提供的 SDK,通过 SDK 与注册中心交互,来实现服务的注册和发现。典型代表:Eureka
  • 应用外注册与发现:业务应用本身不需要通过 SDK 与注册中心打交道,而是通过其他方式与注册中心交互,间接完成服务注册与发现。典型代表:Consul

二者对比:

  • 用内的解决方案一般适用于服务提供者和服务消费者同属于一个技术体系;
  • 应用外的解决方案一般适合服务提供者和服务消费者采用了不同技术体系的业务场景

注册中心选型要考虑的两个问题

  • 高可用性
  • 数据一致性
    • CP 型:牺牲可用性来保证数据强一致性。代表:ZooKeeper、Etcd、Consul
    • AP 型:代表:Eureka、Nacos

而对于注册中心来说,最主要的功能是服务的注册和发现,在网络出现问题的时候,可用性的需求要远远高于数据一致性。即使因为数据不一致,注册中心内引入了不可用的服务节点,也可以通过其他措施来避免,比如客户端的快速失败机制等,只要实现最终一致性,对于注册中心来说就足够了。因此,选择 AP 型注册中心,一般更加合适。

开源 RPC 框架如何选型?

限定语言 RPC

  • Dubbo:仅支持 Java
  • Motan:仅支持 Java
  • Tars:仅支持 C++
  • Spring Cloud:仅支持 Java

跨语言 RPC

  • gRPC:支持 C++、Java、Python、Go、Ruby、PHP、Android Java、Objective-C 等多种语言
  • Thrift:支持 C++、Java、PHP、Python、Ruby、Erlang 等多种语言

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

日志解决方案:ELK

时序数据库解决方案:GraphiteTICKPrometheus

如何搭建一套适合你的服务追踪系统?

代表:Zipkin、PinPoint

如何识别服务节点是否存活?

心跳开关保护机制

问题:服务消费者同时并发访问注册中心获取最新服务信息导致注册中心带宽被打满

方案:需要一种保护机制,即使在网络频繁抖动的时候,服务消费者也不至于同时去请求注册中心获取最新的服务节点信息。

服务节点摘除保护机制

问题:服务提供者节点被大量摘除导致服务消费者没有足够的节点可以调用

方案:需要根据实际业务的情况,设定一个阈值比例,即使遇到刚才说的这种情况,注册中心也不能摘除超过这个阈值比例的节点。

静态注册中心

如何使用负载均衡算法?

负载均衡算法

  • 随机算法

  • 轮询算法

  • 加权轮询算法

  • 最少活跃连接算法

  • 一致性 hash 算法

如何使用服务路由?

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

服务路由的应用场景

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

服务路由的规则

  • 条件路由
    • 排除某个服务节点
    • 白名单和黑名单功能
    • 机房隔离
    • 读写分离
  • 脚本路由

服务路由的获取方式

  • 本地配置
  • 配置中心管理
  • 动态下发

服务端出现故障时该如何应对?

微服务故障种类

  • 集群故障。解决:流量控制
    • 限流
    • 降级
  • 单 IDC 故障。解决:多 IDC 部署、流量切换
    • 多 IDC 部署
      • 同城多活
      • 异地多活
    • 流量切换
      • DNS 解析流量切换
      • RPC 流量切换
  • 单机故障

服务调用失败时有哪些处理手段?

超时

重试

流量控制

如何管理服务配置?

配置类型:

  • 本地配置
  • 配置中心

配置中心代表:

如何搭建微服务治理平台?

服务管理

  • 服务上下线
  • 节点添加 / 删除
  • 服务查询
  • 服务节点查询。这个操作会调用注册中心的节点查询接口,来查询某个服务下一共有多少个节点。

服务治理

  • 限流
  • 降级
  • 切流量

服务监控

问题定位

日志查询

服务运维

  • 发布部署
  • 弹性伸缩

微服务架构该如何落地?

(略)

微服务为什么要容器化?

微服务引入的问题

设计复杂

测试复杂

运维困难

微服务容器化运维:镜像仓库和资源调度

容器运维平台的组成部分

  • 镜像仓库
  • 资源调度
  • 容器调度
  • 服务编排

微服务容器化运维:容器调度和服务编排

容器调度系统代表:SwarmMesosKubernetes

容器调度要解决的问题

  • 主机过滤
    • 存活过滤
    • 硬件过滤
  • 调度策略
  • 服务编排
  • 服务依赖:代表方案:Docker Compose
  • 服务发现
    • 基于 Nginx 的服务发现
    • 基于注册中心的服务发现
    • 弹性伸缩

微服务容器化运维:微博容器运维平台 DCP

微服务如何实现 DevOps?

  • CI(Continuous Integration),持续集成。开发完成代码开发后,能自动地进行代码检查、单元测试、打包部署到测试环境,进行集成测试,跑自动化测试用例。
    • 代码检查
    • 单元测试
    • 集成测试
  • CD(Continuous Deploy),持续部署。代码测试通过后,能自动部署到类生产环境中进行集成测试,测试通过后再进行小流量的灰度验证,验证通过后代码就达到线上发布的要求了,就可以把代码自动部署到线上。

如何做好微服务容量规划?

微服务容量规划的问题

  • 服务数量众多
  • 服务的接口表现差异巨大
  • 服务部署的集群规模大小不同
  • 服务之间还存在依赖关系

容量规划系统的作用是根据各个微服务部署集群的最大容量和线上实际运行的负荷,来决定各个微服务是否需要弹性扩缩容,以及需要扩缩容多少台机器

容量规划系统实施的关键在于两点:

  • 容量评估
    • 选择合适的压测指标
      • 系统类指标:CPU、内存、I/O、带宽等
      • 服务类指标:响应时间、P999 耗时、错误率等
    • 压测获取单机的最大容量
      • 单机压测
        • 通过日志回放等手段,模拟线上流量来对单机进行压测;
        • 通过 TCP-Copy 的方式,把线上机器的流量拷贝过来对单机进行压测。
      • 集群压测
    • 实时和获取集群的运行负荷
  • 调度决策
    • 可以使用水位线来进行调度决策:当集群的水位线位于致命线以下时,就需要立即扩容,在扩容一定数量的机器后,水位线回到安全线以上并保持一段时间后,就可以进行缩容了。
    • 扩容
      • 按数量
      • 按比例
    • 缩容
    • 逐步缩容
    • 为了避免因扩容、缩容导致的水位线抖动,可以多次采集水位线数据,超过 60% 数据满足库哦哦让条件,才真正触发扩容。

微服务多机房部署实践

多机房负载均衡:利用七层负载均衡和四层负载均衡,将流量根据用户就近访问的原则切分流量。

多机房数据同步

主从机房架构

  • 由主机房的处理机来更新本机房的缓存和数据库
  • 其他机房的缓存也通过主机房的处理机来更新
  • 从机房的数据库则通过 MySQL 的 binlog 同步主机房的数据。

独立机房架构

  • 每个机房的处理机接收到写请求后更新各自机房的缓存
  • 只有主机房会更新数据库
  • 从机房的数据库则通过 MySQL 的 binlog 同步主机房的数据。

WMB 消息同步组件的功能就是把一个机房的写请求发给另外一个机房

  • reship,负责把本机房的写请求分发一份给别的机房。
  • collector,负责从别的机房读取写请求,然后再把请求转发给本机房的处理机。

实现 WMB 的消息同步功能有两种方案:

  • MQ:两个机房的 MQ 通过维护状态机来读写请求
  • RPC

多机房数据一致性

微服务混合云部署实践

跨云服务的负载均衡

当服务上云后还需要考虑把一定比例的用户请求路由到云上部署的服务

跨云服务的数据同步

私有云与公有云之间的网络隔离

一般来讲,出于安全的需要,企业内部机房同公有云机房之间的网络是隔离的,为了实现互通,需要架设专门的 VPN 网络或者专线。

数据库能否上云

数据库能否上云的关键取决于数据的隐私性。

跨云服务的容器运维

跨云的主机管理:跨云主机管理的关键点在于,如何对内部私有云的机器和公有云的 ECS 进行管理,

跨云服务发现

跨云弹性扩容

跨云服务编排

下一代微服务架构 Service Mesh

为什么需要 Service Mesh

  • 跨语言服务调用的需要

  • 云原生应用服务治理的需要

Service Mesh 的实现原理

Service Mesh 实现的关键点:

  • 轻量级网络代理 SideCar,它的作用就是转发服务之间的调用;
  • 基于 SideCar 的服务治理也被叫作 Control Plane,它的作用是向 SideCar 发送各种指令,以完成各种服务治理功能。
  • 服务发现
  • 负载均衡
  • 请求路由
  • 故障处理
  • 安全认证
  • 监控上报
  • 日志记录
  • 配额控制

Istio:Service Mesh 的代表产品

Istio 整体架构

Istio 的架构可以说由两部分组成,分别是 Proxy 和 Control Plane。

  • Proxy,就是前面提到的 SideCar,与应用程序部署在同一个主机上,应用程序之间的调用都通过 Proxy 来转发,目前支持 HTTP/1.1、HTTP/2、gRPC 以及 TCP 请求。
  • Control Plane,与 Proxy 通信,来实现各种服务治理功能,包括三个基本组件:Pilot、Mixer 以及 Citadel。