【字节跳动】第十五讲 微服务架构原理与治理实践| 青训营笔记

2022-08-22 13:57:15 浏览数 (1)

讲师介绍

周余发:2018年硕士毕业,曾在美团点评研发效率中心、抖音服务架构担任系统开发。目前为字节跳动Service Mesh研发人员。

相关课程

  • 《架构初探-谁动了我的蛋糕》
  • 《HTTP 框架修炼之道》
  • 《RPC 框架设计与实现》
  • 《从需求到上线全流程》

课程背景

  • 为什么有这门课程? 微服务架构是当前大多数互联网公司的标准架构。
  • 我可以学到什么? 微服务架构的由来及原理; 服务治理功能是如何工作的。

1. 微服务架构介绍

微服务架构的背景由来、架构概览、基本要素

1.1 系统架构演变历史

  • 互联网的爆炸性发展
  • 硬件设施的快速发展
  • 需求复杂性的多样化
  • 开发人员的急剧增加
  • 计算机理论及技术的发展

架构演进过程:单体架构->垂直应用架构->分布式架构->SOA架构->微服务架构

1.1.1 单体架构

all in one process

优势:

  1. 性能最高
  2. 冗余小

劣势:

  1. debug困难
  2. 模块相互影响
  3. 模块分功、并发流程

1.png

1.1.2 垂直应用架构

按照业务线垂直划分

优势:

  1. 业务独立开发维护

劣势:

  1. 不同业务存在冗余
  2. 每个业务还是单体

2.png

1.1.3 分布式架构

抽出业务无关的公共模块

优势:

  1. 业务无关的独立服务

劣势:

  1. 服务模块bug可导致全站瘫痪
  2. 调用关系复杂
  3. 不同服务冗余

3.png

1.1.4 SOA架构(Service Oriented Architecture)

面向服务

优势:

  1. 服务注册

劣势:

  1. 整个系统设计是中心化的
  2. 需要从上至下设计
  3. 重构困难

4.png

1.1.5 微服务架构

彻底的服务化

优势:

  1. 开发效率
  2. 业务独立设计
  3. 自下而上
  4. 故障隔离

劣势:

  1. 治理、运维难度
  2. 观测挑战
  3. 安全性
  4. 分布式系统

5.png

1.2 微服务架构概览

6.png

1.3 微服务架构核心要素

服务治理

服务注册、服务发现、负载均衡、扩缩容、流量治理、稳定性治理 .......

可观测性

日志采集、日志分析、监控打点、监控大盘、异常报警、链路追踪 ......

安全

身份验证、认证授权、访问令牌、审计、传输加密、黑产攻击 ......

2. 微服务架构原理及特征

微服务架构的基本组件、工作原理、流量特征

2.1 基本概念

  • 服务(service)
    • 一组具有相同逻辑的运行实体。
  • 实例(instance)
    • 一个服务中,每个运行实体即为一个实例。
  • 实例与进程的关系
    • 实例与进程之间没有必然对应关系,可以一个实例可以对应一个或多个进程(反之不常见)。
  • 集群(cluster)
    • 通常指服务内部的逻辑划分,包含多个实例。
  • 常见的实例承载形式
    • 进程、VM、k8s pod ......
  • 有状态/无状态服务
    • 服务的实例是否存储了可持久化的数据(列如磁盘文件)。

7.png

如果把HDFS看做一组微服务

8.png

服务间通信

对于单体服务,不同模块通信只是简单的函数调用。

对于微服务,服务间通信意味着网络传输。

9.png

2.2 服务注册及发现

问题1

在代码层面,如何指定调用一个目标服务的地址(ip:port)?

hardcode?

代码语言:javascript复制
// Service A wants to call service B.
client := grpc.NewClient("10.23.45.67:8080")

10.png

问题2

在代码层面,如何指定调用一个目标服务的地址(ip:port)?

DNS?

  • 本地DNS存在缓存,导致延时。
  • 负载均衡问题。
  • 不支持服务实例的探活检查。
  • 域名无法配置端口。

11.png

解决一个ip指定上万端口问题

解决思路新增一个统一的服务注册中心,用于存储服务名到服务实例的映射。

12.png

无损的服务实例上下线过程

  • 下线操作

如果需要下线实例3(10.67.89.12:9007),首先删除服务中心(Service Registry)内的10.67.89.12:9007,等过一段时间服务A调用不到实例3了,这个时候再删除实例3是安全的(因为已经没有流量了),最终完成下线。

  • 上线操作

上线操作与下线完全相反,首先启动一个服务实例4,并且需要一个切换检查(health check)(发送一个请求尝试一下是否成功),之后再讲实例4的ip:端口 注册进入服务中心(Service Registry),注册成功后等待一段时间,服务进行刷新,服务A就会自动连接到实例4上,流量就来了。

14.png

2.3 流量特征

  • 统一网关入口
  • 内网通信多数采用RPC
  • 网络调用链路

15.png

3. 核心服务治理功能

核心的服务治理功能,包括流量治理、服务均衡、稳定性治理

服务发布(deployment),即指让一个服务升级运行新的代码的过程。

3.1 服务发布

服务发布的难点

服务不可用

服务抖动

服务回滚

蓝绿部署

19.png

首先发布绿色的服务,就先将绿色的流量切到蓝色的上面,这个时候绿色没有流量就是安全的不会出现安全问题,之后对绿色进行升级,升级完之后和刚刚步骤一样,先将蓝色的服务切到绿色的上面, 升级蓝色的,全部完成升级后,这个实例就完成了。这样就可以达到无损升级。

