本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~

上图中演示了一个非常简单的微服务架构:

  • 微服务会向注册中心进行注册。
  • 微服务从注册中心读取服务实例列表。
  • 基于读取到的服务实例列表,微服务之间互相调用。
  • 外部访问通过统一的 API 网关。
  • API 网关从注册中心读取服务实例列表,根据访问路径调用相应的微服务进行访问。

在这个微服务架构中的每个进程需要实现的功能都在下图中:

接下来我们逐个分析这个架构中的每个角色涉及的功能、要考虑的问题以及我们这个系列使用的库。

每个微服务的基础功能包括:

  • 输出日志,并且在日志中输出链路追踪信息。并且,随着业务压力越来越大,每个进程输出的日志可能越来越多,输出日志可能会成为性能瓶颈,我们这里使用了 log4j2 异步日志,并且使用了 spring-cloud-sleuth 作为链路追踪的核心依赖。
  • Http 容器:提供 Http 接口的容器,分为针对同步的 spring-mvc 以及针对异步的 spring-webflux 的:
    • 对于 spring-mvc,默认的 Http 容器为 Tomcat。在高并发环境下,请求会有很多。我们考虑通过使用直接内存处理请求来减少应用 GC 来优化性能,所以没有使用默认的 Tomcat,而是使用 Undertow
    • 对于 spring-webflux,我们直接使用 webflux 本身作为 Http 容器,其实底层就是 reactor-http,再底层其实就是基于 Http 协议的 netty 服务器。本身就是异步响应式的,并且请求内存基本使用了直接内存。
  • 微服务发现与注册:我们使用了 Eureka 作为注册中心。我们的集群平常有很多发布,需要快速感知实例的上下线。同时我们有很多套集群,每个集群服务实例节点数量是 100 个左右,如果每个集群使用一个 Eureka 集群感觉有些浪费,并且我们希望能有一个直接管理所有集群节点的管理平台。所以我们所有集群使用同一套 Eureka,但是通过框架配置保证只有同一集群内的实例互相发现并调用
  • 健康检查:由于 K8s 需要进程提供健康检查接口,我们使用 Spring Boot 的 actuator 功能,来作为健康检查接口。同时,我们也通过 Http 暴露了其他 actuator 相关接口,例如动态修改日志级别,热重启等等。
  • 指标采集:我们通过 prometheus 实现进程内部指标采集,并且暴露了 actuator 接口供 grafana 以及 K8s 调用采集。
  • Http 客户端:内部微服务调用都是 Http 调用。每个微服务都需要 Http 客户端。在我们这里 Http 客户端有:
    • 对于同步的 spring-mvc,我们一般使用 Open-feign,并且每个微服务自己维护自己微服务提供的 Open-feign 客户端。我们一般不使用 @LoadBalanced 注解的 RestTemplate
    • 对于同步的 spring-flux,一般使用 WebClient 进行调用
  • 负载均衡:很明显,Spring Cloud 中的负载均衡大多是客户端负载均衡,我们使用 spring-cloud-gateway 作为我们的负载均衡器。
  • 优雅关闭:我们希望微服务进程在收到关闭信号后,在注册中心标记自己为下线;同时收到的请求全部不处理,返回类似于 503 的状态码;并且在所有线程处理完手头的活之后,再退出,这就是优雅关闭。在 Spring Boot 2.3.x 之后,引入了这个功能,在我们这个系列中也会用到。

另外还会有重试机制,限流机制以及断路机制,这里我们先来关心最核心的针对调用其他微服务的 Http 客户端中的这些机制以及需要考虑的问题。

来看几个场景:

1.在线发布服务的时候,或者某个服务出现问题下线的时候,旧服务实例已经在注册中心下线并且实例已经关闭,但是其他微服务本地有服务实例缓存或者正在使用这个服务实例进行调用,这时候一般会因为无法建立 TCP 连接而抛出一个 java.io.IOException,不同框架使用的是这个异常的不同子异常,但是提示信息一般有 connect time out 或者 no route to host。这时候如果重试,并且重试的实例不是这个实例而是正常的实例,就能调用成功。如下图所示:

2.当调用一个微服务返回了非 2XX 的响应码

a) 4XX:在发布接口更新的时候,可能调用方和被调用方都需要发布。假设新的接口参数发生变化,没有兼容老的调用的时候,就会有异常,一般是参数错误,即返回 4XX 的响应码。例如新的调用方调用老的被调用方。针对这种情况,重试可以解决。但是为了保险,我们对于这种请求已经发出的,只重试 GET 方法(即查询方法,或者明确标注可以重试的非 GET 方法),对于非 GET 请求我们不重试。如下图所示:

b) 5XX:当某个实例发生异常的时候,例如连不上数据库,JVM Stop-the-world 等等,就会有 5XX 的异常。针对这种情况,重试也可以解决。同样为了保险,我们对于这种请求已经发出的,只重试 GET 方法(即查询方法,或者明确标注可以重试的非 GET 方法),对于非 GET 请求我们不重试。如下图所示:

3.断路器打开的异常:后面我们会知道,我们的断路器是针对微服务某个实例某个方法级别的,如果抛出了断路器打开的异常,请求其实并没有发出去,我们可以直接重试。

这些场景在线上在线发布更新的时候,以及流量突然到来导致某些实例出现问题的时候,还是很常见的。如果没有重试,用户会经常看到异常页面,影响用户体验。所以这些场景下的重试还是很必要的。对于重试,我们使用 resilience4j 作为我们整个框架实现重试机制的核心

再看下面一个场景:

微服务 A 通过同一个线程池调用微服务 B 的所有实例。如果有一个实例有问题,阻塞了请求,或者是响应非常慢。那么久而久之,这个线程池会被发送到这个异常实例的请求而占满,但是实际上微服务 B 是有正常工作的实例的。

为了防止这种情况,也为了限制调用每个微服务实例的并发(也就是限流),我们使用不同线程池调用不同的微服务的不同实例。这个也是通过 resilience4j 实现的。

如果一个实例在一段时间内压力过大导致请求慢,或者实例正在关闭,以及实例有问题导致请求响应大多是 500,那么即使我们有重试机制,如果很多请求都是按照请求到有问题的实例 -> 失败 -> 重试其他实例,这样效率也是很低的。这就需要使用断路器

在实际应用中我们发现,大部分异常情况下,是某个微服务的某些实例的某些接口有异常,而这些问题实例上的其他接口往往是可用的。所以我们的断路器不能直接将这个实例整个断路,更不能将整个微服务断路。所以,我们使用 resilience4j 实现的是微服务实例方法级别的断路器(即不同微服务,不同实例的不同方法是不同的断路器)。

本小节我们提出了一个简单的微服务架构,并仔细分析了其微服务实例的涉及的公共组件使用的库以及需要考虑的问题,并且针对微服务调用的核心 Http 客户端的重试机制,线程隔离机制和断路器机制需要考虑的问题以及如何设计做了较为详细的说明。接下来我们继续分析关于 Eureka 注册中心以及 API 网关设计需要考虑的机制。

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

SpringCloud升级之路2020.0.x版-2.微服务框架需要考虑的问题的更多相关文章

  1. SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...

  2. SpringCloud升级之路2020.0.x版-1.背景

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...

  3. SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...

  4. SpringCloud升级之路2020.0.x版-9.如何理解并定制一个Spring Cloud组件

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ 我们实现的 S ...

  5. SpringCloud升级之路2020.0.x版-11.Log4j2 监控相关

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford Log4j2 异步 ...

  6. SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...

  7. SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务 ...

  8. SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器. ...

  9. SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...

随机推荐

  1. 生成树协议(STP)

    一.交换网络环路的产生 1.广播风暴的形成 2.多帧复制 3.MAC地址表紊乱 二.STP简介 STP-Spanning Tree Protocol(生成树协议) 逻辑上断开环路,防止广播风暴的产生 ...

  2. 干货 | LuatOS BSP移植教程,简单到复制粘贴!!!

    LuatOS本着自身的开源特性,可以很轻松的嵌入到很多微处理器和微控制器.今天简要讲下如何移植这套系统,上手比较简单,看完基本就会了. 要想做移植,就要先了解需要移植芯片的SDK,LuatOS依赖于F ...

  3. .NetCore使用Docker安装ElasticSearch、Kibana 记录日志

    前言 最近园子里看到一篇<.Net Core with 微服务 - Elastic APM> 的文章(主要用于对接口的调用链.性能进行监控),非常实用,这里讲解.NetCore将日志写入E ...

  4. JUL 日志框架

    1.JUL 简介 JUL 全称 Java Util Logging,位于java.util.logging.Logger 包.它是 java 原生的日志框架,使用时无需另外引用第三方的类库,相对其他的 ...

  5. Java基础篇(JVM)——字节码详解

    这是Java基础篇(JVM)的第一篇文章,本来想先说说Java类加载机制的,后来想想,JVM的作用是加载编译器编译好的字节码,并解释成机器码,那么首先应该了解字节码,然后再谈加载字节码的类加载机制似乎 ...

  6. zbxtable的使用

    实验环境: zabbix server 172.16.1.121 mysql 172.16.1.121 访问端 172.16.1.122 54.1 zbxtable 1 说明 ZbxTable使用Go ...

  7. 5.15、tomcat下部署JPress

    1.说明: jpress类似于wordpress,wordpress是php语言开发的国外开源软件,jpress是java语言 开发的国内开源软件: 2.下载软件包: [root@slave-node ...

  8. Network:java中文转byte出现负数问题

    字节的释义 字节(Byte) 是计算机信息技术用于计量存储容量的一种计量单位,通常情况下 1字节 = 8位(bit),也表示一些计算机编程语言中的数据类型和语言字符. 字符与字节 ASCII码:1个英 ...

  9. ClouderaManager安装kafka报错

    是因为默认的java heap size是50M,将broker_max_heap_size参数设置为512M后,重启kafka服务即可

  10. php mkdir 创建多级目录以及修改权限

    mkdir() 用法:mkdir($path,0777,true); 第一个参数:必须,代表要创建的多级目录的路径:第二个参数:设定目录的权限,默认是 0777,意味着最大可能的访问权:注意:mode ...