OpenLava 完全入门(四):高级功能全解(并行 / 依赖 / 数组 / FairShare)
OpenLava 完全入门(四):高级功能全解(并行 / 依赖 / 数组 / FairShare)
摘要:掌握基础命令后,OpenLava 的高级功能才是提高生产力的关键。本文详细讲解四大高级主题:MPI 并行作业配置、作业依赖(dependency)实现多步骤工作流、作业数组(Job Array)批量提交、FairShare 公平分享深入配置。每个主题都有完整的生产示例,读完你能轻松构建复杂的自动化计算流水线。
前面三篇我们讲了安装部署、基本作业管理、队列与资源。这一篇讲四个高级功能:并行作业、作业依赖、作业数组、FairShare。
零、读前必看
0.1 适合谁
- ✅ 已经能用 bsub 提交普通作业
- ✅ 需要跑 MPI 并行仿真 / EDA 任务
- ✅ 想做 DAG 工作流(A 跑完跑 B,B 跑完跑 C)
- ✅ 一次提交几百个参数化任务
- ✅ 想深入理解 FairShare 调度
一、并行作业(MPI)
1.1 并行作业类型
OpenLava 支持两种并行模式:
| 模式 | 场景 | 例子 |
|---|---|---|
| SMP(共享内存) | 单节点多线程,OpenMP / pthread | 1 台机器 16 核 |
| MPI(跨节点) | 多节点分布式并行,MPI 程序 | 10 台机器 160 核 |
1.2 SMP 并行
最简单,指定 -n 核数 + span[hosts=1] 保证在同一台机器:
# 申请 8 核,都在同一台机器
bsub -n 8 -R "span[hosts=1]" -o smp.out ./omp_program
1.3 MPI 并行(跨节点)
1.3.1 准备工作
MPI 并行需要:
- MPI 库(OpenMPI / MPICH / Intel MPI)
- 节点之间 SSH 免密登录
- NFS 共享可执行文件和数据
# 安装 OpenMPI
yum install -y openmpi openmpi-devel
# 或 Ubuntu: apt install -y openmpi-bin libopenmpi-dev
# 配置 SSH 免密(lsf 用户)
su - lsf
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
# 测试免密(要能连所有计算节点)
ssh compute01 hostname
1.3.2 提交 MPI 作业
# 申请 32 核,分布在 4 台机器(每台 8 核)
bsub -n 32 -R "span[ptile=8]" \
-o mpi.out -e mpi.err \
"mpirun -np 32 /data/mpi_program"
# 或者用 bsub 内置的启动方式(LSF 兼容写法)
bsub -n 32 -R "span[hosts=4]" mpirun.lsf /data/mpi_program
1.3.3 span 参数详解
| span 写法 | 含义 |
|---|---|
span[hosts=1] |
只在 1 台机器上(SMP) |
span[hosts=N] |
分布在 N 台机器上 |
span[ptile=N] |
每台机器最多 N 个进程 |
| 默认(不写) | 调度器自由分配 |
# 16 核,每台机器 4 核 → 用 4 台机器
bsub -n 16 -R "span[ptile=4]" mpirun ...
# 32 核,用 8 台机器
bsub -n 32 -R "span[hosts=8]" mpirun ...
1.3.4 绑定 CPU 亲和性
# 绑核(每个进程绑到一个物理核)
bsub -n 16 -R "span[hosts=2] affinity[core(1)]" mpirun ...
**生产建议**:CPU 密集型 MPI 作业一定要绑核,否则进程在核之间飘,性能下降 10-30%。
1.3.5 查看分配的节点
# 作业脚本里可以看
echo $LSB_HOSTS # 所有分配的主机名
echo $LSB_MCPU_HOSTS # 格式:host1 N host2 M
echo $LSB_DJOB_NUMPROC # 总进程数
# 例:$LSB_MCPU_HOSTS = "compute01 8 compute02 8"
在脚本里用 LSB_MCPU_HOSTS 生成 hostfile:
#!/bin/bash
#BSUB -n 16
#BSUB -R "span[hosts=2]"
# 生成 hostfile
echo $LSB_MCPU_HOSTS | xargs -n2 | while read host n; do
for ((i=0; i<n; i++)); do echo $host; done
done > hostfile.$LSB_JOBID
mpirun -hostfile hostfile.$LSB_JOBID -np $LSB_DJOB_NUMPROC ./my_mpi_prog
rm -f hostfile.$LSB_JOBID
1.4 并行作业常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
| 跨节点跑不起来 | SSH 免密没配好 | 手动 ssh 测试 |
| 程序找不到 | 节点上没有可执行文件 | 放 NFS 共享目录 |
| 性能差 | 没绑核 / 跨机架 | 加 affinity[core(1)] |
| 节点不够 | 槽位不足 | bjobs -p 看原因 |
二、作业依赖(Dependency)
2.1 依赖是什么
作业 B 要等作业 A 完成后才能开始,这就是依赖。可以构建 DAG(有向无环图)工作流。
compile (job 1001)
│
▼
test_pass (job 1002)
│
┌────┴────┐
▼ ▼
sim_a sim_b (并行跑)
(1003) (1004)
│ │
└────┬────┘
▼
report_gen (1005)
2.2 基本依赖语法
# 作业 2 等作业 1 完成(DONE 状态)
bsub -w "done(1001)" job2.sh
# 作业 3 等作业 1 和作业 2 都完成
bsub -w "done(1001) && done(1002)" job3.sh
# 作业 3 等作业 1 或作业 2 任意一个完成
bsub -w "done(1001) || done(1002)" job3.sh
2.3 依赖条件
| 条件 | 含义 |
|---|---|
done(jobid) |
作业正常结束(退出码 0) |
exit(jobid) |
作业异常结束(非 0 退出码) |
ended(jobid) |
作业结束(不管成功失败) |
running(jobid) |
作业开始运行了 |
suspended(jobid) |
作业被挂起了 |
post_done(jobid) |
作业 post-exec 完成 |
2.4 组合条件
# A 完成且 B 完成
-w "done(A) && done(B)"
# A 完成或 B 完成
-w "done(A) || done(B)"
# A 完成且 (B 或 C 完成)
-w "done(A) && (done(B) || done(C))"
# 取反:A 没完成
-w "!done(A)"
2.5 完整流水线示例
#!/bin/bash
# 提交一条完整的编译-测试-仿真-报告流水线
# Step 1: 编译
COMPILE_JOB=$(bsub -J compile -o compile.log "cd /src && make -j8" | awk -F'[<>]' '{print $2}')
echo "编译作业: $COMPILE_JOB"
# Step 2: 测试(等编译完成)
TEST_JOB=$(bsub -J test -w "done($COMPILE_JOB)" -o test.log "./run_tests.sh" | awk -F'[<>]' '{print $2}')
echo "测试作业: $TEST_JOB"
# Step 3: 仿真 A 和 B(等测试通过,并行跑)
SIM_A=$(bsub -J sim_a -w "done($TEST_JOB)" -n 8 -o sim_a.log "./run_sim.sh A" | awk -F'[<>]' '{print $2}')
SIM_B=$(bsub -J sim_b -w "done($TEST_JOB)" -n 8 -o sim_b.log "./run_sim.sh B" | awk -F'[<>]' '{print $2}')
echo "仿真 A: $SIM_A, 仿真 B: $SIM_B"
# Step 4: 生成报告(等两个仿真都完成)
REPORT=$(bsub -J report -w "done($SIM_A) && done($SIM_B)" -o report.log "./gen_report.sh" | awk -F'[<>]' '{print $2}')
echo "报告作业: $REPORT"
2.6 依赖 + 作业名
依赖可以用作业名(job name)代替 job id,更灵活:
# 等名叫 compile 的作业完成
bsub -w "done(compile)" job2.sh
# 等用户 lsf 的名叫 compile 的作业完成
bsub -w "done(compile, lsf)" job2.sh
# 通配符
bsub -w "done(sim_*)" collect.sh
**注意**:作业名可以重复,用名字匹配会匹配所有同名作业。生产里建议用 job id 更可靠。
2.7 查看依赖
# 看作业的依赖条件
bjobs -l 1005 | grep -i depend
# 依赖关系: done(1003) && done(1004)
2.8 常见场景
| 场景 | 依赖写法 |
|---|---|
| 顺序执行 | -w "done(prev_job)" |
| fan-out(一对多) | 多个作业都 -w "done(prev)" |
| fan-in(多对一) | -w "done(a) && done(b) && done(c)" |
| 失败重试 | 检查 exit,重新提交 |
| 条件分支 | done(A) 跑 B,exit(A) 跑 C |
三、作业数组(Job Array)
3.1 作业数组是什么
一次提交 N 个几乎一样的作业,只有一个参数(索引号)不同。比写 for 循环提交高效得多。
典型场景:
- 参数扫描(遍历 100 组参数跑仿真)
- 批量处理 1000 个文件
- Monte Carlo 模拟(跑 500 个随机种子)
3.2 基本用法
# 提交 10 个作业,索引 1-10
bsub -J "myarray[1-10]" -o "out_%I.log" "echo Job index: \$LSB_JOBINDEX; sleep 10"
# 查看
bjobs
# JOBID USER STAT QUEUE JOB_NAME NEXEC_HOST
# 2001 lsf RUN normal myarray[1] 1
# 2001 lsf RUN normal myarray[2] 1
# ...
作业数组共用一个 job id,用 `[index]` 区分子作业。
3.3 索引格式
# 连续数字
-J "array[1-100]"
# 步长为 5
-J "array[1-100:5]" # 1, 6, 11, ..., 96
# 不连续
-J "array[1,3,5,7,9]"
# 混合
-J "array[1-5,10,20-25]"
3.4 在脚本里用索引号
环境变量 $LSB_JOBINDEX 就是当前子作业的索引:
#!/bin/bash
#BSUB -J "sim_array[1-100]"
#BSUB -o "logs/sim_%I.out"
#BSUB -e "logs/sim_%I.err"
#BSUB -n 4
#BSUB -R "rusage[mem=4096]"
echo "Running simulation $LSB_JOBINDEX"
# 用索引构造参数
INPUT_FILE="input_${LSB_JOBINDEX}.dat"
OUTPUT_FILE="output_${LSB_JOBINDEX}.dat"
./run_simulation.sh $INPUT_FILE $OUTPUT_FILE
echo "Simulation $LSB_JOBINDEX done"
提交:
bsub < sim_array.sh
3.5 限制同时运行的数量
# 总共 100 个,但最多同时跑 20 个(防止打爆集群)
bsub -J "array[1-100]%20" command
这个 `%N` 语法叫 **job array throttle**,生产环境非常有用——一下提交 1000 个会把集群资源全占满。
3.6 作业数组管理
# 查看所有子作业
bjobs 2001
bjobs -J "myarray[*]"
# 杀掉整个数组
bkill 2001
# 杀掉单个子作业
bkill "2001[5]"
# 杀掉第 10-20 个
bkill "2001[10-20]"
# 挂起
bstop "2001[1-10]"
# 看历史
bhist 2001
3.7 作业数组 + 依赖
# 等所有子作业都完成
bsub -w "done(2001)" collect_results.sh
# 等第 5 个子作业完成
bsub -w "done(2001[5])" special_task.sh
3.8 完整生产示例:参数扫描
#!/bin/bash
# 提交 500 个参数扫描作业 + 汇总作业
# 1. 确保日志目录存在
mkdir -p logs results
# 2. 提交作业数组(500 个,最多同时 50 个)
ARRAY_JOB=$(bsub -J "param_scan[1-500]%50" \
-n 2 -R "rusage[mem=2048]" \
-o "logs/scan_%I.out" -e "logs/scan_%I.err" \
'python /scripts/scan.py --param $LSB_JOBINDEX --output results/result_$LSB_JOBINDEX.csv' \
| awk -F'[<>]' '{print $2}')
echo "参数扫描作业数组: $ARRAY_JOB"
# 3. 汇总作业(等所有子作业完成)
COLLECT_JOB=$(bsub -J collect_results \
-w "done($ARRAY_JOB)" \
-o "logs/collect.log" \
'python /scripts/collect_results.py --input results/result_*.csv --output final_report.csv' \
| awk -F'[<>]' '{print $2}')
echo "汇总作业: $COLLECT_JOB"
四、FairShare 公平分享
4.1 FairShare 是什么
**FairShare 是一种调度策略,保证用户/项目之间公平分配资源。用得多的用户优先级降低,用得少的用户优先级升高。**
没有 FairShare:用户 A 一次提交 1000 个作业,占满所有资源,其他人只能干等。
有了 FairShare:每个用户有份额,A 用多了优先级下降,B 的作业就能插进来。
4.2 配置 FairShare
在 lsb.queues 里配置:
Begin Queue
QUEUE_NAME = normal
PRIORITY = 30
FAIRSHARE = USER_SHARES[[user1,10] [user2,5] [default,1]]
End Queue
份额含义:
- user1 有 10 份,user2 有 5 份,其他人默认 1 份
- 份额越多,能分到的资源越多
- 长期看,资源分配比例趋近于份额比例
4.3 FairShare 调度原理
调度优先级 = 基础优先级 + FairShare 调整值
FairShare 调整值基于历史使用量和份额的比值:
- 使用量 < 份额 → 优先级上升 → 更容易调到
- 使用量 > 份额 → 优先级下降 → 更难调到
4.4 查看 FairShare 使用情况
# 看队列的 FairShare 情况
bqueues -l normal
# 看用户的 FairShare 使用
busers -q normal
# 或
bfairshare -q normal
4.5 项目级 FairShare
Begin Queue
QUEUE_NAME = normal
PRIORITY = 30
FAIRSHARE = PROJECT_SHARES[[chipA,30] [chipB,20] [chipC,10] [default,5]]
End Queue
提交作业时指定项目:
bsub -P chipA ./job.sh
4.6 多层 FairShare(用户组内再分)
FAIRSHARE = USER_GROUP_SHARES[[group1, 50, [user1,3] [user2,2]] [group2, 50, [default,1]]]
意思:
- 两个组各占 50% 资源
- group1 内部 user1:user2 = 3:2
- group2 内部大家均分
4.7 FairShare 常见参数
| 参数 | 含义 | 推荐值 |
|---|---|---|
USER_SHARES |
用户份额 | 按重要性分配 |
PROJECT_SHARES |
项目份额 | 按项目权重 |
FAIRSHARE_ADJUSTMENT_INTERVAL |
调整间隔(秒) | 120 |
HIST_HALF_LIFE |
历史半衰期(秒) | 3600(1 小时) |
**HIST_HALF_LIFE**:历史使用量的衰减速度。值越大,历史越"重要"——用户用多了要等更久才能恢复优先级。值越小,越容易恢复。
五、其他高级功能
5.1 作业钩子(esub / eexec / eepilog)
| 钩子 | 时机 | 用途 |
|---|---|---|
esub |
作业提交前 | 检查/修改提交参数 |
epsub |
作业提交后 | 记录审计日志 |
eprolog |
作业开始前 | 准备环境、挂载目录 |
epilog |
作业结束后 | 清理资源、回收 license |
示例:eprolog 检查输入文件是否存在
#!/bin/bash
# /opt/openlava/etc/eprolog.sh
if [ ! -f "$LSB_JOBNAME_INPUT" ]; then
echo "Input file not found!" >&2
exit 1
fi
exit 0
配置:
# lsb.params
BEGIN_PARAGON = /opt/openlava/etc/eprolog.sh
END_PARAGON = /opt/openlava/etc/epilog.sh
5.2 运行时限制
# 内存限制(超过自动杀)
bsub -M 8192 command # 单位 KB,8192KB = 8MB??不对
# 不同版本单位可能不同,注意查文档
# CPU 时间限制
bsub -c 2:00 command # 2 小时 CPU 时间
# Wall clock 时间限制
bsub -W 2:00 command # 2 小时墙钟时间
5.3 自动重跑
# 作业失败自动重跑,最多 3 次
bsub -r "exit(0)" -N 3 command
# 注意:不是所有版本都支持 -N 重跑次数,看具体版本
生产里更常用 shell 脚本里自己写重试逻辑。
5.4 作业分组
# 提交到一个作业组
bsub -g /dev/teamA command
# 查看作业组
bjobs -g /dev/teamA
# 作业组级别的 FairShare
# 配置在 lsb.users 里
5.5 资源预留(Reservation)
# 预约明天下午 3 点的 10 个核,跑 2 小时
bsub -R "reserve" -n 10 -b "2024:02:01:15:00" -W 2:00 command
资源预留用于:
- 重要会议前保证有资源跑演示
- 紧急任务确保能按时开始
- 大规模并行作业一次性占足够资源
六、高级功能速查表
| 功能 | 命令/参数 |
|---|---|
| SMP 并行 | bsub -n 8 -R "span[hosts=1]" |
| MPI 并行 | bsub -n 32 -R "span[ptile=8]" |
| 作业依赖 | -w "done(jobid)" |
| 作业数组 | -J "array[1-100]%20" |
| 数组索引 | $LSB_JOBINDEX |
| FairShare | FAIRSHARE = USER_SHARES[...] |
| 查看 FairShare | busers / bfairshare |
| 内存限制 | -M 8192 |
| 运行时限 | -W 2:00 |
| CPU 绑核 | -R "affinity[core(1)]" |
| 资源预留 | -b "2024:02:01:15:00" |
七、写在最后
这四个高级功能是 OpenLava 从"能用"到"好用"的关键:
- 并行作业:让你用得动大任务(MPI/SMP)
- 作业依赖:让你搭得起工作流(DAG)
- 作业数组:让你批量任务一键搞定
- FairShare:让你集群用得公平(大家都有得用)
下一篇(最后一篇)我们讲生产部署与运维:集群高可用、监控告警、日志管理、性能调优、常见故障排查、扩容缩容。
你最想用哪个高级功能?有没有被某个功能坑过? 评论区聊聊。
OpenLava #LSF #MPI #作业依赖 #作业数组 #FairShare #HPC #运维
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。







