springcloud源码分析(一)之采用redis实现注册中心
注册中心
在分布式架构中注册中心起到了管理各种服务功能包括服务的注册、发现、熔断、负载、降级等功能,在分布式架构中起到了不可替代的作用。常见的注册中心有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端口