service call
Ribben load balancing
Spring Cloud Ribbon is a set of client load balancing tools based on Netflix Ribbon .
To put it simply, Ribbon is an open source project released by Netflix. Its main function is to provide client-side software load balancing algorithms and service calls . The Ribbon client component provides a series of comprehensive configuration items such as connection timeout, retry, etc.
Simply put, it is to list all the machines behind Load Balancer (LB for short) in the configuration file, and Ribbon will automatically help you connect these machines based on certain rules (such as simple polling, random connection, etc.). We can easily use Ribbon to implement a custom load balancing algorithm.
ribbon
British [ˈrɪbən] American [ˈrɪbən]
n. (for binding or decoration) belt; ribbon;
Ribbon is also currently in maintenance mode.
Ribbon may be replaced by Spring Cloud LoadBalacer in the future.
What is LB Load Balance?
Simply put, it is to distribute the user's request to multiple services equally, so as to achieve the HA (high availability) of the system.
Common load balancing methods include software Nginx, LVS, and hardware F5.
Differences between Ribbon local load balancing client VS Nginx server load balancing
Nginx is a server load balancer. All client requests will be handed over to nginx, and then nginx will forward the request. That is, load balancing is implemented by the server.
Ribbon local load balancing, when invoking the microservice interface, will obtain the registration information service list from the registration center and cache it locally in the JVM, so as to realize the RPC remote service invocation technology locally.
Centralized LB
That is, use an independent LB facility (it can be hardware, such as F5, or software, such as nginx) between the service consumer and the provider, and the facility is responsible for forwarding the access request to the service provider through a certain strategy ;
In-process LB
Integrating LB logic into the consumer, the consumer learns which addresses are available from the service registry, and then selects a suitable server from these addresses.
Ribbon is an in-process LB. It is just a class library integrated in the consumer process, and the consumer obtains the address of the service provider through it.
One sentence load balancing + RestTemplate call
Ribbon load balancing and Rest calls
Summary: Ribbon is actually a soft load balancing client component, which can be used in combination with other clients that require requests, and the combination with Eureka is just one example.
Ribbon is divided into two steps when working:
- The first step is to choose EurekaServer, which gives priority to servers with less load in the same area.
- The second step is to select an address in the service registration list obtained from the server according to the policy specified by the user.
Among them, Ribbon provides a variety of strategies: such as polling, random and weighted according to response time.
Use Ribbon:
1. By default, when we use the new version of eureka, it integrates ribbon by default:
This is because spring-cloud-starter-netflix-eureka-client comes with spring-cloud-starter-ribbon reference.
2, we can also manually introduce the ribbon
Put it in the order module, because only order needs load balancing when accessing pay
Ribbon can also be used without introducing spring-cloud-starter-ribbon in previous engineering projects.
<dependency>
<groupld>org.springframework.cloud</groupld>
<artifactld>spring-cloud-starter-netflix-ribbon</artifactid>
</dependency>
Use of RestTemplate
getForObject() / getForEntity() - GET request method
getForObject(): The returned object is the object converted from the data in the response body, which can basically be understood as Json.
getForEntity(): The returned object is a ResponseEntity object, which contains some important information in the response, such as response header, response status code, and response body.
@GetMapping("/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id)
{
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();//getForObject()
}else{
return new CommonResult<>(444,"操作失败");
}
}
postForObject() / postForEntity() - POST request method
Ribbon's default load rules
lRule: Select a service to be accessed from the service list according to a specific algorithm
- com.netflix.loadbalancer.RoundRobinRule polling
- com.netflix.loadbalancer.RandomRule Random
- com.netflix.loadbalancer.RetryRule first obtains the service according to the strategy of RoundRobinRule, if the service fails to obtain, it will retry within the specified time
- WeightedResponseTimeRule is an extension of RoundRobinRule, the faster the response, the greater the selection weight of the instance, and the easier it is to be selected
- BestAvailableRule will first filter out services that are in the state of circuit breaker tripping due to multiple access failures, and then select a service with the least concurrency
- AvailabilityFilteringRule filters out faulty instances first, and then selects instances with less concurrency
- ZoneAvoidanceRule is the default rule, which compositely judges the performance of the area where the server is located and the availability of the server to select the server
Ribbon load rule replacement
1. Modify cloud-consumer-order80
2. Pay attention to configuration details
The official documentation clearly gives a warning:
This custom configuration class cannot be placed under the current package and sub-packages scanned by @ComponentScan.
Otherwise, the configuration class we customized will be shared by all Ribbon clients, and the purpose of special customization will not be achieved. ( That is to say, do not package the Ribbon configuration class with the main startup class )
3. Create a new package - myrule
4. Create a new MySelfRule rule class under com.example.myrule
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();
}
}
5. Add @RibbonClient to the main startup class
Indicates that when accessing the service of CLOUD_PAYMENT_SERVICE, use our custom load balancing algorithm
import com.example.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
6. Test
开启cloud-eureka-server7001,cloud-consumer-order80,cloud-provider-payment8001,cloud-provider-payment8002
Browser - Enter http://localhost/consumer/payment/get/31
The serverPort in the returned result jumps repeatedly between 8001 and 8002.
Ribbon default load polling algorithm principle
The default load rotation training algorithm: the number of requests for the rest interface % of the total number of server clusters = the subscript of the actual call server location, and the count of the rest interface starts from 1 after each service restart .
List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”);
like:
- List [0] instances = 127.0.0.1:8002
- List [1] instances = 127.0.0.1:8001
8001+8002 are combined into a cluster, they have a total of 2 machines, and the total number of clusters is 2, according to the principle of the polling algorithm:
- When the total number of requests is 1: 1%2=1 corresponds to the subscript position being 1, then the service address is 127.0.0.1:8001
- When the total request number is 2: 2%2=0 corresponds to a subscript position of 0, then the service address is 127.0.0.1:8002
- When the total request number is 3: 3%2=1 corresponds to the subscript position being 1, then the service address is 127.0.0.1:8001
- When the total request number is 4: 4%2=0 corresponds to a subscript position of 0, then the service address is 127.0.0.1:8002
- And so on…
RoundRobinRule source code analysis
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
//重点关注这方法
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The most well known and basic load balancing strategy, i.e. Round Robin Rule.
*
* @author stonse
* @author Nikos Michalakis <[email protected]>
*
*/
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
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() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
setLoadBalancer(lb);
}
//重点关注这方法。
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;//求余法
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
Ribbon's handwritten polling algorithm
Try to write a local load balancer similar to RoundRobinRule yourself.
- 7001/7002 cluster startup
- 8001/8002 microservice transformation - controller
@RestController
@Slf4j
public class PaymentController{
...
@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
//返回服务接口
return serverPort;
}
...
}
- 80 order microservice transformation
1. ApplicationContextConfig removes the annotation @LoadBalanced, OrderMain80 removes the annotation @RibbonClient
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
//@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
2. Create the LoadBalancer interface
import org.springframework.cloud.client.ServiceInstance;
import java.util.List;
public interface LoadBalancer{
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
3.MyLB
Implement the LoadBalancer interface
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
*/
@Component//需要跟主启动类同包,或者在其子孙包下。
public class MyLB implements LoadBalancer
{
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement()
{
int current;
int next;
do {
current = this.atomicInteger.get();
next = current >= 2147483647 ? 0 : current + 1;
}while(!this.atomicInteger.compareAndSet(current,next));
log.info("*****第几次访问,次数:next: "+next);
return next;
}
//负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances)
{
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
4.OrderController
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import com.lun.springcloud.lb.LoadBalancer;
@Slf4j
@RestController
public class OrderController {
//public static final String PAYMENT_URL = "http://localhost:8001";
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
...
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
...
@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB()
{
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
}
5. The test keeps refreshing http://localhost/consumer/payment/lb, and you can see that 8001/8002 appear alternately.
OpenFeign
basic concept
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign. link
Feign is a declarative WebService client. Using Feign can make writing Web Service clients easier. It is used by defining a service interface and adding annotations on it . Feign also supports pluggable encoders and decoders. Spring Cloud encapsulates Feign to support Spring MVC standard annotations and HttpMessageConverters. Feign can be used in combination with Eureka and Ribbon to support load balancing.
What can Feign do?
Feign aims to make writing Java Http clients easier.
When using Ribbon + RestTemplate earlier, RestTemplate is used to encapsulate http requests to form a set of templated calling methods. However, in actual development, since there may be more than one call to service dependencies, an interface will often be called from multiple places, so usually some client classes are encapsulated for each microservice to wrap the calls of these dependent services. Therefore, Feign made a further package on this basis, and he will help us define and implement the definition of dependent service interfaces. Under the implementation of Feign, we only need to create an interface and use annotations to configure it (in the past, Mapper annotations were marked on the Dao interface, and now a Feign annotation is marked on a microservice interface), and the service can be completed . The interface binding of the provider simplifies the development of automatically encapsulating the service calling client when using Spring cloud Ribbon.
Feign integrates Ribbon
The Ribbon is used to maintain the payment service list information, and the load balancing of the client is realized through polling. Unlike Ribbon, feign only needs to define the service binding interface and implement service invocation in a declarative way elegantly and simply.
The difference between Feign and OpenFeign
Feign | OpenFeign |
---|---|
Feign is a lightweight RESTful HTTP service client in the Spring Cloud component. Feign has a built-in Ribbon, which is used for client load balancing to call the services of the service registry. The way to use Feign is: use Feign's annotations to define an interface, and call this interface to call the service of the service registry. | OpenFeign is that Spring Cloud supports SpringMVC annotations on the basis of Feign, such as @RequesMapping and so on. OpenFeign's @Feignclient can parse the interface under SpringMVc's @RequestMapping annotation, and generate an implementation class through a dynamic proxy, implement load balancing and call other services in the implementation class. |
org.springframework.cloud spring-cloud-starter-feign |
org.springframework.cloud spring-cloud-starter-openfeign |
feign
English [feɪn] Mei [feɪn]
v. Pretend, pretend, pretend (feeling or sick, tired, etc.)
OpenFeign service call
Interface + annotation: microservice call interface + @FeignClient
1. Create a new module
Name: cloud-consumer-feign-order80
2.POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-feign-order80</artifactId>
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>org.example</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
3.YML
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
4. Master Boot
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
5. Business class
Business logic interface + @FeignClient configuration call provider service
Create a new PaymentFeignService interface and add the annotation @FeignClient
import com.lun.springcloud.entities.CommonResult;
import com.lun.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService
{
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
Control layer Controller
import com.example.springcloud.pojo.CommonResult;
import com.example.springcloud.pojo.Payment;
import com.example.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@Slf4j
@RestController
@RequestMapping("/consumer")
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
/**
* 通过id查订单
* @param id 订单id
* @return 订单
*/
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
6. Test
Start two eureka clusters 7001/7002 first
Start 2 more microservices 8001/8002
Start OpenFeign start
http://localhost/consumer/payment/get/1
Feign comes with load balancing configuration items
OpenFeign timeout control
Timeout setting, intentionally set timeout to demonstrate error conditions
1. The service provider 8001/8002 intentionally writes the suspension program
@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
...
@Value("${server.port}")
private String serverPort;
...
@GetMapping(value = "/feign/timeout")
public String paymentFeignTimeout()
{
// 业务逻辑处理正确,但是需要耗费3秒钟
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
...
}
2. The service consumer 80 adds the timeout method PaymentFeignService
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService{
...
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
}
3. The service consumer 80 adds a timeout method to OrderFeignController
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderFeignController
{
@Resource
private PaymentFeignService paymentFeignService;
...
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout()
{
// OpenFeign客户端一般默认等待1秒钟
return paymentFeignService.paymentFeignTimeout();
}
}
4. Test:
Refresh http://localhost/consumer/payment/feign/timeout multiple times
The error Spring Boot default error page will pop up, the main exception: feign.RetryableException: Read timed out executing GET http://CLOUD-PAYMENT-SERVCE/payment/feign/timeout.
OpenFeign waits for 1 second by default, and reports an error after exceeding
OpenFeign client timeout control needs to be enabled in the YML file
#设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
OpenFeign log enhancements
Log printing function
Feign provides a log printing function, and we can adjust the severity level through configuration to understand the details of Http requests in Feign.
To put it bluntly, it is to monitor and output the calls of the Feign interface
log level
- NONE: default, do not display any logs;
- BASIC: only record the request method, URL, response status code and execution time;
- HEADERS: In addition to the information defined in BASIC, there are request and response header information;
- FULL: In addition to the information defined in HEADERS, there are request and response body and metadata.
Configure the logging bean
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig{
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}
The Feign client that needs to open the log in the YML file
logging:
level:
# feign日志以什么级别监控哪个接口
com.example.springcloud.service.PaymentFeignService: debug
Background log view
Get more log information.