知识库
并发编程
  • 分类
  • 标签
  • 归档
友情连接

luoliang

吾生也有涯,知也无涯
并发编程
  • 分类
  • 标签
  • 归档
友情连接
  • 灰度发布、蓝绿部署、金丝雀都是啥?
  • ELK日志系统安装
  • nacos优雅停机
    • 搭建jenkins部署spring-boot项目
    • 安装RocketMQ
    • 安装监控grafana
    • SpringBoot启动脚本
    • Linux常用命令
    • DevOps
    weiluoliang
    2023-12-13
    目录

    nacos优雅停机

    # 需求

    服务提供方关闭时,主动从nacos下线,并且服务调用方能感知到,不再调用这个服务。

    image-20231206104034264

    # 实现方案

    # 需要满足的条件

    1. 服务下线接口 : nacos提供了 服务上下线的接口,在服务关闭时主动调用下线接口
    2. 服务关闭感知的方法 :
      1. 方案一 : 添加钩子 Shutdown Hook , 钩子执行的时间比web容器关闭的时间要晚,会导致服务下线滞后于web容器关闭,这个不符合我们的需求,弃用
      2. 方案二:监听Spring 容器的关闭事件 ContextClosedEvent ,发布事件先于web容器关闭,服务我们的要求。

    # 服务提供方实现(provider)

    1. 监听Spring容器【ContextClosedEvent】事件,当关闭服务会收到这个事件
    @Slf4j
    public class ApplicationShutdownListener implements ApplicationListener<ContextClosedEvent> {
    
        /**
         *  优雅停机等待时间
         */
        @Value("${spring.lifecycle.timeout-per-shutdown-phase:5}")
        private Integer timeout;
        @Resource
        private NacosAutoServiceRegistration nacosAutoServiceRegistration;
    
        /**
         *  监听关闭事件
         * @param event 关闭容器事件
         */
        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            log.info("[nacos]开始下线。。。");
            nacosAutoServiceRegistration.destroy();
            ThreadUtil.sleep(timeout, TimeUnit.SECONDS);
            log.info("[nacos]开始完成。。。");
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    # 服务调用方实现(client)

    客户端比较坑的地方是 spring-cloud-starter-loadbalancer有本地缓存,所以服务提供方下线了,并不会马上感知到,所以需要实现监听服务变更事件,发现变更的时候清空缓存,当再次发起调用时客户端发现没有缓存会再次去拉去nacos的最新配置。

    defaultLoadBalancerCacheManager实现了Spring的缓存接口,所以我们直接找到CacheManager的实例去清空缓存即可。

    @Slf4j
    public class NacosInstancesChangeEventListener extends Subscriber<InstancesChangeEvent> {
        @Resource
        private CacheManager defaultLoadBalancerCacheManager;
        
        @PostConstruct
        public void registerToNotifyCenter(){
            NotifyCenter.registerSubscriber(this);
        }
        @Override
        public void onEvent(InstancesChangeEvent event) {
            log.info("【nacos】接收微服务实例刷新事件:{}, 开始刷新本地存储的微服务实例信息的缓存", JacksonUtils.toJson(event));
            Cache cache = defaultLoadBalancerCacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
            if (cache != null) {
                String key = StrUtil.subAfter(event.getServiceName(),"@@",false);
                log.info("【nacos】开始清空缓存, 服务 ={} ,", key);
                cache.evict(key);
                log.info("【nacos】清空缓存完成, 服务 ={} ,", key);
            }
            log.info("【nacos】实例刷新完成");
        }
    
        @Override
        public Class<? extends com.alibaba.nacos.common.notify.Event> subscribeType() {
            return InstancesChangeEvent.class;
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    # 启动脚本

    如果你使用的是脚本的启动方式,一般会有 start ,stop , restart 这几个方法,stop方法我们不能使用 kill -9 ${pid} ,这种方式无法正常停止程序,应该使用 kill ${pid} 。

    #此u处修改脚本名称:
    APP_NAME=app.jar
    #脚本菜单项
    usage() {
     echo "Usage: sh 脚本名.sh [start|stop|restart|status]"
     exit 1
    }
    
    is_exist(){
     pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `
     #如果不存在返回1,存在返回0
     if [ -z "${pid}" ]; then
     return 1
     else
     return 0
     fi
    }
    
    #启动脚本
    start(){
     echo "start.................."
     is_exist
     if [ $? -eq "0" ]; then
     echo "${APP_NAME} is already running. pid=${pid} ."
     else
    #此处注意修改jar和log文件文件位置:
     nohup /usr/local/java/bin/java -Xms512M -Xmx2048M -jar ./${APP_NAME}.jar --spring.profiles.active=dev  > /dev/null   2>&1 & 
     fi
    }
    
    #停止脚本
    stop(){
     is_exist
     if [ $? -eq "0" ]; then
     kill  $pid
     while true
     do
      is_exist
      if [[ "$?" -eq 1 ]]; then
        echo "do stop ok ! "
        break;
      fi
      echo "do stop ing"
      sleep 0.1
     done
     else
     echo "${APP_NAME} is not running"
     fi
    }
    
    
    #显示当前jar运行状态
    status(){
     is_exist
     if [ $? -eq "0" ]; then
     echo "${APP_NAME} is running. Pid is ${pid}"
     else
     echo "${APP_NAME} is NOT running."
     fi
    }
    
    #重启脚本
    restart(){
     stop
     start
    }
    
    case "$1" in
     "start")
     start
     ;;
     "stop")
     stop
     ;;
     "status")
     status
     ;;
     "restart")
     restart
     ;;
     *)
     usage
     ;;
    esac
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    上次更新: 2024/03/26, 14:40:10
    ELK日志系统安装
    搭建jenkins部署spring-boot项目

    ← ELK日志系统安装 搭建jenkins部署spring-boot项目→

    最近更新
    01
    Linux常用命令
    09-04
    02
    SpringBoot启动脚本
    08-31
    03
    安装监控grafana
    08-30
    更多文章>
    Theme by Vdoing | Copyright © 2022-2024 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式