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

  • 关于Ribbon
  • Ribbon实现负载均衡
  • 使用属性自定义Ribbon配置
  • 负载均衡策略介绍

上篇已经实现了Eureka Server的高可用,理论上已经使得微服务更加完美,但依旧存在些许问题。例如,来自服务消费者的请求如何分摊到部署了多个实例的服务提供者?分摊规则如何取?本篇即揭开Ribbon面纱。

1、Ribbon简介

Ribbon是Netflix发布的负载均衡器,它可以控制HTTP和TCP客户端的行为规则。Ribbon支持多种负载均衡规则帮助服务消费者根据指定规则分摊请求到服务提供者,如轮询、随机、加权响应时间、区域感知轮询等。Ribbon配合Eureka使用更加的高效,配置也得到的极大的简化,大致架构图如下:

2、Ribbon实现负载均衡

由上面的架构图可以清晰的看出,Ribbon配置在服务消费者端。下面开始配置,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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.simons.cn</groupId>
    <artifactId>ticket-consumer-ribbon</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.3.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </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>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.22</version>
        </dependency>
        <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.12</version>
            <scope>provided</scope>
        </dependency> <!--Eureka依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>
    <!-- 引入spring cloud的依赖 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!-- 添加spring-boot的maven插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

启动类TicketConsumerRibbonApplication类中的RestTemplate上加上@LoadBalanced注解即可

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class TicketConsumerRibbonApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

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

}

修改TicketController类:

import com.simons.cn.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@Slf4j
@RestController
public class TicketController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/ticketpurchase")
    public CommonResult purchaseTicket(@RequestParam(required = false, value = "name") String name) {
        CommonResult result = restTemplate.getForObject("http://user-provider/" + "getuserinfo?name=" + name, CommonResult.class);
        return result;
    }
      @GetMapping("/loginfo")
    public void loginfo() {
          ServiceInstance serviceInstance = loadBalancerClient.choose("user-provider");
          log.info("host=" + serviceInstance.getHost() + ",port=" + serviceInstance.getPort() + ",serviceid=" + serviceInstance.getServiceId());
    }
}

注意: user-provider是用户微服务实例中spring.application.name定义的服务提供者名称,一个名称可对应多个服务提供者实例

测试:

启动多个user-provider-eureka服务

启动discovery-eureka服务

启动ticket-consumer-ribbon服务,每个实例端口要不一致

浏览器多次访问: http://localhost:9000/loginfo

2018-07-20 10:17:44.290  INFO 7032 --- [nio-9000-exec-3] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:45.042  INFO 7032 --- [nio-9000-exec-4] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:45.546  INFO 7032 --- [nio-9000-exec-6] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:46.010  INFO 7032 --- [nio-9000-exec-8] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:46.386  INFO 7032 --- [io-9000-exec-10] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:46.709  INFO 7032 --- [nio-9000-exec-3] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:46.992  INFO 7032 --- [nio-9000-exec-4] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:47.218  INFO 7032 --- [nio-9000-exec-6] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:47.390  INFO 7032 --- [nio-9000-exec-8] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:47.545  INFO 7032 --- [io-9000-exec-10] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:47.718  INFO 7032 --- [nio-9000-exec-3] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:47.870  INFO 7032 --- [nio-9000-exec-4] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:48.024  INFO 7032 --- [nio-9000-exec-8] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:48.192  INFO 7032 --- [io-9000-exec-10] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:48.361  INFO 7032 --- [nio-9000-exec-3] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8001,serviceid=user-provider
2018-07-20 10:17:48.521  INFO 7032 --- [nio-9000-exec-4] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8009,serviceid=user-provider
2018-07-20 10:17:48.672  INFO 7032 --- [nio-9000-exec-5] c.simons.cn.controller.TicketController  : host=10.200.121.49,port=8001,serviceid=user-provider

可以发现,每访问一次,idea编辑器控制台输出的日志信息中port,也就是每次请求均匀分配到了各个实例上,默认负载规则为轮询,上述代码已实现此功能。

3、使用属性配置自定义Ribbon配置

特定场景下,需要修改负载均衡规则,如不再使用轮询,改为随机,那么结合属性配置的方式这些将变得很简单

在项目ticket-consumer-ribbon中的application.yml中添加服务提供者的ribbon负载均衡规则

user-provider:    #这里是用户微服务的节点名称
  ribbon:
      NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

user-provider同上面的注意点的含义一致,这样便将负载均衡规则改为了随机。

测试步骤同上,依次访问,依次观察ticket-consumer-ribbon微服务实例控制台的log,可以发现负载规则已经不再是轮询,而是随机了,请求随机分配在两个user-provider实例上了。

4、Ribbon提供以下7种负载均衡策略(此表格借阅自他人博客)

策略名策略声明策略描述实现说明
BestAvailableRulepublic class BestAvailableRule extends ClientConfigEnabledRoundRobinRule选择一个最小的并发请求的server逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server
AvailabilityFilteringRulepublic class AvailabilityFilteringRule extends PredicateBasedRule过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态
WeightedResponseTimeRulepublic class WeightedResponseTimeRule extends RoundRobinRule根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择server。
RetryRulepublic class RetryRule extends AbstractLoadBalancerRule对选定的负载均衡策略机上重试机制。在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRulepublic class RoundRobinRule extends AbstractLoadBalancerRuleroundRobin方式轮询选择server轮询index,选择index对应位置的server
RandomRulepublic class RandomRule extends AbstractLoadBalancerRule随机选择一个server在index上随机,选择index对应位置的server
ZoneAvoidanceRulepublic class ZoneAvoidanceRule extends PredicateBasedRule复合判断server所在区域的性能和server的可用性选择server使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。

项目的github:https://github.com/simonsfan/SpringCloud.git

引申阅读:脱离Eureka使用Ribbon

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页