1.Ribbon是什么

2.Ribbon负载平衡演示

3.Ribbon轮询算法原理和手写

4.OpenFeign是什么

5.OpenFeign应用步骤

6.OpenFeign超时管制

7.OpenFeign日志打印

8.OpenFeign和Feign的区别

1.Ribbon是什么
后面咱们学过了深刻学习springCloud——服务注册核心之eureka,然而服务和服务之间该如何通过注册核心进行调用,以及如果有多个服务提供者的话,如何提供负载平衡的策略,这都是咱们要思考的问题,此时就呈现了Ribbon!

Ribbon是spring Cloud 的一套客户端负载平衡的工具!

Ribbon次要提供的性能是提供客户端软件负载平衡算法服务调用。简略地说,Ribbon能获取该服务的所有实例,他会主动地帮你基于某种规定(比方轮询,随机等)算法去调用这些机器。咱们很容易地应用Ribbon实现自定义的的Load Balancer(简称LB)算法。

然而Ribbon目前也进入了保护模式,代替计划是OpenFeign,不过为了更好地学习OpenFeign,咱们就从Ribbon开始解说,缓缓过渡到OpenFeign.

为了更好地学习Ribbon,咱们先来理解一下负载平衡(LB)是什么
因为咱们的服务都是集群部署的,所以负载平衡就是将客户端的申请,均匀地调配到同一种集群服务的多个实例下面,从而让每一个服务承受到的申请数量差不多,达到HA(高可用)

常见的负载平衡形式:
1)Ribbon本地负载平衡
在调用微服务接口的时候,调用者会从注册核心获取服务器注册表,而后通过算法进行均匀调用,负载平衡是由客户端实现的(过程内LB)
2)Nginx服务端负载平衡
客户端先把所有申请交给nginx,而后由nginx转发实现申请,负载平衡是由服务器端实现的(集中式LB)。

2.Ribbon负载平衡演示

用Ribbon调用服务的流程图:

Ribbon在工作的时候,有以下的步骤:

1)调用者先从Eureka中取出被调用者的服务列表
2)依据用户的策略(轮询,随机等),从server取到的服务注册表中选取一个地址,进行调用。

咱们先筹备两个服务提供者,以便更好地显示出负载平衡的成果。

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.6.6</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.example</groupId>    <artifactId>service-provider-8501</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>service-provider-8501</name>    <description>Demo project for Spring Boot</description>    <properties>        <java.version>1.8</java.version>        <spring-cloud.version>2021.0.1</spring-cloud.version>    </properties>    <dependencies>        <!-- 这里应用eureka-client-->        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>        </dependency>        <!-- 留神要援用spring-boot-starter-web-->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>${spring-cloud.version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies>    </dependencyManagement>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build></project>

yml:

server:  port: 8501eureka:  instance:    hostname: service-provider #eureka服务端的实例名称    prefer-ip-address: true #拜访门路能够显示IP地址    instance-id: ${spring.cloud.client.ip-address}:${server.port}-service-provider #拜访门路的名称格局  client:    register-with-eureka: true     #false示意不向注册核心注册本人。    fetch-registry: true     #false示意本人端就是注册核心,我的职责就是保护服务实例,并不需要去检索服务    service-url:      #集群指向其它eureka      defaultZone: http://127.0.0.1:8001/eureka/,http://127.0.0.1:8002/eureka/spring:  application:    name: service-provider

主启动:

@SpringBootApplication@EnableEurekaClientpublic class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}

测试的controller:

@RestControllerpublic class ProviderController {    @Value("${server.port}")    private String serverPort;    @GetMapping(value = "/payment/get/{id}")    public String getPaymentById(@PathVariable("id") Long id)    {        return "调用胜利,服务端口为"+serverPort;    }}

咱们复制一个截然不同的,改改端口号和名字就能够了。

