基于Eureka搭建Springcloud微服务-5.使用Ribbon实现客户端负载均衡

lingwh原创2020年5月14日大约 14 分钟约 4197 字

5.使用Ribbon实现客户端负载均衡

5.1.章节内容概述

本章节涉及主要内容有:
 5.1.章节内容概述
 5.2.章节内容大纲
 5.3.Ribbon简介
 5.4.硬编码配置方式使用Ribbon实现负载均衡(使用Ribbon自带的负载均衡策略)
 5.5.声明式配置方式使用Ribbon实现负载均衡(使用Ribbon自带的负载均衡策略)
 5.6.硬编码配置方式使用Ribbon实现负载均衡(使用自定义的Ribbon负载均衡策略)
 5.7.声明式配置方式使用Ribbon实现负载均衡(使用自定义的Ribbon负载均衡策略)
具体每个小节中包含的内容可使通过下面的章节内容大纲进行查看。

5.2.章节内容大纲

5.3.Ribbon简介

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随即连接等)去连接这些机器,也可以使用Ribbon实现自定义的负载均衡算法。

官方网址

https://github.com/Netflix/ribbon

5.4.硬编码配置方式使用Ribbon实现负载均衡(使用Ribbon自带的负载均衡策略)

5.4.1.模块简介

基于Ribbon以硬编码配置方式实现的服务消费者,使用Ribbon自带的负载均衡策略,启动端口: 80

5.4.2.模块目录结构

springcloud-consumer-loadbalance-ribbon-hardcode-order80
|-- src
|   •-- main
|       |-- java
|       |   •-- org
|       |       •-- openatom
|       |           |-- myrule
|       |           |   •-- MySelfRule.java
|       |           •-- springcloud
|       |               |-- config
|       |               |   •-- ApplicationContextConfig.java
|       |               |-- controller
|       |               |   •-- OrderConsumerController.java
|       |               •-- OrderServiceConsumerLoadBalanceRibbonHardcode80.java
|       •-- resources
|           •-- application.yml
•-- pom.xml

5.4.3.创建模块

在父工程(springcloud-eureka)中创建一个名为springcloud-consumer-loadbalance-ribbon-hardcode-order80的maven模块,注意:当前模块创建成功后,在父工程pom.xml中<modules></modules>中会自动生成有关当前模块的信息

5.4.4.编写模块pom.xml

<?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>springcloud-eureka</artifactId>
        <groupId>org.openatom</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-consumer-loadbalance-ribbon-hardcode-order80</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <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.openatom</groupId>
            <artifactId>springcloud-api-commons</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--热部署需要加这个-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
        <!--打包多环境-->
        <resources>
            <resource>
                <directory>src/main/resources/</directory>
                <includes>
                    <!--不区分环境:直接加载application.yml配置文件-->
                    <include>application.yml</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

5.4.5.编写模块application.yml

server:
  port: 80

spring:
    application:
        name: SPRINGCLOUD-CONSUMER-LOADBALANCE-RIBBON-HARDCODE-ORDER80 #注意:服务名不要出现_
    devtools:
      restart:
        enabled: true
    logging:
      level: info

eureka:
  client:
    register-with-eureka: true  #表示是否将自己注册进EurekaServer默认为true。
    fetchRegistry: true  #是否从EurekaServer抓取已有的注册信息,默认为true。服务提供端是单节点无所谓,是集群必须设置为true才能配合ribbon使用负载均衡,否则报异常No instances available for SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER
    service-url:
      #单机版
      defaultZone: http://localhost:7001/eureka
      #集群版
      #defaultZone: http://eureka7002:7002/eureka,http://eureka7003:7003/eureka,http://eureka7004:7004/eureka
  instance:
    instance-id: ${spring.application.name} #Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容
    prefer-ip-address: false #访问路径可以显示IP地址,点击Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容地址栏是否显示IP地址

#服务提供端信息
service:
  provider:
    name: SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER
    url: http://${service.provider.name} #服务提供端名称

