跳转至

部署指南

单机部署

1. 准备环境

cd by-framework-python
uv sync
cd by-framework-java
mvn clean install -DskipTests
npm install @byclaw/by-framework
npm run build

2. 启动 Redis

docker run -d --name gateway-redis \
  -p 6379:6379 \
  --restart unless-stopped \
  redis:7-alpine

3. 启动 Worker

uv run python -m by_framework \
  --worker-class my_agent.MyAgent \
  --worker-id worker-01 \
  --redis-host localhost
java -cp target/my-worker.jar MyAgent \
  --worker-id worker-01
node dist/my_agent.mjs

集群部署

by-framework 支持将 Redis 从单机换成 Redis Cluster 部署,三个 SDK 内部已经完整实现了 Cluster 适配(key 加 hash tag、 跨 slot 操作拆分、XREADGROUP 两阶段轮询、cluster-aware SCAN 等),业务代码不需要改动,只需要调整环境变量。

什么时候需要 Redis Cluster

单实例 Redis(哪怕挂了持久化和主从)在数据量或吞吐量触顶时无法再横向扩容。如果你已经在单机部署下遇到了 Redis 内存 / 连接数瓶颈,或者本身就要求多分片高可用,才需要切到 Cluster;否则单机部署(配合主从 + 哨兵做高可用)已经足够, 没必要为了"以防万一"而提前引入 Cluster 的运维复杂度。

1. 准备 Redis Cluster

本地验证可以用 docker-compose 起一个最小的 3 主 3 从集群:

docker-compose.cluster.yml
services:
  redis-cluster:
    image: redis:7-alpine
    command: >
      redis-cli --cluster create
      redis-node-1:6379 redis-node-2:6379 redis-node-3:6379
      redis-node-4:6379 redis-node-5:6379 redis-node-6:6379
      --cluster-replicas 1 --cluster-yes
    depends_on: [redis-node-1, redis-node-2, redis-node-3, redis-node-4, redis-node-5, redis-node-6]
  redis-node-1: &redis-node
    image: redis:7-alpine
    command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --appendonly yes
    ports: ["6371:6379"]
  redis-node-2:
    <<: *redis-node
    ports: ["6372:6379"]
  redis-node-3:
    <<: *redis-node
    ports: ["6373:6379"]
  redis-node-4:
    <<: *redis-node
    ports: ["6374:6379"]
  redis-node-5:
    <<: *redis-node
    ports: ["6375:6379"]
  redis-node-6:
    <<: *redis-node
    ports: ["6376:6379"]

生产环境通常由运维团队提供托管的 Redis Cluster(云厂商托管实例或自建),拿到的是一份 host:port 节点列表。

2. 设置环境变量

只需要设置一个 REDIS_CLUSTER_HOST(逗号分隔的 host:port 列表),SDK 会自动

  1. 把部署形态从 standalone 切换到 cluster
  2. 把 Redis key 的物理格式从 v1(无 hash tag)切换到 v2(带 hash tag,byai_gateway:v2: 前缀)——Cluster 下多 key 操作 必须落在同一个 slot,v1 的 key 格式做不到这一点,会触发 CROSSSLOT 错误,详见 Redis Streams 深度剖析 · Key Schema 版本

不需要再像早期版本那样分别设置 REDIS_MODE=clusterREDIS_KEY_SCHEMA_VERSION=v2 两个变量:

export REDIS_CLUSTER_HOST="10.10.168.203:6371,10.10.168.203:6372,10.10.168.203:6373,10.10.168.203:6374,10.10.168.203:6375,10.10.168.203:6376"
export REDIS_USERNAME=default
export REDIS_PASSWORD=your-password

如果显式设置了 REDIS_MODEREDIS_KEY_SCHEMA_VERSION,显式值始终优先于这条自动推断——这两个变量只在你想覆盖默认行为 (比如临时强制某个进程回退到单机模式)时才需要手动设置。

3. 启动 Worker

export REDIS_CLUSTER_HOST="10.10.168.203:6371,...,10.10.168.203:6376"
uv run python -m by_framework \
  --worker-class my_agent.MyAgent \
  --worker-id worker-01

也可以在代码里显式传入,不依赖环境变量:

run_worker(
    worker_class=MyAgent,
    worker_id="worker-01",
    redis_cluster_nodes=[
        ("10.10.168.203", 6371), ("10.10.168.203", 6372),
        ("10.10.168.203", 6373), ("10.10.168.203", 6374),
        ("10.10.168.203", 6375), ("10.10.168.203", 6376),
    ],
    # redis_cluster_nodes 非空即可推断为集群模式,不需要再单独传 redis_mode="cluster"
)
export REDIS_CLUSTER_HOST="10.10.168.203:6371,...,10.10.168.203:6376"
java -cp target/my-worker.jar MyAgent --worker-id worker-01

RedisClient.getInstance()GatewayWorker 无显式 RedisClient 参数时的默认路径)会自动读取 REDIS_CLUSTER_HOST 并切到 Cluster;需要显式控制时可以自己构造:

RedisConnectionConfig config = RedisConnectionConfig.fromEnv();
RedisClient.init(config);
MyAgent worker = new MyAgent("worker-01");
new WorkerRunner(worker).start();
export REDIS_CLUSTER_HOST="10.10.168.203:6371,...,10.10.168.203:6376"
node dist/my_agent.mjs

runWorker()RunWorkerOptions 目前没有 redisMode / redisClusterNodes 字段

TypeScript 的 runWorker() 只能通过环境变量(REDIS_CLUSTER_HOST)切到集群模式——这条路径本身是可靠的, 因为 runWorker() 内部没有覆盖 mode,会一路 fallback 到 createRedis()REDIS_CLUSTER_HOST 的解析。 但如果要完全脱离环境变量、纯编程式指定集群节点,RunWorkerOptions 还没有暴露对应字段,需要自己先构造好 客户端再传入 redisClient

import { createRedis, runWorker } from '@byclaw/by-framework';

const redisClient = createRedis({
    mode: 'cluster',
    clusterNodes: [
        { host: '10.10.168.203', port: 6371 },
        { host: '10.10.168.203', port: 6372 },
        // ...
    ],
});

await runWorker(MyAgent, { workerId: 'worker-01', redisClient });

4. 数据迁移注意事项

切到 v2 key schema 后,所有参与 hash tag 改造的 key 物理名字会变(例如 byai_gateway:registry:worker:online:{worker_id}byai_gateway:v2:registry:worker:{worker_id}:online),旧 key 下的数据在 新代码里读不到。多数 key 都带 TTL(会话数据 7 天、任务组 1 天、心跳 15 秒等),部署新版本后旧 key 自然过期即可,不需要额外 迁移脚本;只有极少数无 TTL 的 key(如管理员标记状态)需要一次性迁移。建议先在测试环境完整走一遍升级流程,确认数据丢失面 可以接受,再排期上生产。

多 Worker 部署

如需横向扩展,可以启动多个不同 worker_id 的 Worker 进程,并让它们共享同一个 Redis 实例(或同一个 Redis Cluster)与相同的 target_agent_type stream。

生产环境建议

使用连接池

run_worker(
    worker_class=MyAgent,
    redis_max_connections=50
)
// Java SDK 使用 Jedis 连接池,通过配置类设置
RedisConfig config = RedisConfig.builder()
    .maxConnections(50)
    .build();
runWorker(MyAgent, {
    redisMaxConnections: 50,
});

配置监控

import logging
from by_framework.common.logger import setup_logging

setup_logging(level=logging.INFO, use_json=True)
// Java SDK 使用 SLF4J + Logback
// 在 logback.xml 中配置 JSON 格式输出
// TypeScript SDK 使用 console 输出
// 可通过 pluginConfigurator 配置自定义日志插件

环境变量

环境变量 描述 默认值
BYAI_WORKER_CONCURRENCY 最大并发数 50
BYAI_WORKER_FETCH_COUNT 批量获取消息数 10
BYAI_REDIS_MAX_CONNECTIONS Redis 最大连接数 max_concurrency + 10
REDIS_HOST Redis 主机地址(单机模式) localhost
REDIS_PORT Redis 端口(单机模式) 6379
REDIS_DATABASE Redis 数据库号(单机模式)。替代旧的 REDIS_DBREDIS_DB 仍可用,作为过期兼容 fallback,读取时打印一条 deprecation 日志) 0
REDIS_USERNAME Redis 用户名 (无)
REDIS_PASSWORD Redis 密码 (无)
REDIS_CLUSTER_HOST 逗号分隔的 host:port 列表;配置后自动切到 Cluster 模式,并自动把 key schema 推断为 v2(未显式设置 REDIS_MODE / REDIS_KEY_SCHEMA_VERSION 时) (无),即单机模式
REDIS_CLUSTER_NODES REDIS_CLUSTER_HOST 的旧变量名 / 等价别名,未设置 REDIS_CLUSTER_HOST 时才会读取;不会触发 key schema 自动推断 (无)
REDIS_MODE 显式指定 standalone / cluster,优先级高于 REDIS_CLUSTER_HOST 的隐式推断 standalone(未显式设置且无 REDIS_CLUSTER_HOST 时)
REDIS_KEY_SCHEMA_VERSION 显式指定 v1 / v2,优先级高于 REDIS_CLUSTER_HOST 的隐式推断;Cluster 模式下必须最终为 v2,否则启动时 fail-fast 拒绝 v1(未显式设置且无 REDIS_CLUSTER_HOST 时)

run_worker 配置参数

参数 类型 描述 默认值
worker_class Type[GatewayWorker] 必填。业务 Worker 类。 -
worker_id str Worker 实例的唯一标识名。 "worker-1"
redis_host str Redis 服务器地址(单机模式)。 "localhost"
redis_port int Redis 端口(单机模式)。 6379
redis_db int Redis 数据库号(单机模式)。 0
redis_username str Redis 用户名 (可选)。 None
redis_password str Redis 密码 (可选)。 None
redis_mode Literal["standalone", "cluster"] 显式指定部署形态;不传时按环境变量推断(含 REDIS_CLUSTER_HOST 隐式判定)。 None
redis_cluster_nodes List[Tuple[str, int]] 集群节点列表。非空即可推断为集群模式,不需要再单独传 redis_mode="cluster" None
workspace_dir str 任务执行的本地工作目录。 "/tmp/gateway-workspace"
consumer_group str Redis 消费者组名称。 "agent_engines"
max_concurrency int 最大并发处理数。 50
fetch_count int 每次批量获取的消息数量。 10

以上 redis_* 参数全部支持"不传则读环境变量",且显式传参始终优先于环境变量,两条路径可以任意混用。

参数 类型 描述 默认值
workerId String Worker 实例的唯一标识名。 "worker-1"
RedisConnectionConfig RedisConnectionConfig Redis 连接配置,支持单机 / 集群两种模式;不显式传入时 GatewayWorker/WorkerRunnerRedisClient.getInstance() 默认单例,自动从环境变量(含 REDIS_CLUSTER_HOST)解析。 RedisConnectionConfig.fromEnv()
WorkerConfig WorkerConfig Worker 运行参数配置。 默认值

Java 没有一个统一的 run_worker(...) 函数,而是显式构造 RedisClient / WorkerRunnerRedisConnectionConfig 的字段(mode / host / port / db / clusterNodes / username / password)与环境变量一一对应, 参见集群部署一节的示例。

参数 类型 描述 默认值
workerId string Worker 实例的唯一标识名。 "worker-1"
redisHost string Redis 服务器地址(单机模式)。 "localhost"
redisPort number Redis 端口(单机模式)。 6379
redisDb number Redis 数据库号(单机模式)。 0
redisUsername string Redis 用户名 (可选)。 undefined
redisPassword string Redis 密码 (可选)。 undefined
redisClient Redis 预先构造好的 Redis 客户端,传入后跳过上面几个字段,直接复用。目前是 TypeScript 里唯一的纯编程式集群配置方式RunWorkerOptions 还没有 redisMode / redisClusterNodes 字段)。 undefined
workspaceDir string 任务执行的本地工作目录。 "/tmp/gateway-workspace"
consumerGroup string Redis 消费者组名称。 "agent_engines"
maxConcurrency number 最大并发处理数。 50
fetchCount number 每次批量获取的消息数量。 10
pluginList Plugin[] 插件实例列表。 []
pluginDir string 插件目录路径。 undefined

集群模式在 TypeScript 里只能通过 REDIS_CLUSTER_HOST 环境变量触发(runWorker() 不会覆盖 mode,会一路 fallback 到底层 createRedis() 对该环境变量的解析),或者通过上面的 redisClient 字段自己传入已经用 createRedis({ mode: 'cluster', clusterNodes: [...] }) 构造好的客户端。