EurekaClient 优雅上下线方案

背景

服务的提供者下线时,调用者依然会发送请求到下线的服务提供者实例上,导致请求失败影响用户体验

解决思路

服务提供者下线时,及时通知调用者,从负载均衡列表中删除下线的实例

 

前提知识

EurekaServer 端  应用列表有两份,都是使用 Map 来维护,其中

ReadOnlyMap: 作为缓存来优化性能,所有的客户端拉取应用信息读取该 Map

ReadWriteMap: 作为应用实例下线时存储的 Map

两个 Map 之间的关系: 间隔一定时间ReadOnlyMap 去同步 ReadWriteMap

EurekaClient 和 Ribbon 之间也有同步策略,默认 30s

如下图

调整拉取间隔, 关闭 EurekaServer 缓存

当有实例下线, EurekaServer 发起接口调用到EurekaClient, EurekaClient中该接口负责主动拉取服务列表.

调整之后的关系如下图:

 

 

代码

EurekaServer

配置

#关闭读取缓存
eureka.server.use-read-only-response-cache=false

主动通知客户端来拉取服务列表

package com.wekj.eureka;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient;
import org.springframework.cloud.netflix.eureka.server.InstanceRegistry;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @author 胡说
 * @data 2020-08-05 16:27
 * 服务的优雅下线:
 * 当有实例下线时,主动通知 client 过来拉取最新的服务列表
 */
@Component
public class GraceShutdownProcessor  {
    private Logger log = LoggerFactory.getLogger(this.getClass());
    @Autowired
    DiscoveryClient discoveryClient;
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    ThreadPoolTaskExecutor cacheExecutor;

    private volatile long lastTime= 0;

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }


    @EventListener
    public synchronized void onApplicationEvent(EurekaInstanceCanceledEvent event) throws InterruptedException {
        // 一次下线会多次触发该事件,做个拦截忽略.
        if (System.currentTimeMillis()-lastTime<5000) {
            return;
        }
        log.info("收到应用【{}】实例【{}】下线通知",event.getAppName(),event.getServerId());
        notifyDown(event);
        log.info("完成【{}】实例【{}】下线通知",event.getAppName(),event.getServerId());
        lastTime = System.currentTimeMillis();
    }

    public void notifyDown(EurekaInstanceCanceledEvent event) {
//获取所有服务名
        List<String> services = discoveryClient.getServices();
        if (services == null || services.isEmpty()) {
            return;
        }
        for (String serviceName : services) {
//根据服务名获取所有实例
            List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
            if (instances == null || instances.isEmpty()) {
                continue;
            }
//遍历所有实例,逐个通知
            for (ServiceInstance instance : instances) {
                if (((EurekaDiscoveryClient.EurekaServiceInstance) instance).getInstanceInfo().getInstanceId().equalsIgnoreCase(event.getServerId())) {
                    continue;
                }
                cacheExecutor.submit(new SendNotifyRequest(serviceName, instance, restTemplate));
            }
        }
    }


}

 

EurekaClient

# 优雅上下线:每隔 5s去 eureka-server 拉取应用列表
eureka.client.registry-fetch-interval-seconds=5
# ribbon 主动更新服务列表间隔 单位毫秒
ribbon.ServerListRefreshInterval=5000

通过反射调用,主动拉取服务列表

package com.wekj.boot.eureka;

import cn.hutool.core.util.ReflectUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;

/**
 * @author 胡说
 * @data 2020-07-08 16:43
 * 处理服务的优雅上下线
 */
@Component
@RestController
public class GraceUpDownProcess  {
    private Logger log = LoggerFactory.getLogger(this.getClass());


    @Autowired
    ApplicationContext context;

    /**
     * 用来主动调用从 eurekaServer 获取服务列表
     */
    @GetMapping("invokeRefresh")
    public void offline() {
        log.debug("开始主动获取服务列表......");
        com.netflix.discovery.DiscoveryClient bean = context.getBean(com.netflix.discovery.DiscoveryClient.class);
        Method refreshRegistry = ReflectUtil.getMethod(bean.getClass(), "refreshRegistry");
        refreshRegistry.setAccessible(true);
        ReflectUtil.invoke(bean, "refreshRegistry");
        log.debug("主动获取服务列表完成.");
    }


}

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值