Let us implement client-side load balancing by following these steps:
- First, create a Maven project ch10-eureka-client that contains pom.xml with SpringCloudFinchley dependencies. Just add the core starter POM such as webflux, the actuator and the following SpringCloudNetflixRibbon module dependencies:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency>
- Then, create a typical bootstrap class suited for a Eureka client that fetches the registry and automatically registers itself to the Eureka server:
@SpringBootApplication @EnableDiscoveryClient public class HRSEurekaClientBootApplication { public static void main(String[] args) { SpringApplication.run(HRSEurekaClientBootApplication.class, args); } }
- In its src/main/resources, create the necessary application.properties for this Eureka client instance:
spring.application.name=hrs-client eureka.client.service-url.defaultZone= http://localhost:5566/eureka/ server.port=8076 # same as the previous recipe
- Create a webflux Configuration inside org.packt.microservice.client.config with the injected RestTemplate and AsyncRestTemplate. To apply Spring Cloud Netflix Ribbon algorithm for client-side load balancing, add @LoadBalanced annotation to each injected @Bean:
@Configuration @EnableWebFlux public class WebFluxConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } @Bean @LoadBalanced public AsyncRestTemplate asyncRestTemplate(){ AsyncRestTemplate art = new AsyncRestTemplate(); return art; } }
- Copy all the needed entity models from the three microservice projects to the package org.packt.microsrevice.client.model.data for JSON encoding/decoding.
- Now, create the client controller that will consume any endpoints from these three microservices registered in the Eureka server. This time the service URL (for example, IP address, port, and so on) does not need to be known, since Ribbon will assist with finding the viable and healthy instances through their declared Eureka service names EMPS-SERVICE-INSTANCE, DEPTS-SERVICE-INSTANCE, and LOGIN-SERVICE-INSTANCE:
@Controller public class AccessRestController { private String instanceEmp = "http://EMPS-SERVICE-INSTANCE"; private String instanceDept = "http://DEPTS-SERVICE-INSTANCE"; @Autowired protected RestTemplate restTemplate; @Autowired private AsyncRestTemplate asyncRestTemplate; @GetMapping(value="/accessListEmps", produces= MediaType.APPLICATION_JSON_VALUE) @ResponseBody public List<Employee> blockListEmp() { HttpHeaders headers = new HttpHeaders(); headers.set("Accept", MediaType.APPLICATION_JSON_VALUE); HttpEntity<String> entity = new HttpEntity<>(headers); ResponseEntity<List> response = RestTemplate .exchange(instanceEmp + "/listEmp", HttpMethod.GET, entity, List.class); return response.getBody(); } @GetMapping(value="/accessListDepts", produces= MediaType.APPLICATION_JSON_VALUE) @ResponseBody public List<Employee> blockListDepts() { HttpHeaders headers = new HttpHeaders(); headers.set("Accept", MediaType.APPLICATION_JSON_VALUE); HttpEntity<String> entity = new HttpEntity<>(headers); ResponseEntity<List> response = restTemplate .exchange(instanceDept + "/listDept", HttpMethod.GET, entity, List.class); return response.getBody(); } @RequestMapping(value="/asyncSelectEmp/{id}", produces=MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Employee asyncSelectEmps( @PathVariable("id") Integer id){ String url = instanceEmp + "/callSelectEmp/{id}.json"; HttpMethod method = HttpMethod.GET; HttpHeaders headers = new HttpHeaders(); headers.set("Accept", MediaType.APPLICATION_JSON_VALUE); HttpEntity<String> requestEntity = new HttpEntity<String>("params", headers); ListenableFuture<ResponseEntity<Employee>> future = asyncRestTemplate .exchange(url, method, requestEntity, Employee.class, id); try { ResponseEntity<Employee> entity = future.get(); return entity.getBody(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } return null; } }
- In the case of consuming reactive REST services, directly inject the LoadBalancerClient into AccessRestController to explicitly search the service instance name from any of the healthiest instance of the registry that can provide the service URL of the desired reactive endpoint such as /selectReactEmps:
@Autowired private LoadBalancerClient loadBalancer; @RequestMapping("/accessReactClients") public Flux<Employee> sayFlux() { ServiceInstance serviceInstance= loadBalancer.choose("EMPS-SERVICE-INSTANCE"); String baseUrl=serviceInstance.getUri().toString(); return WebClient.create().method(HttpMethod.GET) .uri(baseUrl + "/selectReactEmps").contentType( MediaType.APPLICATION_JSON_UTF8).retrieve() .bodyToFlux(Employee.class); }
The Ribbon API that executes the client-side load balancing algorithm is org.springframework.cloud.client.loadbalancer.LoadBalancerClient. The annotation @LoadBalanced configures RestTemplate and AsyncRestTemplate to become a LoadBalancerClient type. The problem arises only when @LoadBalanced annotation is directly applied to the new reactive WebClient because it generates an exception:
o.s.web.reactive.function.client - java.net.UnknownHostException: EMPS-SERVICE-INSTANCE
o.s.web.reactive.function.client - java.net.UnknownHostException: EMPS-SERVICE-INSTANCE
- Save all files. Run Maven commands clean spring-boot:run -U and refresh the Eureka server page. If the HRS-CLIENT instance has been registered, run the entire client endpoints indicated in AccessRestController.