优点:简单,稳定

缺点:需要两倍资源(但是可以从流量低峰入手,会降低资源消耗)

灰度发布(金丝雀发布)

金丝雀背景:金丝雀(canary) 对瓦斯极其敏感,17世纪时,英国矿工在下井前会先放入一只金丝雀,以确保矿井中没有瓦斯。

20.png

21.png

整个流程就是:先放入一个金丝雀的服务,启动如果正常没有问题,将之前的一个服务进行替换掉,之后重复操作,直到将旧的全部替换完成。

灰度发布难点:流量切换很麻烦有很大挑战,并且如果出现一个问题例如完成了百分之九十九就要进行服务回滚,将之前的完成部分全部回退回去,所以还需要有蓝绿部署。这也就是节省了资源所需要的代价。

3.2 流量治理

在微服务架构下,我们可以基于地区、集群、实例、请求等维度,对端到端流量的路由路径进行精确控制。

22.png

3.3 负载均衡

负载均衡(Load Balance)负责分配请求在每个下游实例上的分布。

常见的LB策略

  • Round Robin
  • Random
  • Ring Hash
  • Least Request
  • ......

23.png

3.4 稳定性治理

线上服务总是会出现问题的,这与程序的正确性无关。

  • 网络攻击
  • 流量突增
  • 机房断电
  • 光纤被挖
  • 机器故障
  • 网络故障
  • 机房空调故障
  • ......

24.png

为了避免上述问题给出了微服务架构中典型的稳定性治理功能

  • 限流

25.png

  • 熔断

26.png

  • 过载保护

27.png

  • 降级

28.png

3.5 小结

  • 服务发布:蓝绿部署、灰度发布
  • 基于地区、集群、实例、请求等维度的流量治理功能。
  • 几种常见的负载均衡策略
  • 微服务架构中的稳定性治理功能

4. 字节跳动服务治理实践

  • 字节跳动在微服务架构稳定性治理中,对请求重试策略的探索及实践
  • 重试的意义、重试的难点、重试策略、重试效果验证

4.1 重试的意义

代码语言:javascript复制
func LocalFunc(x int) int { 
    res := calculate(x * 2) 
    return res 
}

可能有哪些异常?

  • 参数非法
  • OOM(Out Of Memory)
  • NPE(Null Pointer Exception)
  • 边界case
  • 系统崩溃
  • 死循环
  • 程序异常退出

是否有重试必要?没必要

代码语言:javascript复制
func RemoteFunc(ctx context.Context, x int) (int, error) { 
    ctx2, defer_func := context.WithTimeout(ctx, time.Second) 
    defer defer_func() 
    
    res, err := grpc_client.Calculate(ctx2, x * 2)
    return res, err 
}

可能有哪些异常?

  • 网络抖动
  • 下游负载高导致超时
  • 下游机器宕机
  • 本地机器负载高,调度超时
  • 下游熔断、限流
  • ......

重试可以避免掉偶发的错误,提高SLA(Service-Level Agreement)

代码语言:javascript复制
func RmotFunc(ctx context.Context, x int) (int , error) { 
    ctx2, defer_func := context.WithTimeout(ctx, time.Second) 
    defer defer_func() 
    
    res, err := grpc_client.Calculate(ctx2, x * 2) 
    return res, err 
} 
func RemoteFuncRetry(ctx context.Context, x int) (res int, err error) {
    for i := 0; i < 3; i   { 
        if res,err = RemoteFunc(ctx, x); err == nil {
            return 
        }
    }
    return
} 

重试的意义

  • 降低错误率
    • 假设单词请求的错误概率为0.01,那么连续两次错误概率则为0.0001。
  • 降低长尾延时
    • 对于偶尔耗时较长的请求,重试请求有机会提前返回。
  • 容忍暂时性错误
    • 某些时候系统会有暂时性异常(例如网络抖动),重试可以尽量规避。
  • 避开下游故障实例
    • 一个服务中可能会有少量实例故障(例如机器故障),重试其他实例可以成功。

4.2 重试的难点

既然重试这么多好处,为什么默认不用呢?

有以下几个难点

  • 幂等性
  • 重试风暴
  • 超时设置

重试风暴(很容易出现雪崩)

雪崩和调用链有关系

29.png

上面这个图片清楚的描述出了由服务A重试3词,到服务B加上之前的就需要9次是一个幂的请求次数,最终达到一定量就会出现雪崩

那如何解决呢?

4.3 重试策略

设定一个重试比例阈值(例如%1),重试次数占所有请求比例不超过该阈值。

30.png

防止链路重试

链路层面的防重试风暴的核心是限制每层都发生重试,理想情况下只有最下一层发生重试。可以返回特殊的status表明“请求失败,但别重试”。

31.png

Hedged requests

对于可能超时(或延时高)的请求,重新向另一个下游实例发送一个相同的请求,并等待先到达的响应。

32.png

4.4 重试效果验证

实际验证经过上述重试策略后,在链路上发生的重试放大效应。

33.png

4.5 总结

  • 重试的意义及难点
  • 应对重试风暴的策略

课程总结

  • 微服务架构介绍
  • 微服务架构原理及特征
  • 核心服务治理功能
  • 字节跳动服务治理实践

0 人点赞