0%

微服务

微服务是单体服务(SOA)演变至今,对于分布式、可维护性,解耦等思想的新的应用组织模式。

微服务的“微”,指的是业务功能小。

总览

微服务除了服务们本身之外,要想维护整个应用的运行,以下组件必不可少:

  • 网关
  • 服务注册与发现
  • 服务治理,包括限流、熔断与降级
  • 负载均衡
  • 链路追踪
  • 配置中心

微服务之间的通信方式一般有 HTTP 和 RPC 两种。

一般来讲会偏向于采用 HTTP:因为在生态中各个微服务独立开发的原因,对开发语言无强要求,所以倾向于对语言更解耦的 HTTP,而不是对服务之间通信格式有强要求的 RPC。

接下来简单介绍一下重要的一些组件需要注意的地方。


网关

网关在微服务中不显山不露水。

没网关,也能访问服务,但是问题在于:

  • 如果服务多起来了,客户端需要实现访问不同服务的逻辑,会增加客户端复杂度;
  • 另外如果服务需要鉴权,则每个服务的鉴权处理都需要分别实现和维护;
  • 而且流量控制不好做

另外,如果服务想同时支持 HTTP 和 RPC 或另外的多种通信方式,client 要分别维护不同语言的 SDK。

以上的问题都可以通过网关的功能覆盖,也就是说,网关的好处在于:

  • 针对所有请求进行统一鉴权、限流、缓存、日志(打点)
  • 协议转化。针对后端多种不同的协议,在网关层统一处理之后,以 HTTP 对外提供服务
  • 提供统一的错误码
  • 请求转发,且可以基于网关实现内网和外网的隔离

应用

网关鉴权:与统一的认证中心进行交互,实现身份认证和访问权限控制

灰度发布:网关与分流引擎交互,将不同比例的流量分发到新/旧不同的服务中;前期切小部分(如 10%)流量至新服务,如效果不好/存在 bug 就回退,否则就会增大流量比例

服务注册

实现原理

在 Spring Cloud 中,各种类型的微服务组件实现服务注册的原理是一样的:

  • 实现 spring-cloud-commons 包的 ServiceRegistry 接口方法;
  • 加载自动配置类的时候(在 spring-cloud-commonsspring.factories 中加载):
    • AutoServiceRegistrationAutoConfiguration 实现了接口 AutoConfiguration
    • 另一个抽象类 AbstractAutoServiceRegistration 又实现了 AutoServiceRegistrationAutoConfiguration
    • AbstractAutoServiceRegistration 同时还实现了 ApplicationListener 接口,接收到 WebServiceInitializedEvent 后,对 event 进行绑定(被废弃的方法);
    • 通过以上继承关系加载具体的自动配置类。
  • “绑定”服务:调用 ServiceRegistry 的 register() 接口方法

服务发现

在传统系统部署中,服务运行在一个固定的已知 IP 和端口上。如果一个服务需要调用另一个服务,可以通过地址直接调用。
但在虚拟化或容器化环境中,服务实例的启动和销毁非常频繁,因此服务地址也在动态变化。
这种情况就需要服务发现机制。

此时服务间的调用不是通过直接调用具体的实例地址,而是通过服务名发起调用。调用方需要向服务注册中心咨询服务,获取服务的实例清单,从而访问具体的服务实例。

调用流程:

  • 各个服务将自己的 ip 和端口发送至注册中心,注册中心维护服务注册表;
  • 某个服务要调用另外的服务的时候,从注册中心获取服务列表并查询,最终执行想要的服务调用;
  • 同时每个服务会保留一份服务注册表的本地缓存,定期更新;
  • 下次访问的时候会先请求本地缓存,没有的话才会请求注册中心。

服务发现有两种实现方式:

基于客户端的服务发现:由客户端提供负载均衡算法,根据获得的注册表进行服务访问

  • 优点:客户端知道所有可用服务的实际网络地址,可非常方便地实现负载均衡,从而请求对应的服务;
  • 缺点:耦合性强,针对不同语言,每个服务的客户端都得实现一套服务发现的功能

基于服务端的服务发现:由服务端的负载均衡组件提供负载均衡算法,负载均衡组件查询注册表并进行负载均衡的访问

  • 优点:服务发现逻辑对客户端不透明。客户端不需要知道服务列表,只需向负载均衡组件发送请求即可;
  • 缺点:必须要关心负载均衡组件的高可用性

服务治理

