“This is my NTH day of participating in the First Challenge 2022. For details: First Challenge 2022.”

Blocking and non-blocking

Blocking is when a program waits for the process or thread to complete its current task without doing anything else. Instead of blocking, this means that the current thread can process something while doing something else, without waiting for the current event to complete.

Blocking and non-blocking clients

For requests, we have clients that require some request encapsulation, which can be divided into two categories: blocking and non-blocking.

Blocking clients take the common RestTemplate as an example. This is a common client request wrapper. To create a load-balancing RestTemplate, look at its Bean:

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

Underneath, RestTemplate uses the Java Servlet API based on the Thread-per-request model. In a blocking client, this means that the thread is blocked until the Web client receives the response. The problem with blocking is that each thread consumes a certain amount of memory and CPU cycles.

With concurrency, sooner or later, requests waiting for results will pile up. As a result, the program creates many threads that deplete the thread pool or consume all available memory. We also experienced performance degradation due to frequent CPU thread switching.

In Spring5, a new client abstraction is proposed: the Reactive client WebClient, which uses the asynchronous non-blocking solution provided by the Spring Reactive Framework. So, when RestTemplate creates new threads, Webclient creates task-like threads for them, and underneath, the Reactive framework queues these tasks and executes them only when appropriate responses are available. WebClient is part of the Spring WebFlux library. So, we can also use smooth functional API programming and combine response types as declarations. If you want to use WebClient, you can also create:

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

case

Let’s say we have a very slow reST-Service and test it with blocking and non-blocking clients.

The block 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"); }Copy the code

After starting the service request, it prints:

block api enter [{" host ", "10.244.0.55", "instanceId" : "71 f96128 e6 ec - 97-3 bb2-11 - ac1f6ba00d36", "metadata" : {" kubectl. Kubernetes. IO/last - appli ed-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"cas-server-se rvice\",\"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" : "71 fc1c14 e6 ec - 97-3 bb2-11 - ac1f6ba00d36", "metadata" : {" $ref ":" $[0]. Meta data"},"namespace":"system-server","port":2000,"scheme":"http","secure":false,"serviceId":"cas-server-service","uri":"ht Tp: / / 10.244.0.56:2000 "}] block API exitCopy the code

Non-blocking type

The print above matches our overdue, so let’s 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; }Copy the code

After executing the code, look at the print:

no block api enter no block api exit [{" host ", "10.244.0.55", "instanceId" : "71 f96128 e6 ec - 97-3 bb2-11 - ac1f6ba00d36", "metadata" : {" kubectl. Kubernetes. IO/last - appli ed-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"cas-server-se rvice\",\"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" : "71 fc1c14 e6 ec - 97-3 bb2-11 - ac1f6ba00d36", "metadata" : {" $ref ":" $[0]. Meta data"},"namespace":"system-server","port":2000,"scheme":"http","secure":false,"serviceId":"cas-server-service","uri":"ht Tp: / / 10.244.0.56:2000}]"Copy the code

In this case, WebClient completes the method by returning a Mono producer. 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.

Switch from blocking to non-blocking

You can convert the previous blocking request to a non-blocking request if you use Spring5. In this case, you can write the following code:

@GetMapping("/hello")
public Mono<String> hello() {
    return Mono.fromCallable(() -> restTemplate.getForObject("http://diff-ns-service-service/all/getService", String.class))
            .subscribeOn(Schedulers.elastic());
}
Copy the code

After this, when requesting access, the body of information returned by the provider service is returned directly:

{" result ": {" status" : 200, "code" : 0, "MSG" : "success"}, "data" : [{" host ":" 10.244.0.55 ", "instanceId" : "71 f96128 e6 ec - 97-3 bb2-11 - ac 1f6ba00d36","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","po rt.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" : "71 fc1c14-3 bb1-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"}}]Copy the code

It is important to note that the request should be returned directly to the standard body of the service provider. Otherwise, it can only get the information:

{"result":{"status":200,"code":0,"msg":"success"},"data":{"scanAvailable":true}}
Copy the code

This means that the callable is true this time, but this is not the information we need, we still need the business data returned by itself. Standardization of the provider’s return is required, since the information is returned directly to the front end, such as the receiving browser.

Custom return body

From the previous example, we can see that when the request is sent and the response is returned directly to the subscriber (browser), but sometimes we may need to make some custom return body, such as some status code, description, description, etc.

Now, what do we do about it? We can see that in webClient, we provide a block function, which can block the return of information to the front end, can be used to encapsulate the return result:

@GetMapping("/test2")
public Response<String> test2() {
    Mono<String> resp = webClientBuilder.build()
                    .get()
                    .uri("http://diff-ns-service-service/all/getService")
                    .retrieve()
                    .bodyToMono(String.class);

    return Response.ok(resp.block(Duration.ofSeconds(2)));
}
Copy the code

Given a maximum amount of time waiting for an exception to occur, the timeout will throw a RunTimeException. In this way, we can customize the message body return format.

{" result ": {" status" : 200, "code" : 0, "MSG" : "success"}, "data" : "" \" [{\ \ \ "the host \ \ \" : \ \ \ "10.244.0.55 \ \ \", \ \ \ "instanceId \ \ \" : \ \ \ "7 1f96128-3bb1-11ec-97e6-ac1f6ba00d36\\\",\\\"metadata\\\":{\\\"kubectl.kubernetes.io/last-applied-configuration\\\":\\\"{ \\\\\\\"apiVersion\\\\\\\":\\\\\\\"v1\\\\\\\",\\\\\\\"kind\\\\\\\":\\\\\\\"Service\\\\\\\",\\\\\\\"metadata\\\\\\\":{\\\ \\\\"annotations\\\\\\\":{},\\\\\\\"name\\\\\\\":\\\\\\\"cas-server-service\\\\\\\",\\\\\\\"namespace\\\\\\\":\\\\\\\"sy stem-server\\\\\\\"},\\\\\\\"spec\\\\\\\":{\\\\\\\"ports\\\\\\\":[{\\\\\\\"name\\\\\\\":\\\\\\\"cas-server01\\\\\\\",\\\ \\\\"port\\\\\\\":2000,\\\\\\\"targetPort\\\\\\\":\\\\\\\"cas-server01\\\\\\\"}],\\\\\\\"selector\\\\\\\":{\\\\\\\"app\\ \\\\\":\\\\\\\"cas-server\\\\\\\"}}}\\\\n\\\",\\\"port.cas-server01\\\":\\\"2000\\\",\\\"k8s_namespace\\\":\\\"system-se rver\\\"},\\\"namespace\\\":\\\"system-server\\\",\\\"port\\\":2000,\\\"scheme\\\":\\\"http\\\",\\\"secure\\\":false,\\\ "ServiceId \ \ \" : \ \ \ "cas - server - service \ \ \", \ \ \ "uri \ \ \" : \ \ \ "http://10.244.0.55:2000\\\"}, {\ \ \ "the 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\ \ \ ": \ \ \" the cas server - service \ \ \ ", \ \ \ "uri \ \ \" : \ \ \ "http://10.244.0.56:2000\\\"}] \ ""}"Copy the code

Of course, if the other party’s message body has been output according to our standard format, we can directly return the message body.

conclusion

In most cases, the RestTemplate will continue to be used, but in some cases reactive non-blocking requests will still be required, with far fewer system resources. WebClient is a better choice.