专栏首页SpringCloud专栏eureka监听各服务状态,下线、重连等,并做相应的处理

eureka监听各服务状态,下线、重连等,并做相应的处理

在一些场景下,我们需要监听eureka服务中心的一些状态,譬如某个微服务挂掉了,我们希望能监听到,并给管理员发送邮件通知。

Eureka的server端会发出5个事件通知,分别是:

EurekaInstanceCanceledEvent 服务下线事件 EurekaInstanceRegisteredEvent 服务注册事件 EurekaInstanceRenewedEvent 服务续约事件 EurekaRegistryAvailableEvent Eureka注册中心启动事件 EurekaServerStartedEvent Eureka Server启动事件

我们可以从源码中很方便地看到

eureka的server端源码一共也没几个类,Controller是给界面用的,还有一些配置类,我们简单来看几个。

@Configuration
@CommonsLog
public class EurekaServerInitializerConfiguration
        implements ServletContextAware, SmartLifecycle, Ordered {

    ...

    @Override
    public void start() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //TODO: is this class even needed now?
                    // 初始化eureka server
                    eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
                    log.info("Started Eureka Server");

                    // 传递eureka注册事件,找来找去没看到有listen去处理这件事件
                    // spring包中没有去处理,如果我们有需要可以自己处理
                    // https://github.com/spring-cloud/spring-cloud-netflix/issues/1726
                    publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                    // 设置在spring容器关闭的时候执行stop方法
                    EurekaServerInitializerConfiguration.this.running = true;
                    publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
                }
                catch (Exception ex) {
                    // Help!
                    log.error("Could not initialize Eureka servlet context", ex);
                }
            }
        }).start();
    }


    @Bean
    public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
            EurekaServerContext serverContext) {
        return new EurekaServerBootstrap(this.applicationInfoManager,
                this.eurekaClientConfig, this.eurekaServerConfig, registry,
                serverContext);
    }
    ...
}
@CommonsLog
public class EurekaServerBootstrap {

    ....

    public void contextInitialized(ServletContext context) {
        try {
            //初始化eureka环境
            initEurekaEnvironment();
            //初始化eureka上下文
            initEurekaServerContext();

            context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
        }
        catch (Throwable e) {
            log.error("Cannot bootstrap eureka server :", e);
            throw new RuntimeException("Cannot bootstrap eureka server :", e);
        }
    }

