由於Spring Cloud對Etcd的支援一直沒能從孵化器中出來,所以目前來說大多使用者還在使用Eureka和Consul,之前又因為Eureka 2.0不在開源的訊息,外加一些博眼球的標題黨媒體使得Eureka的使用者有所減少,所以,相信在選擇Spring Cloud的使用者群體中,應該有不少使用者會選擇Consul來做服務註冊與發現。
本文就來說一下,當我們使用Spring Cloud最新的Finchley版 + Consul 1.2.x時候最嚴重的一個坑:多例項註冊的問題。
問題解讀
問題:該問題可能在開發階段不一定會發現,但是線上上部署多例項的時候,將會發現Consul中只有一個例項。
原因:造成該問題的主要原因是Spring Cloud Consul在註冊的時候例項名(InstanceId)採用了:“服務名-埠號”(即:{spring.application.name}-{server.port})的值,可以看到這個例項名如果不改變埠號的情況下,例項名都是相同的。如果熟悉Spring Cloud Consul的讀者,可能會問老版本也是這個規則,怎麼沒有這個問題呢?。主要是由於Consul對例項唯一性的判斷標準也有改變,在老版本的Consul中,對於例項名相同,但是服務地址不同,依然會認為是不同的例項。在Consul 1.2.x中,服務例項名成為了叢集中的唯一標識,所以,也就導致了上述問題。
解決方法
既然知道了原因,那麼我們要解決它就可以有的放矢了。下面就來介紹兩個具體的解決方式:
方法一:通過配置屬性指定新的規則
下面舉個例子,通過spring.cloud.consul.discovery.instance-id引數直接來配置例項命名規則。這裡比較粗暴的通過隨機數來一起組織例項名。當然這樣的組織方式並不好,因為隨機數依然有衝突的可能,所以您還可以用更負責的規則來進行組織例項名。
spring.cloud.consul.discovery.instance-id=${spring.application.name}-${random.int[10000,99999]}
方法二:通過擴充套件ConsulServiceRegistry來重設例項名
由於通過配置屬性的方式對於定義例項名的能力有限,所以我們希望可以用更靈活的方式來定義。這時候我們就可以通過重寫ConsulServiceRegistry的register方法來修改。比如下面的實現:
public class MyConsulServiceRegistry extends ConsulServiceRegistry {
public MyConsulServiceRegistry(ConsulClient client, ConsulDiscoveryProperties properties, TtlScheduler ttlScheduler, HeartbeatProperties heartbeatProperties) {
super(client, properties, ttlScheduler, heartbeatProperties);
}
@Override
public void register(ConsulRegistration reg) {
reg.getService().setId(reg.getService().getName() + “-” + reg.getService().getAddress() + “-” + reg.getService().getPort());
super.register(reg);
}
}
上面通過拼接“服務名”-“ip地址”-“埠號”的方式,構造了一個絕對唯一的例項名,這樣就可以讓每個服務例項都能正確的註冊到Consul上了。