深入理解SpringCloud之DiscoveryClient探究

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:   当我们使用@DiscoveryClient注解的时候,会不会有如下疑问:它为什么会进行注册服务的操作,它不是应该用作服务发现的吗?下面我们就来深入的探究一下其源码。 一、Springframework的LifeCycle接口   要搞明白这个问题我们需要了解一下这个重要的接口: /*...

  当我们使用@DiscoveryClient注解的时候,会不会有如下疑问:它为什么会进行注册服务的操作,它不是应该用作服务发现的吗?下面我们就来深入的探究一下其源码。

一、Springframework的LifeCycle接口

  要搞明白这个问题我们需要了解一下这个重要的接口:

/*
 * Copyright 2002-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.context;

/**
 * A common interface defining methods for start/stop lifecycle control.
 * The typical use case for this is to control asynchronous processing.
 * <b>NOTE: This interface does not imply specific auto-startup semantics.
 * Consider implementing {@link SmartLifecycle} for that purpose.</b>
 *
 * <p>Can be implemented by both components (typically a Spring bean defined in a
 * Spring context) and containers  (typically a Spring {@link ApplicationContext}
 * itself). Containers will propagate start/stop signals to all components that
 * apply within each container, e.g. for a stop/restart scenario at runtime.
 *
 * <p>Can be used for direct invocations or for management operations via JMX.
 * In the latter case, the {@link org.springframework.jmx.export.MBeanExporter}
 * will typically be defined with an
 * {@link org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler},
 * restricting the visibility of activity-controlled components to the Lifecycle
 * interface.
 *
 * <p>Note that the Lifecycle interface is only supported on <b>top-level singleton
 * beans</b>. On any other component, the Lifecycle interface will remain undetected
 * and hence ignored. Also, note that the extended {@link SmartLifecycle} interface
 * provides integration with the application context's startup and shutdown phases.
 *
 * @author Juergen Hoeller
 * @since 2.0
 * @see SmartLifecycle
 * @see ConfigurableApplicationContext
 * @see org.springframework.jms.listener.AbstractMessageListenerContainer
 * @see org.springframework.scheduling.quartz.SchedulerFactoryBean
 */
public interface Lifecycle {

    /**
     * Start this component.
     * <p>Should not throw an exception if the component is already running.
     * <p>In the case of a container, this will propagate the start signal to all
     * components that apply.
     * @see SmartLifecycle#isAutoStartup()
     */
    void start();

    /**
     * Stop this component, typically in a synchronous fashion, such that the component is
     * fully stopped upon return of this method. Consider implementing {@link SmartLifecycle}
     * and its {@code stop(Runnable)} variant when asynchronous stop behavior is necessary.
     * <p>Note that this stop notification is not guaranteed to come before destruction: On
     * regular shutdown, {@code Lifecycle} beans will first receive a stop notification before
     * the general destruction callbacks are being propagated; however, on hot refresh during a
     * context's lifetime or on aborted refresh attempts, only destroy methods will be called.
     * <p>Should not throw an exception if the component isn't started yet.
     * <p>In the case of a container, this will propagate the stop signal to all components
     * that apply.
     * @see SmartLifecycle#stop(Runnable)
     * @see org.springframework.beans.factory.DisposableBean#destroy()
     */
    void stop();

    /**
     * Check whether this component is currently running.
     * <p>In the case of a container, this will return {@code true} only if <i>all</i>
     * components that apply are currently running.
     * @return whether the component is currently running
     */
    boolean isRunning();

}
View Code

  该接口定义启动/停止生命周期控制方法,当spring ioc容器启动或停止时将发送一个启动或者停止的信号通知到各个组件,因此我们可以在对应的方法里做我们想要的事情。我们可以通过类图发现我们常用的ClasspathXmlApplicationContext类就实现了该接口

   下面我们来简单演示一下案例,创建类MyLifeCycle:

package org.hzgj.spring.study.context;

import org.springframework.context.SmartLifecycle;

public class MyLifeCycle implements SmartLifecycle {
    @Override
    public void start() {
        System.out.println("MyLifeCycle start ....");
    }

    @Override
    public void stop() {
        System.out.println("MyLifeCycle stop .....");
    }

    @Override
    public boolean isRunning() {
        return false;
    }

    @Override
    public boolean isAutoStartup() {
        return true;
    }

    @Override
    public void stop(Runnable callback) {

    }

