Introduction
We continue to build on the code from the previous blog and add Ribbon components to provide client-side load balancing. Load balancing is an important part of realizing high-concurrency, high-performance, and scalable services. It can distribute requests to different servers in a cluster to reduce the burden on each server. Client load balancing runs in client programs, such as our web project, and then randomly selects a server to send requests by obtaining a list of cluster IP addresses. Compared with server-side load balancing, it does not need to consume server resources.
basic environment
- JDK 1.8
- Maven 3.3.9
- IntelliJ 2018.1
- Git
Project source code
Update configuration
This time we need to start two production service programs locally to verify load balancing, so we need to provide a different port for the second program. The configuration of the Spring Cloud configuration service center will override the local system environment variables by default, and we need to set the port of the product service through the system environment variable, so we need to modify the configuration file of the product service in the configuration center git repositoryproduct-service.yml
server:
port: 8081
spring:
cloud:
config:
allow-override: true
override-system-properties: false
allow-override
The default value of is true. It is written to illustrate that it means to allow the configuration items of the remote configuration center to override the local configuration, not to allow the local configuration to override the remote configuration. Of course we can set it to false, but in order to provide more precise override rules, the default value is left here. We added override-system-properties=false
that although the configuration file of the remote configuration center can overwrite the local configuration, it should not overwrite the local system variables. After the modification is completed, submit it to the git repository.
In addition, add some logs to the productService
project ProductController
to verify whether the load balancing is in effect:
package cn.zxuqian.controllers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
private static Logger log = LoggerFactory.getLogger(ProductController.class);
@RequestMapping("/products")
public String productList() {
log.info("Access to /products endpoint");
return "外套,夹克,毛衣,T恤";
}
}
Configure Ribbon for the web
First pom.xml
add Ribbon's dependencies in:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
Then modify the Application
class and add the following code:
@EnableCircuitBreaker
@EnableDiscoveryClient
@RibbonClient(name = "product-service")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate rest(RestTemplateBuilder builder) {
return builder.build();
}
}
The annotations are used here @RibbonClient(name = "product-service")
to mark this project as a Ribbon load balancing client. It needs to select one of the product service clusters to access the required services. The name
attributes here correspond to the attributes configured in the productService project spring.application.name
. @LoadBalanced
The annotation indicates the uri that RestTemplate
will be configured to automatically use Ribbon LoadBalancerClient
to select the service and send the request.
ProductService
Add the following code to our class:
@Service
public class ProductService {
private final RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
public ProductService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@HystrixCommand(fallbackMethod = "backupProductList")
public String productList() {
List<ServiceInstance> instances = this.discoveryClient.getInstances("product-service");
if(instances != null && instances.size() > 0) {
return this.restTemplate.getForObject(instances.get(0).getUri() + "/products", String.class);
}
return "";
}
public String backupProductList() {
return "夹克,毛衣";
}
public String productListLoadBalanced() {
return this.restTemplate.getForObject("http://product-service/products", String.class);
}
}
A new method is added here , which accesses the same service as the productListLoadBalanced
previous productList
method, but uses Ribbon Client for load balancing. The host of the uri here becomes the product-service
name of the service to be accessed, and the properties @RibbonClient
configured in name
be consistent. Finally ProductController
add the following code to our:
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/products")
public String productList() {
return productService.productList();
}
@RequestMapping("/productslb")
public String productListLoadBalanced() {
return productService.productListLoadBalanced();
}
}
To create a method dedicated to handling /productslb
requests, call the method that productServie
provides load balancing.
At this point, our code is complete. The code seems simple, but in fact, all configurations use the default values. Ribbon provides two ways to configure Ribbon Client, programmatic and configuration. Now let's briefly introduce it, and then we will see how to modify its configuration when we go deeper into Ribbon. Ribbon provides the following configuration (interface on the left, default implementation on the right):
IClientConfig
ribbonClientConfig: DefaultClientConfigImplIRule ribbonRule
: ZoneAvoidanceRuleIPing ribbonPing
: DummyPingServerList<Server>
ribbonServerList: ConfigurationBasedServerListServerListFilter<Server>
ribbonServerListFilter: ZonePreferenceServerListFilterILoadBalancer
ribbonLoadBalancer: ZoneAwareLoadBalancerServerListUpdater
ribbonServerListUpdater: PollingServerListUpdater
Because our project uses Eureka, some configuration items are different from the default implementation. For example, Eureka uses DiscoveryEnabledNIWSServerList
substitution ribbonServerList
to get a list of services registered on Eureka. Below is a simple Congiguration class from Spring's official website:
public class SayHelloConfiguration {
@Autowired
IClientConfig ribbonClientConfig;
@Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl();
}
@Bean
public IRule ribbonRule(IClientConfig config) {
return new AvailabilityFilteringRule();
}
}
Ribbon will not send Ping to check the health status of the server by default, the default is normal, and then IRune is implemented by default as ZoneAvoidanceRule
a zone to avoid AWS EC2 problems, which is not used in the local test environment, and then replaced with AvailabilityFilteringRule
, This can open the circuit breaker function that comes with Ribbon to filter servers that are not working properly.
test
First start our configserver
configuration center service, then start the registry
Eureka registration and discovery service, and then start two productService
, the first one we can use the spring-boot:run
plug-in to start normally, the second we need to provide it with a new port, which can be started with the following command :
$ SERVER_PORT=8082 mvn spring-boot:run
Finally, start our web
client project, access http://localhost:8080/productslb
it, and refresh it a few times. You will see that productService
our logs will appear randomly in the two running command line windows:
Access to /products endpoint
Welcome to my blog, Zhang Xuqian's blog