    protected void initEurekaEnvironment() throws Exception {
        log.info("Setting the eureka configuration..");

        //如果在云环境中运行,需要传递java命令行属性-Deureka.datacenter = cloud,以便Eureka客户端/服务器知道初始化AWS云特定的信息
        String dataCenter = ConfigurationManager.getConfigInstance()
                .getString(EUREKA_DATACENTER);
        if (dataCenter == null) {
            log.info(
                    "Eureka data center value eureka.datacenter is not set, defaulting to default");
            ConfigurationManager.getConfigInstance()
                    .setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
        }
        else {
            ConfigurationManager.getConfigInstance()
                    .setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
        }

        //eureka 运行环境,java命令行属性-Deureka.environment=eureka-client-{test,prod}
        String environment = ConfigurationManager.getConfigInstance()
                .getString(EUREKA_ENVIRONMENT);
        if (environment == null) {
            ConfigurationManager.getConfigInstance()
                    .setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
            log.info(
                    "Eureka environment value eureka.environment is not set, defaulting to test");
        }
        else {
            ConfigurationManager.getConfigInstance()
                    .setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, environment);
        }
    }

    // eureka使用XStream来对json、xml格式的转换
    protected void initEurekaServerContext() throws Exception {
        // 向后兼容
        //注册状态转换器
        JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
                XStream.PRIORITY_VERY_HIGH);
        XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
                XStream.PRIORITY_VERY_HIGH);

        //判断是否亚马逊的数据中心
        if (isAws(this.applicationInfoManager.getInfo())) {
            this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
                    this.eurekaClientConfig, this.registry, this.applicationInfoManager);
            //
            this.awsBinder.start();
        }

        //初始化eureka server上下文
        EurekaServerContextHolder.initialize(this.serverContext);

        log.info("Initialized server context");

        // 从相邻的eureka节点复制注册表
        int registryCount = this.registry.syncUp();
        // 默认每30秒发送心跳,1分钟就是2次
        // 修改eureka状态为up
        this.registry.openForTraffic(this.applicationInfoManager, registryCount);

        // 注册所有监控
        EurekaMonitors.registerAllStats();
    }

    ...
}
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements ApplicationContextAware {
    private static final Log log = LogFactory.getLog(InstanceRegistry.class);
    private ApplicationContext ctxt;
    private int defaultOpenForTrafficCount;

    public InstanceRegistry(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs, EurekaClient eurekaClient, int expectedNumberOfRenewsPerMin, int defaultOpenForTrafficCount) {
        super(serverConfig, clientConfig, serverCodecs, eurekaClient);
        this.expectedNumberOfRenewsPerMin = expectedNumberOfRenewsPerMin;
        this.defaultOpenForTrafficCount = defaultOpenForTrafficCount;
    }

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.ctxt = context;
    }

    public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
        super.openForTraffic(applicationInfoManager, count == 0 ? this.defaultOpenForTrafficCount : count);
    }

    public void register(InstanceInfo info, int leaseDuration, boolean isReplication) {
        this.handleRegistration(info, leaseDuration, isReplication);
        super.register(info, leaseDuration, isReplication);
    }

    public void register(InstanceInfo info, boolean isReplication) {
        this.handleRegistration(info, this.resolveInstanceLeaseDuration(info), isReplication);
        super.register(info, isReplication);
    }

    public boolean cancel(String appName, String serverId, boolean isReplication) {
        this.handleCancelation(appName, serverId, isReplication);
        return super.cancel(appName, serverId, isReplication);
    }

    public boolean renew(String appName, String serverId, boolean isReplication) {
        this.log("renew " + appName + " serverId " + serverId + ", isReplication {}" + isReplication);
        List<Application> applications = this.getSortedApplications();
        Iterator var5 = applications.iterator();

        while(var5.hasNext()) {
            Application input = (Application)var5.next();
            if (input.getName().equals(appName)) {
                InstanceInfo instance = null;
                Iterator var8 = input.getInstances().iterator();

                while(var8.hasNext()) {
                    InstanceInfo info = (InstanceInfo)var8.next();
                    if (info.getId().equals(serverId)) {
                        instance = info;
                        break;
                    }
                }
		//这里发布重连事件
                this.publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId, instance, isReplication));
                break;
            }
        }

        return super.renew(appName, serverId, isReplication);
    }

    protected boolean internalCancel(String appName, String id, boolean isReplication) {
        this.handleCancelation(appName, id, isReplication);
        return super.internalCancel(appName, id, isReplication);
    }

    private void handleCancelation(String appName, String id, boolean isReplication) {
        this.log("cancel " + appName + ", serverId " + id + ", isReplication " + isReplication);
        this.publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
    }

    private void handleRegistration(InstanceInfo info, int leaseDuration, boolean isReplication) {
        this.log("register " + info.getAppName() + ", vip " + info.getVIPAddress() + ", leaseDuration " + leaseDuration + ", isReplication " + isReplication);
        this.publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, isReplication));
    }

    private void log(String message) {
        if (log.isDebugEnabled()) {
            log.debug(message);
        }

    }

    private void publishEvent(ApplicationEvent applicationEvent) {
        this.ctxt.publishEvent(applicationEvent);
    }

    private int resolveInstanceLeaseDuration(InstanceInfo info) {
        int leaseDuration = 90;
        if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
            leaseDuration = info.getLeaseInfo().getDurationInSecs();
        }

        return leaseDuration;
    }
}

差不多能用上的就这几个类了,这5个server端事件在这几个类中都能找到。

知道了事件名,要监听就很简单了

package com.tianyalei.server;

import com.netflix.appinfo.InstanceInfo;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaRegistryAvailableEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaServerStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * Created by wuweifeng on 2017/10/10.
 */
@Component
public class EurekaStateChangeListener {
    
    @EventListener
    public void listen(EurekaInstanceCanceledEvent eurekaInstanceCanceledEvent) {
        //服务断线事件
        String appName = eurekaInstanceCanceledEvent.getAppName();
        String serverId = eurekaInstanceCanceledEvent.getServerId();
        System.out.println(appName);
        System.out.println(serverId);
    }

    @EventListener
    public void listen(EurekaInstanceRegisteredEvent event) {
        InstanceInfo instanceInfo = event.getInstanceInfo();
        System.out.println(instanceInfo);
    }

    @EventListener
    public void listen(EurekaInstanceRenewedEvent event) {
        event.getAppName();
        event.getServerId();
    }

    @EventListener
    public void listen(EurekaRegistryAvailableEvent event) {

    }

    @EventListener
    public void listen(EurekaServerStartedEvent event) {
        //Server启动
    }
}

在server项目里加上这个监听类,然后在各个事件被触发时就能在这里监听并处理了。

譬如我启动一个client

在这里就能看到新注册的client的各项详细信息。 然后我再关掉client

可以看到挂掉的client的appName,serverId等信息。那么我们就可以在这里做一些处理,譬如邮件通知管理员,某某服务挂掉了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 短小强悍!一个基于 Redis 的限流系统的设计~

    本文讲述基于 Redis 的限流系统的设计,主要会谈及限流系统中限流策略这个功能的设计;在实现方面,算法使用的是令牌桶算法来,访问 Redis 使用 lua 脚...

    java思维导图
  • 多线程通信的三大法器,你真的会用吗?

    wait, notify, notifyAll 是多线程之间通信最重要的 3 个方法,今天,栈长给大家普及一下它们的知识要点及应用实战。

    Java技术栈
  • Java必会技术图,是可以帮你薪资翻倍的

    可以说,Java是现阶段中国互联网公司中,覆盖度最广的研发语言,掌握了Java技术体系,不管在成熟的大公司,快速发展的公司,还是创业阶段的公司,都能有立足之地。

    奋斗蒙
  • Intellij IDEA Debug 调试技巧

    F8: 跳到下一步 F7:进入到代码 Alt+shift+F7:强制进入代码 Shift+F8:跳到下一个断点 Atl+F9:运行到光标处 ctrl+shift...

    Java技术栈
  • Golang 之协程详解

      对于 进程、线程,都是有内核进行调度,有 CPU 时间片的概念,进行 抢占式调度(有多种调度算法)

    李海彬
  • 「小程序JAVA实战」小程序的事件(11)

    IT故事会
  • 「小程序JAVA实战」 小程序私有页面的生命周期以及导航(10)

    PS:这块主要是对配置的生命周期的熟悉,了解下redirectTo 和 navigateTo 之前的区别。

    IT故事会
  • IntelliJ IDEA 2018.3 正式版发布!

    IntelliJ IDEA 今年的最后一次大版本更新 2018.3 现已正式发布,值得关注的更新包括支持 Java 12、Git submodule、GitHu...

    Java技术栈
  • 分布式作业系统 Elastic-Job-Lite 源码分析 —— 作业监听器

    摘要: 原创出处 http://www.iocoder.cn/Elastic-Job/job-listener/ 「芋道源码」欢迎转载,保留摘要,谢谢!

    芋道源码
  • 「小程序JAVA实战」 小程序抽离公用方法进行模块化(12)

    区别如果用了 ${} 最外层需要用``符号,如果你喜欢老套路可以按照我的 "Goodbye " + name + " !" 这种。

    IT故事会

扫码关注云+社区

领取腾讯云代金券