Skip to content

Snail Job:分布式任务调度与业务重试使用指南 ​

订单已经付款,通知仓储系统的请求却超时了。直接返回失败会让后续履约停住;在请求线程里连续重试,又可能放大下游故障。团队需要留下失败任务,在下游恢复后继续执行,同时能查到哪笔订单仍未处理。

Snail Job 把业务重试和定时任务调度放在同一个管理平台中:服务端保存任务、安排执行并提供控制台,客户端接入业务应用,真正执行 Java 方法。定期对账可以注册为调度任务,失败的订单通知可以注册为重试场景,两者分别配置和观察。仓库简介还提供工作流、多语言客户端和告警入口。

下面以 2.0.2 为基准,从单机 Docker 环境走到一个成功的调度批次和一次失败通知的恢复。Java 示例使用 JDK 21、Spring Boot 4.0.3,与这个版本的构建配置保持一致。

GitHub 仓库信息 ​

信息内容
项目标题Snail Job
项目描述分布式任务重试与任务调度平台,支持失败任务重放、集中管理和任务编排。
GitHub 仓库aizuda/snail-job
官网地址Snail Job 官方文档
主要开发语言Java 98.9%、JavaScript 0.99%、CSS 0.06%
开源许可证Apache-2.0
最近代码更新2026-07-24(GitHub pushed_at,核对日期:2026-10-08)

调度和重试各管什么 ​

定时调度回答“什么时候执行、交给哪个客户端”。例如每分钟生成一次对账摘要,集群模式会按路由策略选择一个在线客户端;广播模式则适合让多个客户端分别执行维护任务。若一次执行需要处理许多数据,还可以继续探索静态分片、Map 和 MapReduce。集群模式示例给出了任务类型、路由、阻塞和超时的配置方式。

业务重试回答“这次已经失败的调用,后面如何继续”。Java 方法上的 @Retryable 可以选择只在本地重试、只交给服务端重试,或先本地再远程。远程重试会涉及场景、参数上报、等待间隔和最大次数;达到终止条件后还需要运维人员判断是否恢复执行。一次 HTTP 请求返回失败,与后台是否已经留下待重试记录,是两个需要分别观察的结果。

重试也不能替业务代码完成幂等处理。Snail Job 的幂等 ID 用于限制同一组、同一场景中重复的在途重试记录;任务完成后,同一个 ID 仍可能产生新任务。支付扣款、发券、通知等副作用需要业务唯一键、数据库约束或下游幂等接口共同保护。幂等 ID 说明描述了这个边界。

工作流进一步处理任务之间的先后关系,例如“拉取订单 → 对账 → 生成报表”。官方工作流介绍列出了任务节点和决策节点;第一次接入应先把单任务和失败恢复跑通,再增加编排。

是否适合当前系统 ​

已经有多实例 Java 应用,需要集中维护任务、查看失败调用、控制重试节奏的团队,可以重点评估 Snail Job。代价包括一个长期运行的服务端、数据库维护、客户端依赖,以及服务端与业务客户端之间的网络连通。

只有单实例、少量周期性方法时,Spring 的 @Scheduled 通常更省维护成本。已有 XXL-JOB 体系而主要需求仍是集中调度时,迁移应先证明新增重试能力带来的价值;复杂批处理与任务编排也可以对照 PowerJob。这里比较的是引入理由,不把官网的功能勾选表当作其他产品“不支持”的证据。

接入前尤其要确认版本线。2.0.2 的根构建文件设置 Java 21、Spring Boot 4.0.3,Maven Central 中同版 starter 也引用 Spring 7。官网部分客户端页面仍介绍 JDK 8、JDK 17 和 Spring Boot 2/3 的历史路径,不能据此将 2.0.2 依赖直接加入旧应用。已有项目先在隔离分支核对依赖树、启动、序列化和任务回调,再确定匹配的客户端版本。2.0.2 Release提到低版本客户端连接高版本服务端的修复,但没有给出覆盖所有组合的兼容矩阵。

