詳解如何獨(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:6667
c、 測(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)文章
Project?Reactor源碼解析publishOn使用示例
這篇文章主要為大家介紹了Project?Reactor源碼解析publishOn使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08Java Spring 控制反轉(zhuǎn)(IOC)容器詳解
這篇文章主要為大家詳細(xì)介紹了Spring控制反轉(zhuǎn)IoC入門使用的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10通過Java創(chuàng)建Socket連接到服務(wù)器方式
這篇文章主要介紹了通過Java創(chuàng)建Socket連接到服務(wù)器方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11基于eclipse.ini內(nèi)存設(shè)置的問題詳解
本篇文章是對(duì)eclipse.ini內(nèi)存設(shè)置的問題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05SpringBoot使用ResponseBodyEmitter處理流式日志和進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何使用ResponseBodyEmitter處理流式日志和進(jìn)度條,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-02-02