5.4.6.编写模块config

package org.openatom.springcloud.config;

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

5.4.7.编写模块controller

package org.openatom.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.openatom.springcloud.entities.CommonResult;
import org.openatom.springcloud.entities.Payment;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderConsumerController {

    //单机版
//    public static final String PAYMENT_URL = "http://localhost:8001";
//    public static final String PAYMENT_URL = "http://localhost:8002";

    //集群版
    //从配置文件中动态获取远程调用地址
    @Value("${service.provider.url}")
    private String SERVICE_PROVIDER_URL;

    //从配置文件中动态获服务提供端名称
    @Value("${service.provider.name}")
    private String SERVICE_PROVIDER_NAME;

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment) {
        return restTemplate.postForObject(SERVICE_PROVIDER_URL +"/provider/payment/create",payment, CommonResult.class);
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentByIdReturnObject(@PathVariable("id") Long id){
        return restTemplate.getForObject(SERVICE_PROVIDER_URL+"/provider/payment/get/"+id,CommonResult.class);
    }

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPaymentByIdReturnResponseEntity(@PathVariable("id") Long id) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(SERVICE_PROVIDER_URL+"/provider/payment/get/"+id,CommonResult.class);
        if(entity.getStatusCode().is2xxSuccessful()){
            return entity.getBody();
        }else{
            return new CommonResult<>(444,"操作失败");
        }
    }
}

5.4.8.编写负载均衡规则配置类

package org.openatom.myrule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 自定义Ribbon负载均衡规则,这个MySelfRule编写后在主启动类使用
 */
@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule() {
        return new RandomRule();//定义为随机
    }
}

这里使用return new RandomRule();,这代表使用的负载均衡算法是RandomRule,Ribbon默认提供了七种负载均衡的算法策略,具体使用哪一种,请根据实际需求灵活选择,这里提供关于七种负载均衡算法的介绍

RoundRobinRule(轮询策略,轮询是Ribbon默认使用的负载均衡算法)
第一次到A,第二次就到B,第三次又到A,第四次又到B......
具体实现是一个负载均衡算法: 第N次请求 % 服务器集群的总数 = 实际调用服务器位置的下标

RandomRule(随机策略)
从服务提供者的列表中随机选择一个服务实例进行调用

RetryRule(轮询重试策略)
按照轮询策略来获取服务,如果获取的服务实例为null或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回null。

WeightedResponseTimeRule(响应速度决定权重策略)
根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。

BestAvailableRule(最优可用策略)
判断最优其实用的是并发连接数。选择并发连接数较小的server发送请求。

AvailabilityFilteringRule(可用性敏感策略)
先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。

ZoneAvoidanceRule(区域内可用性能最优策略)
基于AvailabilityFilteringRule基础上做的,首先判断一个zone的运行性能是否可用.剔除不可用的区域zone的所有server,然后再利用AvailabilityPredicate过滤并发连接过多的server。

5.4.9.编写模块主启动类

package org.openatom.springcloud;

import org.openatom.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;

/**
 * 使用Ribbon实现负载均衡:Java硬编码方式
 */
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER",configuration= MySelfRule.class)
public class OrderServiceConsumerLoadBalanceRibbonHardcode80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceConsumerLoadBalanceRibbonHardcode80.class, args);
    }
}

5.4.10.测试模块

启动相关服务
测试硬编码配置方式使用Ribbon实现负载均衡(使用Ribbon自带的负载均衡策略)
在浏览器中访问
http://localhost/consumer/payment/get/1
第一次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8001","data":{"id":1,"serial":"15646546546"}}
第二次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8002","data":{"id":1,"serial":"15646546546"}}
第二次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8002","data":{"id":1,"serial":"15646546546"}}
第四次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8001","data":{"id":1,"serial":"15646546546"}}
可以看到四次访问返回的结果中,四次返回结果是没有规律的,因为采用的RandomRule(随机策略),实际返回结果可能不是上面的情况,但是一定是随机进行服务调用的

