注冊中心
在分布式架構中注冊中心起到了管理各種服務功能包括服務的注冊、發現、熔斷、負載、降級等功能,在分布式架構中起到了不可替代的作用。常見的注冊中心有eureka,zookeeper等等,在springcloud中,它封裝了Netflix公司開發的Eureka模塊來實現服務的注冊與發現,簡單的來說注冊中心里會存放着我們的ip、端口、業務,如果是只是存儲我們可以想到很多,數據庫,文件,內存,redis都是可以的存的。那么今天小編這里就把redis當成注冊中心來實現。
配置與分析
在springcloud中我們創建一個業務模塊去找注冊中心時,一般會@EnableDiscoveryClient、@EnableEurekaClient、@EnableEurekaServer,不過@EnableEurekaClient和@EnableEurekaServer都是Eureka給我提供的。這里我們就要SpringCloud給我們提供的@EnableDiscoveryClient,點擊進入@EnableDiscoveryClient源碼中。
/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.client.discovery; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; /** * Annotation to enable a DiscoveryClient implementation. * @author Spencer Gibb */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(EnableDiscoveryClientImportSelector.class) public @interface EnableDiscoveryClient { /** * If true, the ServiceRegistry will automatically register the local server. */ boolean autoRegister() default true; }
我們可以看到autoRegister()方法,我們可以看上面的注釋,如果為true,將把本地服務注冊到ServiceRegistry中。ServiceRegistry可以理解為將服務注冊到注冊中心的一個通道,那么我們想把服務注冊到注冊中心你個方法就必須返回true。
可以看到注解上的注解@Import(EnableDiscoveryClientImportSelector.class)
這個注解引入了這個類,那我們再進入這個類
/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.client.discovery; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.cloud.commons.util.SpringFactoryImportSelector; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.Order; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.type.AnnotationMetadata; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; /** * @author Spencer Gibb */ @Order(Ordered.LOWEST_PRECEDENCE - 100) public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector<EnableDiscoveryClient> { @Override public String[] selectImports(AnnotationMetadata metadata) { String[] imports = super.selectImports(metadata); AnnotationAttributes attributes = AnnotationAttributes.fromMap( metadata.getAnnotationAttributes(getAnnotationClass().getName(), true)); boolean autoRegister = attributes.getBoolean("autoRegister"); if (autoRegister) { List<String> importsList = new ArrayList<>(Arrays.asList(imports)); importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration"); imports = importsList.toArray(new String[0]); } else { Environment env = getEnvironment(); if(ConfigurableEnvironment.class.isInstance(env)) { ConfigurableEnvironment configEnv = (ConfigurableEnvironment)env; LinkedHashMap<String, Object> map = new LinkedHashMap<>(); map.put("spring.cloud.service-registry.auto-registration.enabled", false); MapPropertySource propertySource = new MapPropertySource( "springCloudDiscoveryClient", map); configEnv.getPropertySources().addLast(propertySource); } } return imports; } @Override protected boolean isEnabled() { return new RelaxedPropertyResolver(getEnvironment()).getProperty( "spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE); } @Override protected boolean hasDefaultFactory() { return true; } }
我們可以看到selectImports方法里的判斷,autoRegister的判斷,當我autoRegister()為true時才會執行List的添加,添加的內容我們可以發現Lis添加的內容,只不過是個配置類,不過我們順藤摸瓜就可以找到serviceregistry的包,我們可以發現包下有許多包是做配置的,那么我們找到最核心的類AbstractAutoServiceRegistration類找到他的父類AbstractDiscoveryLifecycle,在這個類中我們找到抽象register()方法,看一下上面的注釋我們就可以發現這個才是用來注冊的方法,接下我們找到重寫的start()方法可以看到register()方法在這里被調用。
AbstractAutoServiceRegistration源碼
package org.springframework.cloud.client.serviceregistry; import org.springframework.cloud.client.discovery.AbstractDiscoveryLifecycle; /** * Lifecycle methods that may be useful and common to {@link ServiceRegistry} implementations. * * TODO: document the lifecycle * * @param <R> registration type passed to the {@link ServiceRegistry}. * * @author Spencer Gibb */ @SuppressWarnings("deprecation") public abstract class AbstractAutoServiceRegistration<R extends Registration> extends AbstractDiscoveryLifecycle implements AutoServiceRegistration { private final ServiceRegistry<R> serviceRegistry; private AutoServiceRegistrationProperties properties; @Deprecated protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry) { this.serviceRegistry = serviceRegistry; } protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) { this.serviceRegistry = serviceRegistry; this.properties = properties; } protected ServiceRegistry<R> getServiceRegistry() { return this.serviceRegistry; } protected abstract R getRegistration(); protected abstract R getManagementRegistration(); /** * Register the local service with the {@link ServiceRegistry} */ @Override protected void register() { this.serviceRegistry.register(getRegistration()); } /** * Register the local management service with the {@link ServiceRegistry} */ @Override protected void registerManagement() { R registration = getManagementRegistration(); if (registration != null) { this.serviceRegistry.register(registration); } } /** * De-register the local service with the {@link ServiceRegistry} */ @Override protected void deregister() { this.serviceRegistry.deregister(getRegistration()); } /** * De-register the local management service with the {@link ServiceRegistry} */ @Override protected void deregisterManagement() { R registration = getManagementRegistration(); if (registration != null) { this.serviceRegistry.deregister(registration); } } @Override public void stop() { if (this.getRunning().compareAndSet(true, false) && isEnabled()) { deregister(); if (shouldRegisterManagement()) { deregisterManagement(); } this.serviceRegistry.close(); } } @Override protected boolean shouldRegisterManagement() { if (this.properties == null || this.properties.isRegisterManagement()) { return super.shouldRegisterManagement(); } return false; } }
AbstractDiscoveryLifecycle源碼
/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.client.discovery; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PreDestroy; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.core.env.Environment; /** * Lifecycle methods that may be useful and common to various DiscoveryClient implementations. * * @deprecated use {@link org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration} instead. This class will be removed in the next release train. * * @author Spencer Gibb */ @Deprecated public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle, ApplicationContextAware, ApplicationListener<EmbeddedServletContainerInitializedEvent> { private static final Log logger = LogFactory.getLog(AbstractDiscoveryLifecycle.class); private boolean autoStartup = true; private AtomicBoolean running = new AtomicBoolean(false); private int order = 0; private ApplicationContext context; private Environment environment; private AtomicInteger port = new AtomicInteger(0); protected ApplicationContext getContext() { return context; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; this.environment = this.context.getEnvironment(); } @Deprecated protected Environment getEnvironment() { return environment; } @Deprecated protected AtomicInteger getPort() { return port; } @Override public boolean isAutoStartup() { return this.autoStartup; } @Override public void stop(Runnable callback) { try { stop(); } catch (Exception e) { logger.error("A problem occurred attempting to stop discovery lifecycle", e); } callback.run(); } @Override public void start() { if (!isEnabled()) { if (logger.isDebugEnabled()) { logger.debug("Discovery Lifecycle disabled. Not starting"); } return; } // only set the port if the nonSecurePort is 0 and this.port != 0 if (this.port.get() != 0 && getConfiguredPort() == 0) { setConfiguredPort(this.port.get()); } // only initialize if nonSecurePort is greater than 0 and it isn't already running // because of containerPortInitializer below if (!this.running.get() && getConfiguredPort() > 0) { register(); if (shouldRegisterManagement()) { registerManagement(); } this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration())); this.running.compareAndSet(false, true); } } @Deprecated protected abstract int getConfiguredPort(); @Deprecated protected abstract void setConfiguredPort(int port); /** * @return if the management service should be registered with the {@link ServiceRegistry} */ protected boolean shouldRegisterManagement() { return getManagementPort() != null && ManagementServerPortUtils.isDifferent(this.context); } /** * @return the object used to configure the registration */ @Deprecated protected abstract Object getConfiguration(); /** * Register the local service with the DiscoveryClient */ protected abstract void register(); /** * Register the local management service with the DiscoveryClient */ protected void registerManagement() { } /** * De-register the local service with the DiscoveryClient */ protected abstract void deregister(); /** * De-register the local management service with the DiscoveryClient */ protected void deregisterManagement() { } /** * @return true, if the {@link DiscoveryLifecycle} is enabled */ protected abstract boolean isEnabled(); /** * @return the serviceId of the Management Service */ @Deprecated protected String getManagementServiceId() { // TODO: configurable management suffix return this.context.getId() + ":management"; } /** * @return the service name of the Management Service */ @Deprecated protected String getManagementServiceName() { // TODO: configurable management suffix return getAppName() + ":management"; } /** * @return the management server port */ @Deprecated protected Integer getManagementPort() { return ManagementServerPortUtils.getPort(this.context); } /** * @return the app name, currently the spring.application.name property */ @Deprecated protected String getAppName() { return this.environment.getProperty("spring.application.name", "application"); } @Override public void stop() { if (this.running.compareAndSet(true, false) && isEnabled()) { deregister(); if (shouldRegisterManagement()) { deregisterManagement(); } } } @PreDestroy public void destroy() { stop(); } @Override public boolean isRunning() { return this.running.get(); } protected AtomicBoolean getRunning() { return running; } @Override public int getOrder() { return this.order; } @Override public int getPhase() { return 0; } @Override @Deprecated public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { // TODO: take SSL into account // Don't register the management port as THE port if (!"management".equals(event.getApplicationContext().getNamespace())) { this.port.compareAndSet(0, event.getEmbeddedServletContainer().getPort()); this.start(); } } }
回到AbstractAutoServiceRegistration,我們可以參考AbstractAutoServiceRegistration中重新的regiter()方法調用了serviceRegistry的register()方法,那么我們進入register()方法參考一下注釋我們可以發現傳入的參數類,主要傳入我們的ip 端口 業務等等,那么方法的參數是它的一個方法,那么我們重寫他AbstractAutoServiceRegistration類看一下這個方法我們需要返回一個什么類進去。
package com.rk.ytl.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import org.springframework.context.annotation.Configuration; /** * @author 楊天樂 * @date 2018/4/20 16:26 */ @Configuration public class RedisAutoServiceRegistration extends AbstractAutoServiceRegistration { @Autowired private MyServiceInstance myServiceInstance; @Autowired private AutoServiceRegistrationProperties autoServiceRegistrationProperties; @Value("${server.port}") private Integer port; protected RedisAutoServiceRegistration(ServiceRegistry serviceRegistry) { super(serviceRegistry); } @Override protected Registration getRegistration() { return myServiceInstance; } @Override protected Registration getManagementRegistration() { return null; } @Override protected int getConfiguredPort() { return port; } @Override protected void setConfiguredPort(int port) { } @Override protected Object getConfiguration() { return null; } @Override protected boolean isEnabled() { return autoServiceRegistrationProperties.isEnabled(); } }
這個是我寫好的,我們重寫完之后我們可以看到返回了一個Registration對象,那么可以根據源碼找到Registration對象繼承了ServiceInstance,我們看一下源碼
/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.client; import java.net.URI; import java.util.Map; /** * Represents an instance of a Service in a Discovery System * @author Spencer Gibb */ public interface ServiceInstance { /** * @return the service id as registered. */ String getServiceId(); /** * @return the hostname of the registered ServiceInstance */ String getHost(); /** * @return the port of the registered ServiceInstance */ int getPort(); /** * @return if the port of the registered ServiceInstance is https or not */ boolean isSecure(); /** * @return the service uri address */ URI getUri(); /** * @return the key value pair metadata associated with the service instance */ Map<String, String> getMetadata(); }
(注意:這里一定要把isEnable()方法設置為true,不然是不可以的,至於為什么,大家可以參考AbstractDiscoveryLifecycle的start()方法如果不為true是執行不了register()方法的)
端口,ip,業務都在這里,那么我們就是找他了,再寫一個類去實現它。
package com.rk.ytl.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.stereotype.Component; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.Map; /** * @author 楊天樂 * @date 2018/4/20 16:29 */ @Component public class MyServiceInstance implements Registration { @Value("${spring.application.name}") private String applicationName; @Value("${server.port}") private Integer port; @Override public String getServiceId() { return applicationName; } @Override public String getHost() { String host = ""; try { InetAddress inetAddress = InetAddress.getLocalHost(); host =inetAddress.getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } return host; } @Override public int getPort() { return port; } @Override public boolean isSecure() { return false; } @Override public URI getUri() { return null; } @Override public Map<String, String> getMetadata() { return null; } }
然后把他注入進來就可以了。參數完成了那么我們回頭看是哪個類的register()方法是serviceRegistry類的,那么上面說到了serviceRegistry類是一個把服務中到服務中心的通道,那么我們就可以自己實現通過
serviceRegistry通向redis的通道。
package com.rk.ytl.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; /** * @author 楊天樂 * @date 2018/4/20 17:02 */ @Component public class MyServiceRegistry implements ServiceRegistry { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public void register(Registration registration) { ValueOperations<String, String> redis = stringRedisTemplate.opsForValue(); String hostname= registration.getHost()+":"+registration.getPort(); String serviceId = registration.getServiceId(); redis.set(serviceId,hostname); } @Override public void deregister(Registration registration) { stringRedisTemplate.delete(registration.getServiceId()); } @Override public void close() { } @Override public void setStatus(Registration registration, String status) { } @Override public Object getStatus(Registration registration) { return null; } }
接下來配置一個啟動類就可以了
package com.rk.ytl; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @author 楊天樂 * @date 2018/4/20 16:12 */ @SpringBootApplication @EnableDiscoveryClient public class RedisRegisterApplication { public static void main(String[] args) { SpringApplication.run(RedisRegisterApplication.class,args); } }
yml配置:
spring: redis: host: redis地址 application: name: redis-register-center server: port: 9090
運行后我們在redis里就可以看見我們的本機的ip端口

