阻止 Spring Boot 应用程序关闭,直到所有当前请求都完成

Posted

技术标签:

【中文标题】阻止 Spring Boot 应用程序关闭,直到所有当前请求都完成【英文标题】:Prevent Spring Boot application closing until all current requests are finished 【发布时间】:2019-10-04 23:30:57 【问题描述】:

我们有一个 Spring Boot (2.0.4) 应用程序公开了许多端点,其中一个端点使客户端有时可以检索非常大的文件 (~200 GB)。该应用程序通过配置了滚动更新策略的 Kubernetes 部署在 Pod 中公开。

当我们通过将映像设置为最新版本来更新我们的部署时,Pod 会被破坏,新的 Pod 会启动。我们为新请求提供无缝服务。然而,当前的请求可能而且确实会被切断,这对于正在下载非常大文件的客户端来说可能很烦人。

我们可以在部署规范中配置 Container Lifecycle Pre-Stop 挂钩,以便在通过其 PID 向应用发送关闭信号之前注入暂停。这有助于防止任何新流量流向已设置为终止的 pod。有没有办法暂停应用程序关闭过程,直到所有当前请求都完成(这可能需要几十分钟)?

这是我们在 Spring Boot 应用程序中尝试过的:

实现一个拦截ContextCloseEvents的关闭监听器;不幸的是,我们无法可靠地检索活动请求列表。在关闭过程的这个阶段,任何可能有用的执行器指标都不可用。

通过实现HttpSessionListener 并覆盖sessionCreated/Destroy 方法来更新计数器来计算活动会话。这会失败,因为这些方法不是在单独的线程上调用的,因此始终在关闭侦听器中报告相同的值。

我们应该尝试任何其他策略吗?来自应用程序本身、容器,还是直接通过 Kubernetes 资源描述符?建议/帮助/指针将不胜感激。

编辑:我们管理集群,因此我们只是在通过修改后的 pod 规范对我们的部署进行管理更新期间尝试减轻对当前连接的客户端的服务中断

【问题讨论】:

用 preStop 钩子注入暂停有什么问题?只要记住还要通过注入暂停的持续时间来增加terminationGracePeriodSeconds。您甚至可以进一步检查 netstat 表,看看是否还有任何已建立的网络连接。 【参考方案1】:

您可以增加terminationGracePeriodSeconds,默认为 30 秒。但不幸的是,没有什么可以阻止集群管理员强制删除您的 pod,并且整个节点可能会消失的各种原因。

【讨论】:

感谢您的回复。我们运行集群,因此我们不会尝试缓解节点中断、硬件故障等,我们只是尝试在对部署的 pod 映像进行托管更新期间管理服务流量。我将更新问题以使其更清楚。 另外,我们要避免的事情之一是猜测,terminationGracePeriodSeconds 似乎与当前连接的客户端无关,因此无论到 pod 的流量如何,都会被调用.我们只想暂停 kill 信号,直到客户端自愿断开连接。【参考方案2】:

我们结合上述方法来解决我们的问题。

将terminationGracePeriodSeconds 增加到我们期望在生产中看到的绝对最大值 添加 livenessProbe 以防止 Traefik 过早路由到我们的 pod 引入了一个 pre-stop 挂钩,用于注入暂停并调用监控脚本:
    使用我们的集群 Traefik 服务的外部地址监控与我们的进程 (pid 1) 的 ESTABLISHED 连接的 netstat 将 TERM 发送到 pid 1

请注意,因为我们从监控脚本将 TERM 发送到 pid 1,所以 Pod 将在此时终止,并且 TerminationGracePeriodSeconds 永远不会被命中(它是作为预防措施存在的)

这是脚本:

#!/bin/sh

while [ "$(/bin/netstat -ap 2>/dev/null | /bin/grep http-alt.*ESTABLISHED.*1/java | grep -c traefik-ingress-service)" -gt 0 ]
do
  sleep 1
done

kill -TERM 1

这是新的 pod 规范:

containers:
  - env:
    - name: spring_profiles_active
      value: dev
    image: container.registry.host/project/app:@@version@@
    imagePullPolicy: Always
    lifecycle:
      preStop:
        exec:
          command:
          - /bin/sh
          - -c
          - sleep 5 && /monitoring.sh
    livenessProbe:
      httpGet:
        path: /actuator/health
        port: 8080
      initialDelaySeconds: 60
      periodSeconds: 20
      timeoutSeconds: 3
    name: app
    ports:
    - containerPort: 8080
    readinessProbe:
      httpGet:
        path: /actuator/health
        port: 8080
      initialDelaySeconds: 60
    resources:
      limits:
        cpu: 2
        memory: 2Gi
      requests:
        cpu: 2
        memory: 2Gi
  imagePullSecrets:
  - name: app-secret
  serviceAccountName: vault-auth
  terminationGracePeriodSeconds: 86400

【讨论】:

【参考方案3】:

尝试优雅地关闭您的 Spring Boot 应用程序。

这可能会有所帮助:

https://dzone.com/articles/graceful-shutdown-spring-boot-applications

此实现将确保您的任何活动连接都不会被终止,并且应用程序将在关闭之前优雅地等待它们完成。

【讨论】:

以上是关于阻止 Spring Boot 应用程序关闭,直到所有当前请求都完成的主要内容,如果未能解决你的问题,请参考以下文章

spring-boot 2 优雅关闭网络

Spring boot + spring security - 如何阻止应用层上的CORS请求?

如何阻止 Spring Boot 添加会话 cookie?

Spring Boot 提供被安全阻止的静态内容

如何运行部署在 AWS 上的 Spring Boot 程序 [关闭]

Groovy 和 JPA 阻止 Spring Boot CrudRepository 进行插入