5.5.声明式配置方式使用Ribbon实现负载均衡(使用Ribbon自带的负载均衡策略)

5.5.1.模块简介

基于Ribbon以声明式配置方式实现的服务消费者,使用Ribbon自带的负载均衡策略,启动端口: 80

5.5.2.模块目录结构

springcloud-consumer-loadbalance-ribbon-configuration-order80
|-- src
|   •-- main
|       |-- java
|       |   •-- org
|       |       •-- openatom
|       |           •-- springcloud
|       |               |-- config
|       |               |   •-- ApplicationContextConfig.java
|       |               |-- controller
|       |               |   •-- OrderConsumerController.java
|       |               •-- OrderServiceConsumerLoadBalanceRibbonConfiguration80.java
|       •-- resources
|           •-- application.yml
•-- pom.xml

5.5.3.创建模块

在父工程(springcloud-eureka)中创建一个名为springcloud-consumer-loadbalance-ribbon-configuration-order80的maven模块,注意:当前模块创建成功后,在父工程pom.xml中<modules></modules>中会自动生成有关当前模块的信息

5.5.4.编写模块pom.xml

<?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>springcloud-eureka</artifactId>
        <groupId>org.openatom</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-consumer-loadbalance-ribbon-configuration-order80</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <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.openatom</groupId>
            <artifactId>springcloud-api-commons</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
    <!--热部署需要加这个-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
        <!--打包多环境-->
        <resources>
            <resource>
                <directory>src/main/resources/</directory>
                <includes>
                    <!--不区分环境:直接加载application.yml配置文件-->
                    <include>application.yml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

5.5.5.编写模块application.yml

server:
  port: 80
spring:
    application:
        name: SPRINGCLOUD-CONSUMER-LOADBALANCE-RIBBON-CONFIGURATION-ORDER80 #注意:服务名不要出现_
    devtools:
      restart:
        enabled: true
    logging:
      level: info

eureka:
  client:
    register-with-eureka: true  #表示是否将自己注册进EurekaServer默认为true。
    fetchRegistry: true  #是否从EurekaServer抓取已有的注册信息,默认为true。服务提供端是单节点无所谓,是集群必须设置为true才能配合ribbon使用负载均衡,否则报异常No instances available for SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER
    service-url:
      #单机版
      defaultZone: http://localhost:7001/eureka
      #集群版
      #defaultZone: http://eureka7002:7002/eureka,http://eureka7003:7003/eureka,http://eureka7004:7004/eureka
  instance:
    instance-id: ${spring.application.name} #Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容
    prefer-ip-address: false #访问路径可以显示IP地址,点击Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容地址栏是否显示IP地址

#某个/某些服务的Ribbon配置
SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER: #服务提供端名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule   #Ribbon负载均衡规则类所在的路径,自带七种规则,也可以是自定位规则的类所在的路径

#服务提供端信息
service:
  provider:
    name: SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER  #服务提供端名称
    url: http://${service.provider.name} #服务提供端调用地址

yml中关于Ribbon负载均衡策略的配置
SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER:
ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule 
这里使用com.netflix.loadbalancer.RandomRule ,这代表使用的负载均衡算法是RandomRule,Ribbon默认提供了七种负载均衡的算法策略,具体使用哪一种,请根据实际需求灵活选择,这里提供关于七种负载均衡算法的介绍

RoundRobinRule(轮询策略,轮询是Ribbon默认使用的负载均衡算法)
第一次到A,第二次就到B,第三次又到A,第四次又到B......
具体实现是一个负载均衡算法: 第N次请求 % 服务器集群的总数 = 实际调用服务器位置的下标

RandomRule(随机策略)
从服务提供者的列表中随机选择一个服务实例进行调用

RetryRule(轮询重试策略)
按照轮询策略来获取服务,如果获取的服务实例为null或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回null。

WeightedResponseTimeRule(响应速度决定权重策略)
根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。

BestAvailableRule(最优可用策略)
判断最优其实用的是并发连接数。选择并发连接数较小的server发送请求。

