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.
image.png

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

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

2.Ribbon负载均衡演示

用Ribbon调用服务的流程图:
image.png

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

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

我们先准备两个服务提供者,以便更好地显示出负载均衡的效果。

image.png

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: 8501

eureka:
  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
@EnableEurekaClient
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

测试的controller:

@RestController
public 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: 8401

eureka:
  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
@EnableEurekaClient
public 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;

@Configuration
public class ApplicationContextConfig
{
    @Bean
    @LoadBalanced//负载均衡调用
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

controller:

@RestController
public 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刷新,就会轮询两个服务,达到负载均衡的目的了。

image.png

image.png

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

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

配置类:


@Configuration
public 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);
    }

}

重启一下,看看访问的规则,是随机的。

image.png

轮询的源码分析:

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

image.png
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使用步骤

我们新建一个项目:
image.png

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: 8402

eureka:
  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
@EnableFeignClients
public 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);

}
@RestController
public class ConsumerController {

    @Autowired
    private ProviderFeignService providerFeignService;

    @GetMapping("/payment/get/{id}")
    public String create(@PathVariable("id") Long id)
    {
        //直接使用
        return providerFeignService.getPaymentById(id);
    }

}

调用结果:

image.png

6.OpenFeign超时控制

在调用一个接口的时候,一定要有超时控制,这在工作中有两个好处:
1)防止一个接口拖垮整个系统,导致整个功能不可用。
2)可以保护自己,如果这是别人的接口,超时了,可以把问题丢给别人,防止自己背锅,因为这本来就是别人的锅报的错不应该在你的接口上

方法很简单,我们做一个配置就行了,

yml


ribbon:
  #建立连接所用时间
  ReadTimeout: 60000
  #建立连接后从服务端获取资源的最大时间
  ConnectTimeout: 60000

7.OpenFeign日志打印

Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的详情,从而进行监控和输出。

yml

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

配置类:

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

运行结果:
image.png

8.OpenFeign和Feign的区别

其实OpenFeign出来之前,还有一个Feign组件,不过Feign已经停止维护了,所以我们直接使用OpenFeign就好。

feignOpenFeign
内置了Ribbon,用来做客户端的负载均衡,去调用服务列表的服务在Feign的基础上支持了Spring MVC的注解
使用Feign注解调用接口,调用这个接口,就可以调用服务注册中心的服务@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

苏凌峰
73 声望38 粉丝

你的迷惑在于想得太多而书读的太少。