Spring Cloud 之 ribbon

参考:<<重新定义springcloud>>

  各个视频

什么是ribbon?它解决了什么问题

  • Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,他是基于Netflix Ribbon实现的。
  • 它不像spring cloud服务注册中心,配置中心,API网关那样独立部署,但是它几乎在每一个spring cloud微服务中,包括feign提供的声明式服务调用也是基于Ribbon实现的。
  • ribbon提供了很多种负载均衡算法,例如 轮询、随机 等等。甚至是包含自定义的负载均衡算法。
  • 他解决了微服务的负载均衡的问题

负载均衡解决方案的分类

目前业界主流的负载均衡方案课分为俩类:

1、集中式负载均衡,就是在consumer和provider之间使用独立的负载均衡设施(可以是硬件,如F5,也可以是软件 ,nginx)由这些设施 负责把访问请求 通过 某种策略转发至provider

2、进程内负载均衡,将负载均衡逻辑集成到consumer,consumer从注册中心获取有哪些服务地址,从中通过某种策略挑选出一个provider

Ribbon 就是属于后者,他只是一个类库,集成于consumer进程,consumer通过他来获取到provider的地址

他们的区别架构图

 Ribbon的负载均衡算法策略

id 命名 策略类 描述
1 轮询算法 RoundRibbonRule 按顺序循环选择server
2 随机算法 randomRule 随机选择server
3 重试策略 retryRule 在一个配置时间内当选择的server不成功,则一直尝试选择一个可用的server
4 最低并发策略 BestAvailableRule 逐个考擦server,如果server断路器打开,则忽略,选择请求中并发最小的server,
 5  可用过滤策略  availabilityFilteringRule  过滤掉一直连接失败并被标记为ciruruit tripped的server,过滤掉哪些高并发连接的server(active connections超过配置的阈值)
 响应时间加权策略  responseTimeWeightRule  根据server的响应时间分配权重,响应时间越长,权重越低,响应时间越短,权重越高,被选中的概率就越高,这个策略很贴切,综合了。网络 磁盘 IO等因素
 7  区域权衡策略  zoneavoidanceRule  

 综合判断server所在区域的性能和server的可用性轮询选择server,判断一个区域是否可用,如果不可用直接将这个区域的sever都丢弃

如:多机房情况

使用RIbbon实现的入门实例:

1.先创建一个父工程:只有pom文件 hello-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>

    <groupId>com.xiaodao</groupId>
    <artifactId>hello-springcloud</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>eureka_server</module>
        <module>client-a</module>
        <module>client-a</module>
        <module>ribbon-client</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
    </parent>
    <!--  利用传递依赖,公共部分  -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- springboot web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <!-- 管理依赖 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</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>
View Code

2.创建一个eurekaserver服务:client-a

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">
    <parent>
        <artifactId>hello-springcloud</artifactId>
        <groupId>com.xiaodao</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka_server</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
View Code

bootstrap.yml 和启动类

server:
  port: 8888
eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/