AvailabilityFilteringRule(可用性敏感策略)
先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。

ZoneAvoidanceRule(区域内可用性能最优策略)
基于AvailabilityFilteringRule基础上做的,首先判断一个zone的运行性能是否可用.剔除不可用的区域zone的所有server,然后再利用AvailabilityPredicate过滤并发连接过多的server。

5.5.6.编写模块config

package org.openatom.springcloud.config;

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

5.5.7.编写模块controller

package org.openatom.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.openatom.springcloud.entities.CommonResult;
import org.openatom.springcloud.entities.Payment;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderConsumerController {

    //单机版
//    public static final String PAYMENT_URL = "http://localhost:8001";
//    public static final String PAYMENT_URL = "http://localhost:8002";

    //集群版
    //从配置文件中动态获取远程调用地址
    @Value("${service.provider.url}")
    private String SERVICE_PROVIDER_URL;

    //从配置文件中动态获服务提供端名称
    @Value("${service.provider.name}")
    private String SERVICE_PROVIDER_NAME;

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment) {
        return restTemplate.postForObject(SERVICE_PROVIDER_URL +"/provider/payment/create",payment, CommonResult.class);
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentByIdReturnObject(@PathVariable("id") Long id) {
        return restTemplate.getForObject(SERVICE_PROVIDER_URL+"/provider/payment/get/"+id,CommonResult.class);
    }

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPaymentByIdReturnResponseEntity(@PathVariable("id") Long id) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(SERVICE_PROVIDER_URL+"/provider/payment/get/"+id,CommonResult.class);
        if(entity.getStatusCode().is2xxSuccessful()){
            return entity.getBody();
        }else{
            return new CommonResult<>(444,"操作失败");
        }
    }
}

5.5.8.编写模块主启动类

package org.openatom.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * 使用Ribbon实现负载均衡:yml配置方式
 *      优点:可以在配置文件中动态修改服务提供端名称
 */
@SpringBootApplication
@EnableEurekaClient
public class OrderServiceConsumerLoadBalanceRibbonConfiguration80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceConsumerLoadBalanceRibbonConfiguration80.class, args);
    }
}

5.5.9.测试模块

启动相关服务
测试硬编码配置方式使用Ribbon实现负载均衡(使用Ribbon自带的负载均衡策略)
在浏览器中访问
http://localhost/consumer/payment/get/1
第一次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8001","data":{"id":1,"serial":"15646546546"}}
第二次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8002","data":{"id":1,"serial":"15646546546"}}
第三次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8002","data":{"id":1,"serial":"15646546546"}}
第四次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8001","data":{"id":1,"serial":"15646546546"}}
可以看到四次访问返回的结果中,四次返回结果是没有规律的,因为采用的RandomRule(随机策略),实际返回结果可能不是上面的情况,但是一定是随机进行服务调用的

5.6.硬编码配置方式使用Ribbon实现负载均衡(使用自定义的Ribbon负载均衡策略)

5.6.1.模块简介

基于Ribbon以硬编码式配置方式实现的服务消费者,使用自定义的Ribbon负载均衡策略,启动端口: 80

5.6.2.模块目录结构

springcloud-consumer-loadbalance-ribbon-custom-strategy-hardcode-order80
|-- src
|   •-- main
|       |-- java
|       |   •-- org
|       |       •-- openatom
|       |           •-- springcloud
|       |               |-- config
|       |               |   •-- ApplicationContextConfig.java
|       |               |-- controller
|       |               |   •-- OrderConsumerController.java
|       |               |-- loadbalance
|       |               |   •-- MyRoundRobinRule.java
|       |               •-- OrderServiceConsumerLoadBalanceRibbonCustomerStrategyHardcode80.java
|       •-- resources
|           •-- application.yml
•-- pom.xml

5.6.3.创建模块

