SpringCloud Ribbon 简单使用


1 说明

因为 SpirngBoot, SpringCloud 的各个版本之间差异还是挺大的, 所以在参照本博客进行学习时, 有可能出现因为版本不一致, 而出现不同的问题。
如果可以和本项目使用的环境保持一致, 即使不一致, 也尽可能不要跨大版本。

环境清单

框架 版本
JDK 1.8
Spring Boot 2.1.4.RELEASE
Spring Cloud Greenwich.SR1

2 准备

2.1 Maven 父模块配置

  1. 先建立一个 Maven 的父模块, 也就是整个项目里面只有一个 pom 文件
  2. 在父 pom 里面添加一些共用的配置, 比如 SpringBoot, SpringCloud 的依赖
<?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">
    <modelVersion>4.0.0</modelVersion>

    <!-- 手动引入 spring boot 版本依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>lcn29.github.io</groupId>
    <artifactId>spring-cloud-eureka</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <dependencyManagement>
        <dependencies>
            <!-- 手动引入 spring cloud 对应的配置 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!-- 手动 引入 spring cloud 的基础模块-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
    </dependencies>

    <!-- 省略一些打包相关的配置 -->
</project>

2.2 启动 2 个服务端 (注册中心)

  1. 在父模块里面新建一个子模块, 模块名 eureka-server
  2. 在子模块引入 Eureka 服务端需要的依赖
<dependencies>
    <!-- eureka 服务端的依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>
  1. 在启动类上加上注解 @EnableEurekaServer
@EnableEurekaServer
@SpringBootApplication
public class EurekaServe {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServe.class, args);
    }
}
  1. 在 src/main/resources 里面新建 3 个配置文件, 分别是 application.yml, application-8081.yml, application-8081.yml

  2. application.yml 里面加上这 2 个配置

# eureka 服务端的通用配置
spring:
  application:
    name: server-eureka

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}
  1. application-8081.yml 里面加上这 2 个配置
# eureka 服务端 1 的配置
server:
  port: 8081
eureka:
  instance:
    hostname: eureka-8081.com
  client:
    service-url:
      defaultZone: http://eureka-8082.com:8082/eureka/
  1. application-8082.yml 里面加上这 2 个配置
# eureka 服务端 1 的配置
server:
  port: 8082
eureka:
  instance:
    hostname: eureka-8082.com
  client:
    service-url:
      defaultZone: http://eureka-8081.com:8081/eureka/
  1. 在电脑的 hosts 文件里面添加这 2 行映射
127.0.0.1 eureka-8081.com
127.0.0.1 eureka-8082.com
  1. 为了让我们的子模块能够启动为 2 个服务端, 需要我们做一下启动的配置
  1. Idea 找到右上角的这个
    Alt '模块多实例配置'
  1. 点一下左边的 +, 找到 SpringBoot 项, 配置一下下面的三项
    Alt '模块多实例参数配置'
    (如果 Program arguments 没有的话, 可以在绿色区域搜索添加)
  1. Program arguments 配置的内容为 --spring.profiles.active=8081, 作用是启动这个 SpringBoot 应用时使用8081 环境, 也就是对应 application-8081.yml 里面的配置
  1. 同理, 在配置多一条 8082 环境的启动命令, 这样子模块就能启动 2 个程序了

2.3 搭建 2 个服务提供方

  1. 在父模块里面新建一个子模块, 模块名 client-provider

  2. 在模块里面添加如下依赖

<!-- eureka 服务端的依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- web 功能的支持, 没有这个 客户端就会启动完就结束程序 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 然后在启动类上加上注解 @EnableEurekaClient
@EnableEurekaClient
@SpringBootApplication
public class EurekaClientOne {
    public static void main(String[] args){
        SpringApplication.run(EurekaClientOne.class, args);
    }
}
  1. 同样在 src/main/resources 里面新建 3 个配置文件, 分别是 application.yml, application-9091.ymlapplication-9092.yml

  2. application.yml 里面加上这 2 个配置

spring:
  application:
    name: client-provider

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}
  client:
    service-url:
      # 向服务端的注册地址
      defaultZone: http://eureka-8081.com:8081/eureka/,http://eureka-8082.com:8082/eureka
  1. application-9091.yml 里面加上端口配置
server:
  port: 9091
  1. application-9092.yml 里面加上端口配置
server:
  port: 9092
  1. 服务提供方新增一个的 Rest Api
