🌈往期回顾
第一期:Spring Cloud Alibaba前景如何?
第二期:Spring Cloud Alibaba Nacos服务治理
第三期:Spring Cloud Alibaba Sentinel - 分布式系统的流量防卫兵
第四期:Spring Cloud Alibaba Ribbon - 负载均衡
第五期:Spring Cloud Alibaba Gateway - 服务网关
第六期:SpringCloudAlibaba - 扩展Ribbon支持Nacos权重
在扩展Ribbon实现同一集群服务优先调用之前我们先来回顾一下Nacos服务发现领域模型,下面是我画的一个领域模型图:
领域模型
数据模型
Nacos 数据模型 Key 由三元组唯一确定, Namespace默认是空串,公共命名空间(public),分组默认是 DEFAULT_GROUP。
服务领域模型
配置领域模型
围绕配置,主要有两个关联的实体,一个是配置变更历史,一个是服务标签(用于打标分类,方便索引),由 ID 关联。
了解完Nacos领域模型时候,下面开始实现通过扩展Ribbon实现同集群按按权重优先调用,首先自定义NacosSameWeigthedRule,通过继承AbstractLoadBalancerRule实现重写调用的规则策略,⌨实现的思路是:
- 先找到指定服务的所有实例instancesA
- 过滤出相同集群的所有实例 instancesB
- 如果发现同集群的instancesB为空,才跨集群调用instancesA
- 基于权重的负载均衡,返回1个可调用的健康实例
代码实现
package com.jacklin.mamba.contentcenter.post.config;
import cn.hutool.core.collection.CollectionUtil;
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.alibaba.nacos.client.naming.core.Balancer;
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;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 扩展Ribbon - 同一集群下的优先调用
* <p>
* 北京机房的内容中心优先调用北京机房的用户中心实例,如果找不到北京机房的用户实例,才调用南京机房的用户实例
*
* @author: jacklin
* @date: 2022/6/9 22:31
*/
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosServiceManager nacosServiceManager;
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
try {
//拿到配置文件中的集群名称
String clusterName = nacosDiscoveryProperties.getClusterName();
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//获取想要调用的服务名称
String serviceName = loadBalancer.getName();
NamingService namingService = nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());
//1.找到指定服务的所有实例A
List<Instance> instances = namingService.selectInstances(serviceName, true);
//2.过滤出相同集群的所有实例B
List<Instance> sameClusterInstances = instances.stream().filter(instance -> Objects.equals(instance.getClusterName(), clusterName)).collect(Collectors.toList());
//3.如果B是空,就调用A
List<Instance> instancesToBeChosen = new ArrayList<>();
if (CollectionUtil.isEmpty(sameClusterInstances)) {
//如果同一个集群下没有找到实例
instancesToBeChosen = instances;
log.warn("发生跨集群调用serviceName = {},clusterName = {},instances = {}", serviceName, clusterName, instances);
} else {
instancesToBeChosen = sameClusterInstances;
}
//4.基于权重的负载均衡算法,返回1个可调用实例
Instance chooseInstance = ExtendBalancer.getHostByRandomWeightExtend(instancesToBeChosen);
log.info("选择的实例是 instance = {},port = {}", chooseInstance, chooseInstance.getPort());
return new NacosServer(chooseInstance);
} catch (NacosException e) {
log.error("同一集群下的优先调用异常,{}", e);
e.printStackTrace();
}
return null;
}
/**
* 这里扩展Balancer的getHostByRandomWeightExtend,由于getHostByRandomWeightExtend是protect修饰不能被修改,可以通过继承的方式来调用Balancer.getHostByRandomWeightExtend();
*
* @author: jacklin
* @since 2022/6/9 22:58
**/
static class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeightExtend(List<Instance> hosts) {
return Balancer.getHostByRandomWeight(hosts);
}
}
}
全局配置
/**
* Ribbon负载均衡规则配置
*
* @author jacklin
* @Date: 2022-01-06 22:28
*/
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new NacosSameClusterWeightedRule();
}
//@Bean
//public IPing ping(){
// return new PingUrl();
//}
}
现在我们通过内容中心调用用户中心,假设内容中心集群部署在北京机房,用户中心分布部署在北京机房和广州机房,配置文件信息如下:
content-center.application.yml (北京机房实例,端口9081)
server:
port: 9081
spring:
datasource:
url: jdbc:mysql://localhost:3306/content_center?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: content-center
cloud:
nacos:
discovery:
#指定nacos server地址
server-addr: localhost:8848
#服务名称
service: content-center
#集群部署在北京机房(命名为:BJ)
cluster-name: BJ
# 开启Ribbon饥饿加载(Ribbon默认是懒加载模式,会导致首次情况RT过长)
ribbon:
eager-load:
enabled: true
# 指定Ribbon请求user-center服务的时候启饥饿加载,多个可以用","分隔
clients: user-center
user-center.application.yml (北京机房实例1,端口8080)
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_center?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: user-center
cloud:
nacos:
discovery:
#指定nacos server地址
server-addr: localhost:8848
#服务名称
service: user-center
# 指定namespace
# namespace: 82758abc-a175-4b25-be4e-af74ee82697d
# 指定集群名称(北京集群-BJ)
cluster-name: BJ
metadata:
instance: a
version: v1.0.0
user-center.application.yml (广州机房实例2,端口8081)
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_center?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: user-center
cloud:
nacos:
discovery:
#指定nacos server地址
server-addr: localhost:8848
#服务名称
service: user-center
# 指定namespace
# namespace: 82758abc-a175-4b25-be4e-af74ee82697d
# 指定集群名称(广州集群-GZ)
cluster-name: GZ
metadata:
instance: a
version: v1.0.0
现在分别都将内容中心(北京9081)、用户中心(北京8080&广州8081)启动起来,通过内容中心调用用户中心相关接口观察调用日志情况:
用户实例信息:
内容中心通过RestTemplate调用用户中心实例(10次),发现请求都打到了北京机房(同集群)的用户中心实例上:
当把北京机房集群的用户实例下线之后,再次访问发现:
这时候发现请求达到了广州机房的用户中心,发生了跨集群调用,至此我们就已经扩展实现Ribbon同集群优先调用了。