在父工程(springcloud-eureka)中创建一个名为springcloud-consumer-loadbalance-ribbon-custom-strategy-hardcode-order80的maven模块,注意:当前模块创建成功后,在父工程pom.xml中<modules></modules>中会自动生成有关当前模块的信息

5.6.4.编写模块pom.xml

<?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>springcloud-eureka</artifactId>
        <groupId>org.openatom</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-consumer-loadbalance-ribbon-custom-strategy-hardcode-order80</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <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.openatom</groupId>
            <artifactId>springcloud-api-commons</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
    <!--热部署需要加这个-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
        <!--打包多环境-->
        <resources>
            <resource>
                <directory>src/main/resources/</directory>
                <includes>
                    <!--不区分环境:直接加载application.yml配置文件-->
                    <include>application.yml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

5.6.5.编写模块application.yml

server:
  port: 80

spring:
    application:
        name: SPRINGCLOUD-CONSUMER-LOADBALANCE-RIBBON-CUSTOM-STRATEGY-HARDCODE-ORDER80 #注意:服务名不要出现_
    devtools:
      restart:
        enabled: true
    logging:
      level: info

eureka:
  client:
    register-with-eureka: true  #表示是否将自己注册进EurekaServer默认为true。
    fetchRegistry: true  #是否从EurekaServer抓取已有的注册信息,默认为true。服务提供端是单节点无所谓,是集群必须设置为true才能配合ribbon使用负载均衡,否则报异常No instances available for SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER
    service-url:
      #单机版
      defaultZone: http://localhost:7001/eureka
      #集群版
      #defaultZone: http://eureka7002:7002/eureka,http://eureka7003:7003/eureka,http://eureka7004:7004/eureka
  instance:
    instance-id: ${spring.application.name} #Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容
    prefer-ip-address: false #访问路径可以显示IP地址,点击Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容地址栏是否显示IP地址

#服务提供端信息
service:
  provider:
    name: SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER
    url: http://${service.provider.name} #服务提供端名称

5.6.6.编写模块config

package org.openatom.springcloud.config;

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

5.6.7.编写模块controller

package org.openatom.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.openatom.springcloud.entities.CommonResult;
import org.openatom.springcloud.entities.Payment;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderConsumerController {

    //单机版
//    public static final String PAYMENT_URL = "http://localhost:8001";
//    public static final String PAYMENT_URL = "http://localhost:8002";

    //集群版
    //从配置文件中动态获取远程调用地址
    @Value("${service.provider.url}")
    private String SERVICE_PROVIDER_URL;

    //从配置文件中动态获服务提供端名称
    @Value("${service.provider.name}")
    private String SERVICE_PROVIDER_NAME;

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment) {
        return restTemplate.postForObject(SERVICE_PROVIDER_URL +"/provider/payment/create",payment, CommonResult.class);
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentByIdReturnObject(@PathVariable("id") Long id){
        return restTemplate.getForObject(SERVICE_PROVIDER_URL+"/provider/payment/get/"+id,CommonResult.class);
    }

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPaymentByIdReturnResponseEntity(@PathVariable("id") Long id) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(SERVICE_PROVIDER_URL+"/provider/payment/get/"+id,CommonResult.class);
        if(entity.getStatusCode().is2xxSuccessful()){
            return entity.getBody();
        }else{
            return new CommonResult<>(444,"操作失败");
        }
    }
}

5.6.8.编写自定义的负载均衡算法策略

package org.openatom.springcloud.loadbalance;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 这个自定义的负载均衡算法编写好之后配置在主启动类中
 */
@Component
public class MyRoundRobinRule extends AbstractLoadBalancerRule {
    public MyRoundRobinRule() {
    }

    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;
            while(server == null) {
                if (Thread.interrupted()) {
                    return null;
                }
                List<Server> upList = lb.getReachableServers();
                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }
                int index = this.chooseRandomInt(serverCount);
                server = (Server)upList.get(index);
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }
                    server = null;
                    Thread.yield();
                }
            }
            return server;
        }
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

5.6.9.编写模块主启动类

package org.openatom.springcloud;

