09.25 SpringCloud下微服務多個實例,調試時只調用本機提供服務的方法

Spring cloud的微服務,針對一個特定的服務,可能會有多個提供者,這本身對於部署以後提高系統健壯性和擴展性很有好處,但是在調試期間,會給開發者帶來一定的不便。

我們發佈微服務後,一般都是通過feignClient來執行調用的,也許我們的系統有十幾個或者幾十個微服務,相應的調用客戶端也有很多,微服務之間也有各種調用關係,不可避免會出現多個微服務依賴於一個特定微服務的情況。

比如,我們發佈了一個用戶及授權微服務,我們的登錄模塊需要使用這個微服務,用戶及權限管理也要使用這個微服務,很有可能登錄和權限管理是兩個開發者在分別維護,這樣他們在調試時會分別啟動各自的授權微服務,關係如圖:

SpringCloud下微服務多個實例,調試時只調用本機提供服務的方法

這樣,在服務註冊中心那裡就會得到AuthorizationServer微服務的多個服務提供者,在調試時,因為feignClient的負載平衡通過Ribbon來實現,而Ribbon默認使用的是輪詢策略(RoundRobinRule),它會輪流調用所有的可用服務,這會導致什麼樣的後果呢?就是調試人員A、B、C、D分別在調試過程中啟動了AuthorizationServer服務,他們在調試時,只有1/4的機會會調用本機的服務,為了調試一個功能,需要重試多次才能輪到自己的調試服務環境,這確實會帶來一定的問題,畢竟每個人的調試的功能不同,需要的測試數據很可能不同。為了解決這個問題,需要想辦法讓每次調用都只調用本機提供的服務。

那麼,有辦法實現這個功能嗎?答案是有的,而且不太複雜;我們先討論一下實現的思路:既然feignClient通過Ribbon找到需要的服務,那麼我們就可以讓Ribbon實現一種只調用本機的策略就可以了。具體一點就是,

  1. 通過接口獲取到所有的可用服務
  2. 獲取本機的ip地址
  3. 對比每一個可用服務的ip地址,如果和本機ip匹配,就是本機提供的服務了,這個服務就是我們要返回的服務。

Ribbon的規則通過繼承類AbstractLoadBalancerRule來實現,我們也可以繼承一下這個類,實現自己的規則類(具體實現見代碼註釋):

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
/**
* 只調用本機提供的服務
*/
public class CallLocalRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
if (lb == null) {
return null;
}
//選中的服務
Server choose = null;

//所有可用服務列表
List<server> allList = lb.getReachableServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//本機ip
String ip = "";
try {
InetAddress addi = InetAddress.getLocalHost();
//獲取本機ip
ip = addi.getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
//遍歷可用服務,找到服務地址為本機ip的服務
for (Server server : allList) {

String host = server.getHost().toLowerCase();
if (host.equals(ip)) {
choose = server;
break;
}
}
//如果沒有找到本機服務,取第一個可用服務
if (choose == null) {
choose = allList.get(0);
}
return choose;
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
/<server>

寫好這個規則類後,就是在系統裡具體使用了,使用起來也很簡單,在調用微服務的配置文件(application.yml)裡設置一下即可:

AuthorizationServer:
ribbon:
NFLoadBalancerRuleClassName: com.ggnykj.tools.loadbalancer.CallLocalRule

其中,AuthorizationServer是微服務的名稱,com.ggnykj.tools.loadbalancer.CallLocalRule是實現調用本地規則的類名稱。

上述方法可以靈活的對服務的調用進行處理,還可以有更多形式的應用,比如根據其它系統的配置來計算需要調用的服務,或者乾脆實現一個自己的輪詢、隨機調用等。如果只是簡單的指定本地調試的服務器或者指定的服務器,還可以通過對feignclient添加url註解來實現,比如:

@FeignClient(name="AuthorizationServer",url="http://localhost:9606")

public interface UserControllerClient {

但是這種實現本地調試的原理和本文講解的原理不一樣,本文是通過負載均衡服務來獲取要調用的服務的,而通過配置url註解可以直接跳過負載均衡服務,直接根據該url構造要請求的服務地址,性能會更高一些,兩者的具體實現代碼在類FeignClientFactoryBean中:

@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}

其中:

 if (!StringUtils.hasText(this.url)) {

判斷是否已經配置了url註解,如果沒有就通過負載均衡服務來獲取。

如果使用這種方式,使用起來最簡單,就是需要修改源代碼;使用本文的方式,需要增加配置,發佈前別忘了刪除這個配置,畢竟這個只是方便調試使用的,正式發佈的時候還是使用其他合適的規則吧。


分享到:


相關文章: