嘘~ 正在从服务器偷取页面 . . .

SpringCloud Ribbon


SpringCloud Ribbon 客户端负载均衡与服务调用

相关链接:

一、概述

Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡工具。

Project Status: On Maintenance

https://github.com/Netflix/ribbon

可能的替换方案:Spring Cloud Starter Loadbalancer

二、Ribbon

Ribbon 是Netflix 发布的开源项目,主要是提供客户端负载均衡算法和服务调用

负载均衡 Load Balance :

将请求平摊分配到多个服务单元,从而达到系统服务的高可用。

常见负载均衡软件:Nginx、LVS、硬件F5等

Nginx 是服务器端负载均衡,客户端所有请求都会交给Nginx服务器,然后由Ninx 服务器根据分配算法实现请求分配转发。

Ribbon 是客户端负载均衡,在调用微服务接口时,会在服务注册中心获取注册的服务列表后缓存到客户端的JVM,从而实现在客户端实现RPC进行远程服务调用技术。

Ribbon核心组件IRule相关接口及均在均衡算法:

Ribbon 接口

IRule接口会根据特定算法从服务列表中选取一个要访问的服务。

Ribbon自带的负载均衡算法:

(1)轮询算法。默认的负载均衡算法,源码com.netflix.loadbalancer.RoundRobinRule

(2)随机算法。源码com.netflix.loadbalancer.RandomRule

(3)重试算法。先按照RoundRobinRule的策略轮询获取服务,如果获取服务失败则在指定时间内会进行重试。源码com.netflix.loadbalancer.RetryRule

(4)响应权重算法。对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择。源码com.netflix.loadbalancer.WeightedResponseTimeRule

(5)最可用算法。Ribbon会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。源码com.netflix.loadbalancer.BestAvailableRule

(6)可用过滤算法。先过滤掉故障实例,再选择并发较小的实例。源码com.netflix.loadbalancer.AvailabilityFilteringRule

(7)区域可用算法。默认规则,复合判断server所在区域的性能和server的可用性选择服务器。源码com.netflix.loadbalancer.ZoneAvoidanceRule

三、Ribbon使用

用在客户端

作为客户端的组件必定是用在客户端/调用者工程中使用。

默认情况下,Eureka 客户端已经封装了Ribbon 组件。官方说明https://spring.io/projects/spring-cloud-netflix

如果在没有封装Ribbon 的环境中使用,可以试试下面的,如果版本不行,就自己找一下maven的依赖引入即可。

1、POM中引入依赖

    <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-ribbon</artifactId>
   </dependency>

2、Config 配置。

千万注册是调用者/消费端工程。

package com.xiaocai.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 {

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

3、RestTemplate调用

Ribbon和Eureka或者Consul整合后消费端可以直接调用服务而不用再关心地址和端口号。

消费端的controller示例:

package com.xiaocai.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
public class UserConsulController {

    public static final String HTTP_MS_URL = "http://MS-CLOUD-USER-SERVICE";

    @Resource 
    private RestTemplate restTemplate;

    @GetMapping("/consumer/user/consul")
    public String user(){
      String result = restTemplate.getForObject(HTTP_MS_URL+"/v1/user/consul",String.class);
      return result;
    }

}

服务端user_01的controller示例:

package com.xiaocai.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class UserConsulController {

    @GetMapping("/v1/user/consul")
    public String user(){

      return "我是服务端user_01, 我的端口号是 8001";
    }

}

服务端user_02的controller示例:

package com.xiaocai.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class UserConsulController {

    @GetMapping("/v1/user/consul")
    public String user(){

      return "我是服务端user_02, 我的端口号是 8002";
    }

}

4、测试

访问消费端:http://localhost:6001/consumer/user/consul

默认情况下是轮询,反复访问地址,在浏览器上会交替出现下

我是服务端user_01, 我的端口号是 8001

我是服务端user_02, 我的端口号是 8002

四、自带负载均衡规则替换

1、添加自定义规则类

需要注意的细节是,这个自定义的配置类一定要和springboot 启动类所在位置隔离,不能在springboot启动类的同一层级或子级目录。否则配置会被所有的Ribbon客户端共享,达不到特殊化定制目的。

package com.xiaocai.myrule;

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

@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule(){
        return new RandomRule();//定义为随机
        //如果想自定义算法,可以在此处new  MyRule().
    }
}

2、在启动类声明

使用 @RibbonClient 注解在主启动类中,声明自定义规则

@RibbonClient(name = "MS-CLOUD-USER-SERVICE",configuration = MySelfRule.class)

示例:

package com.xiaocai.springcloud;

import com.xiaocai.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;

@EnableDiscoveryClient //此处注册consul
@SpringBootApplication
@RibbonClient(name = "MS-CLOUD-USER-SERVICE",configuration = MySelfRule.class)
public class UserConsumerMain6001 {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerMain6001.class,args);
    }

}

@EnableDiscoveryClient表示此处注册consul服务。如果使用Eureka 使用 @EnableEurekaClient注解。

3、测试

启动测试,检查设置的随机算法RandomRule是否生效。

五、自定义负载均衡

需要注意的细节同上,这个自定义的类/接口一定要和springboot 启动类所在位置隔离,不能在springboot启动类的同一层级或子级目录。

1、取消默认

去掉 @LoadBalanced注解,取消默认的负载均衡方式。

2、添加负载均衡接口

package com.xiaocai.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

public interface LoadBalancer {
     //收集服务器总共有多少台能够提供服务的机器,并放到list里面
    ServiceInstance instances(List<ServiceInstance> serviceInstances);

}

3、添加负载均衡算法实现

重新写轮询算法。不能遗漏@Component 注解。

package com.xiaocai.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
//自定义写的轮询
@Component
public class UserLoadBalance implements LoadBalancer {

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    //坐标
    private final int getAndIncrement(){
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            next = current >= Integer.MAX_VALUE ? 0 : current + 1;
        }while (!this.atomicInteger.compareAndSet(current,next));  //第一个参数是期望值,第二个参数是修改值
        System.out.println("*******第几次访问,次数next: "+next);
        return next;
    }

    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {  //得到机器的列表
       int index = getAndIncrement() % serviceInstances.size(); //得到服务器的下标位置
        return serviceInstances.get(index);
    }
}

六、其他

后续遇到再补充。



版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Small-Rose / 张小菜 !
评论
  目录