接下来是服务消费者:

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.6.6</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.example</groupId>    <artifactId>ribbon-cunsumer-8401</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>ribbon-cunsumer-8401</name>    <description>Demo project for Spring Boot</description>    <properties>        <java.version>1.8</java.version>        <spring-cloud.version>2021.0.1</spring-cloud.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>${spring-cloud.version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies>    </dependencyManagement>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build></project>

yml:

server:  port: 8401eureka:  instance:    hostname: ribbon-cunsumer #eureka服务端的实例名称    prefer-ip-address: true #拜访门路能够显示IP地址    instance-id: ${spring.cloud.client.ip-address}:${server.port}-ribbon-cunsumer #拜访门路的名称格局  client:    register-with-eureka: true     #false示意不向注册核心注册本人。    fetch-registry: true     #false示意本人端就是注册核心,我的职责就是保护服务实例,并不需要去检索服务    service-url:      #集群指向其它eureka      defaultZone: http://127.0.0.1:8001/eureka/,http://127.0.0.1:8002/eureka/spring:  application:    name: ribbon-cunsumer

主启动:

@SpringBootApplication@EnableEurekaClientpublic class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}

测试类:
config:

//这是一个不便咱们应用http调用的一个服务类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;@Configurationpublic class ApplicationContextConfig{    @Bean    @LoadBalanced//负载平衡调用    public RestTemplate getRestTemplate()    {        return new RestTemplate();    }}

controller:

@RestControllerpublic class consumerController {    //通过eureka的调用的服务地址    public static final String PAYMENT_URL = "http://SERVICE-PROVIDER";    @Resource    private RestTemplate restTemplate;    @GetMapping("/payment/get/{id}")    public String create(@PathVariable("id") Long id)    {        return restTemplate.getForObject(PAYMENT_URL +"/payment/get/"+id,String.class);    }}

咱们启动服务后,调用http://localhost:8401/payment...,始终F5刷新,就会轮询两个服务,达到负载平衡的目标了。

3.Ribbon轮询算法的更换和原理

api的调用其实并不是难事,咱们能够试着来配一下其它负载平衡的算法,以及更进一步地看看负载平衡算法的源码。

配置类:

@Configurationpublic class MyLoadBalanceConfig {    @Bean    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,                                                                   LoadBalancerClientFactory loadBalancerClientFactory){        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);        //随机拜访为RandomLoadBalancer,轮询拜访为RoundRobinLoadBalancer        return new RandomLoadBalancer(loadBalancerClientFactory                .getLazyProvider(name, ServiceInstanceListSupplier.class),//RoundRobinLoadBalancer                name);    }}

主启动:

@SpringBootApplication@EnableEurekaClient@LoadBalancerClients(defaultConfiguration = {MyLoadBalanceConfig.class})public class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}

重启一下,看看拜访的规定,是随机的。

