共计 7523 个字符,预计需要花费 19 分钟才能阅读完成。
前言
ribbon 是 Netflix 开源的客户端负载平衡工具,ribbon 实现一系列的负载平衡算法,通过这些负载平衡算法去查找相应的服务。ribbon 被大家所熟知,可能是来源于 spring cloud,明天就来聊聊如何独自应用 ribbon 来实现业务客户端负载平衡
实现要害
springcloud ribbon 获取服务列表是通过注册核心,而独自应用 ribbon,因为没有注册核心加持,就得独自配置服务列表
示例
1、在业务我的项目中 pom 引入 ribbon GAV
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon</artifactId>
<version>2.2.2</version>
</dependency>
不过引进去,发现如果引入 netfiix 的相干的类,比方 IPing,会发现引不进去,起因是因为这个 GAV 外面依赖的 jar 的生命周期是 runtime,即在运行期或者测试阶段才失效,在编译阶段是不失效的。如果咱们为了不便,能够间接独自引入
spring cloud ribbon
<dependency>
<groupId>org.springframework.cloud</groupId>-->
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>-->
<version>2.2.2.RELEASE</version>
</dependency>
本文咱们是想脱离 springcloud,间接应用 ribbon,因而咱们能够间接 引入如下 GAV
<!-- 外围的通用性代码 -->
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-core</artifactId>
<version>${ribbon.version}</version>
</dependency>
<!-- 基于 apache httpClient 封装的 rest 客户端,集成了负载平衡模块,内嵌 http 心跳检测 -->
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-httpclient</artifactId>
<version>${ribbon.version}</version>
</dependency>
<!-- 负载平衡模块 -->
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-loadbalancer</artifactId>
<version>${ribbon.version}</version>
</dependency>
<!-- IClientConfig 配置相干 -->
<dependency>
<groupId>com.netflix.archaius</groupId>
<artifactId>archaius-core</artifactId>
<version>0.7.6</version>
</dependency>
<!-- IClientConfig 配置相干 -->
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.8</version>
</dependency>
2、创立 ribbon 元数据配置类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RuleDefinition {
/**
* 服务名称
*/
private String serviceName;
/**
* 命名空间,当服务名雷同时,能够通过 namesapce 来进行隔离辨别
* 未指定默认为 public
*/
@Builder.Default
private String namespace = DEFAULT_NAMESPACE;
/**
* 自定义负载平衡策略,未指定默认为轮询
*/
@Builder.Default
private String loadBalancerRuleClassName = RoundRobin;
/**
* 自定义心跳检测,未指定不检测
*/
@Builder.Default
private String loadBalancerPingClassName = DummyPing;
/**
* 服务列表,多个用英文逗号隔开
*/
private String listOfServers;
/**
* 该优先级大于 loadBalancerPingClassName
*/
private IPing ping;
/**
* 心跳距离,不配置默认是 10 秒,单位秒
*/
private int pingIntervalSeconds;
/**
* 该优先级大于 loadBalancerRuleClassName
*/
private IRule rule;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = PREFIX)
public class LoadBalanceProperty {
public static final String PREFIX = "lybgeek.loadbalance";
private List<RuleDefinition> rules;
public Map<String,RuleDefinition> getRuleMap(){if(CollectionUtils.isEmpty(rules)){return Collections.emptyMap();
}
Map<String,RuleDefinition> ruleDefinitionMap = new LinkedHashMap<>();
for (RuleDefinition rule : rules) {String key = rule.getServiceName() + RULE_JOIN + rule.getNamespace();
ruleDefinitionMap.put(key,rule);
}
return Collections.unmodifiableMap(ruleDefinitionMap);
}
}
3、创立负载平衡工厂【外围实现】
private final LoadBalanceProperty loadBalanceProperty;
// key:serviceName + nameSpace
private static final Map<String, ILoadBalancer> loadBalancerMap = new ConcurrentHashMap<>();
public ILoadBalancer getLoadBalancer(String serviceName,String namespace){
String key = serviceName + RULE_JOIN + namespace;
if(loadBalancerMap.containsKey(key)){return loadBalancerMap.get(key);
}
RuleDefinition ruleDefinition = getAvailableRuleDefinition(serviceName,namespace);
IPing ping = ruleDefinition.getPing();
if(ObjectUtils.isEmpty(ping)){// 无奈通过 ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + PING_CLASS_NAME, ruleDefinition.getLoadBalancerPingClassName());
//LoadBalancerBuilder 没提供通过 ClientConfig 配置 ping 办法,只能通过 withPing 批改
ping = getPing(serviceName,namespace);
}
IRule rule = ruleDefinition.getRule();
if(ObjectUtils.isEmpty(rule)){// 也能够通过 ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + RULE_CLASS_NAME, ruleDefinition.getLoadBalancerRuleClassName());
rule = getRule(serviceName,namespace);
}
// 配置服务列表
ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + SERVER_LIST, ruleDefinition.getListOfServers());
// 因为服务列表目前是配置写死,因而敞开列表更新,否则当触发定时更新时,会从新将服务列表状态复原原样, 这样会导致 server 的 isLive 状态不精确
// 不设置默认采纳 com.netflix.loadbalancer.PollingServerListUpdater
ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + SERVERLIST_UPDATER_CLASS_NAME, EmptyServerListUpdater.class.getName());
IClientConfig config = new DefaultClientConfigImpl(namespace);
config.loadProperties(serviceName);
ZoneAwareLoadBalancer<Server> loadBalancer = getLoadBalancer(config, ping, rule);
loadBalancerMap.put(key,loadBalancer);
if(ruleDefinition.getPingIntervalSeconds() > 0){
// 默认每隔 10 秒进行心跳检测
loadBalancer.setPingInterval(ruleDefinition.getPingIntervalSeconds());
}
return loadBalancer;
}
public ZoneAwareLoadBalancer<Server> getLoadBalancer(IClientConfig config, IPing ping, IRule rule){ZoneAwareLoadBalancer<Server> serverZoneAwareLoadBalancer = LoadBalancerBuilder.newBuilder()
.withClientConfig(config)
.withPing(ping)
.withRule(rule)
.buildDynamicServerListLoadBalancerWithUpdater();
return serverZoneAwareLoadBalancer;
}
/**
* 获取 iping
* @param serviceName
* @param namespace
* @return
*/
@SneakyThrows
public IPing getPing(String serviceName, String namespace){RuleDefinition ruleDefinition = getAvailableRuleDefinition(serviceName,namespace);
Class<?> loadBalancerPingClass = ClassUtils.forName(ruleDefinition.getLoadBalancerPingClassName(), Thread.currentThread().getContextClassLoader());
Assert.isTrue(IPing.class.isAssignableFrom(loadBalancerPingClass),String.format("loadBalancerPingClassName : [%s] is not Iping class type",ruleDefinition.getLoadBalancerPingClassName()));
return (IPing) BeanUtils.instantiateClass(loadBalancerPingClass);
}
/**
* 获取 loadbalanceRule
* @param serviceName
* @param namespace
* @return
*/
@SneakyThrows
public IRule getRule(String serviceName, String namespace){RuleDefinition ruleDefinition = getAvailableRuleDefinition(serviceName,namespace);
Class<?> loadBalancerRuleClass = ClassUtils.forName(ruleDefinition.getLoadBalancerRuleClassName(), Thread.currentThread().getContextClassLoader());
Assert.isTrue(IRule.class.isAssignableFrom(loadBalancerRuleClass),String.format("loadBalancerRuleClassName : [%s] is not Irule class type",ruleDefinition.getLoadBalancerRuleClassName()));
return (IRule) BeanUtils.instantiateClass(loadBalancerRuleClass);
}
private RuleDefinition getAvailableRuleDefinition(String serviceName,String namespace){Map<String, RuleDefinition> ruleMap = loadBalanceProperty.getRuleMap();
Assert.notEmpty(ruleMap,"ruleDefinition is empty");
String key = serviceName + RULE_JOIN + namespace;
RuleDefinition ruleDefinition = ruleMap.get(key);
Assert.notNull(ruleDefinition,String.format("NOT FOUND AvailableRuleDefinition with serviceName : [{}] in namespace:[{}]",serviceName,namespace));
return ruleDefinition;
}
外围实现类 :com.netflix.loadbalancer.LoadBalancerBuilder 利用该类创立相应的负载平衡
4、测试
a、 新起 2 个服务提供者占用 6666 端口、和 6667 端口
b、 在生产端的 application.yml 配置如下内容
lybgeek:
loadbalance:
rules:
- serviceName: provider
namespace: test
loadBalancerPingClassName: com.github.lybgeek.loadbalance.ping.TelnetPing
pingIntervalSeconds: 3
# loadBalancerRuleClassName: com.github.lybgeek.loadbalance.rule.CustomRoundRobinRule
listOfServers: 127.0.0.1:6666,127.0.0.1:6667
c、 测试类
@Override
public void run(ApplicationArguments args) throws Exception {ServerChooser serverChooser = ServerChooser.builder()
.loadBalancer(loadbalanceFactory.getLoadBalancer("provider", "test"))
.build();
while(true){Server reachableServer = serverChooser.getServer("provider");
if(reachableServer != null){System.out.println(reachableServer.getHostPort());
}
TimeUnit.SECONDS.sleep(1);
}
}
当服务提供者都失常提供服务时,察看控制台
能够察看以轮询的形式调用服务提供者,当断掉其中一台服务提供者时,再察看控制台
会发现只调用服务失常的那台
总结
独立应用 ribbon 其实不会很难,次要对 LoadBalancerBuilder 这个 API 相熟就能够定制本人想要的负载均衡器。springcloud 从 2020 版本就将 ribbon 废除了,转而要搀扶其亲儿子 loadbalancer,就目前性能而言,loadbalancer 还没 ribbon 丰盛,通过本文留念一下被 springcloud 遗弃的 ribbon
demo 链接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-ribbon-loadbalance