乐趣区

关于springcloud:深入学习springCloudRibbonOpenFeign负载均衡服务接口调用

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

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

}

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

轮询的源码剖析:

    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: 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);
    }

}

调用后果:

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;
    }
}

运行后果:

8.OpenFeign 和 Feign 的区别

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

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