Python 和 Go 客户端位于独立仓库;官网使用限制说明将这两类客户端的接入范围列为 Job。下面的业务重试示例使用 Java,不把多语言调度等同于多语言重试。

Docker 搭建服务 ​

主路径采用 固定源码构建 + MySQL 8.0.33。官方提供 opensnail/snail-job 镜像运行方式,但本次未能读取 Docker Hub 的 2.0.2 镜像元数据,因此不将未经核验的镜像 tag 作为必需步骤。需要更短路径时,可以在自行确认目标 tag、架构和对应 SQL 后使用官方镜像部署说明。

准备 Docker Engine 或 Docker Desktop、Compose v2 及以上、Git、JDK 21、Maven 3.6.3 及以上、OpenSSL 和 Bash。官网建议服务器至少 2 核 CPU、4GB 内存、50GB 可用空间;本地构建还需为 Maven 缓存和镜像预留空间,这不是本示例的实测资源下限。仓库镜像工作流声明 linux/amd64 和 linux/arm64;本地构建使用当前机器架构,开始前应确认 Dockerfile 基础镜像和 MySQL 镜像支持目标平台。

部署步骤按 2026-10-08 核对的资料整理,尚未实际启动容器。示例配置保留数据库和服务端日志,方便重复启动与后续维护;单机拓扑没有提供高可用保障。

构建固定版本 ​

在自己的工作目录执行,确保 snail-job-lab 尚不存在,避免覆盖已有数据:

bash
mkdir snail-job-lab
cd snail-job-lab
git clone --depth 1 --branch vsj2.0.2 https://github.com/aizuda/snail-job.git upstream
cd upstream
java -version
mvn -version
mvn -B -DskipTests clean package
test -s snail-job-server-starter/target/snail-job-server-exec.jar
docker build -t snail-job-local:2.0.2 snail-job-server-starter
cd ..

成功信号是服务端可执行 JAR 存在,并生成 snail-job-local:2.0.2 镜像。Maven 会下载依赖并执行项目构建插件;先阅读构建文件,确认允许在当前机器执行。仓库UI 模块默认启用 skipFrontend,同时携带已打包的控制台资源;普通构建无需另取前端仓库或自行启用前端编译。

如果提示 class file version 或编译目标不匹配,检查 Maven 输出的 Java home 是否确实指向 JDK 21。依赖下载失败时先检查 Maven 仓库访问;不要通过随机降级 Spring Boot 或删除模块来绕过失败。

初始化配置和数据库脚本 ​

仍在 snail-job-lab 中,使用 Bash 执行下面整个初始化块。脚本拒绝覆盖已有 .env,生成两份独立随机数据库口令并限制文件权限:

bash
bash <<'BASH'
set -euo pipefail
test ! -e .env || { echo '.env already exists; initialization stopped'; exit 1; }
umask 077
cat > .env <<EOF
MYSQL_ROOT_PASSWORD=$(openssl rand -hex 24)
MYSQL_PASSWORD=$(openssl rand -hex 24)
EOF
chmod 600 .env
mkdir -p init
sed '/^DROP DATABASE IF EXISTS snail_job;$/d; /^CREATE DATABASE snail_job;$/d; /^USE snail_job;$/d' \
  upstream/doc/sql/snail_job_mysql.sql > init/01-schema.sql
test -s init/01-schema.sql
BASH

原始 SQL 依次删除、创建并选择 snail_job 数据库。这里由 MySQL 镜像的 MYSQL_DATABASE 建库并选择数据库,因此移除 DROP DATABASE、CREATE DATABASE 和 USE 三句,保留同版本表结构和初始数据。否则初始化脚本会删掉镜像刚建立的数据库。MySQL 的初始化脚本只会在数据目录为空时执行;改动脚本后重启已有卷不会重新初始化。

创建 compose.yaml:

yaml
name: snail-job-lab

services:
  mysql:
    image: mysql:8.0.33
    restart: unless-stopped
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?required}
      MYSQL_DATABASE: snail_job
      MYSQL_USER: snail_job
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:?required}
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_general_ci
      - --lower_case_table_names=1
    volumes:
      - mysql-data:/var/lib/mysql
      - ./init/01-schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
    healthcheck:
      test:
        - CMD-SHELL
        - 'MYSQL_PWD="$${MYSQL_PASSWORD}" mysql --protocol=TCP -h127.0.0.1 -usnail_job -e "SELECT 1 FROM snail_job.sj_namespace LIMIT 1"'
      interval: 10s
      timeout: 5s
      retries: 30
      start_period: 30s

  server:
    image: snail-job-local:2.0.2
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      TZ: Asia/Shanghai
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/snail_job?useSSL=false&characterEncoding=utf8&useUnicode=true
      SPRING_DATASOURCE_USERNAME: snail_job
      SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD:?required}
      SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.cj.jdbc.Driver
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - server-logs:/snailjob/server/data/log

volumes:
  mysql-data:
  server-logs:

networks:
  default:
    name: snail-job-lab

这是基于官方 MySQL 初始化路径和服务端配置整理的两服务示例。仓库的 doc/docker/docker-compose.yaml 提供多个测试数据库,不包含 Snail Job 服务,不需要把所有数据库一起启动。上述 MySQL 不映射宿主机端口,服务端使用仅限 snail_job 数据库的账户;root 口令只用于数据库管理。

服务端的 Web 端口为 8080,路径为 /snail-job;Java 通信端口为 17888。当前只把 Web 入口绑定到本机,17888 留在容器网络中。.env 属于敏感文件,容器环境变量也能被具备 Docker 管理权限的人读取,不能当作专用秘密管理系统。

启动和验收 ​

在 snail-job-lab 中执行:

bash
docker compose config --quiet
docker compose up -d
docker compose ps
docker compose logs --tail=100 mysql server
curl -fS -o /dev/null http://127.0.0.1:8080/snail-job/

MySQL 应变为 healthy,服务端应保持运行,浏览器应能打开 本机控制台。HTTP 有响应只说明 Web 入口可访问,后面还要用真实执行器验证调度。官方入门文档给出的首次账号为 admin,密码为 admin;第一次登录后立即修改密码,再考虑开放远程访问。

若 MySQL 始终 unhealthy,先查看初始化日志。若出现重复建库,检查挂载的是否为处理后的 init/01-schema.sql。若服务端报数据库认证失败,确认 .env 没有在已有卷初始化后被重新生成;修改环境变量不会自动修改库内口令。

如果首次初始化已经失败,仅修改 SQL 不足以让已有数据目录重新执行脚本。只在确认这是新建且没有业务数据的失败演示环境后,才在 snail-job-lab 中执行以下清理;命令会永久删除失败的 MySQL 数据,保留服务端日志卷:

bash
docker compose stop server mysql
docker compose rm -f mysql
docker volume rm snail-job-lab_mysql-data
docker compose up -d
docker compose ps

重新观察 MySQL healthy 和控制台登录。已有任务或业务数据的环境应先备份并诊断,不能用这组命令恢复服务。

接入 Java 执行器 ​

下面创建一个独立 Spring Boot 示例,包含调度执行器和可恢复的通知失败。业务客户端也放入 snail-job-lab 网络,使服务端能访问客户端声明的 demo:17889。如果只将宿主机应用的 127.0.0.1 注册给容器,服务端会访问自己的容器,任务就无法执行。

准备空间、组和场景 ​

在控制台创建命名空间 demo,复制生成的唯一标识,不要把显示名称作为 namespace。切换到该空间后创建组 demo_group,启用组,设置一个独立随机 Token 并保存。组名、Token 和 namespace 必须与客户端一致;@EnableSnailJob 中显式设置的组名优先于配置文件,本例将组名统一放在配置中。

