• 基于微服务(eureka)的优雅发布设计说明


    背景介绍

    如图1中在基于springcloud的微服务架构中,我们使用eureka作为注册中心,zuul作为网关,用户请求会先进入网关,网关中会通过ribbon组件缓存eureka中注册的服务列表,在

             图1: 基于springcloud(eureka作为注册中心,zuul作为网关)的微服务架构图

    服务列表中进行选择分发,分发到相应的服务.服务间的调用的逻辑也是类似于zuul网管的分发逻辑.当有服务有新版本或修复服务bug后,需要对原有服务进行重新发布部署,在以往我们使用传统的强制发布去发布服务,传统的发布过程如下图2

    图2: 传统的服务发布过程

    我们这里假设正在发布的是A服务,由于A服务在发布时kill -9 pid没有通知到eureka,euraka服务列表中暂时还会存在这个已经被杀死的服务A,当前的A服务是不可用的,那么当用户请求时如果分发到此服务将不可用,导致用户请求失败,如图3,并且在kill -9 pid结束服务进程时可能存在正在处理的业务或者功能,强制的结束会导致异常中断,脏数据等问题,如图3.

    图3: A服务使用传统发布过程发布

    传统的强制发布过程严重降低了服务的可用性,在生产环境中服务发布过程中对于用户的体验是极差的不能接受的!进而我们设计了一套优雅的发布过程.

    优雅发布设计细节

    预备知识

    Eureka的 RestFul接口:

    请求名称

    请求方式

    HTTP地址

    请求描述

    查询所有服务

    GET

    /eureka/apps

    HTTP code为200时表示成功,返回XML/JSON数据内容

    变更服务状态

    PUT

    /eureka/apps/{appID}/{instanceID}/status?value=DOWN

    服务上线、服务下线等状态变动,HTTP code为200时表示成功

    引用: Eureka REST operations · Netflix/eureka Wiki · GitHub

    Eureka中服务的状态说明:

    状态值

    说明

    UP

    上线服务

    DOWN

    删除服务

    OUT_OF_SERVICE

    将某个实例设置为暂停服务,这个和删除(DOWN)不同,如果你手动调用删除,但如果客户端还活着,定时任务还是会将实例注册上去。但是改成这个状态,定时任务更新不了这个状态 

    Kill -9 和 kill -15的区别:

    9) SIGKILL

    用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。

    15) SIGTERM

    程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。

    优雅发包过程介绍

    整个优雅发布的过程,如图4

    图4: 优雅发布的过程

     

     

    1. 调用发布脚本传入参数:服务名(appID),jar包名,java服务占用的端口($port)等
       
    2. 通过jar包名,使用脚本(ps)查询当前服务进程号
       
    3. 判断是否存在服务进程,存在服务进程继续往下走,不存在的话直接跳转到发布新版服务(第10步)
       
    4. 通过脚本查询当前服务器的ip(ip addr)
       
    5. 通过服务名,服务实例id=$ip:$port(这里要求注册到eureka的实例id为$ip+:+$port), 调用eureka服务状态变更接口设置服务实例状态为暂停(OUT_OF_SERVICE)
      说明: 这一步主要是告知eureka当前服务实例需要暂停提供服务,但是由于ribbon的缓存机制,服务间的服务列表缓存是不会及时刷新的,并且这个服务实例内可能还存在未处理完的请求,所以不能立即执行关闭服务(kill -15)操作
       
    6. 调用eureka服务列表接口循环判断服务实例状态是否为暂停(OUT_OF_SERVICE),如果到达暂停服务最大尝试时间(420s),本次发布中断
      说明: 这里是一个check的过程,保证调用的服务状态变更为暂停接口一定成功,这里需要设置一个最大的check时间420s,避免无限循环
       
    7. 确定服务实例状态变更为暂停成功后,等待指定时间(70s),保证剩余web请求处理完
      说明: 这里的70s考虑到ribbon的服务列表缓存刷新时间和最后请求到达的最大处理时间,所以这里的时间大于等于ribbon的服务列表缓存刷新时间(ribbon.ServerListRefreshInterval默认为30秒)+请求最大处理时间(ribbon.ReadTimeout默认值为5秒,我们的服务设置的为30秒).一般来说还需要考虑在ribbon从eureka获取列表所花的时间,所以再在上调10秒.
       
    8. 使用kill -15(SIGTERM) pid关闭服务进程
      说明: 使用kill -15 pid可以使程序进入正常关闭流程,保证java中的关闭钩子正常运行,程序执行收尾操作,避免了强制关闭导致的程序强制中断关闭
       
    9. 循环检查服务进程是否正常退出,如果到达服务正常关闭最大的超时间(120秒)仍然未关闭,使用kill -9 pid强制关闭服务
      说明: 由于kill -15 pid可以被阻塞和处理,要求程序自己正常退出,存在程序退出逻辑堵塞的可能,所以需要增加一个check的机制,设置一个程序正常处理完剩余任务的最大等待时间,这里设置120秒的超时时间,最终通过kill -9 pid保证程序一定被关闭.
    10. 发布新版服务
    11. 服务启动后,还需要循环调用eureka服务列表接口判断服务实例是否已经注册到服务列表中,只有真正注册到eureka服务列表后才能证明服务是真正的启动,才可以被各个服务的ribbon获取到.由于存在服务内部出错导致注册失败的情况,这里也需要设置一个最大的超时时间(我们这里设置的为420秒),避免无限循环检测的问题
      说明: 这里主要是考虑之前设置了服务实例状态为暂停(OUT_OF_SERVICE),有时eureka会遗留这状态,为免这个问题,可以主动调用一次状态变更为UP即可
    12. 保证当前服务发布成功,再发布其他服务器(同时发布)的当前服务

    优雅发布过程是如何保证接口可用的

    如图5,发布A服务的过程,优先发布’A服务-服务器1’,在优雅发布的第1-7步中服务实例都是可用的,当第7步完成后,所有的服务中(包括zuul网关)的ribbon服务列表缓存中,当前正在发布的实例的状态将会是暂停的,此时新进来的请求将不会再进入当前实例,并且当前实例的请求都应该处理完成并返回了

    图5: A服务-服务器1正在使用优雅发布发布中(完成第7步后)

    直到’A服务-服务器1’发布成功,’A服务-服务器1’当前服务变为可用状态,紧接着可以发布服务器2-n的A服务, 服务器2-n发布A服务时在第1-7步中服务实例都是可用的,当第7步完成后,所有的服务中(包括zuul网关)的ribbon服务列表缓存中, 服务器2-n中正在发布的实例的状态将会是暂停的,此时新进来的请求将不会再进入正在发布的实例中,并且正在发布的实例的请求都应该处理完成并返回了,如图6

    图6: A服务-服务器2-n正在使用优雅发布发布中(完成第7步后)

    直到所有的A服务的服务器都发布完成,新版A服务发布结束,整个过程没有请求的丢失和中断

    优雅发布带来的优点和解决的痛点

    1. 保证了在发包过程,接口依然是可用的,避免了发包过程的接口不可用问题
    2. 发布过程先使用kill -15 pid再使用kill -9 pid进行关闭进程保证了java应用的正常关闭,关闭时可以完成收尾操作,避免强制中断执行流程
  • 相关阅读:
    周杰伦十五张专辑(珍藏版)2000-2022年
    HarmonyOS应用开发:鸿蒙自定义组件slot插槽,体现的更强大!
    天龙八部门派采集任务坐标
    次元裂缝已打开,AI绘画突飞猛进,其潜力究竟有多大
    vscode的快捷键
    【JavaEE初阶--多线程进阶】JUC里的一些组件和多线程中的一些集合类
    ping可视化工具——gping
    企业架构LNMP学习笔记26
    jsp儿童网站系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
    Elastic Stack 8.0 安装 - 保护你的 Elastic Stack 现在比以往任何时候都简单
  • 原文地址:https://blog.csdn.net/huang007guo/article/details/127773744