一、前言
在微服务技术体系中,已经列出了构建微服务架构所需要的技术。
这篇文章只是列出了构建微服务所需要的技术,是微服务所需技术的整体介绍。
但是并没有说明构建一个微服务的具体工作是怎么做的?需要做哪些事情?这一篇文章就来说说这些问题。
二、构建微服务
2.1 基本流程
基本流程
通常在构建一个简单的微服务时,有一个客户端,一个服务端,一个注册中心。
- 编写服务端程序,服务启动时,会把自己提供的服务注册到注册中心
- 编写客户端程序,客户启动时,会到注册中心查找自己需要的服务
- 客户端找到服务后,就会调用服务端提供的服务
这3步就构建了一个简单的微服务。
在上面3步中,又有哪些需要注意的步骤?定义服务。
服务定义
如何定义一个服务?而且各种语言都通用的,各种开发都可以使用的一种语言。这就是 IDL 。
在微服务中,最简单的一个请求模型就是:请求 <-> 应答。那怎么定义请求部分的数据格式,返回部分的数据格式?
其实“巨人”已经给我们准备好了,那就是 protocol buffers 和 thrift。这是目前比较常用 2 种 IDL 语言。
这里选择 protocol buffers 。protocol buffers 是一种轻量级的结构化数据存储格式,与语言、平台无关、可扩展的序列化格式。它的性能和效率比 JSON、XML 都要好很多。因为它是二进制存储,所以可读性就比较差。
比如定义一个服务、请求、响应的消息格式:
// 定义一个服务
service Simple{
rpc SimpleService (SimpleRequest) returns (SimpleResponse){};
}
// 定义发送请求信息
message SimpleRequest{
string data = 1;
}
// 定义响应信息
message SimpleResponse{
int32 code = 1;
string value = 2;
}
然后在用 protoc 命令可以生成相应语言的程序。把数据序列化。
我用的较多是 Go 语言,所以都会生成 Go 语言。
服务传输协议
上面的服务定义好了,用命令序列化了,那怎么把数据传输出去?
是直接用 TCP 还是 应用层协议。
应用层协议有http协议,还可以自己定义协议。没有特殊情况,一般都会选择 http 协议,现在也有 http2 协议了。比如 RPC 框架 gRPC 就是用的 http2 协议。
2.2 服务注册/发现
在上面基本3流程里,有一个服务注册中心,这个有什么作用?
一个公司业务,如果用微服务架构来开发。那么就有多个微服务来提供服务,这么多的微服务,怎么存储?客户端才能更好的发现并使用这些服务?
- 服务容易被客户端查找
- 服务修改了,可以通知客户端
服务注册中心就应运而生,为客户端和服务端提供这些服务。
服务端可以把服务注册到服务注册中心,客户端订阅服务,变更了还可以通知到客户端。
可以在本地缓存服务列表,如果和服务中心网络断了,还可以提供服务。
注册中心常用软件:
我常用的是 etcd ,k8s 也在用这个软件。
2.3 服务监控
为什么需要监控?
当运行的服务发生故障时,IT 人员需要第一时间知道服务出现了问题并修复故障,而不能等到用户不能使用服务时。那样的话,公司业务可能受损,产品体验就会下降,导致用户流失。
所以服务监控是必要措施,遇到 IT 故障时主动通知相关开发人员,开发人员就可以积极主动去修复故障。
监控实际上应该是 IT 的基础设施,不管是以前的单体应用,还是后来的微服务架构,监控都是不可缺少的一环。
现在比较常用的监控工具就是:Prometheus,这个是 CNCF 下的一款软件产品。
当然还有很多其他的监控软件,可以一并看看,对比,然后选择适合企业业务的监控软件。
2.4 服务链路追踪
有了上面的服务监控,为什么还要服务链路追踪呢?
如果你是单体架构,当你遇到故障时,服务监控就可以解决问题。架构比较简单,服务调用层次少。
如果你是微服务架构,一个微服务可能调用好几个其他的微服务,并且有可能你调用的服务也调用了其他服务,这样复杂的调用链路关系,当服务出了故障时,排查错误就不止要排查一个微服务了,可能这条微服务上的其他服务也需要排查,也及是这条服务的整个调用链路关系,都需要排查。
这时,链路追踪就可以派上用场了。
现在的分布式链路追踪系统,都是从 google Dapper 论文而来的。
可以看看这篇 Dapper 论文:Dapper.pdf
2.5 服务治理
为什么要服务治理呢?
因为在微服务 api 运行过程中,会出现一系列的问题,这些问题如下:
- 调用方和被调用方,被调用方因为网络暂时不可用了,怎么办?
- 如果一个微服务突然访问量增大,影响了与它调用相关的微服务运行状态,比如导致其他服务调用变慢,这时怎么办?
- 如果访问量继续增大,导致微服务 A 不可用了,与之调用关联的 B、C 服务也慢慢变得不可用了,怎么办?
- 如果某个服务的服务器宕机、延迟或某一服务不可用了,怎么办?
... ...
等等的一些微服务运行过程中会出现的问题。为了处理这些问题,就出现了服务治理的一些方法。
服务超时/重试
服务超时:
在一个规定时间内,对方没有应答,那么就可以认为请求超时。认定为超时,就可以进一步处理。比如说重试,或者直接记录错误。
服务重试:
由于网络不稳定,导致调用服务失败,或者调用超时,这时候就可以通过重试,重新调用请求,增加调用成功率。
一般情况下,服务超时和服务重试,是结合在一起使用的。
服务限流
第一种:如果某一 API 流量变大,影响了其他服务,可以进行限流。
这个可以解决访问量变大的问题。
第二种:对于某一特定请求服务有限定次数请求,比如 100次/s 。
服务熔断
熔断就是关掉某一个服务,使其他服务不受之影响。
如果遇到访问量很大,或系统异常或Load过高出现暂时不可用,微服务 api 请求的就会失败,如果失败率超过了规定的阙值,那么就可以进行熔断处理,保护其他的微服务 api。
这还有一个形象的叫法:电路熔断器。日常的电路开关,就是用的这种工作模式,电流过大,就会跳闸,然后可以手工恢复。
服务降级
当访问量变大,超过了设置的阙值时,就可以将一些非重要、非核心的业务进行降级处理,暂停其对外提供服务。保护核心 API 访问不受影响。
依赖隔离
为什么要隔离?也是为了某一服务不可用,不影响其他服务。
比如服务 A 调用服务 B,B 又调用了服务 C,如果这时候 C 访问量变大或机器宕机了,B 调用 C 服务访问不了或者 hang 住了,那么 B 服务器资源消耗会上涨,如果继续上涨到不可用,那么这个条 API 服务可能就不能服务,为了避免这种情况出现,可以对 C 服务进行隔离。从而避免级联故障出现。
这种隔离方式还有一个专门的模式:舱壁隔离(Bulkhead Isolation)。
在造船行业,往往使用此种模式对船舱进行隔离建造,利用舱壁将不同的船舱隔离起来,这样如果一个船舱破了进水,只损失一个船舱,其它船舱可以不受影响,而借鉴造船行业的经验,这种模式也在软件行业得到使用。
舱壁模式(Bulkhead):
隔离了每个工作负载或服务的关键资源,如连接池、内存和 CPU。使用舱壁避免了单个工作负载(或服务)消耗掉所有资源,从而导致其他服务出现故障的场景。这种模式主要是通过防止由一个服务引起的级联故障来增加系统的弹性。
fallback
这个 fallback 怎么译文,应急模式,备用模式,回退模式,都不是很形象。
在上面的服务超时、重试、熔断或者限流发生时,为了及时的恢复服务或不影响用户体验,可以提供回退的机制,常见的回退策略有:
-
自定义处理:
在这种情景下,可以使用默认数据,本地缓存数据来临时支撑,或者使用备份服务获取数据,适用于业务的关键流程与严重影响用户体验的场景,如商家/产品信息等核心服务 -
fail-silent
直接返回空值或缺省值,适用于可降级功能的场景,如产品推荐之类的功能,数据为空也不太影响用户体验 -
fail-fast 快速失败
直接抛出异常,适用于数据非强依赖的场景,如非核心服务超时的处理 -
failsafe
失败安全,出现异常时,直接忽略,通常用于吸入日志操作的场景 -
failback
失败自动恢复,后台记录失败请求数据,定时重发请求。通常用于消息通知等场景 -
failover
失败自动切换,当出现失败,重试其他服务器
2.6 网关 gateway
为什么需要网关,网关有哪些作用?
微服务网关提供了整个微服务 API 请求的入口。可以隔离后端的 API 服务,聚合 API 服务,对用户请求进行统一处理。
gateway 实现的功能,比如上面的一些限流、熔断、监控等,当然还有其他的一些功能:
- 黑白名单机制
- 统一登录验证/认证
- 跨域问题
- 日志拦截
- 权限控制
- 服务治理一些功能:限流、熔断 等
- 负载均衡
- 灰度发布
网关软件:
上面都是基于 nginx + lua 构建的网关。
还有 traefik
sping cloud gateway ,等等。
2.7 微服务框架
还有一个重要的框架,就是编写微服务的框架。
现在主流构建微服务的语言是 Go 和 java。
Go 的微服务框架:
Java 的微服务框架:
2.8 配置中心
最后随着微服务的数量增多,配置文件也变得越来越多,比如数据库配置,服务器配置,下发规则配置等各种配置,这时候可能你就要建立一个配置中心。
常用的配置中心: