springcloud应用注册到多个注册中心以及多个服务实例的聚合
背景:
废话不多说,直接开始正题。
以下注册及发现以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的负载均衡策略以及远程调用了。大功告成。