草庐IT

Spring Cloud 微服务优雅下线 + 灰度发布的正确姿势,写得太好了!

Java技术栈 2023-04-17 原文

前言

在生产环境中,如何保证在服务升级的时候,不影响用户的体验,这个是一个非常重要的问题。如果在我们升级服务的时候,会造成一段时间内的服务不可用,这就是不够优雅的。那什么是优雅的呢?主要就是指在服务升级的时候,不中断整个服务,让用户无感知,进而不会影响用户的体验,这就是优雅的。

实际上,优雅下线是目标,而不是手段,它是一个相对的概念,例如kill PIDkill -9 PID都是暴力杀死服务,相对于kill -9 PID来说,kill PID就是优雅的。但如果单独拿kill PID出来说,我们能说它是优雅的下线策略吗?肯定不是啊,就是这个道理。

因此,本文讲述的优雅下线仅能称之为“相对的优雅下线”,但相对于暴力的杀死服务,已经足够优雅了。常见的优雅解决方案,主要包括优雅下线和灰度发布。而实际上,灰度发布的范围就已经包含优雅下线了。

最后,在本文中,我们主要讲述基于 Spring Cloud 和 Euraka 的优雅下线以及灰度发布。

优雅下线

常见的下线方式

方式一:kill PID

使用方式:kill java进程ID

该方式借助的是 Spring Boot 应用的 Shutdown hook,应用本身的下线也是优雅的,但如果你的服务发现组件使用的是 Eureka,那么默认最长会有 90 秒的延迟,其他应用才会感知到该服务下线,这意味着:该实例下线后的 90 秒内,其他服务仍然可能调用到这个已下线的实例。因此,该方式是不够优雅的 。

方式二:/shutdown端点

Spring Boot 提供了/shutdown端点,可以借助它实现优雅停机。

使用方式:在想下线应用的application.yml中添加如下配置,从而启用并暴露/shutdown端点:

management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: shutdown

发送 POST 请求到/shutdown端点

curl -X http://你想停止的服务地址/actuator/shutdown

该方式本质和方式一是一样的,也是借助 Spring Boot 应用的 Shutdown hook 去实现的。

Spring Boot 基础就不介绍了,推荐看这个免费教程:

https://github.com/javastacks/spring-boot-best-practice

方式三:/pause端点

Spring Boot 应用提供了/pause端点,利用该端点可实现优雅下线。

使用方式:在想下线应用的application.yml中添加配置,从而启用并暴露/pause端点:

management:
  endpoint:
    # 启用pause端点
    pause:
      enabled: true
    # 启用restart端点,之所以要启用restart端点,是因为pause端点的启用依赖restart端点的启用
    restart:
      enabled: true
  endpoints:
    web:
      exposure:
        include: pause,restart

发送 POST 请求到/actuator/pause端点:

curl -X POST http://你想停止的服务实例地址/actuator/pause

执行后的效果类似下图:

如图所示,该应用在 Eureka Server 上的状已被标记为DOWN,但是应用本身其实依然是可以正常对外服务的。在 Spring Cloud 中,Ribbon 做负载均衡时,只会负载到标记为UP的实例上。

利用这两点,你可以:先用/pause端点,将要下线的应用标记为DOWN,但不去真正停止应用;然后过一定的时间(例如 90 秒,或者自己做个监控,看当前实例的流量变成 0 后)再去停止应用,例如kill应用。

缺点 & 局限

方式四:/service-registry端点

使用方式:在想下线应用的application.yml中添加配置,从而暴露/service-registry端点:

management:
  endpoints:
    web:
      exposure:
        include: service-registry

发送 POST 请求到/actuator/service-registry端点:

curl -X "POST" "http://localhost:8000/actuator/service-registry?status=DOWN" \
   -H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"

实行后的效果类似如下图:

优雅的下线方式

在上文中,我们讲述了四种常见的下线方式,对比来看,方式四 是一种比较优雅的下线方式。

在实际项目中,我们可以先使用/service-registry端点,将服务标记为DOWN,然后监控服务的流量,当流量为 0 时,即可升级该服务。当然,这里假设我们部署了多个服务实例,当一个服务实例DOWN掉之后,其他服务实例仍然是可以提供服务的,如果就部署一台服务的话,那么讨论优不优雅就没那么重要了。

除了上述的下线方式之外,还有一种利用EurekaAutoServiceRegistration对象达到优雅下线的目标。

  • 执行eurekaAutoServiceRegistration.start()方法时,当前服务向 Eureka 注册中心注册服务;
  • 执行eurekaAutoServiceRegistration.stop()方法时,当前服务会向 Eureka 注册中心进行反注册,注册中心收到请求后,会将此服务从注册列表中删除。

示例代码如下:

@RestController
@RequestMapping(value = "/graceful/registry-service")
public class GracefulOffline {

    @Autowired
    private EurekaAutoServiceRegistration eurekaAutoServiceRegistration;