import org.openatom.springcloud.loadbalance.MyRoundRobinRule;
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;

/**
 * 使用Ribbon实现负载均衡:Java硬编码方式
 *      缺点:不能在配置文件中动态修改服务提供端名称
 */
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "SPRING-CLOUD-PROVIDER-EUREKA-PAYMENT-SERVICE",configuration= MyRoundRobinRule.class)
public class OrderServiceConsumerLoadBalanceRibbonCustomerStrategyHardcode80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceConsumerLoadBalanceRibbonCustomerStrategyHardcode80.class, args);
    }
}

5.6.10.测试模块

启动相关服务
测试硬编码配置方式使用Ribbon实现负载均衡(使用Ribbon自带的负载均衡策略)
在浏览器中访问
http://localhost/consumer/payment/get/1
第一次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8001","data":{"id":1,"serial":"15646546546"}}
第二次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8002","data":{"id":1,"serial":"15646546546"}}
第三次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8002","data":{"id":1,"serial":"15646546546"}}
第四次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8001","data":{"id":1,"serial":"15646546546"}}
可以看到四次访问返回的结果中,四次返回结果是没有规律的,因为采用的MyRoundRobinRule(自定义策略,这个策略的效果也是随机调用),实际返回结果可能不是上面的情况,但是一定是随机进行服务调用的

5.7.声明式配置方式使用Ribbon实现负载均衡(使用自定义的Ribbon负载均衡策略)

5.7.1.模块简介

基于Ribbon以声明式配置方式实现的服务消费者,使用自定义的Ribbon负载均衡策略,启动端口: 80

5.7.2.模块目录结构

springcloud-consumer-loadbalance-ribbon-custom-strategy-configuration-order80
|-- src
|   •-- main
|       |-- java
|       |   •-- org
|       |       •-- openatom
|       |           •-- springcloud
|       |               |-- config
|       |               |   •-- ApplicationContextConfig.java
|       |               |-- controller
|       |               |   •-- OrderConsumerController.java
|       |               |-- loadbalance
|       |               |   •-- MyRoundRobinRule.java
|       |               •-- OrderServiceConsumerLoadBalanceRibbonCustomerStrategyConfiguration80.java
|       •-- resources
|           •-- application.yml
•-- pom.xml

5.7.3.创建模块

在父工程(springcloud-eureka)中创建一个名为springcloud-consumer-loadbalance-ribbon-custom-strategy-configuration-order80的maven模块,注意:当前模块创建成功后,在父工程pom.xml中<modules></modules>中会自动生成有关当前模块的信息

5.7.4.编写模块pom.xml

<?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>springcloud-eureka</artifactId>
        <groupId>org.openatom</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-consumer-loadbalance-ribbon-custom-strategy-configuration-order80</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <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.openatom</groupId>
            <artifactId>springcloud-api-commons</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
    <!--热部署需要加这个-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
        <!--打包多环境-->
        <resources>
            <resource>
                <directory>src/main/resources/</directory>
                <includes>
                    <!--不区分环境:直接加载application.yml配置文件-->
                    <include>application.yml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

5.7.5.编写模块application.yml

server:
  port: 80

spring:
    application:
        name: SPRINGCLOUD-CONSUMER-LOADBALANCE-RIBBON-CUSTOM-STRATEGY-CONFIGURATION-ORDER80 #注意:服务名不要出现_
    devtools:
      restart:
        enabled: true
    logging:
      level: info

eureka:
  client:
    register-with-eureka: true  #表示是否将自己注册进EurekaServer默认为true。
    fetchRegistry: true  #是否从EurekaServer抓取已有的注册信息,默认为true。服务提供端是单节点无所谓,是集群必须设置为true才能配合ribbon使用负载均衡,否则报异常No instances available for SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER
    service-url:
      #单机版
      defaultZone: http://localhost:7001/eureka
      #集群版
      #defaultZone: http://eureka7002:7002/eureka,http://eureka7003:7003/eureka,http://eureka7004:7004/eureka
  instance:
    instance-id: ${spring.application.name} #Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容
    prefer-ip-address: false #访问路径可以显示IP地址,点击Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容地址栏是否显示IP地址

