第一节 微服务架构介绍
1.1 系统架构演变历史
为什么系统架构需要演进?
- 互联网的爆炸性发展
- 硬件设施的快速发展
- 需求复杂性的多样化
- 开发人员的急剧增加
- 计算机理论及技术的发展
单体架构
All in one process(及业务所有功能都在一个程序里)
优势:
- 性能最高
- 冗余小
劣势:
- Debug困难:代码量大,找出错误很麻烦
- 模块相互影响:非核心功能可能导致整体系统崩溃(单一业务功能中的bug可能导致系统整体崩溃)
- 模块分工、开发流程:几乎无法分工且上线困难,一个小功能出问题,所有业务无法部署
垂直应用架构
按照业务线垂直划分
优势:
- 业务独立开发维护
劣势:
- 不同业务线的模块存在冗余,无法复用
- 每个业务还是单体业务,依然具有单体业务的缺点
分布式架构
抽出业务无关的公共模块,成为独立的服务层,支持公共逻辑的去冗余(及支持公共逻辑的复用)
优势:
- 业务无关的独立服务
劣势:
- 服务模块bug可导致全站瘫痪
- 调用关系复杂
- 不同服务之间依然存在冗余
SOA架构(Service Oriented Architecture)
与分布式架构相比,增加服务注册中心,更清晰的划分了业务系统和服务层
优势:
- 出现服务思想和服务注册的功能
劣势:
- 整个系统的设计是中心化的
- 需要从上至下设计(先规划层数和组件)
- 重构困难
微服务架构
彻底的服务化
在开发新功能时无需考虑系统其他服务,仅仅需要按照一定约定搭建功能后部署,服务之间的调用关系可以由微服务架构的基础能力实现。微服务架构中服务拆分力度大,因此单个服务的bug对其余服务的影响小。
优势:
- 开发效率高
- 业务独立设计
- 自下而上
- 故障隔离
劣势:
- 治理、运维难度急剧增加
- 观测挑战
- 安全性
- 分布式系统自身的复杂性
1.2 微服务架构概览
微服务架构在主体之外还需要三大组件:网关(处理外界流量)、服务配置和治理(服务各种配置的基础化)、链路追踪和监控(支持问题定位)
1.3 微服务架构核心要素
服务治理:服务注册、服务发现、负载均衡、扩缩容、流量治理、稳定性治理……
可观测性:日志收集、日志分析、监控打点、监控大盘、异常报警、链路追踪……
安全:身份验证,认证授权、审计、传输加密、黑产攻击……
第二节 微服务架构原理及特征
2.1 基本概念
服务:一组具有相同逻辑的运行实体(一个服务运行同一份代码)
实例:一个服务中,每个运行实体即为一个实例(一个服务就是运行同一份代码的多个实例)
实例与进程的关系:实例与进程之间没有必然的对应关系,一个实例可以对应一个或多个进程,但一个进程对应多个实例则不常见
常见的实例承载形式:进程、VM(虚拟机)、k&s pod……(只要满足运行的特征就可以)
集群:通常指服务内部的逻辑划分,包含多个实例(通常服务包含集群,相同逻辑的实例在一个集群中)
有状态 / 无状态服务:服务的实例是否存储了可持久化的数据,一个存储的服务大概率是有状态,一个代理的服务是无状态
服务间通信:对于单体服务,不同模块通信只是简单的函数调用,但对于微服务,服务间的通信意味着网络传输,通过不同的传输协议通信
2.2 服务注册及发现
在代码层面,如何指定调用一个目标服务的地址(ip:port)?
网络通信功能需要指定远程IP地址和端口,那么我们如何在代码层面指定服务之间通信时的通信地址呢?Hardcode指定地址确实是一种解决方法,但其存在以下问题:
- 微服务环境中地址会变动,无法指定固定地址
- 在多个实例情况下,只指定一个固定地址,则只有一个实例能收到请求
既然在微服务架构中无法指定固定地址,我们能否使用DNS协议呢?
DNS协议存在以下问题:
- 本地DNS存在缓存,导致延时
- 域名使用IP地址时多数情况会选第一个,制造负载均衡问题
- 不支持服务实例的探活检查(可以配置非法不存在IP,导致服务访问错误)
- 域名无法配置端口(一个端口一个服务时,端口数量锁死可运行的服务上限)
问题解决思路:新增一个统一的服务注册中心,用于存储服务名到服务实例的映射,可以理解成一个哈希表或Map
服务B在服务中心注册IP地址和端口(解决无法配置端口的问题),服务A先访问服务注册中心,获得服务B当前地址
服务实例上线及下线过程
假设系统管理员需要下线service B的实例-3,此时我们不能直接下线,因为实例-3还有流量
我们应先在服务注册中心删除实例-3的记录
服务A在一定延迟后发现服务B实例-3的记录消失,服务A将不再调用实例-3,此时实例-3没有流量,可以下线
考虑到实例-3下线,此时服务B中的其余实例压力增大,需要增加新实例缓解压力
先建立新实例,通过health check 确认实例可以处理请求后加入服务注册中心
服务A继续刷新服务注册中心,发现新实例出现,流量恢复
2.3 流量特征
流量从终端用户通过统一的网关入口(负载均衡)进入企业内网,其调用链路为网状
进入的流量是HTTP,HTTP是文本协议,性能低
内部服务流量是RPC,RPC是二进制协议,效率高
第三节 核心服务治理功能
3.1 服务发布
服务发布指让一个服务升级运行新的代码的过程
服务发布具有以下三大难点:
- 服务不可用:服务B更新导致服务A无法调用服务B
- 服务抖动:服务更新时直接停用某个实例,其流量消失,造成服务抖动
- 服务回滚:在新代码发现问题时,直接回滚使用旧版代码
蓝绿部署
关绿色集群流量(流量分配到蓝色集群),升级绿色集群
关蓝色集群流量(分配到绿色集群),升级蓝色集群
蓝绿部署简单,稳定,但需要两倍资源才能实现
在实践中,一般会选择在流量低的时候进行蓝绿部署
灰度发布(金丝雀发布)
先上线一个新代码的实例,确定代码没有问题后,下线一个旧代码实例,重复以上步骤,直到所有旧代码实例都被替换
灰度发布需要精细化流量控制,回滚难度大,对基础设施要求高
3.2 流量治理
流量治理狭义的讲,是对流量的控制
在微服务架构下,我们可以基于地区、集群、实例、请求等维度,对端到端流量的路由路劲进行精细的控制
地区:北京人多,给60%流量,人少的城市给40%流量
集群:少部分流量进入测试集群测试新代码
实例:老机器新能不好,希望处理少些请求,老机器少分配流量
请求:多用于内部用户测试,内部请求(特使标识)导到测试集群,常规流量进入正常请求
3.3 负载均衡(Load Balance)
负责分配请求在每个下游实例上的分布。在一个服务中,通常每个实例的负载应当是大体一致的
常见的负载均衡策略:Round Robin、Random、Ring Hash、Least Request……
3.4 稳定性治理
线上服务总是会出线问题,这与程序的正确性无关
可能出现的非程序性错误问题:网络攻击、流量突增、机房断电、光纤被挖、机器故障、网络故障、机房空调故障……
微服务架构中典型的稳定性功能治理
限流:服务A发向服务B的流量过大时,服务B通过rate limit组件拒绝一部分流量
熔断:但服务B负载过大,服务A无法连接时,服务A通过curcuit breaker组件断开连接拒绝后续流量,在一定时间后再尝试连接
过载保护:dynamic overload组件服务检测服务是否压力大,并在压力大时拒绝一部分流量,避免过载
降级:在服务B压力大时先保证重要流量,不重要的流量先拒绝
第四节 字节跳动服务治理实践
4.1 重试的意义
注意:本地函数出现异常无重试必要,但远程函数有重试必要,这是因为异常可能是由于网络波动等偶发性事件导致的
重试可以避免掉偶发的错误,提高SLA(Service-Level Agreement)
重试可以降低错误率:假设单次请求的错误概率为0.01,那么连续两次错误概率则为0.0001
重试可以降低长尾耗时:对于偶尔耗时较长的请求,重试有机会提前返回
重试容忍暂时性错误:某些时候系统会有暂时性异常(例如网络抖动),重试可以尽量规避
重试可以避开下游故障实例:一个服务中可能会有少量实例故障(例如机器故障),重试其他实例可以成功
4.2 重试的难点
重试好用但在大多数情况下默认不用是因为以下难点:
-
幂等性:多次请求可能会造成数据不一致
-
重试风暴:随着调用深度的增加,重试次数会指数级上涨
服务D出现故障,此时,如果每个服务请求失败后都固定重试3次,则请求数会根据链路层数指数级上涨
- 超时设置:如果允许一次重试,如何确定第一次请求经过多少时间后,才开始重试
4.3 重试策略
限制重试比例
重试只有在大部分请求都成功,少量请求失败时才有必要,因此,可以设定一个重试比例阈值,重试次数占所有请求比例不超过该阈值(例如1%)
防止链路重试
链路层面的防重试风暴的核心是限制每层都发生重试,理想情况下只有最下一层发生重试。
可以返回特殊的status表明“请求失败,但别重试”
缺点:对业务代码有侵入性
Hedged requests
对于可能超时(或延时高)的请求,重新向另一个下游实例发送一个相同的请求,并等待先到达的响应。