SpringCloud-service call

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;

Github - 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

insert image description here

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.

insert image description here

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

RestTemplate Java Doc

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

insert image description here

  • 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();
    }
}

insert image description here

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

official document

Github address

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

insert image description here

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.

insert image description here

Guess you like

Origin blog.csdn.net/m0_49683806/article/details/125940099