@RestController
public class ProviderController {

    @GetMapping("/hello")
    public String hello(HttpServletRequest request) {
        // 返回请求的地址
        return "当前响应来自于" + request.getRequestURL();
    }
}

2.4 搭建 1 个服务调用方

  1. 在父模块里面再建立一个子模块, 模块名client-consumer, 作为服务的调用方

  2. 在模块里面添加如下依赖

    <!-- eureka 服务端的依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    <!-- web 功能的支持, 没有这个 客户端就会启动完就结束程序 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!--远程调用-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  3. 然后在启动类上加上注解 @EnableEurekaClient

@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
public class ClientConsumer {
    public static void main(String[] args){
        SpringApplication.run(ClientConsumer.class, args);
    }
}
  1. application.yml 中添加配置
spring:
  application:
    name: client-consumer
server:
  port: 10101

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}
  client:
    service-url:
      # 向服务端的注册地址
      defaultZone: http://eureka-8081.com:8081/eureka/,http://eureka-8082.com:8082/eureka
  1. 创建一个接口, 用于远程调用
// name 是服务提供方的应用名
@FeignClient(name = "client-provider")
public interface ProviderRemote {
    @GetMapping("/hello")
    String hello();
}
  1. 声明一个 Controller (用于测试), 注入刚声明的接口
@RestController
public class RibbonController {

    @Resource
    private ProviderRemote providerRemote;

    @GetMapping("/ribbon")
    public String ribbon() {
        String resp = providerRemote.ribbon();
        return resp;
    }
}
  1. 依次启动服务端 (注册中心), 服务提供方, 服务调用方

至此, 我们的环境就搭好了, 项目如果正常的话, 这时候访问 http://localhost:10101/ribbon, 服务调用方是可以调用到服务提供方的。

3 Ribbon 负载均衡

3.1 Ribbon 集成

在上面验证环境时, 如果多调用几次接口, 就可以发现服务调用方已经在轮询调用 2 个服务提供方了。
其实, Feign 自身就使用了 Ribbon 的轮询负载进行调用服务。所以当你的服务提供方有多个的时候, 使用 Feign 进行远程调用的话, 不做任何的配置, Feign 默认就使用了轮询的负载在调用服务。

3.2 修改 Ribbon 的负载算法

可以直接修改 application.yml 配置文件, 添加如下配置

# 这里为服务提供者的应用名
client-provider:
  ribbon:
    # 随机算法
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
策略对应的实现类 策略描述
com.netflix.loadbalancer.RoundRobinRule 轮询选择 server
com.netflix.loadbalancer.RandomRule 随机选择一个 server
com.netflix.loadbalancer.BestAvailableRule 选择一个最小的并发请求的 server
com.netflix.loadbalancer.AvailabilityFilteringRule 过滤掉那些因为一直连接失败的被标记为 circuit tripped 的 server, 并过滤掉那些高并发的的 server (active connections 超过配置的阈值)
com.netflix.loadbalancer.WeightedResponseTimeRule 根据响应时间分配一个 weight, 响应时间越长, weight 越小, 被选中的可能性越低
com.netflix.loadbalancer.RetryRule 先按照 RoundRobinRule 的策略获取 server, 如果获取失败则在制定时间内进行重试, 直到获取可用的 server
com.netflix.loadbalancer.ZoneAvoidanceRule 复合判断 server 所在区域的性能和 server 的可用性选择 server

3.3 通过代码形式修改 Ribbon 的负载算法

  1. 新建一个配置类

    public class RibbonRuleConfig {
        @Bean
        public IRule ribbonRule() {
            // 返回我们需要的负载策略
            return new RandomRule();
        }
    }
  2. 指定调用方在调用哪个应用时, 使用什么策略

    // 调用 client-provider 的服务时, 使用  RibbonRuleConfig 配置的策略
    @RibbonClient(name = "client-provider", configuration = RibbonRuleConfig.class)
    public class MyRibbonClient {
    }

3.4 自定义负载策略

只需要实现 AbstractLoadBalancerRule 接口, 重写 choose 方法即可

public class RibbonRule extends AbstractLoadBalancerRule {

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
    }

    @Override
    public Server choose(Object o) {
        // 实现你的逻辑, 最后返回选择的服务实例
        return null;
    }
}

使用方法和配置方式和系统内置的负载算法一样。


  目录