在该组下创建并启用重试场景 order_delivery,选固定等待间隔 10 秒、最大重试次数 5。不要依赖自动创建场景的默认策略,先确认界面里的组和场景都启用,再触发示例。组管理的“初始化场景”选项决定缺失场景能否自动建立;本例采用手动配置。组管理和重试场景说明了相关入口。

添加依赖和配置 ​

在 snail-job-lab 中创建目录:

bash
mkdir -p demo/src/main/java/example/snail
mkdir -p demo/src/main/resources

把以下内容保存为 demo/pom.xml:

xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.3</version>
    <relativePath/>
  </parent>
  <groupId>example</groupId>
  <artifactId>snail-demo</artifactId>
  <version>1.0.0</version>
  <properties>
    <java.version>21</java.version>
    <snail-job.version>2.0.2</snail-job.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>
    <dependency>
      <groupId>com.aizuda</groupId>
      <artifactId>snail-job-client-starter</artifactId>
      <version>${snail-job.version}</version>
    </dependency>
    <dependency>
      <groupId>com.aizuda</groupId>
      <artifactId>snail-job-client-job-core</artifactId>
      <version>${snail-job.version}</version>
    </dependency>
    <dependency>
      <groupId>com.aizuda</groupId>
      <artifactId>snail-job-client-retry-core</artifactId>
      <version>${snail-job.version}</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

starter 的两个核心模块是可选且为 provided 依赖,因此需要按用途显式添加。只做调度时可以去掉重试模块,只做重试时可以去掉调度模块;本例保留两者。三个 2.0.2 构件的 POM 均可在 Maven Central 查到。

保存 demo/src/main/resources/application.yml:

yaml
server:
  port: 8081

snail-job:
  namespace: ${SNAIL_NAMESPACE}
  group: demo_group
  token: ${SNAIL_TOKEN}
  host: demo
  port: 17889
  rpc-type: grpc
  server:
    host: server
    port: 17888
  openapi:
    host: server
    port: 8080
    prefix: snail-job

server:17888 用于 Java 通信,server:8080/snail-job 是 Web/OpenAPI 地址,demo:17889 是服务端访问客户端的地址。不要把控制台端口填到 snail-job.server.port。这些默认值与配置字段可在客户端配置类中核对。

编写示例方法 ​

以下四个文件均放在 demo/src/main/java/example/snail/。先保存 DemoApplication.java:

java
package example.snail;

import com.aizuda.snailjob.client.starter.EnableSnailJob;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableSnailJob
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

保存 ReconciliationJob.java,让控制台调度一个有明确结果的方法:

java
package example.snail;

import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.common.log.SnailJobLog;
import com.aizuda.snailjob.model.dto.ExecuteResult;
import org.springframework.stereotype.Component;

@Component
@JobExecutor(name = "reconciliationDemo")
public class ReconciliationJob {
    public ExecuteResult jobExecute(JobArgs args) {
        SnailJobLog.REMOTE.info("reconciliation demo executed");
        return ExecuteResult.success("reconciliation demo completed");
    }
}

这是对账入口的演示,没有读取真实订单。执行器名称必须与控制台配置一致;注解方式支持无参方法或一个 JobArgs 参数,结果使用 2.0.2 的 com.aizuda.snailjob.model.dto.ExecuteResult。执行器文档给出了方法约束。

保存 DeliveryService.java,用开关模拟下游先故障、后恢复:

java
package example.snail;

import com.aizuda.snailjob.client.core.annotation.Retryable;
import com.aizuda.snailjob.client.core.retryer.RetryType;
import com.aizuda.snailjob.common.log.SnailJobLog;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.stereotype.Service;

@Service
public class DeliveryService {
    private final AtomicBoolean available = new AtomicBoolean(false);