微服务面对高并发请求的时候,当大量请求突然涌入时,如果有一个服务失败,也许导致整条服务链路的服务都失败,这就是服务雪崩

服务熔断服务降级可视为解决服务雪崩的手段。

服务熔断

当某个服务无法正常为调用者提供服务时,为防止服务雪崩,暂时将出现故障的接口隔离开,后续一段时间内该服务调用者的请求会直接失败,直到目标服务恢复正常。

如何知道恢复正常?可以设定 reset timeout,过后进入半开合状态并放行部分流量以便重新检测服务;如果服务仍未恢复,则保持链接断开,直到下一次 timeout。

熔断是框架级别的处理,基本会采用断路器模式实现,达到熔断阈值才会触发熔断。

服务降级

相对于熔断,服务降级多是业务级别的处理。

  • 下游服务因某些原因响应过慢:下游可以停掉不重要业务,释放服务器资源,提高响应速度;
  • 下游服务因某些原因不可用:上游可主动调用本地降级逻辑,避免卡顿,迅速反馈给用户;

实现上多数是在配置中心给非核心业务配上开关,一旦系统扛不住则关闭开关,结束次要进程。

熔断降级常见方案:

  • 平均响应时间:设定响应时间阈值,如果连续 N 个请求对应时刻的平均响应时间均超过阈值,那在往后一个固定时间窗口内,访问会被熔断
  • 异常比例:每秒调用所获得的异常总数比例超过设定阈值:资源自动降级,往后一个固定时间窗口,方法调用会自动返回
  • 异常数量:每秒调用所获得的异常总数超过设定阈值:资源自动降级,往后一个固定时间窗口,方法调用会自动返回

服务限流

通过限制并发访问数或限制一个时间窗口内允许处理的请求数量来保护系统。
一旦达到限制流量,则对当前请求进行处理采取对应拒绝策略,如跳转错误页面、排队、服务降级等。

限流算法

主流的限流算法如下:

计数器算法:指定周期内(如 3 秒)的累计访问次数达到设定阈值(如 150)时,触发限流策略。进入下一个时间周期后,访问次数清零。

一个很明显的缺陷就是临界问题:如果在第 2 - 3 秒进入 150 个请求,3 - 4 秒进入 150 个请求,相当于两秒内进入 300 个请求,完全达不到限流效果。

滑动窗口:固定窗口改良版,解决固定窗口在窗口切换时可能受到两倍于阈值数量的请求的问题:

  • 将一个窗口分为若干等分的小窗口,每个小窗口对应不同时间点,拥有独立的计数器
  • 当请求时间点大于当前窗口最大时间点时,则向前平移一个小窗口,第一个小窗口数据舍弃,整个窗口所有请求数相加不能大于阈值

令牌桶限流:网络流量整形(traffic shaping)和速率控制(rate limiting)中最常用的方法:

  • 匀速往桶里面扔令牌,桶大小固定的话,桶满了就会丢弃多余的令牌;
  • 请求速度大于令牌生成速度:令牌被取空后,会被限流;
  • 请求速度等于令牌生成速度:流量趋于平稳;
  • 请求速度小于令牌生成速度,请求可被正常处理

漏桶限流:不管来的请求数量和并发有多快,请求到达服务的速度是一致的,就像是不管水龙头来的水有多猛,漏桶水滴的速度是恒定的。

主要作用:控制数据注入网络的速度,平滑网络的突发流量。

应用:消息中间件。

配置中心

在没有配置中心的时候:

在微服务架构中,当系统从一个单体应用被拆分成为分布式系统上的一个个服务节点之后,配置文件(application.yml / application.properties)也必须跟着迁移甚至切割。
因此,多个微服务拆分后产生多份配置文件,容易造成配置分散,不好管理,还会伴随着冗余。

我们需要这么一个组件,能够:

  • 发布、修改、删除配置,实现不改动代码就能实现新需求;
  • 中心化,服务从配置中心获得配置信息,应用就没有配置信息了:实现无状态
  • 当配置信息发生变更,可发起通知服务:实现热加载

甚至,我们还希望在配置中心中配置 bean,应用可以通过获取 bean 定义创建不同的服务。

因此配置中心应运而生。

另外,我们还希望配置中心能够满足:

  • 隔离性:配置对于程序来说是只读的,独立于程序存在
  • 配置中心应该能够影响整个服务的生命周期
  • 配置中心的配置应该能支持多种加载方式(SDK、编码、配置文件)
  • 实现环境治理,不同类型环境有不同的配置集