詳解如何獨(dú)立使用ribbon實(shí)現(xiàn)業(yè)務(wù)客戶端負(fù)載均衡
前言
ribbon是Netflix開源的客戶端負(fù)載均衡工具,ribbon實(shí)現(xiàn)一系列的負(fù)載均衡算法,通過這些負(fù)載均衡算法去查找相應(yīng)的服務(wù)。ribbon被大家所熟知,可能是來源于spring cloud,今天就來聊聊如何單獨(dú)使用ribbon來實(shí)現(xiàn)業(yè)務(wù)客戶端負(fù)載均衡
實(shí)現(xiàn)關(guān)鍵
springcloud ribbon獲取服務(wù)列表是通過注冊(cè)中心,而單獨(dú)使用ribbon,因?yàn)闆]有注冊(cè)中心加持,就得單獨(dú)配置服務(wù)列表
示例
1、在業(yè)務(wù)項(xiàng)目中pom引入ribbon GAV
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon</artifactId>
<version>2.2.2</version>
</dependency>不過引進(jìn)去,發(fā)現(xiàn)如果引入netfiix的相關(guān)的類,比如IPing,會(huì)發(fā)現(xiàn)引不進(jìn)去,原因是因?yàn)檫@個(gè)GAV里面依賴的jar的生命周期是runtime,即在運(yùn)行期或者測(cè)試階段才生效,在編譯階段是不生效的。如果我們?yōu)榱朔奖?,可以直接單?dú)引入
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客戶端,集成了負(fù)載均衡模塊,內(nèi)嵌http心跳檢測(cè)-->
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-httpclient</artifactId>
<version>${ribbon.version}</version>
</dependency>
<!-- 負(fù)載均衡模塊-->
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-loadbalancer</artifactId>
<version>${ribbon.version}</version>
</dependency>
<!-- IClientConfig配置相關(guān)-->
<dependency>
<groupId>com.netflix.archaius</groupId>
<artifactId>archaius-core</artifactId>
<version>0.7.6</version>
</dependency>
<!-- IClientConfig配置相關(guān)-->
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.8</version>
</dependency>2、創(chuàng)建ribbon元數(shù)據(jù)配置類
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RuleDefinition {
/**
* 服務(wù)名稱
*/
private String serviceName;
/**
* 命名空間,當(dāng)服務(wù)名相同時(shí),可以通過namesapce來進(jìn)行隔離區(qū)分
* 未指定默認(rèn)為public
*/
@Builder.Default
private String namespace = DEFAULT_NAMESPACE;
/**
* 自定義負(fù)載均衡策略,未指定默認(rèn)為輪詢
*/
@Builder.Default
private String loadBalancerRuleClassName = RoundRobin;
/**
* 自定義心跳檢測(cè),未指定不檢測(cè)
*/
@Builder.Default
private String loadBalancerPingClassName = DummyPing;
/**
* 服務(wù)列表,多個(gè)用英文逗號(hào)隔開
*/
private String listOfServers;
/**
* 該優(yōu)先級(jí)大于loadBalancerPingClassName
*/
private IPing ping;
/**
* 心跳間隔,不配置默認(rèn)是10秒,單位秒
*/
private int pingIntervalSeconds;
/**
* 該優(yōu)先級(jí)大于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、創(chuàng)建負(fù)載均衡工廠【核心實(shí)現(xiàn)】
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);
}
// 配置服務(wù)列表
ConfigurationManager.getConfigInstance().setProperty(serviceName + DOT + namespace + DOT + SERVER_LIST, ruleDefinition.getListOfServers());
// 因?yàn)榉?wù)列表目前是配置寫死,因此關(guān)閉列表更新,否則當(dāng)觸發(fā)定時(shí)更新時(shí),會(huì)重新將服務(wù)列表狀態(tài)恢復(fù)原樣,這樣會(huì)導(dǎo)致server的isLive狀態(tài)不準(zhǔn)確
// 不設(shè)置默認(rèn)采用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){
// 默認(rèn)每隔10秒進(jìn)行心跳檢測(cè)
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;
}核心實(shí)現(xiàn)類:com.netflix.loadbalancer.LoadBalancerBuilder 利用該類創(chuàng)建相應(yīng)的負(fù)載均衡
4、測(cè)試
a、 新起2個(gè)服務(wù)提供者占用6666端口、和6667端口
b、 在消費(fèi)端的application.yml配置如下內(nèi)容
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:6667c、 測(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);
}
}當(dāng)服務(wù)提供者都正常提供服務(wù)時(shí),觀察控制臺(tái)

可以觀察以輪詢的方式調(diào)用服務(wù)提供者,當(dāng)斷掉其中一臺(tái)服務(wù)提供者時(shí),再觀察控制臺(tái)

會(huì)發(fā)現(xiàn)只調(diào)用服務(wù)正常的那臺(tái)
總結(jié)
獨(dú)立使用ribbon其實(shí)不會(huì)很難,主要對(duì)LoadBalancerBuilder這個(gè)API熟悉就可以定制自己想要的負(fù)載均衡器。springcloud從2020版本就將ribbon廢棄了,轉(zhuǎn)而要扶持其親兒子loadbalancer,就目前功能而言,loadbalancer還沒ribbon豐富,通過本文紀(jì)念一下被springcloud遺棄的ribbon
以上就是詳解如何獨(dú)立使用ribbon實(shí)現(xiàn)業(yè)務(wù)客戶端負(fù)載均衡的詳細(xì)內(nèi)容,更多關(guān)于ribbon客戶端負(fù)載均衡的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot使用Maven打包異常-引入外部jar的問題及解決方案
這篇文章主要介紹了SpringBoot使用Maven打包異常-引入外部jar,需要的朋友可以參考下2020-06-06
MyBatis-Plus 集成動(dòng)態(tài)多數(shù)據(jù)源的實(shí)現(xiàn)示例
本文主要介紹了MyBatis-Plus 集成動(dòng)態(tài)多數(shù)據(jù)源的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
SpringBoot動(dòng)態(tài)配置數(shù)據(jù)源的三種實(shí)現(xiàn)方式
本文介紹了Spring Boot動(dòng)態(tài)數(shù)據(jù)源的三種實(shí)現(xiàn)方式:通過配置文件設(shè)置數(shù)據(jù)源,自定義DynamicDataSource繼承AbstractRoutingDataSource實(shí)現(xiàn)動(dòng)態(tài)切換,利用ThreadLocal管理數(shù)據(jù)源標(biāo)識(shí),并結(jié)合AOP控制切換邏輯,同時(shí)需整合MyBatis-Plus配置,需要的朋友可以參考下2025-08-08
SpringBoot實(shí)現(xiàn)微信支付接口調(diào)用及回調(diào)函數(shù)(商戶參數(shù)獲取)
本文詳細(xì)介紹了使用SpringBoot實(shí)現(xiàn)微信支付接口調(diào)用及回調(diào)函數(shù)的步驟,提供了代碼實(shí)現(xiàn)的具體步驟和工具類的創(chuàng)建,感興趣的朋友跟隨小編一起看看吧2024-11-11
Java實(shí)現(xiàn)將png格式圖片轉(zhuǎn)換成jpg格式圖片的方法【測(cè)試可用】
這篇文章主要介紹了Java實(shí)現(xiàn)將png格式圖片轉(zhuǎn)換成jpg格式圖片的方法,涉及java文件讀寫及圖形創(chuàng)建等相關(guān)操作技巧,需要的朋友可以參考下2018-03-03
Java中數(shù)組轉(zhuǎn)list的兩種簡(jiǎn)單實(shí)現(xiàn)方式
這篇文章主要介紹了兩種將數(shù)組轉(zhuǎn)換為L(zhǎng)ist的方法,兩種方法分別是使用Arrays.asList()方法和使用ArrayList構(gòu)造函數(shù),文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-03-03

