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;
}
View Code

我们可以看到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;
    }

}
View Code

我们可以看到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;
    }
}
View Code

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();
        }
    }
}
View Code

回到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();
    }
}
View Code

这个是我写好的,我们重写完之后我们可以看到返回了一个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();
}
View Code

(注意:这里一定要把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;
    }
}
View Code

然后把他注入进来就可以了。参数完成了那么我们回头看是哪个类的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;
    }
}
View Code

接下来配置一个启动类就可以了

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);
    }
}
View Code

yml配置:

spring:
  redis:
    host: redis地址
  application:
    name: redis-register-center
server:
  port: 9090
View Code

运行后我们在redis里就可以看见我们的本机的ip端口

posted @ 2018-04-20 22:25  杨天乐  阅读(6842)  评论(1编辑  收藏  举报