专栏目录
17.Eureka的实例配置 18.Eureka的客户端核心设计和配置 1. 背景 2.微服务框架需要考虑的问题 3.Eureka Server 与 API 网关要考虑的问题 4.maven依赖回顾以及项目框架结构 5.所有项目的parent与spring-framework-common说明 6.微服务特性相关的依赖说明 19.Eureka的服务端设计与配置 7.从Bean到SpringCloud 8.理解 NamedContextFactory 9.如何理解并定制一个Spring Cloud组件 10.使用Log4j2以及一些核心配置 11.Log4j2 监控相关 12.UnderTow 简介与内部原理 13.UnderTow 核心配置 14.UnderTow AccessLog 配置介绍 15.UnderTow 订制 16.Eureka架构和核心概念 20. 启动一个 Eureka Server 集群 21.Spring Cloud LoadBalancer简介 22.Spring Cloud LoadBalancer核心源码 23.订制Spring Cloud LoadBalancer 24.测试Spring Cloud LoadBalancer 25.OpenFeign简介与使用 26.OpenFeign的组件 27.OpenFeign的生命周期-创建代理 28.OpenFeign的生命周期-进行调用 29.Spring Cloud OpenFeign 的解析(1) 30. FeignClient 实现重试 31. FeignClient 实现断路器以及线程隔离限流的思路 32. 改进负载均衡算法 33. 实现重试、断路器以及线程隔离源码 34.验证重试配置正确性 35. 验证线程隔离正确性 36. 验证断路器正确性 37. 实现异步的客户端封装配置管理的意义与设计 38. 实现自定义 WebClient 的 NamedContextFactory 39. 改造 resilience4j 粘合 WebClient 40. spock 单元测试封装的 WebClient(上) 40. spock 单元测试封装的 WebClient(下) 41. SpringCloudGateway 基本流程讲解(1) 41. SpringCloudGateway 基本流程讲解(2) 42.SpringCloudGateway 现有的可供分析的请求日志以及缺陷 43.为何 SpringCloudGateway 中会有链路信息丢失 44.避免链路信息丢失做的设计(1) 44.避免链路信息丢失做的设计(2) 45. 实现公共日志记录

37. 实现异步的客户端封装配置管理的意义与设计

干货满满张哈希
• 阅读 321

37. 实现异步的客户端封装配置管理的意义与设计

本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent

为何需要封装异步 HTTP 客户端 WebClient

对于同步的请求,我们使用 spring-cloud-openfeign 封装的 FeignClient,并做了额外的定制。对于异步的请求,使用的是异步 Http 客户端即 WebClient。WebClient 使用也比较简单,举一个简单的例子即:

//使用 WebClient 的 Builder 创建 WebClient
WebClient client = WebClient.builder()
  //指定基址
  .baseUrl("http://httpbin.org")
  //可以指定一些默认的参数,例如默认 Cookie,默认 HttpHeader 等等
  .defaultCookie("cookieKey", "cookieValue")
  .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 
  .build();

创建好 WebClient 后即可以使用这个 WebClient 进行调用:

// GET 请求 /anything 并将 body 转化为 String
Mono<String> stringMono = client.get().uri("/anything").retrieve().bodyToMono(String.class);
//这里为了测试,采用阻塞获取
String block = stringMono.block();

返回的结果如下所示(请求 http://httporg.bin/anything 会将请求中的所有内容原封不动返回,从这里我们可以看出上面测试的 Header 还有 cokkie 都被返回了):

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip", 
    "Cookie": "TestCookie=TestCookieValue,getAnythingCookie=getAnythingCookieValue", 
    "Getanythingheader": "getAnythingHeaderValue", 
    "Host": "httpbin.org", 
    "Testheader": "TestHeaderValue", 
    "User-Agent": "ReactorNetty/1.0.7"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "12.12.12.12", 
  "url": "http://httpbin.org/anything"
}

我们也可以加入负载均衡的功能,让 WebClient 利用我们内部的 LoadBalancer,负载均衡调用其他微服务,首先注入负载均衡 Filter:

@Autowired
ReactorLoadBalancerExchangeFilterFunction lbFunction;

创建 WebClient 的时候,将这个 Filter 加入:

//使用 WebClient 的 Builder 创建 WebClient
WebClient client = WebClient.builder()
  //指定基址微服务
  .baseUrl("http://微服务名称")
  //可以指定一些默认的参数,例如默认 Cookie,默认 HttpHeader 等等
  .defaultCookie("cookieKey", "cookieValue")
  .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 
  //负载均衡器,改写url
  .filter(lbFunction)
  .build();

这样,这个 WebClient 就能调用微服务了。

但是,这样还不能满足我们的需求:

  1. 需要在 WebClient 加入像 Feignclient 里面加的类似的重试与断路机制,线程隔离就不需要了,因为都是异步请求不会阻塞任务线程。
  2. 需要针对不同的微服务配置不同的连接超时以及响应超时来适应不同微服务。
  3. 这些配置都增加了代码的复杂度,我们需要减少这些代码对于业务的侵入性,最好能通过纯配置实现这些 WebClient 的初始化。

要实现的配置设计以及使用举例

首先,我们要实现的 WebClient,其 Filter 包含三个:

  1. 重试 Filter:重试的 Filter 要在负载均衡 Filter 之前,因为重试的时候,我们会从负载均衡器获取另一个实例进行重试,而不是在同一个实例上重试多次
  2. 负载均衡 Filter,其实就是内置的 ReactorLoadBalancerExchangeFilterFunction
  3. 断路器 Filter:断路器需要在负载均衡之后,因为只有负载均衡之后才能拿到具体本地调用的服务实例,这样我们才能实现基于微服务实例方法级别的断路器

需要重试的场景:

  1. 非 2xx 的响应码返回,并且方法是可以重试的方法。如何定义方法是可以重试的,首先 GET 方法是可以重试的,对于其他方法,根据配置中的是否配置了这个 URL 可以重试决定
  2. 异常重试
    1. 连接异常:例如连接超时,连接中断等等,所有请求的连接异常都可以重试,因为请求并没有发出去。
    2. 断路器异常:该服务实例方法级别的断路器打开了,需要直接重试其他实例,因为请求并没有发出去。
    3. 响应超时异常:这个重试逻辑和非 2xx 的响应码返回一样。

我们需要实现的配置方式是,通过这样配置 application.yml

webclient:
  configs:
    //微服务名称
    testService1:
      //请求基址,第一级域名作为微服务名称
      baseUrl: http://testService1
      //最多的 http 连接数量
      maxConnection: 50
      //连接超时
      connectTimeout: 500ms
      //响应超时
      responseTimeout: 60s
      //除了 GET 方法外,哪些路径还能重试
      retryablePaths:
        - /retryable/**
        - /user/orders

加入这些配置,我们就能获取载对应微服务的 WebClient 的 Bean,例如:

//自动装载 我们自定义的 WebClient 的 NamedContextFactory,这个是我们后面要实现的
@Autowired
private WebClientNamedContextFactory webClientNamedContextFactory;


//通过微服务名称,获取对应的微服务调用的 WebClient
webClientNamedContextFactory.getWebClient("testService1");

接下来,我们会实现这些。

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

37. 实现异步的客户端封装配置管理的意义与设计

评论区
推荐文章

暂无数据