轮询的源码剖析:

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {        if (instances.isEmpty()) {            if (log.isWarnEnabled()) {                log.warn("No servers available for service: " + serviceId);            }            return new EmptyResponse();        }        // TODO: enforce order?        int pos = Math.abs(this.position.incrementAndGet());        //接口的申请次数%集群服务器的总数量=理论调用服务的下标,每次重启服务后,接口拜访次数从0开始        ServiceInstance instance = instances.get(pos % instances.size());        return new DefaultResponse(instance);    }

4.OpenFeign是什么

咱们先上spring cloud官网看一下,openFeign是一个什么样的工具:https://cloud.spring.io/sprin...


Feign是一个申明式的WebService客户端,应用Feign能让编写Web Service客户端更加简略。它的应用办法是定义一个服务接口而后再下面减少注解。Spring cloud对feign进行了封装,使其反对Spring MVC规范注解和HttpMessageConverters。Feign能够与Eureka和Ribbon组合应用以反对负载平衡。

说了这么多,咱们可能还是一头雾水,简略地说:
Feign旨在使编写java Http客户端变得更容易。

后面在应用Ribbon+RestTemplate时,利用RestTemplate对http申请的时候,造成了一套模板化的调用办法。然而在理论开发的过程中,对其它微服务的调用可能不止一处一个接口会被屡次应用,所以针对每一个微服务都自行封装成一些客户端类来包装这些依赖服务的调用,就显得尤为重要。所以Feign依据这个理念进行了封装,它对每个微服务的调用都封装成了一个独立的类。在Feign的实现下,咱们只须要创立一个接口并应用注解的形式来配置它,就能够实现对服务提供方的绑定,进行对立治理,简化了Ribbon的应用,升高了服务调用客户端的开发量。

OpenFeign同时也集成了Ribbon
利用Ribbon保护了服务提供者的列表,并且通过轮询的形式实现了客户端的负载平衡,而与Ribbon不同的是,feign只须要定义服务绑定接口且以申明式的办法,优雅而简略地实现了服务调用。

5.OpenFeign应用步骤

咱们新建一个我的项目:

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.6.6</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.example</groupId>    <artifactId>feign-cunsumer-8402</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>feign-cunsumer-8402</name>    <description>Demo project for Spring Boot</description>    <properties>        <java.version>1.8</java.version>        <spring-cloud.version>2021.0.1</spring-cloud.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-openfeign</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>${spring-cloud.version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies>    </dependencyManagement>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build></project>

yml

server:  port: 8402eureka:  instance:    hostname: feign-cunsumer #eureka服务端的实例名称    prefer-ip-address: true #拜访门路能够显示IP地址    instance-id: ${spring.cloud.client.ip-address}:${server.port}-feign-cunsumer #拜访门路的名称格局  client:    register-with-eureka: true     #false示意不向注册核心注册本人。    fetch-registry: true     #false示意本人端就是注册核心,我的职责就是保护服务实例,并不需要去检索服务    service-url:      #集群指向其它eureka      defaultZone: http://127.0.0.1:8001/eureka/,http://127.0.0.1:8002/eureka/spring:  application:    name: feign-cunsumer

主启动

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication@EnableEurekaClient@EnableFeignClientspublic class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}

测试类:

@Component@FeignClient(value="SERVICE-PROVIDER")//要调用的服务名字public interface ProviderFeignService {    //要调用的接口    @GetMapping(value = "/payment/get/{id}")    String getPaymentById(@PathVariable("id") Long id);}
@RestControllerpublic class ConsumerController {    @Autowired    private ProviderFeignService providerFeignService;    @GetMapping("/payment/get/{id}")    public String create(@PathVariable("id") Long id)    {        //间接应用        return providerFeignService.getPaymentById(id);    }}

调用后果:

6.OpenFeign超时管制

在调用一个接口的时候,肯定要有超时管制,这在工作中有两个益处:
1)避免一个接口拖垮整个零碎,导致整个性能不可用。
2)能够爱护本人,如果这是他人的接口,超时了,能够把问题丢给他人,避免本人背锅,因为这原本就是他人的锅报的错不应该在你的接口上

办法很简略,咱们做一个配置就行了,

yml

ribbon:  #建设连贯所用工夫  ReadTimeout: 60000  #建设连贯后从服务端获取资源的最大工夫  ConnectTimeout: 60000

7.OpenFeign日志打印

Feign提供了日志打印性能,咱们能够通过配置来调整日志级别,从而理解Feign中Http申请的详情,从而进行监控和输入。

yml

logging:  level:  # feign日志以什么级别监控哪个接口    com.example.demo.feign.*: debug

配置类:

@Configurationpublic class FeignConfig{    @Bean    Logger.Level feignLoggerLevel()    {        //NONE:默认的,不显示任何日志;        //BASIC:仅记录申请办法、URL、响应状态码及执行工夫;        //HEADERS:除了 BASIC 中定义的信息之外,还有申请和响应的头信息;        //FULL:除了 HEADERS 中定义的信息之外,还有申请和响应的注释及元数据。        return Logger.Level.FULL;    }}

运行后果:

8.OpenFeign和Feign的区别

其实OpenFeign进去之前,还有一个Feign组件,不过Feign曾经进行保护了,所以咱们间接应用OpenFeign就好。

feignOpenFeign
内置了Ribbon,用来做客户端的负载平衡,去调用服务列表的服务在Feign的根底上反对了Spring MVC的注解
应用Feign注解调用接口,调用这个接口,就能够调用服务注册核心的服务@FeignClient能够解析SpringMVC的@RequestMapping注解下的接口,并通过动静代理的形式产生实现类,实现类中做负载平衡并调用其余服务。