#某个/某些服务的Ribbon配置
SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER: #服务提供端名称
  ribbon:
    NFLoadBalancerRuleClassName: org.openatom.springcloud.loadbalance.MyRandomRule  #Ribbon负载均衡规则类所在的路径,自带七种规则,也可以是自定位规则的类所在的路径

#服务提供端信息
service:
  provider:
    name: SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER
    url: http://${service.provider.name} #服务提供端名称

5.7.6.编写模块config

package org.openatom.springcloud.config;

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

5.7.7.编写模块controller

package org.openatom.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.openatom.springcloud.entities.CommonResult;
import org.openatom.springcloud.entities.Payment;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderConsumerController {

    //单机版
//    public static final String PAYMENT_URL = "http://localhost:8001";
//    public static final String PAYMENT_URL = "http://localhost:8002";

    //集群版
    //从配置文件中动态获取远程调用地址
    @Value("${service.provider.url}")
    private String SERVICE_PROVIDER_URL;

    //从配置文件中动态获服务提供端名称
    @Value("${service.provider.name}")
    private String SERVICE_PROVIDER_NAME;

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment) {
        return restTemplate.postForObject(SERVICE_PROVIDER_URL +"/provider/payment/create",payment, CommonResult.class);
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentByIdReturnObject(@PathVariable("id") Long id){
        return restTemplate.getForObject(SERVICE_PROVIDER_URL+"/provider/payment/get/"+id,CommonResult.class);
    }

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPaymentByIdReturnResponseEntity(@PathVariable("id") Long id) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(SERVICE_PROVIDER_URL+"/provider/payment/get/"+id,CommonResult.class);
        if(entity.getStatusCode().is2xxSuccessful()){
            return entity.getBody();
        }else{
            return new CommonResult<>(444,"操作失败");
        }
    }

}

5.7.8.编写自定义的负载均衡算法策略

package org.openatom.springcloud.loadbalance;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 这个自定义的负载均衡算法编写好之后配置在application.yml中
 */

@Component
public class MyRoundRobinRule extends AbstractLoadBalancerRule {
    public MyRoundRobinRule() {
    }

    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;
            while(server == null) {
                if (Thread.interrupted()) {
                    return null;
                }
                List<Server> upList = lb.getReachableServers();
                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }
                int index = this.chooseRandomInt(serverCount);
                server = (Server)upList.get(index);
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }
                    server = null;
                    Thread.yield();
                }
            }
            return server;
        }
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

5.7.9.编写模块主启动类

package org.openatom.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * 使用Ribbon实现负载均衡:Java硬编码方式
 *      缺点:不能在配置文件中动态修改服务提供端名称
 */
@SpringBootApplication
@EnableEurekaClient
public class OrderServiceConsumerLoadBalanceRibbonCustomerStrategyConfiguration80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceConsumerLoadBalanceRibbonCustomerStrategyConfiguration80.class, args);
    }
}

5.7.10.测试模块

启动相关服务
测试硬编码配置方式使用Ribbon实现负载均衡(使用Ribbon自带的负载均衡策略)
在浏览器中访问
http://localhost/consumer/payment/get/1
第一次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8001","data":{"id":1,"serial":"15646546546"}}
第二次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8002","data":{"id":1,"serial":"15646546546"}}
第三次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8002","data":{"id":1,"serial":"15646546546"}}
第四次访问返回结果
{"code":200,"message":"查询成功,serverPort:  8001","data":{"id":1,"serial":"15646546546"}}
可以看到四次访问返回的结果中,四次返回结果是没有规律的,因为采用的MyRoundRobinRule(自定义策略,这个策略的效果也是随机调用),实际返回结果可能不是上面的情况,但是一定是随机进行服务调用的
上次编辑于: 2022/9/6 00:07:50
贡献者: lingwh
评论