    @Retryable(scene = "order_delivery", retryStrategy = RetryType.ONLY_REMOTE,
               async = false, timeout = 10000)
    public void deliver(String orderId) {
        if (!available.get()) {
            throw new IllegalStateException("warehouse temporarily unavailable");
        }
        SnailJobLog.REMOTE.info("delivery recovered, orderId={}", orderId);
    }

    public void recover() {
        available.set(true);
    }
}

这里选择 ONLY_REMOTE,方便直接观察服务端重试记录;async=false 改为同步上报,timeout 使用注解默认的毫秒单位。业务方法仍保留默认的异常抛出行为。生产代码需要按异常类型排除参数错误、权限拒绝等无需重试的失败,并单独处理业务事务与参数里的敏感数据。

保存 DemoController.java:

java
package example.snail;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {
    private final DeliveryService delivery;

    public DemoController(DeliveryService delivery) {
        this.delivery = delivery;
    }

    @PostMapping("/demo/orders/{orderId}/deliver")
    public ResponseEntity<String> deliver(@PathVariable("orderId") String orderId) {
        try {
            delivery.deliver(orderId);
            return ResponseEntity.ok("delivered");
        } catch (IllegalStateException ex) {
            return ResponseEntity.status(503).body("delivery failed; inspect retry record");
        }
    }

    @PostMapping("/demo/recover")
    public String recover() {
        delivery.recover();
        return "warehouse recovered";
    }
}

控制器通过 Spring 注入的另一个 Bean 调用重试方法,确保经过方法拦截器。不要在同一个对象中用 this.deliver() 代替这个调用。恢复开关仅存在内存中,重启客户端就回到故障状态;两个示例接口只用于本机演示,不要放入生产应用。

启动客户端 ​

保存 demo/Dockerfile:

dockerfile
FROM bellsoft/liberica-openjdk-debian:21-cds
WORKDIR /app
COPY target/snail-demo-1.0.0.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

在 snail-job-lab 中执行下面的 Bash 块,输入自己创建的 namespace 唯一标识和组 Token,生成权限受限的客户端配置文件。不要使用官网示例中的固定 Token:

bash
bash <<'BASH'
set -euo pipefail
test ! -e .env.demo || { echo '.env.demo already exists; initialization stopped'; exit 1; }
read -r -p 'Namespace ID: ' demo_namespace < /dev/tty
read -r -s -p 'Group Token: ' demo_token < /dev/tty
echo
test -n "$demo_namespace" && test -n "$demo_token"
umask 077
printf 'SNAIL_NAMESPACE=%s\nSNAIL_TOKEN=%s\n' "$demo_namespace" "$demo_token" > .env.demo
chmod 600 .env.demo
unset demo_namespace demo_token
BASH
cd demo
mvn -B -DskipTests package
docker build -t snail-job-demo:2.0.2 .
cd ..
docker run -d --name snail-job-demo-client \
  --network snail-job-lab --network-alias demo \
  --env-file .env.demo \
  -p 127.0.0.1:8081:8081 \
  snail-job-demo:2.0.2
docker logs --tail=100 snail-job-demo-client

日志应显示应用启动;在正确命名空间的“在线机器”中,应看到 demo_group 的客户端。这里未向宿主机发布客户端通信端口 17889,也没有向公网发布演示接口。若客户端启动失败,先检查 Token、组状态和服务端日志,而不是不断更换端口。

完成调度和失败恢复 ​

执行一次调度 ​

在当前命名空间新增定时任务:

配置项示例值
任务名称对账演示
组名称demo_group
状态启用
任务类型集群
执行器类型Java
执行器名称reconciliationDemo
触发类型与间隔固定时间,60 秒
路由策略轮询
阻塞策略丢弃
超时时间60 秒
最大重试次数0

保存后先手动执行一次,打开执行批次和任务详情。成功标准是批次执行成功、结果包含 reconciliation demo completed,并可查看远程日志 reconciliation demo executed。固定时间用秒填写;不要将“任务名称”误填成执行器名称,也不要仅凭在线机器列表判定调度正常。

