微服务架构是当前大多数互联网公司的标准架构
本节课将要来学习
- 系统架构的演进历史
- 实际中字节跳动的服务治理功能是怎么实践的
微服务架构介绍
为什么系统架构需要演进?
- 互联网的爆发性发展
- 硬件设施 的快速发展
- 需求复杂性的多样化
- 开发人员急剧增加
- 计算机理论及技术的发展
系统架构演变历史
-
单体架构
特点:
all in one process ,该架构模式没有对业务逻辑代码实现拆分,所有的代码都写入到同一个工程中里面,适合于小公司开发团队或者个人开发以及用户少量的情况。优势:
-
性能最高
-
项目部署在一个节点上,冗余小
劣势:
- 全部项目集中,对于大项目而言 Debug 困难
- 模块之间耦合度高,相互影响,导致可能单个模块崩溃,整个服务下线。与其说是是
all in one 其实真面目是all in boom - 模块分工、开发流程
-
-
垂直应用架构
当单体满足不了需求后,便有了垂直应用架构,通过横向的扩充
特点:按照业务线垂直划分
优势:
- 业务独立开发维护
劣势:
- 不同业务存在冗余
- 每个业务还是单体
-
分布式架构
特点:抽出业务无关的公共模块
优势:
- 业务无关的独立服务
- 服务层的业务可以复用
劣势:
- 服务模块 bug 可导致全站瘫痪
- 调用关系复杂
-
SOA 架构
特点:引入面向服务
优势
- 服务注册
劣势
- 整个系统设计是中心化的
- 需要从上而下设计
- 重构困难
-
微服务架构
来到本节课的重头戏,那么微服务有什么特点:
-
微服务是继SOA后,最流行的服务架构风格之一。
-
按照微服务对系统进行拆分后,每个服务的业务逻辑都更加简单、清晰。服务之间是松耦合的,模块之间的边界也更加清晰。
-
微服务有效降低了软件项目的业务复杂程度,为小团队独立开发、持续交付和部署打下了良好的基础。
-
彻底的服务化
优势
- 开发效率
- 业务独立设计
- 自下而上
- 故障隔离
劣势
- 治理 运维难度
- 观测挑战
- 安全性
- 分布式系统
-
微服务架构核心要素
三要素,分别是
-
服务治理
服务注册
服务发现
负载均衡
扩缩容
流量治理
稳定性治理 -
可观测性
日志采集
日志分析
监控打点
监控大盘
异常报警
链路追踪 -
安全
身份验证
认证授权
访问令牌
审计
传输加密
黑产攻击
字节跳动服务治理实践
本部分将着眼于字节跳动关于微服务架构非常重要的一环——重试,展开来谈谈。
重试的意义
在本地函数调用,比如下面的例子
func LocalFunc(x int) int{
res := calculate(x * 2)
reutrn res
}
虽然只是一个简单的函数,但不能保证函数调用次次成功,可能遇到的异常有以下几种情况
- 参数非法
- OOM(Out Of Memory)
- NPE(Null Pointer Exception)
- 边界 case
- 系统崩溃
- 死循环
- 程序异常退出
- ........
但是针对本地函数抛出异常的情况,其实是没有重试的必要。可能这次的错误还会继承到下次重试中。
然而对于远程函数的调用,也有很多的情况会导致异常,比如之前 在打开抖音会发生什么 课程中学习到的,网络传输最后一公里出现的问题较多。
所以这时候可能导致远程函数调用异常的情况主要围绕在网络,比如 网络抖动 ,下游负载高导致超时,下游机器岩机,本地机器负载高,调度超时
,下游熔断、限流 等异常情况。
这时候就有重试的意义。网络问题进行重试可以避免掉偶发的错误,提高 SLA [服务等级协议 Service-Level Agreement,缩写SLA]
当然除了提高SLA外,重试还有以下作用
-
降低错误率
假设单次请求的错误概率为0.01,美那么连续两次错误概率则为0.0001。
-
降低长尾延时
对于偶尔耗时较长的请求,重试请求有机会提前返回。
长尾延时,举几个直观的栗子:
-
迅雷的吸血下载,最后可能卡在 1% ,多线程下载总有请求耗时长,这时候你会选择继续等待还是停止后继续下载?
事实证明,后者可能更有效(重试的好处不就出来了),而如果你选择前者,你等待的这段时间就被称为长尾延时
-
你发出了 100个 Redis 请求,99个 1s内响应,唯有一个时间 100s。虽然平均时间很低,但仍有 1% 几率的用户拥有糟糕的用户体验。这 1%就是长尾延时。
-
-
容忍暂时性错误
某些时候系统会有暂时性异常(例如网络抖动),重试可以尽量规避。
-
避开下游故障实例
一个服务中可能会有少量实例故障(例如机器故障)重试其他实例可以成功。
看起来,重试的好处很多,那么是否重试
-
PS: 补充概念 P95 P99 是啥?
互联网公司在收到用户关于访问慢的投诉之后,一般会进行优化。但也需要测定实际的响应时间,衡量用户响应时间的长短如果单靠 平均值 ,容易受到众数影响
导致异常的响应时间,在平均值的角度衡量标准下,会被掩盖掉。因此引入 P95 P99 来衡量。
P95:将所有的响应耗时从小到大排列,处于第 95% 位置的值即为 P95 值。(P99 P99.9同理)
通过测量 95% 用户的响应时间,并且优化这个值,可以保证大部分用户的使用体验。
重试的难点
那么重试为什么在实际的运用过程中,却很少见到?这里需要提及三点,着重谈及 重试风暴
-
幂等性
-
重试风暴
A 服务失败假设重试 3次,而 A 每一次服务请求在 B 上也会重试3次,再回报给 A,相当于 B重试了 3 x 3=9 次
此时 B 下还有服务 C,D....等等,他们各自遵守自己服务的重试次数
For 循环 同时他们还会收到独立的可能来自上级服务的第 N 次重试请求假设问题可能来自于 D,而我们的重试本意是让 C 服务重试三次,可是为避免抖动每个服务都设置了重试次数,导致重试次数被放大。
这样服务 D 不仅不能请求成功,还可能导致负载继续升高,甚至直接打挂。
如果在业务环境中如此,便会引起重试风暴,可能导致原本一些正常的服务因重复发生雪崩,集体服务下线。
-
超时设置
该设置多大的值(超时时间)?
重试策略
得知了以上的重试难点,我们有以下办法来防止以上情况的发生。
-
限制重试比例
设置一个重试比例阈值(eg: 1%),重试次数占所有请求比例不超过该阈值。
-
防止链路重试
这种方式是为了防止 重试风暴 情况的发生。
链路层面的防重试风暴的核心是限制每层都发生重试,理想情况下只有最下一层发生重试,可以返回特殊的
status 表明“请求失败,但别重试”。 -
Hedged requests
主要针对于可能超时(或延时高)的请求,重新向另一个下游实例发送一个相同的请求,并等待先到达的响应。
重试效果验证
实际验证经过实践上面的重试策略后,在链路上发生的重试放大效应。
不难看出采用上述的应对策略 与 For 循环语句对比,尤其超时错误 等待的时间 最高能达到 3.7*n