原由
公司項(xiàng)目使用Spring Cloud微服務(wù)架構(gòu),隨著服務(wù)的增加,開發(fā)調(diào)試變得有些麻煩。有些同事的電腦配置不高,無法在本地啟動(dòng)這么多的服務(wù)。公司有自己的dev環(huán)境,對(duì)于開發(fā)當(dāng)前修改的服務(wù)可以直接注冊(cè)到dev環(huán)境,使用其他未修改的服務(wù),如Eureka,config等。但是,如果這個(gè)時(shí)候有前端正在dev調(diào)試,則會(huì)出現(xiàn)網(wǎng)關(guān)轉(zhuǎn)發(fā)到本地開發(fā)中的服務(wù),出現(xiàn)異常。
出現(xiàn)上述情況的原因是因?yàn)镽ibbon默認(rèn)負(fù)載均衡策略是輪詢,當(dāng)一個(gè)服務(wù)出現(xiàn)多個(gè)實(shí)例的時(shí)候,網(wǎng)關(guān)轉(zhuǎn)發(fā)或者服務(wù)消費(fèi)時(shí)就會(huì)采用Ribbon的負(fù)載均衡策略,出現(xiàn)指向開發(fā)本地實(shí)例的情況。
知道原因之后解決方法就呼之欲出了:自定義負(fù)載均衡策略,使dev環(huán)境中的微服務(wù)消費(fèi)或轉(zhuǎn)發(fā)都指定到固定dev環(huán)境中服務(wù),不讓其指定到開發(fā)本地起的服務(wù)。
實(shí)現(xiàn)策略
com.netflix.loadbalancer.IRule是Ribbon的負(fù)載均衡策略接口:
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
Ribbon自帶有幾種策略實(shí)現(xiàn):
- RandomRule:隨機(jī)選取負(fù)載均衡策略,隨機(jī)Random對(duì)象,在所有服務(wù)實(shí)例中隨機(jī)找一個(gè)服務(wù)的索引號(hào),然后從上線的服務(wù)中獲取對(duì)應(yīng)的服務(wù)。
- RoundRobinRule:線性輪詢負(fù)載均衡策略。
- WeightedResponseTimeRule:響應(yīng)時(shí)間作為選取權(quán)重的負(fù)載均衡策略,根據(jù)平均響應(yīng)時(shí)間計(jì)算所有服務(wù)的權(quán)重,響應(yīng)時(shí)間越短的服務(wù)權(quán)重越大,被選中的概率越高。剛啟動(dòng)時(shí),如果統(tǒng)計(jì)信息不足,則使用線性輪詢策略,等信息足夠時(shí),再切換到WeightedResponseTimeRule。
- RetryRule:使用線性輪詢策略獲取服務(wù),如果獲取失敗則在指定時(shí)間內(nèi)重試,重新獲取可用服務(wù)。
- ClientConfigEnabledRoundRobinRule:默認(rèn)通過線性輪詢策略選取服務(wù)。通過繼承該類,并且對(duì)choose方法進(jìn)行重寫,可以實(shí)現(xiàn)更多的策略,繼承后保底使用RoundRobinRule策略。
- BestAvailableRule:繼承自ClientConfigEnabledRoundRobinRule。從所有沒有斷開的服務(wù)中,選取到目前為止請(qǐng)求數(shù)量最小的服務(wù)。
- PredicateBasedRule:抽象類,提供一個(gè)choose方法的模板,通過調(diào)用AbstractServerPredicate實(shí)現(xiàn)類的過濾方法來過濾出目標(biāo)的服務(wù),再通過輪詢方法選出一個(gè)服務(wù)。
- AvailabilityFilteringRule:按可用性進(jìn)行過濾服務(wù)的負(fù)載均衡策略,會(huì)先過濾掉由于多次訪問故障而處于斷路器跳閘狀態(tài)的服務(wù),還有并發(fā)的連接數(shù)超過閾值的服務(wù),然后對(duì)剩余的服務(wù)列表進(jìn)行線性輪詢。
- ZoneAvoidanceRule:本身沒有重寫choose方法,用的還是抽象父類PredicateBasedRule的choose。
其中沒有我們需要的策略,那我們就自己實(shí)現(xiàn)一個(gè)IRule。我們參照默認(rèn)的RoundRobinRule,繼承AbstractLoadBalancerRule(實(shí)現(xiàn)了IRule的loadBanlancer相關(guān)方法):
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author hubert
* @version 1.0
* @date 2019/9/5 上午10:22
*/
@Slf4j
public class MyRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
log.info("key:" + o);
List<Server> allServers = getLoadBalancer().getAllServers();
log.info(allServers.toString());
return allServers.get(0);
}
}
我們需要實(shí)現(xiàn)choose方法來完成我們自己的策略,getLoadBalancer()可以獲取當(dāng)前服務(wù)的所有實(shí)例Server的信息,我們需要從中挑選一個(gè)作為choose方法的返回。這里就簡(jiǎn)單地返回列表第一個(gè)Server。
配置
自定義策略實(shí)現(xiàn)之后需要配置,我們要在服務(wù)調(diào)用方(使用@FeignClient注解的類的方法)進(jìn)行配置。
簡(jiǎn)單配置
如果所有調(diào)用服務(wù)的策略是相同的,我們最簡(jiǎn)單的配置就是在MyRule類上添加@Component注解,讓Spring發(fā)現(xiàn)并注入該類。Ribbon會(huì)優(yōu)先使用我們實(shí)現(xiàn)的策略。
如果針對(duì)不同的服務(wù)需要不同的策略,則可以參考官方實(shí)例的配置。
@RibbonClient配置
這種方式需要在啟動(dòng)類中添加注解:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@RibbonClient(value = "service-hi", configuration = RuleConfig.class)
public class ServiceFeignApplication {
RuleConfig如下:
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author hubert
* @version 1.0
* @date 2019/9/5 上午10:40
*/
@Configuration
public class RuleConfig {
@Bean
public IRule ribbonRule() {
return new MyRule();
}
}
簡(jiǎn)單來說就是注入我們自己實(shí)現(xiàn)的IRule,然后配置給RioonClient。
@RibbonClient的value/name屬性設(shè)置的是被調(diào)用的服務(wù)名(不是當(dāng)前正在配置的服務(wù)名),也即使聲明@FeignClient是指定的value/name。如果當(dāng)前服務(wù)調(diào)用多個(gè)其他服務(wù),可以用@RibbonClient給每個(gè)被調(diào)用服務(wù)設(shè)置不同的策略。
需要注意的是RuleConfig類需要放在啟動(dòng)類的上層(或者不同包名),避免Spring默認(rèn)掃描到。否則會(huì)出現(xiàn)“簡(jiǎn)單配置”效果,即所有服務(wù)都使用這個(gè)策略,無法實(shí)現(xiàn)不同服務(wù)不同策略的效果。
配置文件配置
與@RibbonClient類似,可以為每個(gè)微服務(wù)單獨(dú)配置策略。我們?cè)趛ml配置文件中添加<serverName>.ribbon.NFLoadBalancerRuleClassName:
service-hi:
ribbon:
NFLoadBalancerRuleClassName: com.hubert.feign.MyRule
<serverName>也就是聲明@FeignClient是指定的value/name。
實(shí)驗(yàn)
配置完成之后就是啟動(dòng)實(shí)驗(yàn)了,我們依次啟動(dòng)2個(gè)被調(diào)用服務(wù),以及一個(gè)調(diào)用服務(wù):
2019-09-05 14:31:45.408 INFO 1143 --- [ix-service-hi-2] com.hubert.feign.MyRule : key:null
2019-09-05 14:31:45.409 INFO 1143 --- [ix-service-hi-2] com.hubert.feign.MyRule : [192.168.31.244:8882, 192.168.31.244:8881]
發(fā)現(xiàn)日志打印,說明使用了我們自定義的策略,并且效果也是始終調(diào)用第一個(gè)服務(wù)。