背景:
廢話不多說,直接開始正題。
以下注冊及發現以nacos和consul為例。
1.注冊
解決注冊沖突問題我當時也是參考D神的一篇文章,這里我就不再贅述了。在評論里我會放入D神的文章的連接。
2.發現
其實雙注冊的目的是為了一個微服務可以同時訂閱兩個不同的注冊中心進行服務發現。在我們使用ribbon或者
Feign進行遠程調用的時候其實是基於ribbon這一套結合springcloud的loadbalancer進行服務發現以及遠程調用。
所以我們要做到ribbon的服務聚合。
首先我們要知道ribbon是如何進行服務發現的,在RibbonClientConfiguration默認的ribbon配置中定義了默認的
IloadBalancer。在其實例化時會調用父類DynamicServerListLoadBalancer的構造方法從而更新ServerList。
@Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) { return this.propertiesFactory.get(ILoadBalancer.class, config, name); } return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); }
void restOfInit(IClientConfig clientConfig) { boolean primeConnection = this.isEnablePrimingConnections(); // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList() this.setEnablePrimingConnections(false); enableAndInitLearnNewServersFeature(); updateListOfServers(); if (primeConnection && this.getPrimeConnections() != null) { this.getPrimeConnections() .primeConnections(getReachableServers()); } this.setEnablePrimingConnections(primeConnection); LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString()); } @VisibleForTesting public void updateListOfServers() { List<T> servers = new ArrayList<T>(); if (serverListImpl != null) { servers = serverListImpl.getUpdatedListOfServers(); LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers); if (filter != null) { servers = filter.getFilteredListOfServers(servers); LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers); } } updateAllServerList(servers); }
所以對ribbon進行服務聚合就是如何把兩個注冊中心客戶端返回的Server進行聚合。
首先分析consul以及nacos對ribbon客戶端進行了哪些定制化配置。
@Configuration @ConditionalOnRibbonNacos public class NacosRibbonClientConfiguration { @Autowired private PropertiesFactory propertiesFactory; @Bean @ConditionalOnMissingBean public ServerList<?> ribbonServerList(IClientConfig config, NacosDiscoveryProperties nacosDiscoveryProperties) { if (this.propertiesFactory.isSet(ServerList.class, config.getClientName())) { ServerList serverList = this.propertiesFactory.get(ServerList.class, config, config.getClientName()); return serverList; } NacosServerList serverList = new NacosServerList(nacosDiscoveryProperties); serverList.initWithNiwsConfig(config); return serverList; } @Bean @ConditionalOnMissingBean public NacosServerIntrospector nacosServerIntrospector() { return new NacosServerIntrospector(); } }
@Configuration public class ConsulRibbonClientConfiguration { protected static final String VALUE_NOT_SET = "__not__set__"; protected static final String DEFAULT_NAMESPACE = "ribbon"; @Autowired private ConsulClient client; @Value("${ribbon.client.name}") private String serviceId = "client"; public ConsulRibbonClientConfiguration() { } public ConsulRibbonClientConfiguration(String serviceId) { this.serviceId = serviceId; } @Bean @ConditionalOnMissingBean public ServerList<?> ribbonServerList(IClientConfig config, ConsulDiscoveryProperties properties) { ConsulServerList serverList = new ConsulServerList(this.client, properties); serverList.initWithNiwsConfig(config); return serverList; } @Bean @ConditionalOnMissingBean public ServerListFilter<Server> ribbonServerListFilter() { return new HealthServiceServerListFilter(); } @Bean @ConditionalOnMissingBean public IPing ribbonPing() { return new ConsulPing(); } @Bean @ConditionalOnMissingBean public ConsulServerIntrospector serverIntrospector() { return new ConsulServerIntrospector(); } @PostConstruct public void preprocess() { setProp(this.serviceId, DeploymentContextBasedVipAddresses.key(), this.serviceId); setProp(this.serviceId, EnableZoneAffinity.key(), "true"); } protected void setProp(String serviceId, String suffix, String value) { // how to set the namespace properly? String key = getKey(serviceId, suffix); DynamicStringProperty property = getProperty(key); if (property.get().equals(VALUE_NOT_SET)) { ConfigurationManager.getConfigInstance().setProperty(key, value); } } protected DynamicStringProperty getProperty(String key) { return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET); } protected String getKey(String serviceId, String suffix) { return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix; } }
通過源碼分析我們可以得知,我們需要去定制化一個ServerList去將ConsulServerList以及NacosServerList進行一個組合。
這里我們不能去定義全局的configuration代替ribbon的懶加載,因為很多的Bean都是以子類的實現去返回的。我們對consul
和nacos的ribbon配置進行重寫。
首先排除Nacos和Consul的對ribbon的自動裝配類。
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter; import org.springframework.boot.autoconfigure.AutoConfigurationMetadata; /** * @author dingh * @version 1.0 * @date 2021/06/01 10:52 */ public class CustomizeRibbonAutoConfigurationCondition implements AutoConfigurationImportFilter { private static final String ribbonRegistryAutoConfiguration = "com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration"; private static final String ribbonConsulAutoConfiguration = "org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration"; @Override public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { boolean[] matches = new boolean[autoConfigurationClasses.length]; for (int i = 0; i < autoConfigurationClasses.length; i++) { if (!ribbonRegistryAutoConfiguration.equals(autoConfigurationClasses[i]) && !ribbonConsulAutoConfiguration.equals(autoConfigurationClasses[i])) { matches[i] = true; } } return matches; } }
這里利用AutoConfigurationImportFilter。需要在spring.factories進行配置。
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
demo.selector.CustomizeRibbonAutoConfigurationCondition
之后進行重寫。
@Configuration @EnableConfigurationProperties @ConditionalOnBean(SpringClientFactory.class) @ConditionalOnRibbonNacos @ConditionalOnNacosDiscoveryEnabled @ConditionalOnConsulEnabled @ConditionalOnProperty(value = "spring.cloud.consul.ribbon.enabled", matchIfMissing = true) @AutoConfigureAfter(RibbonAutoConfiguration.class) @RibbonClients(defaultConfiguration = CompositeRibbonConfiguration.class)
//這里將defaultConfiguration指向我們自己的配置類
public class CompositeRibbonAutoConfiguration { }
/** * @author dingh * @version 1.0 * @date 2021/06/01 10:58 */ public class CompositeRibbonConfiguration { protected static final String VALUE_NOT_SET = "__not__set__"; protected static final String DEFAULT_NAMESPACE = "ribbon"; @Autowired private ConsulClient client; @Autowired private PropertiesFactory propertiesFactory; @Value("${ribbon.client.name}") private String serviceId = "client"; @Bean @ConditionalOnMissingBean public ServerList<?> ribbonServerList(IClientConfig config, ConsulDiscoveryProperties consulDiscoveryProperties, NacosDiscoveryProperties nacosDiscoveryProperties) { ConsulServerList consulServerList = new ConsulServerList(this.client, consulDiscoveryProperties); consulServerList.initWithNiwsConfig(config); NacosServerList nacosServerList = new NacosServerList(nacosDiscoveryProperties); nacosServerList.initWithNiwsConfig(config); return new CompositeServerList(consulServerList,nacosServerList); } @Bean @ConditionalOnMissingBean public CompositeServerIntrospector serverIntrospector() { ConsulServerIntrospector consulServerIntrospector = new ConsulServerIntrospector(); NacosServerIntrospector nacosServerIntrospector = new NacosServerIntrospector(); return new CompositeServerIntrospector(consulServerIntrospector,nacosServerIntrospector); }
/** * @author dingh * @version 1.0 * @date 2021/06/01 10:58 */ public class CompositeServerIntrospector implements ServerIntrospector { private ConsulServerIntrospector consulServerIntrospector; private NacosServerIntrospector nacosServerIntrospector; public CompositeServerIntrospector(ConsulServerIntrospector consulServerIntrospector, NacosServerIntrospector nacosServerIntrospector){ this.consulServerIntrospector = consulServerIntrospector; this.nacosServerIntrospector = nacosServerIntrospector; } @Override public boolean isSecure(Server server) { return server instanceof ConsulServer ? consulServerIntrospector.isSecure(server) : nacosServerIntrospector.isSecure(server); } @Override public Map<String, String> getMetadata(Server server) { return server instanceof ConsulServer ? consulServerIntrospector.getMetadata(server) : nacosServerIntrospector.getMetadata(server); } /** * @author dingh * @version 1.0 * @date 2021/06/01 10:59 */ public class CompositeServerList implements ServerList<Server> { private ConsulServerList consulServerList; private NacosServerList nacosServerList; public CompositeServerList(ConsulServerList consulServerList, NacosServerList nacosServerList){ this.consulServerList = consulServerList; this.nacosServerList = nacosServerList; } @Override public List<Server> getInitialListOfServers() { List<Server> list = new ArrayList<>(); list.addAll(consulServerList.getInitialListOfServers()); list.addAll(nacosServerList.getInitialListOfServers()); return list; } @Override public List<Server> getUpdatedListOfServers() { List<Server> list = new ArrayList<>(); list.addAll(consulServerList.getUpdatedListOfServers()); list.addAll(nacosServerList.getUpdatedListOfServers()); return list; }
這里只去重寫了ServerList以及ServerIntrospector。對於consul和nacos只要不同時使用ribbon默認的配置其實都需要進行重寫,
只不過存在必要不必要。比如Iping,consul實現了ConsulPing而nacos使用的默認的dummyPing。
此時啟動consumer以及provider,我們發現兩個注冊中心都注冊了兩個服務。
通過consumer發送請求請求provider我們會發現此時有兩個實例
一個為consul拉取的實例一個為nacos拉取的實例。之后就是ribbon的負載均衡策略以及遠程調用了。大功告成。