    @Override
    public int getPhase() {
        System.out.println("phase");
        return 10;
    }
}
View Code

  在这里我们继承SmartLifeCycle该接口继承了LifeCycle, isRunning方法用于检测当前的组件是否处在运行状态,注意只有当isRunning返回值为false才可以运行

  我们把MyLifeCycle配置到spring配置文件里,通过ClassPathXmlApplicationContext运行 会得到如下结果:

  另外在这里的getPhase方法,这个是定义阶段值(可以理解为优先级,值越小对应的LifeCycle越先执行)

  

二、DiscoveryClient源码探究

@EnableDiscoveyClient

/*
 * 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

  请注意 @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

  这个类重写的方法来自于接口 ImportSelector,我们可以根据 if(autoRegister)下的代码追踪到类:org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration,我们来看一下结构图:

我们可以得知这个类实现了Lifecycle接口,那么我们看一看start方法,此方法在它的父类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

注意在start方法里有一段这个代码:

if (!this.running.get() && getConfiguredPort() > 0) {
            register();
            if (shouldRegisterManagement()) {
                registerManagement();
            }
            this.context.publishEvent(new InstanceRegisteredEvent<>(this,
                    getConfiguration()));
            this.running.compareAndSet(false, true);
        }

请注意register() 这个方法是本类里的抽象方法。那么我们回过头看一下AbstractAutoServiceRegistration类里的代码,我这里只贴出关键部分:

//.....

    protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) {
        this.serviceRegistry = serviceRegistry;
        this.properties = properties;
    }

//......
/**
     * Register the local service with the {@link ServiceRegistry}
     */
    @Override
    protected void register() {
        this.serviceRegistry.register(getRegistration());
    }
View Code

我们可以发现在构造函数里传了一个ServiceRegistry类型,这个接口是SpringCloud给我们提供用于服务注册的接口。在这里EurekaServiceRegistry就是实现了此接口:

/*
 * Copyright 2013-2016 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.netflix.eureka.serviceregistry;

import java.util.HashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;

import com.netflix.appinfo.InstanceInfo;

/**
 * @author Spencer Gibb
 */
public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> {

    private static final Log log = LogFactory.getLog(EurekaServiceRegistry.class);

    @Override
    public void register(EurekaRegistration reg) {
        maybeInitializeClient(reg);

        if (log.isInfoEnabled()) {
            log.info("Registering application " + reg.getInstanceConfig().getAppname()
                    + " with eureka with status "
                    + reg.getInstanceConfig().getInitialStatus());
        }

        reg.getApplicationInfoManager()
                .setInstanceStatus(reg.getInstanceConfig().getInitialStatus());

        if (reg.getHealthCheckHandler() != null) {
            reg.getEurekaClient().registerHealthCheck(reg.getHealthCheckHandler());
        }
    }
    
    private void maybeInitializeClient(EurekaRegistration reg) {
        // force initialization of possibly scoped proxies
        reg.getApplicationInfoManager().getInfo();
        reg.getEurekaClient().getApplications();
    }
    
    @Override
    public void deregister(EurekaRegistration reg) {
        if (reg.getApplicationInfoManager().getInfo() != null) {

            if (log.isInfoEnabled()) {
                log.info("Unregistering application " + reg.getInstanceConfig().getAppname()
                        + " with eureka with status DOWN");
            }

            reg.getApplicationInfoManager().setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);

            //shutdown of eureka client should happen with EurekaRegistration.close()
            //auto registration will create a bean which will be properly disposed
            //manual registrations will need to call close()
        }
    }

    @Override
    public void setStatus(EurekaRegistration registration, String status) {
        InstanceInfo info = registration.getApplicationInfoManager().getInfo();

        //TODO: howto deal with delete properly?
        if ("CANCEL_OVERRIDE".equalsIgnoreCase(status)) {
            registration.getEurekaClient().cancelOverrideStatus(info);
            return;
        }

        //TODO: howto deal with status types across discovery systems?
        InstanceInfo.InstanceStatus newStatus = InstanceInfo.InstanceStatus.toEnum(status);
        registration.getEurekaClient().setStatus(newStatus, info);
    }

    @Override
    public Object getStatus(EurekaRegistration registration) {
        HashMap<String, Object> status = new HashMap<>();

        InstanceInfo info = registration.getApplicationInfoManager().getInfo();
        status.put("status", info.getStatus().toString());
        status.put("overriddenStatus", info.getOverriddenStatus().toString());

        return status;
    }

    public void close() {
    }

}
View Code

