第五届字节跳动青训营Class9笔记 | 青训营笔记

287 阅读5分钟

这是我参与「第五届青训营 」笔记创作活动的第9天

本节课主要讲解了微服务架构的原理和应用。

1.微服务架构介绍

系统架构演变

graph TD
A(单体架构:性能最高冗余小,但是难以debug,模块相互影响)
B(垂直应用架构:按照业务线垂直划分)
C(分布式架构:抽取业务无关的公共模块,复用公共逻辑来去冗余)
D(SOA架构:面向服务,整个系统通过注册中心来获取服务层的服务)
E(微服务架构:自下而上的独立设计,业务独立设计,故障隔离)
A --> B
B --> C
C --> D
D --> E

微服务架构概览

image.png

微服务架构核心要素

  • 服务治理:服务注册、发现,负载均衡,扩缩容,流量治理,稳定性治理
  • 可观测性:日志采集,日志分析,监控打点,监控大盘,异常报警,链路追踪
  • 安全性:身份验证,认证授权,访问令牌,审计,传输加密,黑产攻击

基本概念

  • 服务:一组具有相同逻辑的运行实体(即运行同一个代码)
  • 实例:一个服务中,每个运行实体即为一个实例

实例与进程之间没有必然对应联系,可以一个实例对应一个或多个进程

  • 集群:通常指服务内部的逻辑划分,包含多个实例
  • 常见实例承载形式:进程、VM、k8s pod等
  • 有状态/无状态服务:服务的实例是否存储了持久化数据

服务间通信:对于单体服务,不同模块通信只是简单的函数调用;对于微服务,服务间通信意味着网络传输。

image.png

服务注册和发现

在代码层面,指定调用一个目标服务的地址:使用hardcode

ServiceA wants to call service B.
client := grpc.NewClient(ip)

由于一个服务部署了多个实例在多个端口上,因此不能够写死ip。

ip的调用形式:

1.使用DNS的方式,B服务为每一个实例在不同ip下注册相同端口的DNS

ip := b.service.org:8080
b.service.org={
                10.23.45.67
                10.45.67.89
                10.67.89.12
              }

问题:本地DNS有缓存机制,会导致延时,不支持服务实例的探活检查,同时端口限定

2.服务注册中心存储服务名到服务实例的映射

本质上是构建一个服务内部维护的ip容器,按照容器索引选取,非持久化的方式也保证了同步速度和动态变化需求。

ipList := svc_reg.find("service.b") 
ip := ipList[random(n)] //使用负载均衡算法选取ip

服务实例上线及下线过程

  • 下线:服务A不断记录对B实例的访问情况,当B某个实例没有流量时进行下线。
  • 上线:当服务B发现当前实例压力较大,会对新的实例进行健康检查,通过检查之后即可上线服务注册中心。

流量特征

image.png

使用统一的网关入口,内网通信多数采用RPC(HTTP作为文本协议效率不如使用二进制形式传输数据的RPC),使用网状调用链路。

3.核心服务治理功能

服务发布

服务发布是让一个服务升级运行新的代码的过程

服务发布的难点

  • 服务不可用:服务B升级导致服务A无法调用服务B
  • 服务抖动:服务B升级导致不可预测的错误出现
  • 服务回滚:B服务发现问题回滚时无法被服务A调用

蓝绿部署

image.png

先将流量全部导入蓝色集群,升级绿色集群;再将流量全部导入绿色集群,升级蓝色集群,然后恢复

缺点:两倍资源占用

灰度发布

灰度发布也称为金丝雀发布,思路是逐次上线新服务,并且迭代掉旧的服务

image.png

缺点:需要精细导入流量,且迭代程度越高回滚难度越大

流量治理

在微服务架构下,可以基于地区、集群、实例、请求等维度,对端到端的流量的路由路径进行分配

image.png

负载均衡

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

常见的LB策略:

  • Round Robin
  • Random
  • Ring Hash
  • Least Request

稳定性治理

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

稳定性治理功能:

  • 限流

image.png

  • 熔断

image.png

  • 过载保护

image.png

  • 降级

image.png

字节跳动服务治理实践

重试的意义

本地函数调用

可能的异常:参数非法、OOM(Out Of Memmory)、NPE(Null Pointer Exception)、边界条件、系统崩溃、死循环、程序异常退出

func LocalFunc(x int) int{
    res := calculate(x * 2)
    return res
}

本地的函数调用通常没有重试的意义

远程函数调用

func RemoteFunc(ctx content.Content, xint) (int error){
    ctx2, defer_func := context.WithTimeout(ctx, time.Second) //设置一秒超时
    defer defer func()
    
    res, err := grpc_client.Calculate(ctx2, x * 2)
    return res, err
}

可能的异常:网络抖动,下游负载高导致超时,下游机器宕机,本地机器负载高,调度超时,下游熔断、限流

意义

  • 降低错误率
  • 降低长尾延时,对于偶尔耗时较长的请求,重试请求有机会提前返回
  • 容忍暂时性错误:规避系统的暂时性异常
  • 避开下游故障实例:一个服务中可能会有少量的实例故障,重试其他实例可以成功

重试的难点

  • 幂等性
  • 重试风暴

image.png

  • 超时设置

重试策略

  • 限制重试比例:设定一个重试比例阈值(例如1%),重试次数占所有请求比例不超过该阈值
  • 防止链路重试:链路层面防止重试风暴的核心是限制每层都发生重试,理想情况下只有最下一层发生重试,可以返回特殊的status表明“请求失败,但别重试”。
  • Hedged requests:对于可能超时(或延时高)的请求,重新向另一个下游实例发送一个相同的请求,并等待先到达的响应。

总结

本节课程主要介绍了微服务架构及其相互之间通信、实例的更新发布、流量治理和重试。整体上涵盖了微服务全部内容的关键模块,也为微服务性能的调优提供了很多思路。