Blocking and non blocking
Blocking means that the program will wait for the process or thread to complete the current task without doing anything else. Instead of blocking, it means that the current thread can handle some things and other things at the same time. It does not need to wait for the current event to complete before executing other events.
Blocking and non blocking clients
For requests, we need to use some request encapsulated clients, which can be divided into two categories: blocking and non blocking.
For blocking clients, take the common RestTemplate as an example, which is a common client request encapsulation. To create a load balanced RestTemplate, let's take a look at its Bean:
@LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); }
At the bottom, RestTemplate uses a Java Servlet API based on a thread per request model for each request. In a blocking client, this means that the thread will be blocked until the Web client receives a response. The problem caused by blocking is that each thread consumes a certain amount of memory and CPU cycles.
In case of concurrency, requests waiting for results will pile up sooner or later. In this way, the program will create many threads that will exhaust the thread pool or occupy all available memory. Due to frequent CPU thread switching, we will also encounter the problem of performance degradation.
This proposes a new client abstraction in spring 5: the Reactive client WebClient, which uses the asynchronous non blocking solution provided by the Spring Reactive Framework. Therefore, when the RestTemplate creates new threads, the WebClient creates task like threads for them, and at the bottom, the Reactive framework will queue these tasks and execute them only when appropriate responses are available. WebClient is part of the Spring WebFlux library. Therefore, we can also use fluent functional API programming and combine response types as declarations. If you need to use WebClient, you can also create:
@Bean @LoadBalanced public WebClient.Builder loadBalancedWebClientBuilder() { return WebClient.builder(); }
case
Suppose there is a rest service with very slow response. Let's test it with blocking and non blocking clients respectively.
Blocking type
We use RestTemplate to implement blocking requests:
@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"); }
After starting the service request, it is found that it prints:
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
The above print meets our overdue. Next, let's take a look at non blocking, reactive client requests:
@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; }
After executing the code, look at the print:
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"}]
In this example, the WebClient returns a Mono producer and completes the execution of the method. Once the results are available, the publisher will start sending data to its subscribers. The client (browser) calling this API will also subscribe to the returned Mono object.
conclusion
In most scenarios, RestTemplate will continue to be used, but in some scenarios, reactive non blocking requests are still necessary, and system resources are much less. WebClient is a better choice.