    @RequestMapping("/online")
    public String online() {
        this.eurekaAutoServiceRegistration.start();
        return "execute online method, online success.";
    }

    @RequestMapping("/offline")
    public String offline() {
        this.eurekaAutoServiceRegistration.stop();
        return "execute offline method, offline success.";
    }
}

到这里,我们已经介绍了两种相对优雅的下线方式了。具体如何操作,我们可以根据实际上情况进行包装,或者利用自动化的脚本来实现更加优雅的下线方式。

灰度发布

蓝绿部署

蓝绿部署,英文名为 Blue Green Deployment,是一种可以保证系统在不间断提供服务的情况下上线的部署方式。

如何保证系统不间断提供服务呢?那就是同时部署两个集群,但仅对外提供一个集群的服务,当需要升级时,切换集群进行升级。蓝绿部署无需停机,并且风险较小。其大致步骤为:

  • 部署集群 1 的应用(初始状态),将所有外部请求的流量都打到这个集群上
  • 部署集群 2 的应用,集群 2 的代码与集群 1 不同,如新功能或者 Bug 修复等
  • 将流量从集群 1 切换到集群 2
  • 如集群 2 测试正常,就删除集群 1 正在使用的资源(例如实例),使用集群 2 对外提供服务

因为在使用蓝绿部署的方式时,我们需要控制流量,所以我们需要借助路由服务,如 Nginx 等。

滚动部署

滚动部署,英文名为 Rolling Update,同样是一种可以保证系统在不间断提供服务的情况下上线的部署方式。和蓝绿部署不同的是,滚动部署对外提供服务的版本并不是非此即彼,而是在更细的粒度下平滑完成版本的升级。

如何做到细粒度平滑升级版本呢?滚动部署只需要一个集群,集群下的不同节点可以独立进行版本升级。比如在一个 12 节点的集群中,我们每次升级 4 个节点,并将升级后的节点重新投入使用,周而复始,直到集群中所有的节点都更新为新版本。

这种部署方式相对于蓝绿部署,更加节约资源,因为它不需要运行两个集群。但这种方式也有很多缺点,例如:

  • 没有一个确定 OK 的环境。使用蓝绿部署,我们能够清晰地知道老版本是 OK 的,而使用滚动发布,我们无法确定。
  • 修改了现有的环境。
  • 如果需要回滚,很困难。举个例子,在某一次发布中,我们需要更新 100 个实例,每次更新 10 个实例,每次部署需要 5 分钟。当滚动发布到第 80 个实例时,发现了问题,需要回滚。这时,我们估计就要疯了。
  • 有的时候,我们还可能对系统进行动态伸缩,如果部署期间,系统自动扩容/缩容了,我们还需判断到底哪个节点使用的是哪个代码。尽管有一些自动化的运维工具,但是依然令人心惊胆战。

并不是说滚动发布不好,滚动发布也有它非常合适的场景。

金丝雀部署

金丝雀部署又称灰度部署(或者,灰度发布),英文名为 Canary Deployment,是指在黑与白之间,能够平滑过渡的一种发布方式

金丝雀的名称来源于「矿井中的金丝雀」,早在 17 世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感,空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离。

我们来看一下金丝雀部署的步骤:

  • 准备好部署各个阶段的工件,包括:构建工件,测试脚本,配置文件和部署清单文件
  • 从负载均衡列表中移除掉“金丝雀”服务器
  • 升级“金丝雀”应用(切断原有流量并进行部署)
  • 对应用进行自动化测试
  • 将“金丝雀”服务器重新添加到负载均衡列表中(连通性和健康检查)
  • 如果“金丝雀”在线使用测试成功,升级剩余的其他服务器(否则就回滚)

在金丝雀部署中,常常按照用户量设置路由权重,例如 90% 的用户维持使用老版本,10% 的用户尝鲜新版本。不同版本应用共存,经常与 A/B 测试一起使用,用于测试选择多种方案。

金丝雀部署比较典型的例子,就是我们在使用某个应用的时候,该应用邀请我们进行“内测”或者“新版本体验”,如果我们同意了,那么我们就成了金丝雀。

版权声明:本文为CSDN博主「CG国斌」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_35246620/article/details/109166722

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

有关Spring Cloud 微服务优雅下线 + 灰度发布的正确姿势,写得太好了!的更多相关文章

  1. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  4. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  5. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  6. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

  7. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  8. ruby-on-rails - 如何在发布新的 Ruby 或 Rails 版本时收到通知? - 2

    有人知道在发布新版本的Ruby和Rails时收到电子邮件的方法吗?他们有邮件列表,RubyonRails有一个推特,但我不想听到那些随之而来的喧嚣,我只想知道什么时候发布新版本,尤其是那些有安全修复的版本。 最佳答案 从therailsblog获取提要.http://weblog.rubyonrails.org/feed/atom.xml 关于ruby-on-rails-如何在发布新的Ruby或Rails版本时收到通知?,我们在StackOverflow上找到一个类似的问题:

  9. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  10. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

    require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

随机推荐