A list,
1. What is
- Spring Cloud Ribbon is a client load balancing tool based on Netflix Ribbon.
- To put it simply, the Ribbon is an open source project released by Netflix. Its main function is to provide client-side software load balancing algorithm and service invocation.
- The official documentation
- It is now in maintenance and can be replaced by Open Feign
- Load balancing +RestTemplate to implement load balancing calls
2. Load balancing
- Load balancing (LB), in which user requests are spread across multiple services to achieve high availability (HA) of the system
- There are two load balancing schemes: centralized LB and in-process LB
2.1 Centralized LB
- That is, a separate LB facility is used between the service provider and the consumer, which is responsible for forwarding access requests to the service provider through some policy.
- Examples include Nginx, Gateway, Zuul, etc
2.2 In-process LB
- The load balancing algorithm is integrated into the consumer, which obtains the available address in the registry and then selects a suitable server through the LB algorithm.
- The Ribbon is an in-process LB. It is simply a library that integrates with the consumer process to retrieve the address provided by the service provider.
Second, the experimental
- Ribbon integration in
spring-cloud-starter-netflix-eureka-client
,See the use of Eureka. Service invocation and load balancing can be accomplished with a few simple modifications on this basis
1. RestTemplate
- website
- RestTemplate provides HttpClient with a URL and return type to implement remote method calls.
1.1 Add to IOC container
- First, add it to the IOC container.
@LoadBalanced
Indicates that load balancing is enabled.
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(a) {
return newRestTemplate(); }}Copy the code
1.2 RestTemplate Remote Call
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
RestTemplate restTemplate; // In the ioc container
@Value("${payment.url}")
String paymentUrl; // The URL of the remote call, stored in the configuration file, decoupled
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
CommonResult<Payment> result = restTemplate.getForObject(paymentUrl + "/payment/get/" + id, CommonResult.class); // get method call, and return encapsulated as CommonResult type
log.info("Order query Payment, id:" + id);
returnresult; }}Copy the code
- You can also use
getForEntity()
Method, get the entire response, and get the desired content in the response yourself.
@GetMapping("/payment/getEntity/{id}")
public CommonResult<Payment> getPaymentEntityById(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(paymentUrl + "/payment/get/" + id, CommonResult.class);
log.info("The information obtained is:" + entity.toString());
log.info("StatusCode obtained is:" + entity.getStatusCode());
log.info("StatusCodeValue obtained is:" + entity.getStatusCodeValue());
log.info("Get Headers:" + entity.getHeaders());
if (entity.getStatusCode().is2xxSuccessful()) {
log.info("Query successful:" + id);
return entity.getBody();
} else {
log.info("Query failed:" + id);
return new CommonResult<>(CommonResult.FAIlURE, "Query failed"); }}Copy the code
- If you use the POST method, just change get to POST.
1.3 Configuration File
- Url, you can write specific address, that is, call the address directly. You can also write it to the eureka service name. Obtain all the addresses of the service from eureka and select one from the LB.
payment:
url: "http://CLOUD-PAYMENT-SERVICE"
Copy the code
2. LoadBalancer
- Through the above
@LoadBalanced
Load balancing is enabled. The polling algorithm is used by default, but can be changed to other algorithms.
Class | algorithm |
---|---|
com.netflix.loadbalancer.RoundRobinRule |
Polling, default algorithm |
com.netflix.loadbalancer.RandomRule |
Random algorithm, by generating random number selection server |
com.netflix.loadbalancer.RetryRule |
Obtain the service based on RoundRobinRule. If the service fails to be obtained, the system tries again within a specified period to obtain available services |
WeightedResponseTimeRule |
With an extension to RoundRobinRule, instances that are faster in response have greater selection weight and are easier to select |
BestAvailableRule |
Services that are in the breaker trip state due to multiple access failures are filtered out and the service with the least concurrency is selected |
AvailabilityFilteringRule |
Filter out the failed instances first, and then select the instances with low concurrency |
ZoneAvoidanceRule |
Default rules that compound the performance of the region where the server resides and the availability of the server to select the server |
2.1 Modifying the load Balancing Algorithm
- If you want the algorithm to be specific to a service, you cannot place it in
ComponentScan
Otherwise the load balancing algorithm for all services will be modified. Therefore, it is best to build a new one outsidepackage
To put the LB
@Configuration
public class MyRule {
@Bean
public IRule rule(a) {
return newRandomRule(); }}Copy the code
- On the main startup class, identify the direct mapping between the service and the algorithm
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRule.class)
public class OrderApplication80 {
public static void main(String[] args) { SpringApplication.run(OrderApplication80.class, args); }}Copy the code
- If this method is troublesome, you can also use the configuration file method
CLOUD-PAYMENT-SERVICE: # service name
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # Algorithm selection
Copy the code
3. Load balancing algorithm source
- In the default of
RoundRobinRule
As read source, the rest of the source code is basically similar, just modified to select the server code.
RoundRobinRule
The parent class forAbstractLoadBalancerRule
.AbstractLoadBalancerRule
Implements the interfaceIRule
3.1 IRule
public interface IRule {
Server choose(Object var1); // Select the server, the most important method
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer(a);
}
Copy the code
3.2 AbstractLoadBalancerRule
- Basically nothing, just pull out the common parts for implementation.
public abstract class AbstractLoadBalancerRule implements IRule.IClientConfigAware {
private ILoadBalancer lb; // ILoadBalancer interface, whose main function is to obtain the current server status, number, etc., to provide the calculation parameters for the load balancing algorithm
public AbstractLoadBalancerRule(a) {}public void setLoadBalancer(ILoadBalancer lb) {
this.lb = lb;
}
public ILoadBalancer getLoadBalancer(a) {
return this.lb; }}Copy the code
3.3 RoundRobinRule
- Simply put, polling is implemented through a counter
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter; // Atom class, which keeps a count of where the polling is now
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule(a) {
this.nextServerCyclicCounter = new AtomicInteger(0); / / initialization
}
public RoundRobinRule(ILoadBalancer lb) { / / set the LoadBalancer
this(a);this.setLoadBalancer(lb);
}
public Server choose(ILoadBalancer lb, Object key) { // The most important method is to select the server and return
// Post it below
}
private int incrementAndGetModulo(int modulo) { // Modify the counter and return a selected value, which is the implementation of the polling algorithm
// Post it below
}
public Server choose(Object key) { // interface method in which another method implementation is called
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {}}Copy the code
- In simple terms, this method is based on the current state, select a server to return.
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) { // If there is no LoadBalancer, then there is no wasted effort
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) { // Try 10 times and give up if you can't find the server
List<Server> reachableServers = lb.getReachableServers(); // Get all currently available servers through LB
List<Server> allServers = lb.getAllServers(); // Get the actual servers
int upCount = reachableServers.size(); // Get the number of servers currently available
int serverCount = allServers.size(); // The number of servers, which is the divisor of the mod
if(upCount ! =0&& serverCount ! =0) { // If a server is available and available
int nextServerIndex = this.incrementAndGetModulo(serverCount); // The most critical selection algorithm, puts the current number of servers in, returns a selected number
server = (Server)allServers.get(nextServerIndex); // Retrieve the server according to the subscript
if (server == null) { // If it is empty, it is currently unavailable, and the next loop is entered
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) { // If the server is alive and usable, it is returned directly
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
returnserver; }}}Copy the code
- In simple terms, mod the current counter +1, get a subscript, and return. To avoid the risk of high concurrency, use the CAS method.
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get(); // Get the current value
next = (current + 1) % modulo; / / + 1
} while(!this.nextServerCyclicCounter.compareAndSet(current, next)); // CAS, return on success, return on failure
return next;
}
Copy the code