阻塞与非阻塞客户端

10f6b82eff11147b89eadb284b14c5cc.png

阻塞与非阻塞

阻塞是指程序会一直等待该进程或线程完成当前任务期间不做其它事情。而非阻塞,是指当前线程在处理一些事情的同时,还可以处理其它的事情,并不需要等待当前事件完成才执行其它事件。

阻塞与非阻塞客户端

对于请求当中,我们有需要借助一些请求封装的客户端,这里可以分为两大类:阻塞式、非阻塞式。

阻塞式客户端以常见的 RestTemplate为例,这是一种常见的客户端请求封装,要创建负载平衡RestTemplate,下面看看其Bean:

@LoadBalanced
@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

在底层,RestTemplate 使用了基于每个请求对应一个线程模型(thread-per-request)的 Java Servlet API。在阻塞客户端中,这意味着,直到 Web 客户端收到响应之前,线程都将一直被阻塞下去。而阻塞带来的问题是:每个线程都消耗了一定的内存和 CPU 周期。

如果在并发下,等待结果的请求迟早都会堆积起来。这样,程序将创建很多线程,这些线程将耗尽线程池或占用所有可用内存。由于频繁的 CPU 线程切换,我们还会遇到性能下降的问题。

这在 Spring5 中,提出了一种新的客户端抽象:反应式客户端 WebClient,而 WebClient 使用了 Spring Reactive Framework 所提供的异步非阻塞解决方案。所以,当 RestTemplate创建一个个新的线程时,Webclient是为其创建类似task的线程,并且在底层, Reactive 框架将对这些 task 进行排队,并且仅在适当的响应可用时再执行它们。WebClient 是 Spring WebFlux 库的一部分。所以,我们还可以使用了流畅的函数式 API 编程,并将响应类型作为声明来进行组合。如果需要使用 WebClient,同样可以创建:

@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
    return WebClient.builder();
}

案例

假设这里有一个响应非常慢的服务rest-service,我们分别用阻塞式、非阻塞式客户端来测试一下。

阻塞式

我们利用 RestTemplate实现阻塞式请求:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}


@Autowired
RestTemplate restTemplate;

@GetMapping("/getClientRes")
public Response<Object> getClientRes() throws Exception {
    System.out.println("block api enter");
 HttpHeaders headers = new HttpHeaders();
 MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
 headers.setContentType(type);
 headers.add("Accept", MediaType.APPLICATION_JSON.toString());
 HttpEntity<String> formEntity = new HttpEntity<String>(null, headers);
 String body = "";
 try {
  ResponseEntity<String> responseEntity = restTemplate.exchange("http://diff-ns-service-service/getservicedetail?servicename=cas-server-service",
    HttpMethod.GET, formEntity, String.class);
  System.out.println(JSON.toJSONString(responseEntity));
  if (responseEntity.getStatusCodeValue() == 200) {
      System.out.println("block api exit");
   return Response.ok(responseEntity.getBody());
  }
 } catch (Exception e) {
  System.out.println(e.getMessage());
 }
 System.out.println("block api failed, exit");
 return Response.error("failed");
}

在启动服务请求后,发现其打印:

block api enter

[{"host":"10.244.0.55","instanceId":"71f96128-3bb1-11ec-97e6-ac1f6ba00d36","metadata":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"cas-server-service\",\"namespace\":\"system-server\"},\"spec\":{\"ports\":[{\"name\":\"cas-server01\",\"port\":2000,\"targetPort\":\"cas-server01\"}],\"selector\":{\"app\":\"cas-server\"}}}\n","port.cas-server01":"2000","k8s_namespace":"system-server"},"namespace":"system-server","port":2000,"scheme":"http","secure":false,"serviceId":"cas-server-service","uri":"http://10.244.0.55:2000"},{"host":"10.244.0.56","instanceId":"71fc1c14-3bb1-11ec-97e6-ac1f6ba00d36","metadata":{"$ref":"$[0].metadata"},"namespace":"system-server","port":2000,"scheme":"http","secure":false,"serviceId":"cas-server-service","uri":"http://10.244.0.56:2000"}]

block api exit

上面的打印符合我们的逾期,接下来我们来看看非阻塞、反应式客户端请求:

@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
    return WebClient.builder();
}


