:工程化——许可、并发与批处理容错治理)
HTRI二次开发教程16工程化——许可、并发与批处理容错治理版本与事实声明版本锚点当前Xchanger Suite 9.4许可工具官方文档列表出现HTRI License Manager 2.2另有第三方登记 1.4本机版本以实际为准。许可形态HLHardware LicenseNetHASP USB 硬件锁、SLSoftware License软件锁、网络许可License Server。具体并发上限与价格未公开底账 U6本文不写数字一律以 HTRI 官方渠道为准。示例代码中标识符为占位符数值均为示例性建模不代表任何标准规定。一句话结论HTRI 批处理能否 7×24 无人值守取决于三件事的治理——许可HL 有同一网络的物理可达性约束、并发是共享资源、进程桌面 OLE 会挂死、会残留需要守护组件清点与隔离、幂等失败可安全重跑、断点可续而到底能并发几个没有公开答案只能靠本机实测与官方确认绝不能拍脑袋写死。〇、本篇要解决的认知问题Q1HTRI 的三种许可形态HL/SL/网络许可分别给批处理带来什么约束Q2为什么并发数是本次批处理设计里最不能拍脑袋的参数Q3桌面 OLE 的挂死和残留进程分别是什么怎么治理Q4会话隔离session isolation解决什么问题Q5幂等重跑与失败注入演练怎么落到代码里一、机制解析1.1 许可形态对批处理的约束第 02 篇已登记三种形态这里讲它们对批处理的具体约束形态对批处理的约束HLNetHASP USB 硬件锁软件可在与插锁主机同一网络内的任意计算机使用硬件锁可在服务器间移动而无需激活 →约束是物理可达性批处理机必须在同一网络否则拿不到许可SL软件锁无 USB 设备绑定形态以官方文档为准 → 约束转移到本机激活状态网络许可License Server由 License Manager 集中提供许可 →约束是许可服务器可用性与许可数量为什么这对你重要批处理机常常在虚拟化/云端环境。若你的许可主机在办公网而批处理机在云端网络是否可达就成了能否运行的第一道门而不是脚本逻辑问题。1.2 并发最不能拍脑袋的参数官方不公开并发上限与许可价格底账 U6。这意味着你无法从公开资料算出这台许可服务器能同时跑几个 HTRI 会话盲目开 N 个并行进程可能拿到一堆许可失败、或引发资源竞争、或把机器压垮。经验法则并发策略应是保守起步、实测扩展——先 1 个串行跑通再逐步加并行度并观测许可失败率与机器负载直到失败率上升即回退。并发度是一个需要实测标定的运行参数不是设计常量。最佳实践把并发度做成可配置参数 动态降级——失败率高就自动降并发而不是硬编码。1.3 桌面 OLE 的两类故障挂死hang调用不返回。原因是 OLE 调用阻塞可能因计算量大、也可能因弹出了需要人工确认的对话框。第 07 篇的软超时应对的是前者对后者批处理机必须避免需要交互的配置以本机行为为准。残留进程脚本结束了HTRI 实例还在。原因是异常路径没释放、或进程自身没退干净。结果是资源被占、许可被占、越积越多直到宕机。治理手段try/finally必释放第 07 篇session每 N 个案例后清点进程设单进程案例上限跑够就退出由上层再起一个干净进程——这是最稳的防止累积策略清理动作只报警不越权不擅自 kill 他人会话。1.4 会话隔离会话隔离指每个任务/每个案例使用独立的对象句柄甚至独立进程不共享当前活动案例这类隐式状态。它解决串扰A 案例的输入写到 B、B 的结果记到 A第 07 篇污染一个任务的失败把共享会话搞坏拖垮整批不可复现会话状态不确定导致结果不确定。最佳实践批量任务优先多进程 单进程串行若干案例而不是单进程内多会话并行。前者隔离性最好后者在 COM 下极易出状态问题。1.5 幂等与失败注入幂等同一任务重跑结果一致、不产生副作用重复。落地为账本追加 取最新第 10 篇结果长表去重覆盖第 10 篇已完成案例跳过第 08 篇。失败注入演练主动制造失败超时、许可失败、非法输入验证系统能正确记账、继续、并可在修复后安全重跑。没有做过失败注入的批处理不算工程化——它只是顺利时能跑。1.6 无人值守的四条纪律纪律一所有可恢复的失败都必须记账后继续。一个案例失败不该终止整批。唯一的例外是环境级失败许可服务器不可达、软件未安装——这时继续跑只会产生一堆无意义的失败记录。脚本要能区分案例级失败继续“与环境级失败停止并报警”。纪律二任何等待都要有上限。等待运行完成、等待许可、等待文件——没有超时的等待就是潜在的死锁。第 07 篇的软超时在这里升级为全程超时预算会话有上限、单案例有上限、整批也有上限。纪律三无人值守机必须无交互。需要人工点确认的对话框会静默阻塞整批。批处理环境应使用不需交互的配置与运行模式具体以本机与官方文档为准并把是否可能弹窗作为部署前检查项。纪律四日志要能回答哪一批、哪些案例、为什么。日志不是给机器看的是给半夜被叫醒的你或接手的人看的。结构化日志时间戳 批次 case_id 状态 原因比一行行 print有用十倍。一条判断准则能不能无人值守取决于最坏情况而不是最好情况。设计时要问许可断了会怎样机器重启了会怎样某个案例挂了会怎样答案落在代码里的才叫工程化。二、完整代码与逐行剖析代码 16-1批量守护组件清点 幂等 单进程上限# -*- coding: utf-8 -*- batch_guardian.py —— 批量任务守护组件 用法作为模块被扫描器 import 职责进程清点只报警、单进程案例上限、幂等跳过、并发度自适应。 不写任何并发上限数字并发度由配置与实测决定。 importtimeimportsubprocessimportcsv# 进程名关键字用于清点不同安装可能不同以本机 tasklist 为准PROC_KEYWORDS[htri,xchanger]defcount_leftover(keywordsPROC_KEYWORDS):只读清点疑似残留进程返回匹配行数。try:outsubprocess.run([tasklist,/FO,CSV,/NH],capture_outputTrue,textTrue,timeout30)lines[lforlinout.stdout.splitlines()ifany(kinl.lower()forkinkeywords)]returnlen(lines),linesexceptExceptionase:# noqa: BLE001return-1,[f清点失败{e}]defguard_before_each(n_done,max_per_process,warn_threshold5): 每个案例处理前调用 - 单进程案例数达标则返回 recycle提示上层退出当前进程、由调度重启 - 残留进程超阈值则返回 warn只报警不清理。 ifmax_per_processandn_donemax_per_process:returnrecyclen,_linescount_leftover()ifnwarn_threshold:returnwarnreturnokdefshould_skip(case_id,ledger_path):幂等账本中已 done 的案例跳过。try:latest{}withopen(ledger_path,encodingutf-8-sig)asf:forrincsv.DictReader(f):latest[r[case_id]]rreturnlatest.get(case_id,{}).get(status)doneexceptFileNotFoundError:returnFalseclassAdaptiveConcurrency:并发度自适应失败率升高则降并发不预设上限数字。def__init__(self,start1,minimum1):self.currentstart self.minimumminimum self.success0self.fail0defrecord(self,ok):ifok:self.success1else:self.fail1defmaybe_adjust(self,window20,fail_rate_limit0.2):totalself.successself.failiftotalwindow:returnself.current rateself.fail/totalifratefail_rate_limitandself.currentself.minimum:self.current-1print(f[降并发] 失败率{rate:.0%}并发度降至{self.current})# 重置窗口计数self.successself.fail0returnself.currentif__name____main__:n,linescount_leftover()print(f疑似残留进程{n})print(提示并发度不设默认上限数字——由配置与实测标定)print( 本组件只做清点、幂等跳过、单进程回收与自适应降级。)逐行剖析count_leftover只读清点、返回计数不做清理把报警与越权 kill分开第 07 篇纪律的延续。guard_before_each引入max_per_process单进程案例上限跑够 N 个案例就让上层退出、由调度重启新进程——这是对抗残留累积最稳的手段比在长命进程里反复清点更可靠。should_skip复用第 10 篇账本语义取最新记录幂等跳过是重跑安全的核心。AdaptiveConcurrency不设并发上限数字只根据失败率动态降级minimum1保证不会降到 0。这直接回应 1.2 节——并发是实测参数。fail_rate_limit0.2是经验阈值可调标注清楚它不是官方规定。组件不关心用什么 ProgID守护逻辑与 HTRI 细节解耦可复用。代码 16-2失败注入演练脚本# -*- coding: utf-8 -*- fail_injection.py —— 批处理失败注入演练 用法python fail_injection.py 目的主动制造三类失败验证记账、继续、重跑三条能力。 本脚本用假案例模拟不触碰 HTRI。 importcsvimportrandomfrombatch_guardianimportshould_skipdeffake_run(case_id,mode):模拟一次运行返回 (status, detail)ifmodetimeoutandrandom.random()0.3:returntimeout,模拟超时ifmodelicenseandrandom.random()0.2:returnfailed,模拟许可获取失败ifmodebad_inputandcase_id.endswith(7):returnfailed,模拟非法输入壳径为负returndone,defrun_injection(mode,cases,ledgerinject_ledger.csv,rounds2):# 清空账本演练专用open(ledger,w,encodingutf-8-sig).close()forrdinrange(1,rounds1):forcidincases:ifshould_skip(cid,ledger):continuestatus,detailfake_run(cid,mode)withopen(ledger,a,newline,encodingutf-8-sig)asf:wcsv.writer(f)w.writerow([cid,status,detail,fround{rd},9.4,{}])print(f 第{rd}轮完成)defreport(ledgerinject_ledger.csv):latest{}withopen(ledger,encodingutf-8-sig)asf:forrincsv.reader(f):iflen(r)2:latest[r[0]]r[1]fromcollectionsimportCounter cCounter(latest.values())print(最终状态分布,dict(c))returncif__name____main__:random.seed(42)cases[fcase_{i:04d}foriinrange(1,11)]formodein[timeout,license,bad_input]:print(f演练模式{mode})run_injection(mode,cases)report()print()逐行剖析三类注入对应三类真实故障超时挂死、许可失败并发超限/网络不可达、非法输入脚本逻辑错误覆盖可重试与不可重试两类。random.seed(42)固定随机种子演练可复现——否则每次跑出不同分布无法比较修复效果。两轮运行 should_skip验证重跑安全——第一轮done的案例在第二轮被跳过failed/timeout的被重试。report用Counter输出最终状态分布一眼看出哪类故障留下未完成项。不触碰 HTRI、纯标准库演练可以在任何机器上跑先把治理逻辑验证正确再上真环境。三、常见报错与排查报错 3-1批处理机创建对象时报许可错误。现象com_error或提示无法获取许可。根因网络许可服务器不可达HL 的同网约束未满足、许可被占满、或本机未授权。解法确认批处理机与许可主机网络连通用 HTRI License Manager 查许可状态降低并发度重试AdaptiveConcurrency。报错 3-2进程越跑越多最后机器卡死。现象tasklist中 HTRI 实例累积。根因长命进程反复创建 COM 对象且未彻底释放或异常路径跳过释放。解法设max_per_process跑 N 个案例即回收进程、由调度重启每案例走session的 finally 释放count_leftover定期报警。报错 3-3一个坏案例把整批拖垮。现象某案例挂死后整批不动。根因单会话共享无会话隔离一个失败污染整批。解法多进程 单进程串行若干案例单案例软超时失败记账后继续。报错 3-4重跑后结果表出现重复/矛盾行。现象同一 case_id 多条记录。根因幂等没落实——账本没取最新、长表没去重覆盖。解法统一用第 10 篇的账本语义与长表写法规should_skip跳过done。报错 3-5把并发度硬编码为某个数导致许可冲突。现象并发一开就大片许可失败。根因并发上限无公开依据U6硬编码的数字是猜的。解法改成自适应AdaptiveConcurrency从 1 起步、按失败率降级最终并发度以本机实测与官方确认为准。四、动手练习练习 1守护组件运行代码 16-1。判定打印疑似残留进程数说明并发度不设默认上限的理由。练习 2失败注入运行代码 16-2。判定三种模式各产出一份状态分布bad_input模式下至少一个案例最终为failed第二轮中已done的案例被跳过。练习 3并发标定在真环境里从并发 1 起步逐步提高记录许可失败率随并发度的变化。判定能给出一个失败率开始上升的并发度区间不要求固定值。练习 4单进程回收把max_per_process设为 5跑 20 个案例。判定进程在第 5、10、15、20 个案例处被回收重启最终tasklist无累积残留。五、小结与下一篇预告本篇把批处理从能跑提升到敢无人值守许可层面认清 HL 的物理可达性约束与并发是共享资源进程层面用清点只报警 单进程回收 会话隔离治理挂死与残留幂等层面用账本取最新 长表去重 跳过已完成并发层面坚持实测标定、自适应降级绝不写死上限。产出batch_guardian.py与fail_injection.py两个可复用组件。从第 17 篇起进入实战收官阶段。第 17 篇《实战二空冷器批量校核端到端》把第 02环境/08矩阵/09取数/12XaceXvib/15报表篇的能力串成一条完整的空冷器批量校核流水线从工况设计到交付报告一气呵成。本篇认知问题回显FAQQ1HL/SL/网络许可分别给批处理带来什么约束AHLHardware LicenseNetHASP USB 硬件锁允许软件在与插锁主机同一网络内的任意计算机使用、硬件锁可在服务器间移动而无需激活约束是物理可达性——批处理机必须同网SLSoftware License软件锁无 USB 设备约束在本机激活状态网络许可由 License Server 集中提供约束是许可服务器可用性与许可数量。批处理机常部署在虚拟化/云端网络可达性是第一道门。Q2为什么并发数最不能拍脑袋A官方不公开并发上限与价格底账 U6公开资料无法算出一台许可服务器能同时承载几个 HTRI 会话。盲目开并行会拿到许可失败、引发资源竞争甚至压垮机器。正确做法是保守起步、实测扩展从 1 串行跑通逐步加并发并观测许可失败率与负载失败率上升即回退把并发度做成可配置参数加动态降级而非硬编码常量。Q3桌面 OLE 的挂死与残留进程怎么治理A挂死是调用不返回可能因计算量大或弹出了需人工确认的对话框解决方案是软超时第 07 篇并避免需交互的配置。残留进程是脚本结束而 HTRI 实例仍存活会占资源与许可治理为 try/finally 必释放、每 N 案例清点进程只报警不越权、设置单进程案例上限跑够即回收由调度重启。最稳的是多进程 单进程串行若干案例。Q4会话隔离解决什么问题A解决三类问题串扰A 案例输入写到 B、B 结果记到 A、污染一个任务失败搞坏共享会话拖垮整批、不可复现会话状态不确定导致结果不确定。做法是每个任务/案例用独立对象句柄甚至独立进程不依赖当前活动案例这类隐式状态批量优先多进程 单进程串行而非单进程内多会话并行。Q5幂等重跑与失败注入怎么落地A幂等落地为三条账本追加 取最新、结果长表去重覆盖、已完成案例跳过should_skip。失败注入演练主动制造超时、许可失败、非法输入三类故障用固定随机种子保证可复现跑两轮验证记账—继续—重跑三能力第一轮 done 的案例第二轮被跳过failed/timeout 的被重试最终用状态分布核对。没有做过失败注入的批处理不算工程化。