一,Ribbon的简介
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具
Ribbon提供的主要功能:
- 客户端的软负载均衡
- 服务调用
Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等,简单的说就是在配置文件中列出Load Balancer(简称LB)的所有服务端,Ribbon会自动的帮助开发者基于某种规则(如简单轮询,随机连接等)去连接这些机器
Ribbon的模块:
- 功能区:在其他功能区模块和Hystrix之上集成负载平衡、容错、缓存/批处理的 API
- ribbon-loadbalancer:可以独立使用或与其他模块一起使用的负载平衡器 API
- ribbon-eureka:使用Eureka 客户端为云提供动态服务器列表的API
- 功能区传输:使用具有负载平衡功能的RxNetty支持 HTTP、TCP 和 UDP 协议的传输客户端
- Ribbon-httpclient:基于 Apache HttpClient 构建的 REST 客户端,集成了负载均衡器(已弃用并被功能区模块取代)
LB负载均衡(Load Balance):将客户端请求平摊分配到多个服务上,从而达到系统的HA(高可用)
Ribbon本地负载均衡和Nginx负载均衡的区别:
- Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现的(集中式的负载均衡)
- Ribbon本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程调服务(进程内的负载均衡)
集中式LB:即在服务的消费方和提供方之间使用独立LB(可以是硬件如F5,也可以是软件如Nginx),由该设施负责把访问请求通过某种策略转发到服务提供方
进程内LB:它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址
注:Ribbon默认和Eureka做了整合,所以在引入Eureka Client或Eureka Server时就默认带了Ribbon
Ribbon在维护中:
二,Ribbon负载均衡机制
一,Ribbon的负载过程
Ribbon执行分为两步:
- 先选择EurekaServer,它优先选择在同一个区域负载较少的server
- 根据用户指定的负载均衡策略,在从server取到的服务注册列表中选择一个地址,通过RestTemplate进行访问
Ribbon只是一个负载均衡的服务工具,具体的服务调用需要依赖于RestTemplate(底层Httpclient)进行调用
一般使用Ribbon+RestTemplate进行服务调用
二,Ribbon负载均衡机制和规则替换
一,Ribbon负载均衡机制
Ribbon负载均衡算法通过IRule接口实现,如:轮询算法,随机算法都实现了IRule
IRule接口源码:
public interface IRule {
//服务名称
Server choose(Object var1);
//设置负载均衡算法
void setLoadBalancer(ILoadBalancer var1);
//获取负载均衡算法
ILoadBalancer getLoadBalancer();
}
IRule的实现类:
- RoundRobinRule:轮询
- RandomRule:随机
- RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
- WeightedResponseTimeRule:对RoundRobinRule的拓展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule:会先过滤由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务
- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
二,Ribbon负载均衡的规则替换
官方给出的自定义规则替换的警告:自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则自定义的配置类就会被所有的Ribbon客户端所共享,达不到特殊定制化的目的
注:@ComponentScan所扫描的包就是SpringBoot启动类的包,所有在springboot项目中Ribbon自定义不能和SpringBoot启动类同包
① 父工程依赖
<?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>org.example</groupId>
<artifactId>SpringCloud-Dome</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<lombok.version>1.18.10</lombok.version>
<log4j.version>1.2.17</log4j.version>
<mysql.version>5.1.31</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>2.1.1</mybatis.spring.boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
② maven依赖
<dependencies>
<dependency>
<groupId>com.cloud.commons</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--eureka服务端,eureka内置了Ribbon依赖不需要单独引入-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.cloud.commons</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
③ application配置
server:
port: 8084
spring:
application:
name: cloud-ribbon-client
eureka:
client:
fetch-registry: true #是否抓取已有的注册信息,默认为true,单节点无所谓,集群时必须设置为true才能配合Ribbon使用负载均衡
register-with-eureka: true #是否将自己注册进Eureka Server
service-url:
defaultZone: http://eureka1.com:9090/eureka/,http://eureka2.com:9091/eureka/
④ 自定义IRule(这个包不能为主启动类的子包)
@Configuration
public class MySelfRlue {
@Bean
public IRule Myrule(){
return new RandomRule(); //定义随机
}
}
⑤ 启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
//name位服务名,configuration位自定义Rule的class
@RibbonClient(name = "CLOUD-PAYMENT-SERVER",configuration = MySelfRlue.class)
public class RibbonMain {
public static void main(String[] args) {
SpringApplication.run(RibbonMain.class,args);}
}
⑥ controller
@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVER";
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/payment/create",method = RequestMethod.GET)
public CommonResult<payment> create(payment payments){
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payments,CommonResult.class);
}
@RequestMapping("/payment/select/{id}")
public CommonResult<payment> select(@PathVariable("id") int id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/select/"+id,CommonResult.class);
}
测试:
三,Ribbon默认负载轮询算法原理
轮询负载均衡算法:(rest接口第几次请求数) % (服务器集群总数量) = 实际调用服务器位置下标(每一次服务重启后rest接口计数从1开始)
List<Servicelnstance> instances = discpveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
如:List[0] instance = 127.0.0.1:8002 List[1] instance = 127.0.0.1:8001
8001+8002组合成为集群,它们共计两台机器,集群总数为2
按照轮询算法原理:
- 当总请求数为1时: 1%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
- 当总请求数位2时: 2%2=0对应下标位置为0,则获得服务地址为127.0.0.1:8002
- 当总请求数位3时:3%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
- 当总请求数位4时: 4%2=0对应下标位置为0,则获得服务地址为127.0.0.1:8002
- 如此类推.…………
源码:
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
this.nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
this.setLoadBalancer(lb);
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if (upCount != 0 && serverCount != 0) {
int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server)allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
return next;
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}











Comments | NOTHING