目录
1.前言
2.代码
3.nacos配置
4.测试
5.源码跟踪
6.多服务节点配置
1. 前言
之前新增临时服务器,发现直接在nacos中配置负载均衡未生效,纠结了一阵未解决
本着先快速地解决问题,再优雅地解决问题的原则,先在临时服务器(同一台服务器)多部署了两个服务,以这种LOW逼的方式变向地实现了负载均衡(让高配的服务器权重提高,因为同一台服务器部署了多个服务)
现在有时间了,回过头看了看nacos的自定义负载均衡,然后整理了一下
2. 代码
在API网关中添加如下代码,实现 alibaba gateway 到服务节点的 ribbon 负载均衡
2.1、自定义负载均衡规则、重写choose方法
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 支持Nacos权重配置的负载均衡策略
*
* @author weiheng
* @date 2021-12-20 16:30:56
**/
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosServiceManager nacosServiceManager;
/**
* 读取配置文件,并初始化NacosWeightedRule
*
* @param iClientConfig iClientConfig
*/
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
// do nothing
}
@Override
public Server choose(Object key) {
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 需要请求的微服务名称
String name = loadBalancer.getName();
// 获取服务发现的相关API
NamingService namingService = nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());
try {
// 调用该方法时nacos client会自动通过基于权重的负载均衡算法选取一个实例
Instance instance = namingService.selectOneHealthyInstance(name);
log.info("=============invoke serviceName:{}, ip:{}, port:{}", instance.getServiceName(), instance.getIp(), instance.getPort());
return new NacosServer(instance);
} catch (NacosException e) {
return null;
}
}
}
2.2、将Rule规则提交给spring容器
import com.applet.gateway.config.NacosWeightedRule;
import com.netflix.loadbalancer.IRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
/**
* 网关启动程序
* @author weiheng
*/
@Slf4j
@EnableDiscoveryClient
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class AppletGatewayApplication
{
public static void main(String[] args)
{
SpringApplication.run(AppletGatewayApplication.class, args);
System.out.println("API网关启动成功");
}
/**
* <pre>
* 自定义nacos负载均衡策略
* <pre>
* @date 2021/12/21 9:58
* @author wei.heng
*/
@Bean
public IRule getRule() {
return new NacosWeightedRule();
}
}
3. nacos权重配置
这里本地测试,咱随便改几个值了
4. 测试
使用psotman连续请求 10 次后台接口
可以看到,按权重分配,后台服务拿到的请求
服务 | 权重 | 请求次数 |
---|---|---|
9500 | 0.1 | 1 |
9501 | 0.2 | 6 |
9503 | 0.7 | 3 |
为什么权重高的服务,接收的请求反而少呢?
为什么请求次数不是1:2:7呢?
5.源码跟踪
针对测试结果得到的疑惑,我们跟着源码走一走
源码的核心代码
com.alibaba.nacos.client.naming.utils.Chooser
/**
* Random get one item with weight.
*
* @return item
*/
public T randomWithWeight() {
Ref<T> ref = this.ref;
// 取0到1之间的随机double值
double random = ThreadLocalRandom.current().nextDouble(0, 1);
// 通过二分查找后台执行 index = -index - 1,拿到节点的数据下标
// 注意,这里权重值都是配置的0到1之间的double值
int index = Arrays.binarySearch(ref.weights, random);
if (index < 0) {
index = -index - 1;
} else {
return ref.items.get(index);
}
if (index >= 0 && index < ref.weights.length) {
if (random < ref.weights[index]) {
return ref.items.get(index);
}
}
/* This should never happen, but it ensures we will return a correct
* object in case there is some floating point inequality problem
* wrt the cumulative probabilities. */
return ref.items.get(ref.items.size() - 1);
}
好了,通过上面这段代码,可以看出,这里取的随机数,前面的测试结果应该是正常的
索性又测了一把,这次把请求的次数拉大(做了几十次请求),然后发现确实是按1:2:7得到的服务分发
在次数很少的时候,比如10以内,可能会出现权重低的服务得到请求更多(这个是正常的,因为是随机数获取权重嘛)
6.多服务节点配置
当一个服务节点,需要同时路由到多个服务节点的时候,前面的配置就会抛异常
可以稍微改一下,为各服务节点做独立的 ribbon 配置
6.1 自定义负载均衡规则
import com.netflix.loadbalancer.IRule;
import com.ruoyi.gateway.config.NacosWeightedRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <pre>
* 自定义Nacos负载均衡策略
* <pre>
* @date 2021/12/24 11:34
* @author wei.heng
*/
@Configuration
public class DefaultRibbonConfiguration {
@Bean
public IRule getRule() {
return new NacosWeightedRule();
}
}
6.2 为各个服务单独配置负载均衡
6.2.1 方法1 - 代码配置
import com.deao.common.core.constant.ServiceNameConstants;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;
/**
* @date 2021/12/24 11:38
* @author wei.heng
*/
@Configuration
@RibbonClient(name = ServiceNameConstants.GAS_MSGCENTER ,configuration = DefaultRibbonConfiguration.class)
public class MsgCenterRibbonConfig {
}
import com.deao.common.core.constant.ServiceNameConstants;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;
/**
* @date 2021/12/24 11:38
* @author wei.heng
*/
@Configuration
@RibbonClient(name = ServiceNameConstants.FLOW_SERVICE ,configuration = DefaultRibbonConfiguration.class)
public class FlowRibbonConfig {
}
6.2.2 方法2 - 直接在nacos的yml文件中,对具体服务节点进行ribbon负载策略配置
格式:
[节点名称]:
ribbon:
NFLoadBalancerRuleClassName:
示例:
app-teacher-user:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
app-quality-credit:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
到这里差不多可以投入生产使用了
不过感觉在nacos中配置权重,用起来也不是很方便
这里改成 WeightedResponseTimeRule 先用一段时间试试了
看起来这个更靠谱