第五篇:bs_worker 主流程
前面介绍了bs-admin的主流程以及一个构建任务下发的流程,下一步会重点介绍bs_worker,bs-worker通过心跳下发的不同配置,实现不同的角色,主要包括:processor、builder、merger。

上图是build service的架构,主要包括几个角色:bs admin,processor,builder和merger。今天先重点介绍bs_worker的主流程。
服务工作节点:ServiceWorker
整个bs_worker的入口是ServiceWorker,他负责启动、管理和协调各种具体的"工人角色"(如构建器、处理器等),在启动的时候,完成3件事:
- 自身身份识别(分区ID (PartitionId))的识别。PartitionId 包含了工作节点的详细身份信息,例如它属于哪个应用、哪个版本、扮演什么角色等
- 根据角色创建对应的工作状态处理器(WorkerStateHandler),包括:
- BuilderServiceImpl负责构建索引。
- ProcessorServiceImpl负责数据预处理。
- MergerServiceImpl负责合并索引分片。
- TaskStateHandler 负责执行通用任务。
- AgentServiceImpl 负责管理子进程。
- BuildJobImpl 负责整个 Job 的构建流程。
- 创建心跳执行器,负责驱动整个工作流程
根据3件事,我们分别介绍这3个重要概念。
分区ID
why need
在bs-worker中,每个 ServiceWorker 进程(在成为领导者后)都负责执行一个具体的、与数据分区相关的任务,我们需要有一个统一的方式来标识一个具体的实例。
what 核心职责
proto::PartitionId 是一个 Protobuf 消息结构,定义在 build_service/proto/BasicDefs.proto 文件中。它包含了构成一个唯一工作单元的关键信息。
ini
enum RoleType {
ROLE_UNKNOWN = -1;
ROLE_PROCESSOR = 0;
ROLE_BUILDER = 1;
ROLE_MERGER = 2;
ROLE_JOB = 3;
ROLE_ALTER_FIELD = 4;
ROLE_TASK = 5;
ROLE_AGENT = 6;
}
enum BuildStep {
BUILD_STEP_FULL = 0;
BUILD_STEP_INC = 1;
NO_BUILD_STEP = 2;
BUILD_STEP_IDLE = 3; // inc processor not exist
}
// [from, to]
message Range {
optional uint32 from = 1 [default = 0];
optional uint32 to = 2 [default = 65535];
}
// builder not require dataTable
message BuildId {
optional string dataTable = 1;
optional uint32 generationId = 2;
optional string appName = 3;
}
message PartitionId {
optional RoleType role = 1 [default = ROLE_UNKNOWN];
optional BuildStep step = 2 [default = BUILD_STEP_FULL];
required Range range = 3;
required BuildId buildId = 4;
一些示例:

how
解析的方法:
markdown
1. 去掉第一个 .
2. 根据 . 切分,会有7个部分[0,1,2,3,4,5,6]
- in1.1750125387.processor.full.0.65535.1-clustername-in1-a-5eb9
- in1.1750125387.task.taskid-7516731375828913056-name-fullmerge-taskname-general-task.0.65535.in1-a-5a5a
3. role是第2,上面分别是 processor、task
4. task的话,第3是taskid
5. processor的话,第3个是 full or inc 的步骤表示
6. 4、5分别是 range 的 from、to
7. 如果是processor,6是taskid
8. 如果是其他,则是 cluster
工作状态处理器 (WorkerStateHandler)
why need
worker 项目设计的目标是能够运行不同类型的构建相关任务:比如构建索引(Builder)、处理数据(Processor)、合并分片(Merger)、执行任意任务(Task)、甚至管理子进程(Agent)。
每种类型的任务,其具体执行流程、需要维护的状态信息、如何响应"启动"、"停止"、""等指令的方式都完全不同。Builder 需要处理数据源、调用索引库接口;Processor 需要读写消息队列、执行处理插件;Merger 需要协调不同版本的索引进行合并。
如果 心跳执行器 需要了解并直接调用每种任务类型特有的方法,那么它的代码会变得异常复杂且难以维护,因为它需要包含所有任务类型的逻辑分支。
WorkerStateHandler 的存在正是为了解决这个问题。它是一个基类,定义了一组通用接口(虚函数),所有不同类型的具体工作逻辑(Builder、Processor 等)都必须实现这些接口。这样一来,心跳执行器 只需要持有一个 WorkerStateHandler 类型的指针,并通过这些通用接口与底层的具体实现进行交互,而无需知道这个指针实际指向的是 BuilderServiceImpl、ProcessorServiceImpl 还是其他什么。
what 核心职责
WorkerStateHandler 基类为不同类型的工作进程提供了通用的状态管理框架,其核心职责包括:
- 接收并解析目标状态: 接收 心跳执行器 传递来的 目标状态 字符串,并将其解析成具体的、该角色能理解的指令和参数。
- 执行业务逻辑: 根据解析后的目标状态,执行相应的业务操作,如启动、停止、、更新配置、执行构建/处理/合并流程等。这通常是耗时且复杂的实际工作。
- 维护当前状态: 实时收集并维护自身的工作进度、运行状态、资源使用情况等信息,以便随时能够向 心跳执行器 报告。
心跳执行器(WorkerHeartbeatExecutor)
why need
ServiceWorker 启动后,它代表着一个具体的工作节点。这个工作节点需要不断地与远端的bs-admin进行通信,以确保它:
- 定期"报到":告诉管理员"我还活着,我在这里!"。这就是所谓的"心跳"。
- 接收"任务指令":管理员会根据集群的整体情况,给工作节点下达新的任务或调整现有任务的指令。
- "汇报工作进展":工作节点需要告诉管理员自己当前正在做什么,任务进行到哪一步了,有没有遇到问题。
工作节点心跳执行器 (WorkerHeartbeatExecutor),就像是工作节点内部的"通信和任务分配中心"。它专门负责处理所有与管理员之间的心跳通信、任务接收和状态汇报。它确保了工作节点能与管理员保持实时同步,是分布式系统中节点健康和任务调度顺利进行的关键。
what 核心职责
工作节点心跳执行器主要职责包括:
- 感知目标状态变化: 从 WorkerHeartbeat 更新的 Worker 节点基类 (WorkerNodeBase) 对象中获取最新的"目标状态"。
- 驱动工作执行: 根据获取到的"目标状态",调用具体的业务逻辑处理者(工作状态处理器 WorkerStateHandler)来执行相应的操作。
- 隔离耗时操作: 将处理"目标状态"这样的耗时任务放到单独的 线程池 中执行,避免阻塞心跳和主循环。
- 汇报当前状态: 定期从 工作状态处理器 获取"当前状态",更新到 Worker 节点基类 对象中,供心跳汇报使用。
how 内部工作原理
工作节点心跳执行器周期性地运行,从 Worker 节点基类 获取由 工作心跳 同步的"目标状态",并利用一个单线程的 线程池 将"目标状态"的处理任务 (工作状态处理器 的 handleTargetState 方法) 异步地派发出去,避免阻塞主循环。同时,它也负责获取 工作状态处理器 的"当前状态"并更新到 Worker 节点基类 中供心跳汇报使用,并监控 worker 的健康状况。它确保了 worker 进程既能高效通信,又能可靠地执行任务。
总结
以上就是bs-worker的主要流程,他接受bs-admin的心跳,根据心跳中解析出来的target执行不同的操作,同时在心跳中会将当前的状态返回。
具体不同的任务类型是如何执行的,我们在后面陆续介绍。