@GetMapping(value = "/getClientResByWebClient", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Mono<String> getClientResByWebClient() throws Exception {
    System.out.println("no block api enter");
 Mono<String> resp = webClientBuilder.build().get()
    .uri("http://diff-ns-service-service/getservicedetail?servicename=cas-server-service").retrieve()
    .bodyToMono(String.class);
  resp.subscribe(body -> System.out.println(body.toString()));
  System.out.println("no block api exit");
  return resp;
}

执行完代码后,看打印:

no block api enter

no block api exit

[{"host":"10.244.0.55","instanceId":"71f96128-3bb1-11ec-97e6-ac1f6ba00d36","metadata":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"cas-server-service\",\"namespace\":\"system-server\"},\"spec\":{\"ports\":[{\"name\":\"cas-server01\",\"port\":2000,\"targetPort\":\"cas-server01\"}],\"selector\":{\"app\":\"cas-server\"}}}\n","port.cas-server01":"2000","k8s_namespace":"system-server"},"namespace":"system-server","port":2000,"scheme":"http","secure":false,"serviceId":"cas-server-service","uri":"http://10.244.0.55:2000"},{"host":"10.244.0.56","instanceId":"71fc1c14-3bb1-11ec-97e6-ac1f6ba00d36","metadata":{"$ref":"$[0].metadata"},"namespace":"system-server","port":2000,"scheme":"http","secure":false,"serviceId":"cas-server-service","uri":"http://10.244.0.56:2000"}]

在本例中,WebClient 返回一个 Mono 生产者后完成方法的执行。如果一旦结果可用,发布者将开始向其订阅者发送数据。调用这个API的客户端(浏览器)也将订阅返回的 Mono 对象。

结论

在大部分场景下, RestTemplate 还是继续被使用的,但有些场景下,反应式非阻塞请求还是必须的,系统资源要少得多。WebClient 不失为是一个更好的选择。

下面给大家介绍一本好书《深入了解分布式事务》,该书在当当网目前销售火热,有原理加实战,感兴趣可以点击下方链接购买。

开源项目

  实践项目代码开源:https://gitee.com/damon_one/microservice-k8s

欢迎大家star、fork,欢迎联系我,一起学习。

云原生社区合肥站

云原生社区合肥站正式启动啦,欢迎Base合肥、关注云原生、长期从事云原生的同志们踊跃加入,云原生社区合肥站会因为你们的加入而变得更加美好~

详情参见Issue:https://github.com/cloudnativeto/community/issues/107

欢迎关注个站

c3e2bbf19a7e7f60420d9b8a3a3cb07a.png

往期回顾

微服务自动化部署CI/CD

如何利用k8s拉取私有仓库镜像

个站建设基础教程

ArrayList、LinkedList&nbsp;你真的了解吗?

大佬整理的mysql规范,分享给大家

如果张东升是个程序员

微服务架构设计之解耦合

浅谈负载均衡

Oauth2的认证实战-HA篇

Oauth2的授权码模式《上》

浅谈开发与研发之差异

浅谈&nbsp;Java&nbsp;集合&nbsp;|&nbsp;底层源码解析

基于 Sentinel 作熔断 | 文末赠资料

基础设施服务k8s快速部署之HA篇

今天被问微服务,这几点,让面试官刮目相看

Spring cloud 之多种方式限流(实战)

Spring cloud 之熔断机制(实战)

面试被问finally 和 return,到底谁先执行?

Springcloud Oauth2 HA篇

Spring Cloud Kubernetes之实战一配置管理

Spring Cloud Kubernetes之实战二服务注册与发现

Spring Cloud Kubernetes之实战三网关Gateway

ed55c4ef4d6bd4961c613af6bf2bf7ee.png

7e7cf7b29d2d0322e88af0f3406f9a7c.png

29310ad29ab154fe45720e0ef533ec01.png

关注公众号,回复入群,获取更多惊喜!公众号(程序猿Damon)里回复 ES、Flink、Java、Kafka、MQ、ML、监控、大数据、k8s 等关键字可以查看更多关键字对应的文章。

36d8ec29299b720adaa35dd2fc4b5d3e.gif

ef31d4aea9623bba2f770f72992dac2e.gif

点击 "damon8.cn" 获取更好的阅读体验!

❤️给个「在看」,是对我最大的支持❤️
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值