“丢弃”策略会在前一批次尚未完成时丢弃新的触发,适合这个短任务演示。实际对账是否允许跳过,需要由业务决定;若每次触发都必须处理,重新评估阻塞策略及任务拆分,不要照搬示例。

观察失败通知恢复 ​

先确认 order_delivery 场景已启用,再从宿主机执行:

bash
curl -i -X POST http://127.0.0.1:8081/demo/orders/order-demo-001/deliver

初始开关关闭,示例会返回 503。进入重试任务列表,在 demo_group、order_delivery 下查找对应记录,确认参数包含 order-demo-001,查看执行记录中的失败原因。503 本身不能证明上报成功;找不到记录时先排查场景、组、Token 和上报日志。

马上恢复模拟下游,避免消耗完设置的 5 次重试:

bash
curl -fS -X POST http://127.0.0.1:8081/demo/recover
docker logs --tail=100 snail-job-demo-client

接下来等待一次远程重试,观察原记录变为成功,并出现 delivery recovered, orderId=order-demo-001。同时核对该记录的执行次数和最近执行时间,确认新增执行发生在恢复接口调用之后。恢复接口只打开开关,不主动调用通知方法;后续成功应来自那条待重试任务。若已经到最大次数,先确认下游恢复,再使用控制台提供的手动执行或死信恢复入口,并再次核对状态。

此示例没有真正调用仓储系统,也没有实现订单级持久幂等。生产验收需要追加:同一订单重复调用、下游已成功但响应丢失、客户端执行中断、Token 错误和超过最大次数等情况,确认业务结果不会重复产生。

一次执行如何经过平台 ​

控制台配置的是任务规则,执行代码留在业务客户端;数据库保存规则和执行状态。服务端发出执行请求后,客户端调用已注册的方法,再返回结果或上报执行情况。失败调用的重试则先从客户端进入平台,之后按场景策略重新安排。

Mermaid 流程图
查看源码
flowchart LR
    U[控制台:空间、组、任务与场景] --> S[Snail Job 服务端]
    S <--> DB[(MySQL:规则与执行状态)]
    S -->|调度或重试请求| C[Java 业务客户端]
    C --> J[调度执行器]
    C --> R[业务重试方法]
    C -->|失败上报、结果与日志| S

因此,客户端能连接服务端只是接入的一部分。服务端也要能到达客户端公布的地址;Docker、Kubernetes 或跨网段环境尤其容易在这里卡住。本例统一使用容器 DNS 名称,避免把宿主机回环地址误当作可路由的客户端地址。

数据维护与升级 ​

重建与恢复 ​

snail-job-lab_mysql-data 保存平台数据库,包含命名空间、组及 Token、任务与场景配置、执行状态等;snail-job-lab_server-logs 保存服务端文件日志。.env 保存数据库口令,.env.demo 保存客户端接入配置。容器重建不会删除这两个命名卷,但卷名依赖这里固定的 Compose 项目名。

创建一次演示任务后,在 snail-job-lab 执行:

bash
docker compose up -d --force-recreate server
docker compose ps

重新登录,确认任务和场景仍存在,再手动调度一次。这个验收同时检查数据库保留和重建后的业务连通,不能用“卷还在”替代业务验证。

恢复单元以 一致的 MySQL 备份、对应应用版本和有效配置 为核心。做离线备份前,先停止演示客户端及服务端,防止调度、失败上报和后台清理继续写入:

bash
docker stop snail-job-demo-client
docker compose stop server

按数据库与容器数据备份恢复教程选择数据库导出或数据库停止后的卷备份;不要直接归档仍在写入的 MySQL 数据目录。首次恢复应在隔离环境进行,核对 namespace、组 Token、任务、重试积压和执行记录,并只接入测试客户端手动执行一次任务,避免把待执行的真实业务再次投递。

.env 的口令必须与恢复后的数据库一致;修改文件不会重置账户密码。日志卷用于排障,可独立备份,不能代替任务数据库。需要恢复演示时,先启动服务端并确认数据库可用,再启动客户端:

bash
docker compose up -d server
docker start snail-job-demo-client

升级和回退 ​

升级前保留当前版本源码或构建产物、镜像、Compose 配置、口令和数据库备份;在独立目录检出目标 Release 构建新镜像,不直接覆盖正在使用的本地 tag。先阅读目标版本 Release 与升级说明,核对 SQL 增量迁移和 Java 客户端变化,再决定生产窗口。

doc/sql/snail_job_mysql.sql 是新库全量初始化脚本,不能在已有业务库中重新执行作为升级。官方更新日志中存在表结构变更,但本指南不提供任意旧版本到 2.0.2 的通用迁移 SQL;缺少目标跨度的迁移方案时,应暂停升级并补齐方案。

升级后至少验证登录、客户端注册、一次调度成功和一次失败恢复。若数据库已经迁移,回退镜像可能仍无法运行;需要按旧应用版本和升级前数据库备份一起恢复,并评估恢复点之后产生的业务副作用。通用容器更新命令与停止、删除的区别见Compose 服务生命周期。

暂停演示时,在 snail-job-lab 执行 docker stop snail-job-demo-client 和 docker compose stop,保留容器、网络与数据。需要删除容器和网络时,先执行 docker rm -f snail-job-demo-client,再执行 docker compose down;独立客户端若仍连接网络,会阻止 Compose 删除该网络。这组删除操作仍保留命名卷。docker compose down -v 则会删除平台数据库及服务端日志卷,属于数据清理,不能作为故障排查的常规第一步。源代码、初始化文件、.env、.env.demo 和本地镜像仍需自行管理。

开放远程访问 ​

先更改默认管理员密码、限制管理账号权限、使用独立组 Token,再参考Compose 生产安全和反向代理与 HTTPS。控制台涉及任务配置和脚本执行能力,应作为管理入口限制访问,不能只加 HTTPS 就直接公开。

本例的 MySQL、17888 和 17889 都留在容器网络内。跨主机部署时只向受信任的业务网络开放必要通信,并验证双向路由。客户端执行脚本的权限来自业务进程;不要为运行任务随意挂载 Docker Socket、授予特权或运行不可信脚本。官网旧部署页仍有 Netty 代理片段,而 2.0.2 默认使用 gRPC,不能直接把普通 HTTP 代理配置套到 17888 上。

常见接入问题 ​

现象定位与处理再验证
控制台正常,但客户端不在线核对 namespace 唯一标识、组名、Token、组是否启用,查看客户端注册日志正确空间的在线机器中出现客户端
客户端在线,任务执行失败检查服务端能否解析并访问公布的 demo:17889;宿主机 127.0.0.1 不适用于本例手动执行,批次成功且有结果
找不到执行器检查 @Component 扫描范围、job-core 依赖和 reconciliationDemo 名称控制台选择或填写执行器后成功执行
调用了方法,没有重试记录检查 retry-core、@EnableSnailJob、跨 Bean 调用、组及场景启用状态和上报失败日志用故障开关重新触发并看到记录
重建后数据库认证失败对照初次初始化时的口令;已有卷不会因 .env 修改而更新账户正确凭据下数据库 healthy、服务端稳定运行
老 Spring Boot 项目接入报类缺失核对 2.0.2 的 Java 21 / Spring 7 依赖线,选择经验证的匹配客户端版本依赖树、启动及真实任务回调均通过

完成演示后,下一步可以把 DeliveryService 换成一个具有订单唯一键的真实测试通知接口,并让下游模拟“已处理但响应超时”。这比先增加大量任务更能检验重试是否符合业务语义。

参考资料 ​

全部公开文章由同一个站点构建和发布。