那么至此我们可以总结如下几点:

  1、使用@DiscoveryClient注册服务是利用了LifeCycle机制,在容器启动时会执行ServiceRegistry的register()方法。

  2、使用@DiscoveryClient要比@EnableEurekaClient与@EnableEurekaServer更灵活,因为它屏蔽了对服务注册的实现,我们甚至可以自定义注册中心。

  3、它还会自动去寻找DiscoveryClient接口的实现用作服务发现

 

三、Discoveryclient实战之redis注册中心

 下面我们实现一个基于redis为注册中心的需求,来理解一下Discoveryclient。顺便理解一下Springcloud重要的接口:ServiceRegistry,ServiceInstance,再此之前我们先添加对redis的支持:

   compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis'

1、实现Registration接口

package com.hzgj.lyrk.member;

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.NetworkInterface;
import java.net.URI;
import java.util.Enumeration;
import java.util.Map;

@Component
public class RedisRegistration implements Registration {

    @Value("${server.port}")
    private Integer port;

    @Value("${spring.application.name}")
    private String applicationName;

    private String host;

    public void setHost(String host) {
        this.host = host;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }

    @Override
    public String getServiceId() {

        return applicationName + ":" + getHost() + ":" + getPort();
    }

    @Override
    public String getHost() {
        try {
            if (host == null)
                return getLocalHostLANAddress().getHostAddress();
            else
                return host;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @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;
    }


    public String getServiceName() {
        return this.applicationName;
    }


    public InetAddress getLocalHostLANAddress() throws Exception {
        try {
            InetAddress candidateAddress = null;
            // 遍历所有的网络接口
            for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) {
                NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
                // 在所有的接口下再遍历IP
                for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
                    InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
                    if (!inetAddr.isLoopbackAddress()) {// 排除loopback类型地址
                        if (inetAddr.isSiteLocalAddress()) {
                            // 如果是site-local地址,就是它了
                            return inetAddr;
                        } else if (candidateAddress == null) {
                            // site-local类型的地址未被发现,先记录候选地址
                            candidateAddress = inetAddr;
                        }
                    }
                }
            }
            if (candidateAddress != null) {
                return candidateAddress;
            }
            // 如果没有发现 non-loopback地址.只能用最次选的方案
            InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
            return jdkSuppliedAddress;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
View Code

  该接口继承了ServiceIntance,那么此接口最主要作用就是定义了一个服务实例的规范,比如说它的serviceId是什么,端口号是什么等

2、实现ServiceRegistry的接口

package com.hzgj.lyrk.member;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.data.redis.core.StringRedisTemplate;

public class RedisServiceRegistry implements ServiceRegistry<RedisRegistration> {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void register(RedisRegistration registration) {
        String serviceId = registration.getServiceId();
        redisTemplate.opsForList().leftPush(serviceId, registration.getHost() + ":" + registration.getPort());

    }

    @Override
    public void deregister(RedisRegistration registration) {
        redisTemplate.opsForList().remove(registration.getServiceId(), 1, registration.getHost() + ":" + registration.getPort());
    }

    @Override
    public void close() {
        //redisTemplate.d
        System.out.println("closed ...");
    }

    @Override
    public void setStatus(RedisRegistration registration, String status) {

    }

    @Override
    public <T> T getStatus(RedisRegistration registration) {
        return null;
    }


}
View Code

  该接口主要作用是定义如何进行服务注册 ,服务注销,设置与获取服务状态等操作

3、继承 AbstractAutoServiceRegistration抽象类

package com.hzgj.lyrk.member;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;

public class RedisAutoServiceRegistration extends AbstractAutoServiceRegistration<RedisRegistration> {

    @Autowired
    private RedisRegistration redisRegistration;

    protected RedisAutoServiceRegistration(ServiceRegistry<RedisRegistration> serviceRegistry, AutoServiceRegistrationProperties properties) {
        super(serviceRegistry, properties);
        // serviceRegistry.register(getRegistration());
    }

    @Override
    protected int getConfiguredPort() {
        return redisRegistration.getPort();
    }

    @Override
    protected void setConfiguredPort(int port) {

    }

    @Override
    protected Object getConfiguration() {
        return null;
    }

    @Override
    protected boolean isEnabled() {
        return true;
    }

    @Override
    protected RedisRegistration getRegistration() {
        return redisRegistration;
    }

    @Override
    protected RedisRegistration getManagementRegistration() {
        return null;
    }
}
View Code

 

4、定义DiscoveryClient的实现类RedisDiscoveryClient

package com.hzgj.lyrk.member;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class RedisDiscoveryClient implements DiscoveryClient {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public String description() {
        return "redis注册中心的服务发现";
    }

    @Override
    public ServiceInstance getLocalServiceInstance() {
        return null;
    }

    @Override
    public List<ServiceInstance> getInstances(String serviceId) {

        return redisTemplate.opsForList().range(serviceId, 0, -1).
                parallelStream().map((Function<String, ServiceInstance>) s -> {
            RedisRegistration redisRegistration = new RedisRegistration();
            redisRegistration.setApplicationName(serviceId);
            String hostName = StringUtils.split(s, ":")[0];
            String port = StringUtils.split(s, ":")[1];
            redisRegistration.setHost(hostName);
            redisRegistration.setPort(Integer.parseInt(port));
            //redisRegistration
            return redisRegistration;
        }).collect(Collectors.toList());


    }

    @Override
    public List<String> getServices() {
        List<String> list = new ArrayList<>();
        list.addAll(redisTemplate.keys("*"));
        return list;
    }
}
View Code

  该类主要是针对于redis注册中心的服务发现

5、定义自动装配的类用以创建对应的bean

package com.hzgj.lyrk.member;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
@EnableConfigurationProperties(RedisConfig.class)
@ConditionalOnProperty(value = "spring.redis.registry.enabled", matchIfMissing = true)
public class RedisRegistryAutoConfiguration {

    @Bean
    RedisServiceRegistry redisServiceRegistry(RedisConfig redisConfig) {
        System.out.println(redisConfig.getHost());

        return new RedisServiceRegistry();
    }

    @Bean
    RedisAutoServiceRegistration redisAutoServiceRegistration(RedisServiceRegistry redisServiceRegistry) {
        return new RedisAutoServiceRegistration(redisServiceRegistry, new AutoServiceRegistrationProperties());
    }

    @Bean
    @Primary
    RedisDiscoveryClient redisDiscoveryClient() {
        return new RedisDiscoveryClient();
    }
}
View Code

6、定义启动类

package com.hzgj.lyrk.member;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;


@EnableDiscoveryClient
@SpringBootApplication(exclude = {SimpleDiscoveryClientAutoConfiguration.class, CompositeDiscoveryClientAutoConfiguration.class})
public class MemberApplication {

    public static void main(String[] args) {

        ConfigurableApplicationContext applicationContext = SpringApplication.run(MemberApplication.class, args);
        DiscoveryClient discoveryClient = applicationContext.getBean(DiscoveryClient.class);
        discoveryClient.getServices().forEach(action -> {
            System.out.println(action);
        });
    }
}
View Code

  这里在SpringbootApplication注解里排除DiscoveryClient的默认装配。

当我们启动成功后可以发现,控制台已经输出对应的服务名称与地址:

  我们再次通过gradle打包生成jar文件并运行:

java -jar member-server-0.0.1-SNAPSHOT.jar --server.port=8800

  我们可以看到redis里已经缓存的有服务注册的值了:

 

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7月前
|
消息中间件 NoSQL Java
Spring Cloud项目实战Spring Cloud视频教程 含源码
Spring Cloud项目实战Spring Cloud视频教程 含源码
104 1
|
2月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
41 0
|
4月前
|
Java Spring
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
|
4月前
|
Java Spring 容器
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
|
4月前
|
存储 Java Spring
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
|
4月前
|
SQL Java 数据库连接
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
|
4月前
|
Java 开发工具 Spring
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known
|
4月前
|
NoSQL Java Redis
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
|
4月前
|
Java Spring
【Azure 应用服务】记一次Azure Spring Cloud 的部署错误 (az spring-cloud app deploy -g dev -s testdemo -n demo -p ./hellospring-0.0.1-SNAPSHOT.jar --->>> Failed to wait for deployment instances to be ready)
【Azure 应用服务】记一次Azure Spring Cloud 的部署错误 (az spring-cloud app deploy -g dev -s testdemo -n demo -p ./hellospring-0.0.1-SNAPSHOT.jar --->>> Failed to wait for deployment instances to be ready)
|
4月前
|
Java Maven Python
【Azure Spring Cloud】部署Azure spring cloud 失败
【Azure Spring Cloud】部署Azure spring cloud 失败