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端口


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM