周余发:2018年硕士毕业,曾在美团点评研发效率中心、抖音服务架构担任系统开发。目前为字节跳动Service Mesh研发人员。
微服务架构的背景由来、架构概览、基本要素
架构演进过程:单体架构->垂直应用架构->分布式架构->SOA架构->微服务架构
all in one process
优势:
劣势:
1.png
按照业务线垂直划分
优势:
劣势:
2.png
抽出业务无关的公共模块
优势:
劣势:
3.png
面向服务
优势:
劣势:
4.png
彻底的服务化
优势:
劣势:
5.png
6.png
服务治理
服务注册、服务发现、负载均衡、扩缩容、流量治理、稳定性治理 .......
可观测性
日志采集、日志分析、监控打点、监控大盘、异常报警、链路追踪 ......
安全
身份验证、认证授权、访问令牌、审计、传输加密、黑产攻击 ......
微服务架构的基本组件、工作原理、流量特征
7.png
如果把HDFS看做一组微服务
8.png
服务间通信
对于单体服务,不同模块通信只是简单的函数调用。
对于微服务,服务间通信意味着网络传输。
9.png
在代码层面,如何指定调用一个目标服务的地址(ip:port)?
hardcode?
// Service A wants to call service B.
client := grpc.NewClient("10.23.45.67:8080")
10.png
在代码层面,如何指定调用一个目标服务的地址(ip:port)?
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
15.png
核心的服务治理功能,包括流量治理、服务均衡、稳定性治理
服务发布(deployment),即指让一个服务升级运行新的代码的过程。
服务不可用
服务抖动
服务回滚
19.png
首先发布绿色的服务,就先将绿色的流量切到蓝色的上面,这个时候绿色没有流量就是安全的不会出现安全问题,之后对绿色进行升级,升级完之后和刚刚步骤一样,先将蓝色的服务切到绿色的上面, 升级蓝色的,全部完成升级后,这个实例就完成了。这样就可以达到无损升级。
优点:简单,稳定
缺点:需要两倍资源(但是可以从流量低峰入手,会降低资源消耗)
金丝雀背景:金丝雀(canary) 对瓦斯极其敏感,17世纪时,英国矿工在下井前会先放入一只金丝雀,以确保矿井中没有瓦斯。
20.png
21.png
整个流程就是:先放入一个金丝雀的服务,启动如果正常没有问题,将之前的一个服务进行替换掉,之后重复操作,直到将旧的全部替换完成。
灰度发布难点:流量切换很麻烦有很大挑战,并且如果出现一个问题例如完成了百分之九十九就要进行服务回滚,将之前的完成部分全部回退回去,所以还需要有蓝绿部署。这也就是节省了资源所需要的代价。
在微服务架构下,我们可以基于地区、集群、实例、请求等维度,对端到端流量的路由路径进行精确控制。
22.png
负载均衡(Load Balance)负责分配请求在每个下游实例上的分布。
常见的LB策略
23.png
线上服务总是会出现问题的,这与程序的正确性无关。
24.png
为了避免上述问题给出了微服务架构中典型的稳定性治理功能
25.png
26.png
27.png
28.png
func LocalFunc(x int) int {
res := calculate(x * 2)
return res
}
可能有哪些异常?
是否有重试必要?没必要
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)
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
}
重试的意义
既然重试这么多好处,为什么默认不用呢?
有以下几个难点
雪崩和调用链有关系
29.png
上面这个图片清楚的描述出了由服务A重试3词,到服务B加上之前的就需要9次是一个幂的请求次数,最终达到一定量就会出现雪崩
那如何解决呢?
设定一个重试比例阈值(例如%1),重试次数占所有请求比例不超过该阈值。
30.png
链路层面的防重试风暴的核心是限制每层都发生重试,理想情况下只有最下一层发生重试。可以返回特殊的status表明“请求失败,但别重试”。
31.png
对于可能超时(或延时高)的请求,重新向另一个下游实例发送一个相同的请求,并等待先到达的响应。
32.png
实际验证经过上述重试策略后,在链路上发生的重试放大效应。
33.png