/**
* Created by xiaodao
* date: 2019/7/17
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

3.创建一个provider 

   <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class ClientAApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(ClientAApplication.class, args);
    }
}

controller:

package com.xiaodao.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping("/add")
    public String add(Integer a, Integer b, HttpServletRequest request){
        return " From Port: "+ request.getServerPort() + ", Result: " + (a + b);
    }
}
View Code

3.创建第3个项目:ribbon-client

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

bootstrap.yml

spring:
  application:
    name: ribbon-loadbalancer
server:
  port: 7777
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8888/eureka/
#默认是hostname注册,改成ip注册
instance: prefer-ip-address: true

启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class RibbonLoadbalancerApplication {

    public static void main(String[] args) {
        SpringApplication.run(RibbonLoadbalancerApplication.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

testController:

@RestController
public class TestController {
    
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/add")
    public String add(Integer a, Integer b) {
        String result = restTemplate
                .getForObject("http://CLIENT-A/add?a=" + a + "&b=" + b, String.class);
        System.out.println(result);
        return result;
    }
}

现在我们的项目都创建完成了.我们启动俩个client-a不过端口不一样.启动ribbon-client 启动 hello-springcloud

 

当我们请求localhost:7777/add?a=1&b=10 的时候我们可以看到打印的日志:

 From Port: 7070, Result: 11
 From Port: 7071, Result: 11
 From Port: 7070, Result: 11
 From Port: 7071, Result: 11
 From Port: 7070, Result: 11
 From Port: 7071, Result: 11
 From Port: 7070, Result: 11

换一个中策略来实现

前面提到过那么多策略模式.我们模式的是轮询模式,现在来挑选一个最简单的可以看出来的随机

我们有配置文件和java俩种配置

   @Bean
    public IRule ribbonRule() {
        return new RandomRule();//这里配置策略,和配置文件对应
    }

 配置文件方式:

spring:
  application:
    name: ribbon-loadbalancer
server:
  port: 7777
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8888/eureka/

  instance:
    prefer-ip-address: true
CLIENT-A:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

点对点直连,让ribbon调试更加方便快捷

什么是点对点直连?

cusumer和provider点对点直连不经过注册中心,为什么呢?因为开发环境都是有eureka来做注册中心,开发中大家的idea都是启动的,你要和对方进行联调,很难联调

 

如果我和张三联调.负载均衡经常发给李四和王五,这种情况没法联调.有人说我要和张三联调,让李四和王五先停下来.这怎么行?

如何实现呢?

1.在consumer就是你要调用对方的自己的服务:

将uereka移除

  <!--  <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>-->
@SpringBootApplication
//@EnableDiscoveryClient
public class RibbonLoadbalancerApplication {

    public static void main(String[] args) {
        SpringApplication.run(RibbonLoadbalancerApplication.class, args);
    }
    
  @Bean
//    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    /*@Bean
    public IRule ribbonRule() {
        return new RandomRule();//这里配置策略,和配置文件对应
    }*/
}

pom文件:

spring:
  application:
    name: ribbon-loadbalancer
server:
  port: 8082
#eureka:
#  client:
#    serviceUrl:
#      defaultZone: http://localhost:8888/eureka/
#
#  instance:
#    prefer-ip-address: true
#禁用eureka
ribbon:
  eureka:
    enabled: false
CLIENT-A:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    listOfServers: http://127.0.0.1:7070,http://127.0.0.1:7071

在调用的时候需要这么写:

@RestController
public class TestController {
    
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;//ribbon 负载均衡客户端

    @GetMapping("/add")
    public String add(Integer a, Integer b) {
        ServiceInstance si=loadBalancerClient.choose("CLIENT-A");
        StringBuffer sb=new StringBuffer("");
        sb.append("http://");
        sb.append(si.getHost());
        sb.append(":");
        sb.append(si.getPort());
        sb.append("/add?a="+a+"&b="+b);
        System.out.println(sb.toString());
        String result = restTemplate.getForObject(sb.toString(), String.class);
//        String result = restTemplate
//                .getForObject("http://CLIENT-A/add?a=" + a + "&b=" + b, String.class);
        System.out.println(result);
        return result;
    }
}

这个时候,就可以实现点对点的联调.

关于@loadBanced的实现.

就是基于实际是通过实现 ClientHttpRequestInterceptor 实现的

LoadBalancerInterceptor
LoadBalancerClient
RibbonLoadBalancerClient

然后经过LoadBalancerAutoConfiguration自动配置将loadbalanceInterceptor变为resttemplate的的拦截器

@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {

   @Bean
   public LoadBalancerInterceptor ribbonInterceptor(
         LoadBalancerClient loadBalancerClient,
         LoadBalancerRequestFactory requestFactory) {
      return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
   }

   @Bean
   @ConditionalOnMissingBean
   public RestTemplateCustomizer restTemplateCustomizer(
         final LoadBalancerInterceptor loadBalancerInterceptor) {
      return restTemplate -> {
         List<ClientHttpRequestInterceptor> list = new ArrayList<>(
               restTemplate.getInterceptors());
         list.add(loadBalancerInterceptor);
         restTemplate.setInterceptors(list);
      };
   }

}

这里不做多讲.毕竟在下也是只知皮毛.

 

posted @ 2019-07-17 13:47  北京de小刀  阅读(742)  评论(0编辑  收藏  举报