foreword
Ribbon is Netflix's open source client load balancing tool. Ribbon implements a series of load balancing algorithms, and uses these load balancing algorithms to find corresponding services. Ribbon is well known to everyone, and it may come from spring cloud. Today, let’s talk about how to use ribbon alone to achieve business client load balancing.
Realize the key
The springcloud ribbon obtains the service list through the registration center, and the ribbon is used alone. Because there is no registration center blessing, the service list must be configured separately
example
1. Introduce ribbon GAV into the pom in the business project
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon</artifactId>
<version>2.2.2</version>
</dependency>
However, after importing it, I found that if I introduce netfiix-related classes, such as IPing, I will find that it cannot be introduced. The reason is that the life cycle of the jar that GAV depends on is runtime, that is, it only takes effect during the runtime or test phase, and it takes effect during the compilation phase. is not valid. For convenience, we can directly introduce
spring cloud ribbon separately
<dependency>
<groupId>org.springframework.cloud</groupId>-->
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>-->
<version>2.2.2.RELEASE</version>
</dependency>
In this article, we want to break away from springcloud and use ribbon directly, so we can directly introduce the following 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. Create ribbon metadata configuration class
@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. Create a load balancing factory [core implementation]
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;
}
Core implementation class : com.netflix.loadbalancer.LoadBalancerBuilder Use this class to create the corresponding load balancer
4. Test
a. Two new service providers occupy port 6666 and port 6667
b. The application.yml configuration on the consumer side is as follows
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. Test class
@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);
}
}
When the service provider is providing services normally, observe the console
You can observe that the service provider is called in a polling manner, and when one of the service providers is disconnected, observe the console
You will find that only the one with normal service is called
Summarize
It is actually not difficult to use the ribbon independently. You can customize the load balancer you want if you are familiar with the LoadBalancerBuilder API. Springcloud has abandoned the ribbon since the 2020 version, and instead supports its own son loadbalancer. As far as the current functions are concerned, loadbalancer is not as rich as ribbon. Through this article, I will commemorate the ribbon abandoned by springcloud
demo link
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-ribbon-loadbalance