一、Ribbon负载均衡
如上图所示,负载均衡就是避免单个服务的实例处理大批量请求而导致其他实例空闲,造成资源浪费。负载均衡分为客户端、服务端的负载均衡,它们最大的区别在于维护服务器的清单保存的位置不同,如:Ribbon属于客户端负载均衡,客户端根据注册中心的服务实例的状态,判定是否访问某个实例。那么判定访问哪个实例,就是负载均衡策略要做的事。Ribbon的负载均衡有三个主要接口:com.netflix.loadbalancer.IPing(服务的检测)、com.netflix.loadbalancer.IRule(定义均衡策略)、com.netflix.loadbalancer.ILoadBalancer(根据IRule请求实例)。
二、Ribbon的负载均衡策略
如上图所示,是IRule的类图。如果需要自定义均衡策略,则继承com.netflix.loadbalancer.AbstractLoadBalancerRule抽象类。如下图所示是Ribbon的7种均衡策略。默认是com.netflix.loadbalancer.ZoneAvoidanceRule(综合判定来选择服务实例)。
三、自定义负载均衡策略
注意:自定义均衡策略,则继承com.netflix.loadbalancer.AbstractLoadBalancerRule抽象类。覆写代码参考该抽象类的实现类。如下代码所示。核心代码是chooseIndex(int reachableCount)方法。
package com.common.instance.gateway.config.ribbon;
import com.log.util.LogUtil;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* @description 自定义Ribbon负载均衡策略
* @author TCM
* @version 1.0
* @date 2021/4/1 10:58
**/
@Configuration
public class MyRibbonBalancerRule extends AbstractLoadBalancerRule {
// 所有服务实例
private volatile int total;
// 实例的索引
private volatile int index;
// 可用的所有实例
List<Server> upList = new ArrayList<>();
public Server choose(ILoadBalancer lb, Object key) {
LogUtil.info(String.format("lb=%s,key=%s", lb, key.toString()));
// 负载均衡器是否为null
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
// 当前线程是否已中断
if (Thread.interrupted()) {
return null;
}
// 获取负载均衡器中的所有服务实例
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
if (total == 0) {
// 可用的所有实例
upList = lb.getReachableServers();
}
// 修改当前索引
chooseIndex(lb.getReachableServers().size());
// 根据索引获取服务
server = upList.get(index);
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
// 修改当前索引
protected void chooseIndex(int reachableCount) {
if (total < 3) {
if (upList.size() != reachableCount) {
index = 0;
}
total ++;
} else {
total = 0;
index ++;
if (index >= reachableCount) {
index = 0;
}
}
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
gateway的yml配置内容:ribbon.NFLoadBalancerRuleClassName: com.common.instance.gateway.config.ribbon.MyRibbonBalancerRule
# hystrix开启
feign:
hystrix:
enabled: true
hystrix:
command:
default: # default全局有效,service id指定应用有效
execution:
timeout:
# true则超时根据熔断超时,false则ribbon控制
enabled: true
isolation:
thread:
timeoutInMilliseconds: 4000 # 断路器超时时间,默认1000ms
# ribbon配置
ribbon:
OkToRetryOnAllOperations: true # 所有请求重试,默认false
ReadTimeout: 3000 # 负载均衡超时时间,默认值5000ms
ConnectTimeout: 1000 # 请求连接的超时时间,默认值2000ms
MaxAutoRetries: 0 # 当前实例的重试次数,默认0
MaxAutoRetriesNextServer: 2 # 切换实例的重试次数,默认1
NFLoadBalancerRuleClassName: com.common.instance.gateway.config.ribbon.MyRibbonBalancerRule
spring:
cloud:
gateway:
# gateway发现该eureka下的所有服务
discovery:
locator:
enabled: false
lowerCaseServiceId: true
default-filters:
- name: Hystrix
args:
name: default
fallbackUri: 'forward:/fallback/global'
routes:
- id: ${application.name.instance-demo}
predicates:
- Path=/${application.name.instance-demo}/**
uri: lb://${application.name.instance-demo}
gateway路由接口代码:
package com.common.instance.demo.controller;
import com.common.instance.demo.core.Response;
import com.common.instance.demo.service.RibbonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @description ribbon负载均衡策略
* @author TCM
* @version 1.0
* @date 2021/3/29 20:30
**/
@RestController
@RequestMapping("/ribbon")
@Api(tags = "Ribbon测试")
public class RibbonController {
@Resource
private RibbonService ribbonService;
@GetMapping("/rule")
@ApiOperation("Ribbon策略")
public Response<String> testRibbonRule(int mills) {
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
e.printStackTrace();
}
String result = ribbonService.testRibbonRule();
return Response.success(result);
}
}
package com.common.instance.demo.service.impl;
import com.common.instance.demo.service.RibbonService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* @author TCM
* @version 1.0
* @description TODO
* @date 2021/3/29 20:41
**/
@Service
public class RibbonServiceImpl implements RibbonService {
@Value("${spring.cloud.client.ip-address}")
private String ipAddress;
@Value("${server.port}")
private String port;
@Override
public String testRibbonRule() {
return String.format("Ribbon均衡策略测试:主机%s:%s", ipAddress, port);
}
}
测试结果:
连续3次:发生熔断
{
"success": false,
"code": "500",
"message": "网络发生异常,请稍后重试!",
"tip": "操作失败",
"data": null
}
连续3次:9012端口
{
"success": true,
"code": "200",
"message": "操作成功",
"tip": "操作成功",
"data": "Ribbon均衡策略测试:主机192.168.1.107:9012"
}
连续3次:9013端口
{
"success": true,
"code": "200",
"message": "操作成功",
"tip": "操作成功",
"data": "Ribbon均衡策略测试:主机192.168.1.107:9013"
}