-
一. 可视化全链路,运维效率大幅升级一站式可视化调度面板,作业执行状态、日志、告警实时可视、全程可追溯;异常自动推送告警,故障快速定位,无需人工熬夜排查;完整留存调度执行记录,满足内控审计、符合监管合规要求,实现调度透明化、运维轻量化。二. 在信创国产化全面推进的竞争力纯国产自研,无海外技术依赖,原生适配鲲鹏、飞腾、麒麟、统信、达梦、人大金仓等全栈信创生态,调度日历、节假日规则、监管审计逻辑完全贴合国内本土政策,深度适配国产服务器、数据库、中间件,完美契合信创政策要求,规避技术“卡脖子”风险,保障企业核心数据与业务安全,可无缝替代进口ControlM,业务零中断迁移。三. 敏捷易运维,多模式快速开发 创新图形拖拽式、Excel批量导入、API开发、一键智能布局四种作业开发模式,大幅降低调度开发门槛;全流程可视化大屏、血缘分析、流程图监控,实时查看作业执行全生命周期;多维度告警策略,支持短信、微信、邮件、平台推送,异常秒定位、快速处置,大幅降低运维压力。四. 分层解耦架构,各司其职、独立扩展· 界面接入层:提供可视化Web控制台、大屏监控、运维门户,支持拖拽式作业编排、批量导入、权限管控、审计管理,多终端适配,操作轻量化。 · 调度核心层:采用微服务+去中心化集群,包含调度引擎、任务编排、依赖解析、负载均衡、告警中心、日志审计、API 网关等模块,各服务独立部署、独立升级,互不影响。 · 执行Agent层:分布式轻量化执行代理,部署在业务服务器、云主机、容器节点,就近执行任务,支持跨 Windows/Linux/大型机/国产服务器,适配全环境作业。 在这个数据定义未来的时代,Moia Control 以其稳定、高效、安全、易用的特质,正在重新定义企业级任务调度的标准。它不仅是一个高效的执行引擎,更是企业数字化转型道路上值得信赖的智慧伴侣,助力企业在复杂的数字基建中从容驾驭,稳健前行。 MoiaControl产品试用和企业统一调度解决方案可以访问本产品官网地址:https://www.moia.com.cn/
-
批量作业调度在企业数据架构中处于一个特殊的位置——它不属于任何一个具体的业务环节,却是几乎所有数据处理流程能够自动运转的前提条件。数据采集:依赖调度按时触发ETL任务,数据计算:依赖调度按序编排Spark/SQL作业,报表生成:依赖调度在上游数据就绪后自动启动汇总程序,账务处理:依赖调度在日切窗口内严格按序执行清算流程。调度系统的稳定性和效率直接决定了企业数据资产的时效性和业务流程的连续性。然而,这一“基础设施”定位也使批量作业调度面临独特挑战:需要对接企业内几乎所有涉及批量处理的异构系统,管理规模已从千级攀升至十万级乃至更高,数万作业构成的依赖链路中任何节点异常都可能出现级联传播的情况。运维人员需要的不再是“出了问题再排查”,而是全链路可观测、快速定位、精准干预的系统化能力。MoiaControl正是定位于这一核心基础设施层的企业级统一任务调度平台,由北京先进数通完全自主研发,历经二十余年迭代,覆盖银行、证券、保险、制造等行业超百家企业。本文将围绕统一管控、高可用架构、规模化弹性、智能运维、合规迁移五个维度,深入解读其技术能力与实践价值。 一、 打破孤岛:构建企业级调度"一张网现实困境:企业在数字化建设中往往形成多个“调度孤岛”——数据仓库有自己的ETL调度,业务系统用另一套批处理,报表平台又独立运行。当一个上游作业延迟,下游团队往往要逐一排查才能定位问题,跨系统的作业协调更依赖人工沟通,效率极低。 MoiaControl的破局之道:平台提供统一的任务编排引擎,支持将分散在不同系统、不同技术栈中的作业纳入统一管控。通过可视化配置界面,业务人员可直观构建复杂的作业依赖关系,系统自动处理执行顺序与依赖触发。无论是跨库的数据抽取、多环节的报表生成,还是涉及外部接口的订单清算,均可在同一平台完成全链路编排,实现“一处配置、全局联动”的调度效果。开发者无需在调度逻辑上反复调试,将精力真正释放到业务创新中。二、 韧性架构:让调度系统成为“不怕故障”的底座现实困境:调度系统的可用性往往被低估——一直到它宕机。传统调度方案中,单点故障可能导致整个数据链路中断,而简单的冷备方案切换时间长、数据一致性难保障,无法满足业务连续性要求。 MoiaControl的破局之道:平台采用分层解耦的分布式架构设计,将Web服务层、调度核心层、执行节点层独立部署,任何一层的局部故障都不会波及其他层级。调度核心层支持多节点集群运行,当某个节点出现异常时,集群内的其他节点可自动接管其任务,无需人工干预。平台还内置完善的故障恢复机制。通过事务状态持久化、执行数据本地缓存及自动补发等技术,确保系统在重启或灾难恢复后能够自动接续中断的任务,最大限度减少业务影响。针对跨数据中心场景,平台提供成熟的容灾部署方案,帮助企业构建同城或异地的调度高可用体系。三、 弹性应对:从容驾驭规模化调度挑战现实困境:随着数据中台、数据湖建设的推进,企业调度任务量从千级跃升至十万甚至百万级。传统基于轮询扫描的调度方式,在任务量激增时响应速度急剧下降,依赖作业间的触发延迟可能达到分钟级,严重影响数据时效性。 MoiaControl的破局之道:平台底层采用事件驱动架构,摒弃低效的定时扫描模式,任务状态变化即刻触发后续依赖,将作业间衔接延迟压缩至毫秒量级。执行层节点采用无状态设计,支持弹性伸缩——业务高峰期可快速扩容新节点接入集群,低谷期可动态释放资源,整个过程无需重启现有服务,调度业务零中断。在异构环境兼容方面,平台原生支持包括Shell脚本、SQL存储过程、Python程序、Spark/Hive大数据任务在内的多种主流作业类型,并提供插件扩展机制,企业可按需接入自定义任务类型,无需等待产品版本更新。配合节点级、作业级、资源级等多维度并发控制策略,平台能够在资源有限的情况下实现精细化调度,避免关键业务因资源争抢而延迟。四、 从被动到主动:重塑调度运维体验现实困境:传统的调度运维往往是“救火式”——作业失败了才知道,出了问题才去查。运维人员需要登录多个系统查看日志,人工比对上下游关系才能定位根因。夜间值班面对十几块监控屏幕已是常态,身心俱疲。 MoiaControl的破局之道:平台构建了一体化的调度运维中心,将分散的监控信息汇聚为统一视图。运维人员可通过流程图、拓扑图、列表等多种视角,实时掌握全链路任务执行状态。当某个节点出现异常,平台的依赖溯源能力可快速追溯该节点的完整上下游关系,帮助运维人员在数分钟内定位问题源头及影响范围,而非逐层排查。平台还支持自定义告警策略,可根据作业失败、超时、重试次数等多种条件触发通知,并支持多渠道分发。更值得关注的是,平台正在引入AI能力——运维人员可通过自然语言询问“昨天夜间哪些任务执行失败”或“本月任务平均耗时趋势”,平台可基于运行数据自动生成分析结果与优化建议,让调度运维从经验驱动转向数据驱动。五、 合规先行与存量迁移:兼顾当下与未来现实困境:信创政策的推进让许多企业面临调度平台国产化替代的压力。然而,替换一个运行多年的调度系统并非易事——历史作业配置的迁移、业务逻辑的平滑过渡、与现有系统的兼容对接,每一个环节都暗藏风险。 MoiaControl的破局之道:在信创适配方面,平台已完成与主流国产服务器芯片、操作系统、数据库及中间件的全面兼容认证,可满足企业核心系统自主可控的合规要求。在存量系统迁移方面,平台针对Control-M等主流调度软件提供了专业的配置迁移方案。通过专用工具可直接解析原有配置,将历史作业快速导入新平台,减少人工逐项重配的工作量。某金融机构在实际迁移过程中,成功将超过15000个存量作业平稳切换至MoiaControl,原系统运行环境保持不变,迁移完成后业务零中断。此外,平台提供开放的集成接口,便于与企业已有的监控系统、统一认证平台、办公协同系统等对接,降低系统整合的技术门槛,实现调度平台与企业IT生态的无缝融合。 调度系统的选择,本质上是企业对数据处理效率、业务连续性与运维成本的综合权衡。MoiaControl以二十余年的行业深耕为基础,将稳定可靠的架构设计、灵活适配的扩展能力与持续演进的智能化特性融为一体,为企业提供从调度规划、平台建设到持续运营的全周期支撑。
-
批量作业调度在企业数据架构中处于一个特殊的位置——它不属于任何一个具体的业务环节,却是几乎所有数据处理流程能够自动运转的前提条件。数据采集:依赖调度按时触发ETL任务,数据计算:依赖调度按序编排Spark/SQL作业,报表生成:依赖调度在上游数据就绪后自动启动汇总程序,账务处理:依赖调度在日切窗口内严格按序执行清算流程。调度系统的稳定性和效率直接决定了企业数据资产的时效性和业务流程的连续性。然而,这一“基础设施”定位也使批量作业调度面临独特挑战:需要对接企业内几乎所有涉及批量处理的异构系统,管理规模已从千级攀升至十万级乃至更高,数万作业构成的依赖链路中任何节点异常都可能出现级联传播的情况。运维人员需要的不再是“出了问题再排查”,而是全链路可观测、快速定位、精准干预的系统化能力。MoiaControl正是定位于这一核心基础设施层的企业级统一任务调度平台,由北京先进数通完全自主研发,历经二十余年迭代,覆盖银行、证券、保险、制造等行业超百家企业。本文将围绕统一管控、高可用架构、规模化弹性、智能运维、合规迁移五个维度,深入解读其技术能力与实践价值。 一、 打破孤岛:构建企业级调度"一张网现实困境:企业在数字化建设中往往形成多个“调度孤岛”——数据仓库有自己的ETL调度,业务系统用另一套批处理,报表平台又独立运行。当一个上游作业延迟,下游团队往往要逐一排查才能定位问题,跨系统的作业协调更依赖人工沟通,效率极低。 MoiaControl的破局之道:平台提供统一的任务编排引擎,支持将分散在不同系统、不同技术栈中的作业纳入统一管控。通过可视化配置界面,业务人员可直观构建复杂的作业依赖关系,系统自动处理执行顺序与依赖触发。无论是跨库的数据抽取、多环节的报表生成,还是涉及外部接口的订单清算,均可在同一平台完成全链路编排,实现“一处配置、全局联动”的调度效果。开发者无需在调度逻辑上反复调试,将精力真正释放到业务创新中。二、 韧性架构:让调度系统成为“不怕故障”的底座现实困境:调度系统的可用性往往被低估——一直到它宕机。传统调度方案中,单点故障可能导致整个数据链路中断,而简单的冷备方案切换时间长、数据一致性难保障,无法满足业务连续性要求。 MoiaControl的破局之道:平台采用分层解耦的分布式架构设计,将Web服务层、调度核心层、执行节点层独立部署,任何一层的局部故障都不会波及其他层级。调度核心层支持多节点集群运行,当某个节点出现异常时,集群内的其他节点可自动接管其任务,无需人工干预。平台还内置完善的故障恢复机制。通过事务状态持久化、执行数据本地缓存及自动补发等技术,确保系统在重启或灾难恢复后能够自动接续中断的任务,最大限度减少业务影响。针对跨数据中心场景,平台提供成熟的容灾部署方案,帮助企业构建同城或异地的调度高可用体系。三、 弹性应对:从容驾驭规模化调度挑战现实困境:随着数据中台、数据湖建设的推进,企业调度任务量从千级跃升至十万甚至百万级。传统基于轮询扫描的调度方式,在任务量激增时响应速度急剧下降,依赖作业间的触发延迟可能达到分钟级,严重影响数据时效性。 MoiaControl的破局之道:平台底层采用事件驱动架构,摒弃低效的定时扫描模式,任务状态变化即刻触发后续依赖,将作业间衔接延迟压缩至毫秒量级。执行层节点采用无状态设计,支持弹性伸缩——业务高峰期可快速扩容新节点接入集群,低谷期可动态释放资源,整个过程无需重启现有服务,调度业务零中断。在异构环境兼容方面,平台原生支持包括Shell脚本、SQL存储过程、Python程序、Spark/Hive大数据任务在内的多种主流作业类型,并提供插件扩展机制,企业可按需接入自定义任务类型,无需等待产品版本更新。配合节点级、作业级、资源级等多维度并发控制策略,平台能够在资源有限的情况下实现精细化调度,避免关键业务因资源争抢而延迟。四、 从被动到主动:重塑调度运维体验现实困境:传统的调度运维往往是“救火式”——作业失败了才知道,出了问题才去查。运维人员需要登录多个系统查看日志,人工比对上下游关系才能定位根因。夜间值班面对十几块监控屏幕已是常态,身心俱疲。 MoiaControl的破局之道:平台构建了一体化的调度运维中心,将分散的监控信息汇聚为统一视图。运维人员可通过流程图、拓扑图、列表等多种视角,实时掌握全链路任务执行状态。当某个节点出现异常,平台的依赖溯源能力可快速追溯该节点的完整上下游关系,帮助运维人员在数分钟内定位问题源头及影响范围,而非逐层排查。平台还支持自定义告警策略,可根据作业失败、超时、重试次数等多种条件触发通知,并支持多渠道分发。更值得关注的是,平台正在引入AI能力——运维人员可通过自然语言询问“昨天夜间哪些任务执行失败”或“本月任务平均耗时趋势”,平台可基于运行数据自动生成分析结果与优化建议,让调度运维从经验驱动转向数据驱动。五、 合规先行与存量迁移:兼顾当下与未来现实困境:信创政策的推进让许多企业面临调度平台国产化替代的压力。然而,替换一个运行多年的调度系统并非易事——历史作业配置的迁移、业务逻辑的平滑过渡、与现有系统的兼容对接,每一个环节都暗藏风险。 MoiaControl的破局之道:在信创适配方面,平台已完成与主流国产服务器芯片、操作系统、数据库及中间件的全面兼容认证,可满足企业核心系统自主可控的合规要求。在存量系统迁移方面,平台针对Control-M等主流调度软件提供了专业的配置迁移方案。通过专用工具可直接解析原有配置,将历史作业快速导入新平台,减少人工逐项重配的工作量。某金融机构在实际迁移过程中,成功将超过15000个存量作业平稳切换至MoiaControl,原系统运行环境保持不变,迁移完成后业务零中断。此外,平台提供开放的集成接口,便于与企业已有的监控系统、统一认证平台、办公协同系统等对接,降低系统整合的技术门槛,实现调度平台与企业IT生态的无缝融合。 调度系统的选择,本质上是企业对数据处理效率、业务连续性与运维成本的综合权衡。MoiaControl以二十余年的行业深耕为基础,将稳定可靠的架构设计、灵活适配的扩展能力与持续演进的智能化特性融为一体,为企业提供从调度规划、平台建设到持续运营的全周期支撑。
-
在数字化转型深度渗透的今天,企业级数据中心承载着核心业务的稳定运行与数据流转,而企业级统一任务调度作为数据中心的“中枢神经”,其高可用性直接决定了业务连续性、数据安全性与运维效率。当企业面临多系统任务繁杂、跨数据中心容灾需求迫切、运维成本高等痛点时,一款成熟的高可用调度平台,成为破解行业困境的关键。MoiaControl作为先进数通自研的企业级统一批量任务调度产品,历经二十余年迭代优化,以微服务架构为核心,凭借成熟的双中心高可用部署方案,为企业数据中心提供稳定、高效、可落地的调度解决方案,助力企业实现调度管理的标准化、智能化升级。一、MoiaControl产品定位企业级统一调度的核心载体,破解行业核心痛点MoiaControl基于微服务架构深度设计,聚焦企业级批量任务调度核心需求,可快速搭建企业级统一批量任务调度平台,打破传统调度系统“分散管理、链路混乱、运维复杂”的瓶颈。平台核心能力覆盖各类批量任务系统的集中统一管理,可对任务启动条件、执行链路进行全流程统一调度与全域管控,完美满足DAG流程编排控制要求,同时提供一体化监控与运维管理能力。通过MoiaControl企业可实现内部多系统任务“统一调度规范、统一监控状态、统一调度运维”,有效解决多系统调度协同难、故障排查慢、数据处理效率低等问题,大幅提升数据流转与业务处理的整体效能,为企业数字化运营筑牢调度根基。二、核心诉求:企业级调度平台,高可用是底线亦是核心对于企业级用户而言,统一调度平台的高可用性直接关系到核心业务的连续性——如果调度系统宕机,将导致数据处理中断、业务流程停滞,进而引发巨大的经济损失与品牌风险。当前,企业数据中心不仅需要满足本地节点的高可用需求,更需具备跨数据中心容灾能力,应对站点级故障、自然灾害等突发情况,确保业务“不中断、数据不丢失、服务不降级”。相较于传统主备部署方案存在的切换延迟高、数据一致性难保障、运维成本高的痛点,MoiaControl依托成熟的双中心部署架构,兼顾本地高可用与跨中心容灾,既解决了单中心故障导致的业务中断问题,又通过科学的部署设计,降低了运维复杂度,实现“稳定运行、快速切换、可靠恢复”的核心目标,匹配金融、制造、互联网等多行业企业的核心诉求。三、实战案例:双中心部署落地,赋能金融行业合规与高效运维3.1 客户背景与核心需求山东省XXXX有限公司,采用单实例、多法人商业银行架构,核心需求是实现多家成员银行共用一套核心业务系统,同时严格保障各成员行数据与账务隔离,提供统一的技术运维服务。该客户的核心痛点是:任务调度时,需对应不同成员行的业务,既要实现全流程统一调度运维,又要确保各成员行数据、财务完全独立,避免数据泄露与账务混乱,同时需满足金融行业高可用、高合规的严苛要求。3.2 双中心方案设计:主备架构+科学配置,兼顾稳定与高效3.2.1 日常运营模式:主备分离,各司其职MoiaControl为该客户量身打造双中心主备部署架构,日常运营严格遵循“主中心运行、备中心待命”的核心原则,确保系统运行的稳定性与资源利用的合理性,具体运行规则如下:l 主中心:常态启动调度应用(MoiaControl)、消息中间件(RabbitMQ)及缓存中间件(Redis),全面承担所有业务调度、数据交互及任务执行工作,保障日常业务的顺畅运转;l 备中心:调度应用、RabbitMQ及Redis均处于停止待命状态,不参与日常业务调度,仅在主中心出现故障、需进行主备切换时快速启动,确保业务无缝衔接;l 数据层:主备中心共用同一套数据库,从根源上确保核心业务数据的一致性,避免数据同步延迟、冗余或冲突等问题;中间件(RabbitMQ、Redis)数据独立存储,日常不进行数据同步,进一步提升系统独立性与安全性。3.2.2 部署与配置原则:四大核心,筑牢高可用根基为保障主备切换的稳定性、高效性,以及日常运行的独立性,MoiaControl双中心部署配置严格遵循四大核心原则,规避传统部署方案的短板:1. 调度应用独立:主备中心的MoiaControl应用各自独立部署,运行状态互不干扰,避免主中心故障传导至备中心,降低系统整体故障风险;2. 中间件隔离:主备中心的RabbitMQ、Redis实例完全独立,数据存储相互隔离,日常不设置数据同步机制,杜绝主备数据冲突,提升系统稳定性;3. 中间件数据切换规则:执行主备切换时,先清空备中心中间件现有数据,再通过人工执行MoiaControl平台提供的自动脚本修复备中心中间件数据,确保数据与切换时的业务状态完全匹配,保障切换后业务正常运转;4. 数据库共用:主备中心共用同一数据库,避免核心数据冗余或不一致,同时降低数据同步的运维成本,契合企业级用户“高效运维”的核心需求。3.2.3 方案核心优势:三大价值,赋能企业降本增效MoiaControl双中心部署方案通过“域名统一化+IP差异化”的创新设计,结合科学的配置原则,实现数据隔离、运维提效、可靠性保障三大核心价值,完美适配客户需求,同时彰显产品核心竞争力:(1)数据隔离与稳定性双保障中间件(Redis、RabbitMQ)通过IP独立配置实现完全数据隔离,有效避免主备中心数据冲突或冗余,契合金融行业“数据隔离”的合规要求;调度应用与微服务注册独立,确保主备中心运行状态互不干扰,大幅降低跨中心故障传导风险,保障系统7×24小时稳定运行,满足金融行业核心业务的高可用需求。(2)运维效率大幅提升,降低人力成本主备切换时,仅需调整数据库域名指向及Agent执行节点域名指向的IP地址,无需修改应用配置,简化切换流程,缩短故障恢复时间;中间件数据采用“切换时清空+脚本修复”的方式,无需日常进行数据同步操作,大幅简化运维成本,同时保障切换后数据的有效性;更值得一提的是,双中心主备切换的所有操作均已整理优化为脚本,配置到客户内部自动切换管理平台,切换时可由管理平台自动快速完成,无需人工繁琐操作,进一步提升运维效率,减少人为操作失误。(3)可靠性升级,契合企业级严苛需求数据库共用设计确保核心业务数据一致性,避免数据同步延迟导致的业务异常,保障数据安全与业务连续性;域名与IP的分层配置,既适配主备切换的灵活性,又通过固定配置规则减少人为操作失误,进一步提升系统可靠性。相较于同类产品的高可用方案,MoiaControl无需复杂的集群部署,即可实现高效容灾切换,兼顾稳定性与性价比。3.3MoiaControl高可用双中心设计示意图四、总结:MoiaControl——企业级调度高可用的优选方案在企业数字化转型加速、核心业务对调度系统依赖度日益提升的今天,高可用性已成为企业选择统一调度平台的核心考量。MoiaControl凭借微服务架构的灵活性、双中心部署的成熟性,以及在金融行业的实战落地经验,既解决了企业“调度分散、运维复杂、容灾不足”的核心痛点,又通过差异化优势,为企业提供“稳定、高效、低成本”的统一调度解决方案。无论是金融行业的合规性需求,还是制造、互联网等行业的高效运维需求,MoiaControl都能通过定制化的双中心高可用部署方案,实现“主备中心按需配置、稳定运行、快速切换、可靠恢复”的目标,助力企业提升数据处理效率、降低运维成本、保障业务连续性。选择MoiaControl,让统一调度成为企业数字化运营的核心驱动力,为企业高质量发展筑牢技术根基。除了本次案例介绍的主备中心相互隔离,主备中心采用“Active + Standby”的模式外,MoiaControl还提供有其他双中心部署方案,可以根据自身双中心硬件配置或业务需求部署双中心,具体双中心方案可以敬请期待后续方案介绍。
-
产品定位MoiaControl 基于微服务架构进行设计,是一款批量任务调度产品,可建设为企业级统一批量任务调度平台。平台可实现各类批量任务系统的集中统一管理,对任务启动条件、执行链路进行统一调度与全域管控,满足 DAG 流程编排控制要求,并提供一体化的监控与运维管理能力。MoiaControl高可用部署架构企业级统一调度平台的高可用建设为核心目标之一,平台需同时满足本地数据中心高可用要求,以及跨数据中心容灾能力。MoiaControl的微服务架构是怎样的?MoiaControl 基于微服务架构构建,整体采用三层架构体系:上层为 WEB 服务层,提供可视化功能操作入口;中层为调度核心层,承载各类调度管控逻辑,实现任务调度全流程的规则配置与中枢控制;下层为任务执行层(Agent 节点层),负责接收调度指令,完成任务实际执行及运行状态反馈管控。本地数据中心调度平台的高可用性应该关注什么?本地数据中心调度平台高可用建设,重点围绕调度核心层运行稳定性、WEB 服务层高可用能力、Agent 执行层异常快速恢复三大核心展开。MoiaControl 通过集群化部署保障调度核心层稳定运行;依托 WEB 应用冗余及集群部署架构,实现 WEB 服务层高可用;平台 Agent 服务采用无状态设计,可在异常故障后实现快速自愈恢复。本地数据中心调度平台的高可用部署架构MoiaControl 调度平台的数据中心高可用部署架构,可实现三层架构分离部署,达成各层级独立高可用建设目标。平台采用页面操作层、调度核心层、Agent 节点层三层分离架构,各层级解耦独立运行:页面操作层异常故障不会影响后台批量任务的自动调度;调度核心层以集群部署规避单点故障,内部服务异常后可快速重启并自动纳入集群服务,恢复运行能力;Agent 节点基于逻辑集群架构,具备弹性伸缩能力,无需重启即可完成节点动态扩缩。调度核心层采用多服务器集群部署,构建完整的调度控制核心集群体系。页面操作层可基于 Nginx、东方通、宝兰德等中间件集群进行部署,提供稳定可靠的前端访问入口。Agent 节点层采用专属集群设计架构,节点间不建立代码级互联,有效降低 Agent 部署带来的硬件资源消耗,使更多硬件资源投入至任务实际运行环节。该层集群由平台原生能力统一托管,通过系统内置功能完成 Agent 集群的构建与管理,集群动态扩缩容过程中,无需对现有 Agent 节点进行重启,保障任务运行不受影响。
-
批量作业调度的现状当前批量作业调度软件普遍面临着一些问题:Ø调度方式原始落后时至今日仍然有一些系统使用人工调度或操作系统的crontab方式调度。在如今追求自动化甚至智能化的时代已显得非常原始和低效,容易出错且难以监控,已成为这类系统的致命性问题。Ø使用开源软件:如XXL-JOB、Airflow调度系统若使用开源软件,则面临学习成本高、无商业服务保障、Bug修复不及时以及软件生命周期不确定等风险。Ø调度自主研发调度系统伴随项目自主研发,软件质量也难以保证,需求扩展性差。Ø系统间协调交互困难各系统独立建设,没有统一的标准和规范,无法简单有效地实现系统间的交互。当一个系统出现问题,可能需要运维人员逐个联系上游系统确认问题根源,运维效率低下。Ø作业规模变大随着ODS、BIG DATA的建设,批量处理作业规模越来越大,相对应的调度场景更加多样,系统调度逻辑也更加复杂,系统开发人员很大一部分精力花费在了调度逻辑的控制,而非业务处理本身。另外,随着作业规模的增长,对调度性能和稳定性、扩展性提出了更高要求,一些现有系统已经逐渐不能满足要求。Ø系统越来越多带来管理和运维困难系统越来越多,不同系统技术要求不同,批处理作业管理越来越复杂,IT技术异构风险变大。一个技术人员很难同时熟悉多个系统,导致需要大量的技术人员分别管理和运维,夜间值班人员同时开着十几个甚至更多监控屏幕也成为常态和痛点。这些问题很显然造成了运维投入的增加。Ø不符合信创要求随着信创对服务器、操作系统、数据库的要求越来越普遍,国外的部分软件越来越难以达到国内信创的要求。Ø定制化要求即使一款成熟的软件,在特定场景下也需要和客户现有的运行环境进行对接,如客户自有的监控系统,自有的单点登录系统,自有的OA系统,告警通知(短信/邮件/钉钉)等,开源或国外软件往往缺乏良好的扩展框架,导致与现有系统对接困难。2. 产品介绍MoiaControl是北京先进数通完全自主研发的企业级调度与监控管理系统,多年来一直专注于调度领域,产品用户覆盖银行、证券、保险、制造等十余个行业。在业界也具有良好的口碑,被中国软件行业协会评选为优秀软件产品。MoiaControl秉承稳定、高效、安全、易用的设计理念,将多年实施积累的业务场景需求和调度核心技术有效结合,开箱即用,为企业量身打造统一任务调度平台。MoiaControl广泛应用于企业级数据与业务处理场景,能够构建统一的数据处理流水线,实现从数据采集、处理到输出的全流程自动化。在数据仓库建设中,平台可高效支撑ETL调度,完成数据抽取、转换与加载任务的自动执行,确保数据链路稳定可靠。在大数据领域,MoiaControl提供完善的大数据作业调度与大数据任务调度能力,支持对Spark、Hive等计算任务的统一管理,适用于复杂的数据分析与处理场景。同时,针对周期性数据处理需求,平台可实现高效的离线计算调度,保障批量计算任务按时、稳定执行。在数据集成方面,系统支持灵活的数据同步任务调度,可实现多系统之间的数据交换与整合,支撑企业数据中台与数据治理体系建设。此外,针对企业日常业务处理需求,MoiaControl可调度各类批处理任务,如账务处理、订单清算等关键业务流程。在运维层面,平台通过统一调度能力实现运维自动化,减少人工干预,提升系统运行效率与稳定性。通过任务编排、依赖控制及自动重试机制,企业可以构建高效、可靠的一体化调度体系。3. 产品历史先进数通MOIA产品研发走过十多年,研发了多个版本,直至今日的MoiaControlV6。其间产品用户覆盖银行、证券、保险、制造、零售、通讯等多个行业的用户。MoiaControl在今后也会持续投入研发,不断优化和丰富产品功能,为用户提供更好的产品。4. 客户规模目前中国市场(含港、澳),客户规模已经超过100家,单一集群规模超过了1200多个节点,单一客户作业规模超过400000个。5. 产品定位MoiaControl定位为企业级调度平台,位于从硬件设施到业务数据展现的中间层,通过集群管理功能对下层物理节点进行管理、控制及监控,通过作业调度功能对批量作业进行有效调度,最终将业务数据展现给用户。6. 功能架构MoiaControl分为界面层、计算层和执行层。MoiaControl采用先进的微服务架构设计,将调度中心、执行引擎、任务管理、监控告警等核心模块进行解耦,实现系统的高内聚与低耦合,便于灵活扩展与独立升级。在此基础上,平台全面支持云原生调度能力,可无缝运行于Kubernetes等容器环境,具备弹性伸缩、高可用部署及资源动态调度能力,满足企业在不同规模下的调度需求。系统内置完善的多租户机制,支持不同业务域、部门或项目之间的资源隔离与权限控制,通过细粒度的访问控制(RBAC)与租户隔离策略,保障数据安全与系统稳定运行,适用于大型组织及集团化场景。在扩展能力方面,MoiaControl采用灵活的插件化架构设计,支持任务类型、调度策略、执行器及告警方式的插件扩展,用户可根据业务需求快速接入自定义任务类型(如Spark、Shell、SQL、Python等),实现系统能力的持续演进与生态扩展。同时,平台提供标准化的REST API/SDK接口,支持与企业现有系统(如数据治理平台、指标平台、统一调度平台等)进行无缝集成,方便开发者进行二次开发与自动化接入,构建统一、高效的调度与数据处理体系。7. 全面兼容信创全面支持国产化生态,满足信创要求:✔ 国产主流服务器鲲鹏/龙芯/海光/兆芯/飞腾/申威等✔ 国产主流操作系统银河麒麟/统信 UOS/欧拉等✔ 应用服务器东方通/金蝶/普元/中创/宝兰德等✔ 国产主流数据库达梦(DM)金仓(Kingbase)OceanBaseTDSQLGaussDB/OpenGaussGoldenDBGBase......8. 核心优势8.1.调度稳定Ø高可用设计调度Server及代理Agent支持分布式集群模式,避免单点故障风险,为高可用提供有力保障。Ø故障恢复通过完善的事务控制、队列数据持久化、Agent数据临时本地缓存并自动补发等设计,保障在发生系统灾难、系统重启等场景下无需人工干预即可自动恢复调度。Ø众多成功的实际应用案例十几年来MoiaControl在多个行业积累了大量用户,拥有众多的成功案例。在产品的容错、高可用、异常处理机制等方面积累了丰富的经验。8.2. 调度高效Ø依托微服务架构的优势,采用全事件驱动,摒弃传统的轮循扫描机制,使用资源抢占模式,去中心化的作业分配策略,从而提高调度效率。随着作业量的增长,调度效率不会出现明显变化,保障在百万级作业量情况下的稳定调度,使依赖的作业间触发时间低至毫秒级。Ø引入redis内存式数据库,提高系统处理效率,并降低对资料库的资源消耗。8.3. 便捷的开发方式Ø图形化点选拖拽式开发ØExcel批量开发提供各类配置数据的Excel批量开发方式,在Excel模板中填写后导入即可。Ø一键智能布局智能布局前:智能布局后: 8.4. 丰富的监控运维手段Ø多维度监控视角Ø血缘分析可分析所选对象的上下游所有依赖关系,包括跨流程跨系统依赖。可用于问题快速定位和影响分析。血缘分析图示例:Ø生命周期查询可查询作业当次及历史的完整生命周期,便于作业异常分析及调优分析。生命周期示例:Ø多种人工干预功能系统提供丰富的人工干预手段,在保障自动化调度的同时也为各种人工运维场景预留了人工干预的功能。运维人员仅需要在页面进行人工干预操作即可完成干预动作,避免登录后台操作所带来的风险。8.5. 完善的对外接口提供RESTFUL方式的完善的开发接口,包含调度开发配置、配置信息查询、配置信息删除、监控、人工干预等完善的接口,便于与第三方系统对接。通过MoiaControl提供的服务接口,可以在完全不使用MoiaControl界面的情况下,完成调度信息的配置、运行状态和日志的获取以及人工运维。8.6. 应用数据隔离在统一调度场景下,各项目/系统调度配置数据有隔离需求时,系统提供以应用为单位隔离应用数据的功能。8.7. 系统热扩展系统具良好的扩展性设计。支持不停机热扩展,即时生效,可以在不影响现有调度运转的情况下任意扩展server和agent节点。8.8. AI智能运维系统支持提供基于大模型的AI智能问答服务,为用户提供覆盖产品安装、功能介绍、操作指引、作业配置的多场景自然语言智能化解答。系统支持AI智能数据分析:用户通过自然语言输入查询意图,AI可自动识别自然语言意图生成基于运行数据的趋势分析、异常分析及优化建议报告。9. 客户收益从大量客户反馈信息来看,使用MoiaControl后,相较于原来的调度方式有大幅度提升:Ø调度效率提升50%+Ø运维成本降低60%+Ø故障恢复时间缩短80%+Ø实现了多活的部署架构要求
-
最近拜读了https://bbs.huaweicloud.com/blogs/450734这篇文章,里面讲解将ALL-reduce和GEMM融合的分块方式只切分M轴。因为通信任务调用的Hccl API要求分块数据内存连续,若按N轴切分,则每行数据都被切断,导致通信数据的内存不连续,不满足通信要求;若按M轴切分,则每行数据都是内存连续的,满足通信要求。看完后有两个疑问想请教下:文章里提到只对M轴切分,是否可以认为只对左矩阵切分,每个GPU拿到部分左矩阵数据,而右矩阵不切分,每个GPU拿到完整的右矩阵数据若只对M轴切分,则多卡通信汇聚数据的时候,理论上不需要将多卡的数据进行求和,这里为啥需要使用all-reduce而不是all-gather(我知道目前也是支持all-gather与gemm融合的,只不过all-reduce的这个分块方式令我有些困惑)由于我刚接触该融合特性,如果上面的理解有不到位的地方还请指正,多谢!
-
已经参考过文档里的解决办法了,有没有遇到过同样问题的盆友,求解决办法,感谢(是C++ 用vs写的)
-
许可证正常导入,设计器运行脚本提示检查许可证失败,请检查本地许可文件或与Studio/助手的连接是否正常建立并确保许可未过期请问是什么原因?
-
1.Handler的实现原理从四个方面看 Handler 、 Message 、 MessageQueue 和 LooperHandler: 负责消息的发送和处理Message: 消息对象,类似于链表的一个结点 ;MessageQueue: 消息队列,用于存放消息对象的数据结构 ;Looper: 消息队列的处理者(用于轮询消息队列的消息对象 )Handler 发送消息时调用 MessageQueue 的 enqueueMessage 插入一条信息到 MessageQueue,Looper 不断轮询调用MeaasgaQueue 的 next 方法 如果发现 message 就调用 handler 的 dispatchMessage , dispatchMessage 被成功调 用,接着调用handlerMessage() 。2.子线程中能不能直接new一个Handler,为什么主线程可以 主线程的Looper第一次调用loop方法,什么时候,哪个类 不能,因为Handler 的构造方法中,会通过 Looper.myLooper() 获取 looper 对象,如果为空,则抛出异常, 主线程则因为已在入口处ActivityThread 的 main 方法中通过 Looper.prepareMainLooper() 获取到这个对象, 并通过 Looper.loop() 开启循环,在子线程中若要使用 handler ,可先通过 Loop.prepare 获取到 looper 对象,并使用 Looper.loop()开启循环。3.Handler导致的内存泄露原因及其解决方案1.Java 中非静态内部类和匿名内部类都会隐式持有当前类的外部引用2. 我们在 Activity 中使用非静态内部类初始化了一个 Handler, 此 Handler 就会持有当前 Activity 的引用。3. 我们想要一个对象被回收,那么前提它不被任何其它对象持有引用,所以当我们 Activity 页面关闭之后 , 存在引用关 系:" 未被处理 / 正处理的消息 -> Handler 实例 -> 外部类 ", 如果在 Handler 消息队列 还有未处理的消息 / 正在处理消 息时 导致Activity 不会被回收,从而造成内存泄漏解决方案 :1. 将 Handler 的子类设置成 静态内部类 , 使用 WeakReference弱引用持有 Activity 实例2. 当外部类结束生命周期时,清空 Handler 内消息队列4.一个线程可以有几个Handler,几个Looper,几个MessageQueue对象 一个线程可以有多个Handler, 只有一个 Looper 对象 , 只有一个 MessageQueue 对象。 Looper.prepare() 函数中知道,。在Looper 的 prepare 方法中创建了 Looper 对象,并放入到 ThreadLocal 中,并通过 ThreadLocal 来获取 looper 的对象, ThreadLocal 的内部维护了一个 ThreadLocalMap 类 ,ThreadLocalMap 是以当前 thread 做为 key 的 , 因此可以得 知,一个线程最多只能有一个Looper 对象, 在 Looper 的构造方法中创建了 MessageQueue 对象,并赋值给 mQueue字段。因为 Looper 对象只有一个,那么 Messagequeue 对象肯定只有一个。5.Message对象创建的方式有哪些 & 区别,Message.obtain()怎么维护消息池的1.Message msg = new Message();每次需要 Message 对象的时候都创建一个新的对象,每次都要去堆内存开辟对象存储空间2.Message msg = Message.obtain();obtainMessage 能避免重复 Message 创建对象。它先判断消息池是不是为空,如果非空的话就从消息池表头的 Message取走 , 再把表头指向 next 。如果消息池为空的话说明还没有 Message 被放进去,那么就 new 出来一个 Message 对象。消息池使用 Message 链表 结构实现,消息池默认最大值 50 。消息在 loop 中被 handler 分发消费之后会执行回收的操作,将该消息内部数据清空并添加到消息链表的表头。3.Message msg = handler.obtainMessage(); 其内部也是调用的 obtain() 方法6.Handler 有哪些发送消息的方法sendMessage(Message msg)sendMessageDelayed(Message msg, long uptimeMillis)post(Runnable r)postDelayed(Runnable r, long uptimeMillis)sendMessageAtTime(Message msg,long when)7.Handler的post与sendMessage的区别和应用场景1. 源码sendMessagesendMessage-sendMessageAtTime-enqueueMessage 。postsendMessage-getPostMessage-sendMessageAtTime-enqueueMessage getPostMessage 会先生成一个 Messgae,并且把 runnable 赋值给 message 的 callback2.Looper->dispatchMessage 处理时public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}dispatchMessage 方法中直接执行 post 中的 runnable 方法。而 sendMessage 中如果 mCallback 不为 null 就会调用 mCallback.handleMessage(msg) 方法,如果 handler 内的 callback不为空,执行 mCallback.handleMessage(msg) 这个处理消息并判断返回是否为 true ,如果返回 true ,消息 处理结束,如果返回false,handleMessage(msg) 处理。否则会直接调用 handleMessage 方法。post方法和 handleMessage 方法的不同在于,区别就是调用 post 方法的消息是在 post 传递的 Runnable 对象的 run 方法中处理,而调用sendMessage 方法需要重写 handleMessage 方法或者给 handler 设置 callback ,在 callback 的handleMessage 中处理并返回 true应用场景post 一般用于单个场景 比如单一的倒计时弹框功能 sendMessage 的回调需要去实现 handleMessage Message 则做为参数 用于多判断条件的场景。8.handler postDealy后消息队列有什么变化,假设先 postDelay 10s, 再 postDelay 1s, 怎么处理这2条消息sendMessageDelayed-sendMessageAtTime-sendMessage————————————————版权声明:本文为CSDN博主「界斗士」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_14931305/article/details/126245284
-
一、常见数据结构非线程安全的数据结构:ArrayList,LinkedList,ArrayQueue,HashMap,HashSet线程安全的数据结构:Vector,Stack,Hashtable,CopyOnWriteArrayList,ConcurrentHashMap二、ArrayList2-1 线程不安全的原因看源码public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! // 该方法是容量保障,当容纳不下新增的元素时会进行扩容 elementData[size++] = e; return true;}分析:当数组长度为10,而size = 9时,此时A线程判断可以容纳,B线程也来判断发现可以容纳(这是因为add非原子操作)。当A添加完之后,B线程再添加的话,就会报错(数组越界异常)而且这一步elementData[size++] = e也非原子性的.可以拆分为elementData[size] = e 和 size ++;在多线程的情况下很容易出现elementData[size] = e1; elementData[size] = e2; size++; size++; 的情况2-2 Vector实现安全Vector的add()源码: public synchronized void addElement(E obj) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = obj; }分析: Vector的add方法加了synchronized ,而ArrayList没有,所以ArrayList线程不安全,但是,由于Vector加了synchronized ,变成了串行,所以效率低。回到目录…三、CopyOnWriteArrayListCopyOnWrite容器即写时复制的容器。// java.util.concurrent包下List<String> list = new CopyOnWriteArrayList<String>();123-1 如何实现线程安全? 通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。 这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。3-2 特征CopyOnWriteArrayList(写数组的拷贝)是ArrayList的一个线程安全的变体,CopyOnWriteArrayList和CopyOnWriteSet都是线程安全的集合,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。它绝对不会抛出ConcurrentModificationException的异常。因为该列表(CopyOnWriteArrayList)在遍历时将不会被做任何的修改。CopyOnWriteArrayList适合用在“读多,写少”的并发场景中,比如缓存、白名单、黑名单。它不存在“扩容”的概念,每次写操作(add or remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作要加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为开始读的那一刻已经确定了读的对象是旧对象。3-3 缺点在写操作时,因为复制机制,会导致内存占用过大。不能保证实时性的数据一致,“脏读”。回到目录…四、HashMap4-1 底层原理不清楚的小白看看之前两篇文章,就可以很容易搞懂HashMap的底层实现原理了。 Java数据结构之哈希表 JDK中的Set和Map解析4-2 线程不安全的原因单看 HashMap 中的 put 操作:JDK1.7头插法 –> 将链表变成环 –> 死循环JDK1.8尾插法 –> 数据覆盖回到目录…五、ConcurrentHashMap// java.util.concurrent包下Map<Integer, String> map = new ConcurrentHashMap<>();125-1 实现原理JDK1.7时,采用分段锁JDK1.8时,只针对同一链表内互斥,不是同一链表内的操作就不需要互斥。但是一旦遇到需要扩容的时候,涉及到所有链表,此时就不是简单的互斥了。扩容的过程:当A线程put 操作时发现需要扩容,则它自己创建一个扩容后的新数组。A线程只把当前桶中的节点重新计算哈希值放入新数组中,并且标记该桶元素已经迁移完成。由于其它桶中的元素还没有迁移,所以暂时还不删除旧数组。等其它线程抢到锁并在桶内做完操作时,需要义务的将该桶节点全部搬移并标记桶。直到最后一个线程将最后一桶节点搬移完毕,则它需要把旧数组删除。5-2 与Hashtable的区别HashTable和HashMap的实现原理几乎一样,差别在于:HashTable不允许key和value为null;HashTable是线程安全的。 但是HashTable线程安全的策略实现代价却比较大,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把全表锁。当一个线程访问或操作该对象,那其他线程只能阻塞。 所以说,Hashtable 的效率低的离谱,几近废弃。————————————————版权声明:本文为CSDN博主「一只咸鱼。。」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq15035899256/article/details/125961682
-
【功能模块】在鲲鹏openlab , openEuler 20.03 (LTS-SP1)上安装Hyper-tuner_2.5.RC1【操作步骤&问题现象】1、新建任务-进程/线程性能分析, 采样类型:系统性能,进程性能,2、任务状态:任务采集失败,pidstat命令执行失败,请确认pidstat命令能否正常执行。3、后台可执行pidstat【截图信息】【日志信息】(可选,上传日志内容或者附件)2022-08-06 04:17:57 Sat|509023:281460682279808|process_sampling.py:137|sampling_task|CRITICAL|start the collect task2022-08-06 04:18:57 Sat|509023:281460682279808|process_sampling.py:147|sampling_task|WARNING|[ERROR] SysPerf.SysProcess.Collect.SampleStartError
-
系统 :win 10已安装 vc【功能模块】安装问题【操作步骤&问题现象】1、安装weautomate 后启动软件,【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
目录web 框架中的 requestThreadLocalcontextvarsc.Tokencontextvars.Context小结Python 在 3.7 的时候引入了一个模块:contextvars,从名字上很容易看出它指的是上下文变量(Context Variables),所以在介绍 contextvars 之前我们需要先了解一下什么是上下文(Context)。Context 是一个包含了相关信息内容的对象,举个例子:"比如一部 13 集的动漫,你直接点进第八集,看到女主角在男主角面前流泪了"。相信此时你是不知道为什么女主角会流泪的,因为你没有看前面几集的内容,缺失了相关的上下文信息。所以 Context 并不是什么神奇的东西,它的作用就是携带一些指定的信息。web 框架中的 request我们以 fastapi 和 sanic 为例,看看当一个请求过来的时候,它们是如何解析的。1234567891011121314151617181920212223242526272829303132# fastapifrom fastapi import FastAPI, Requestimport uvicorn app = FastAPI() @app.get("/index")async def index(request: Request): name = request.query_params.get("name") return {"name": name} uvicorn.run("__main__:app", host="127.0.0.1", port=5555) # ------------------------------------------------------- # sanicfrom sanic import Sanicfrom sanic.request import Requestfrom sanic import response app = Sanic("sanic") @app.get("/index")async def index(request: Request): name = request.args.get("name") return response.json({"name": name}) app.run(host="127.0.0.1", port=6666)发请求测试一下,看看结果是否正确。可以看到请求都是成功的,并且对于 fastapi 和 sanic 而言,其 request 和 视图函数是绑定在一起的。也就是在请求到来的时候,会被封装成一个 Request 对象、然后传递到视图函数中。但对于 flask 而言则不是这样子的,我们看一下 flask 是如何接收请求参数的。123456789101112from flask import Flask, request app = Flask("flask") @app.route("/index")def index(): name = request.args.get("name") return {"name": name} app.run(host="127.0.0.1", port=7777)我们看到对于 flask 而言则是通过 import request 的方式,如果不需要的话就不用 import,当然我这里并不是在比较哪种方式好,主要是为了引出我们今天的主题。首先对于 flask 而言,如果我再定义一个视图函数的话,那么获取请求参数依旧是相同的方式,但是这样问题就来了,不同的视图函数内部使用同一个 request,难道不会发生冲突吗?显然根据我们使用 flask 的经验来说,答案是不会的,至于原因就是 ThreadLocal。ThreadLocalThreadLocal,从名字上看可以得出它肯定是和线程相关的。没错,它专门用来创建局部变量,并且创建的局部变量是和线程绑定的。1234567891011121314151617181920212223242526272829import threading # 创建一个 local 对象local = threading.local() def get(): name = threading.current_thread().name # 获取绑定在 local 上的 value value = local.value print(f"线程: {name}, value: {value}") def set_(): name = threading.current_thread().name # 为不同的线程设置不同的值 if name == "one": local.value = "ONE" elif name == "two": local.value = "TWO" # 执行 get 函数 get() t1 = threading.Thread(target=set_, name="one")t2 = threading.Thread(target=set_, name="two")t1.start()t2.start()"""线程 one, value: ONE线程 two, value: TWO"""可以看到两个线程之间是互不影响的,因为每个线程都有自己唯一的 id,在绑定值的时候会绑定在当前的线程中,获取也会从当前的线程中获取。可以把 ThreadLocal 想象成一个字典:1234{ "one": {"value": "ONE"}, "two": {"value": "TWO"}}更准确的说 key 应该是线程的 id,为了直观我们就用线程的 name 代替了,但总之在获取的时候只会获取绑定在该线程上的变量的值。而 flask 内部也是这么设计的,只不过它没有直接用 threading.local,而是自己实现了一个 Local 类,除了支持线程之外还支持 greenlet 的协程,那么它是怎么实现的呢?首先我们知道 flask 内部存在 "请求 context" 和 "应用 context",它们都是通过栈来维护的(两个不同的栈)。123456# flask/globals.py_request_ctx_stack = LocalStack()_app_ctx_stack = LocalStack()current_app = LocalProxy(_find_app)request = LocalProxy(partial(_lookup_req_object, "request"))session = LocalProxy(partial(_lookup_req_object, "session"))每个请求都会绑定在当前的 Context 中,等到请求结束之后再销毁,这个过程由框架完成,开发者只需要直接使用 request 即可。所以请求的具体细节流程可以点进源码中查看,这里我们重点关注一个对象:werkzeug.local.Local,也就是上面说的 Local 类,它是变量的设置和获取的关键。直接看部分源码:12345678910111213141516171819202122232425262728293031323334353637383940414243# werkzeug/local.py class Local(object): __slots__ = ("__storage__", "__ident_func__") def __init__(self): # 内部有两个成员:__storage__ 是一个字典,值就存在这里面 # __ident_func__ 只需要知道它是用来获取线程 id 的即可 object.__setattr__(self, "__storage__", {}) object.__setattr__(self, "__ident_func__", get_ident) def __call__(self, proxy): """Create a proxy for a name.""" return LocalProxy(self, proxy) def __release_local__(self): self.__storage__.pop(self.__ident_func__(), None) def __getattr__(self, name): try: # 根据线程 id 得到 value(一个字典) # 然后再根据 name 获取对应的值 # 所以只会获取绑定在当前线程上的值 return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): ident = self.__ident_func__() storage = self.__storage__ try: # 将线程 id 作为 key,然后将值设置在对应的字典中 # 所以只会将值设置在当前的线程中 storage[ident][name] = value except KeyError: storage[ident] = {name: value} def __delattr__(self, name): # 删除逻辑也很简单 try: del self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name)所以我们看到 flask 内部的逻辑其实很简单,通过 ThreadLocal 实现了线程之间的隔离。每个请求都会绑定在各自的 Context 中,获取值的时候也会从各自的 Context 中获取,因为它就是用来保存相关信息的(重要的是同时也实现了隔离)。相应此刻你已经理解了上下文,但是问题来了,不管是 threading.local 也好、还是类似于 flask 自己实现的 Local 也罢,它们都是针对线程的。如果是使用 async def 定义的协程该怎么办呢?如何实现每个协程的上下文隔离呢?所以终于引出了我们的主角:contextvars。contextvars该模块提供了一组接口,可用于在协程中管理、设置、访问局部 Context 的状态。12345678910111213141516171819202122232425import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试") async def get(): # 获取值 return c.get() + "~~~" async def set_(val): # 设置值 c.set(val) print(await get()) async def main(): coro1 = set_("协程1") coro2 = set_("协程2") await asyncio.gather(coro1, coro2) asyncio.run(main())"""协程1~~~协程2~~~"""ContextVar 提供了两个方法,分别是 get 和 set,用于获取值和设置值。我们看到效果和 ThreadingLocal 类似,数据在协程之间是隔离的,不会受到彼此的影响。但我们再仔细观察一下,我们是在 set_ 函数中设置的值,然后在 get 函数中获取值。可 await get() 相当于是开启了一个新的协程,那么意味着设置值和获取值不是在同一个协程当中。但即便如此,我们依旧可以获取到希望的结果。因为 Python 的协程是无栈协程,通过 await 可以实现级联调用。我们不妨再套一层:123456789101112131415161718192021222324252627282930import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试") async def get1(): return await get2() async def get2(): return c.get() + "~~~" async def set_(val): # 设置值 c.set(val) print(await get1()) print(await get2()) async def main(): coro1 = set_("协程1") coro2 = set_("协程2") await asyncio.gather(coro1, coro2) asyncio.run(main())"""协程1~~~协程1~~~协程2~~~协程2~~~"""我们看到不管是 await get1() 还是 await get2(),得到的都是 set_ 中设置的结果,说明它是可以嵌套的。并且在这个过程当中,可以重新设置值。12345678910111213141516171819202122232425262728293031323334353637383940import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试") async def get1(): c.set("重新设置") return await get2() async def get2(): return c.get() + "~~~" async def set_(val): # 设置值 c.set(val) print("------------") print(await get2()) print(await get1()) print(await get2()) print("------------") async def main(): coro1 = set_("协程1") coro2 = set_("协程2") await asyncio.gather(coro1, coro2) asyncio.run(main())"""------------协程1~~~重新设置~~~重新设置~~~------------------------协程2~~~重新设置~~~重新设置~~~------------"""先 await get2() 得到的就是 set_ 函数中设置的值,这是符合预期的。但是我们在 get1 中将值重新设置了,那么之后不管是 await get1() 还是直接 await get2(),得到的都是新设置的值。这也说明了,一个协程内部 await 另一个协程,另一个协程内部 await 另另一个协程,不管套娃(await)多少次,它们获取的值都是一样的。并且在任意一个协程内部都可以重新设置值,然后获取会得到最后一次设置的值。再举个栗子:12345678910111213141516171819202122232425262728import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试") async def get1(): return await get2() async def get2(): val = c.get() + "~~~" c.set("重新设置啦") return val async def set_(val): # 设置值 c.set(val) print(await get1()) print(c.get()) async def main(): coro = set_("古明地觉") await coro asyncio.run(main())"""古明地觉~~~重新设置啦"""await get1() 的时候会执行 await get2(),然后在里面拿到 c.set 设置的值,打印 "古明地觉~~~"。但是在 get2 里面,又将值重新设置了,所以第二个 print 打印的就是新设置的值。\如果在 get 之前没有先 set,那么会抛出一个 LookupError,所以 ContextVar 支持默认值:1234567891011121314151617181920import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试", default="哼哼") async def set_(val): print(c.get()) c.set(val) print(c.get()) async def main(): coro = set_("古明地觉") await coro asyncio.run(main())"""哼哼古明地觉"""除了在 ContextVar 中指定默认值之外,也可以在 get 中指定:1234567891011121314151617181920import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试", default="哼哼") async def set_(val): print(c.get("古明地恋")) c.set(val) print(c.get()) async def main(): coro = set_("古明地觉") await coro asyncio.run(main())"""古明地恋古明地觉"""所以结论如下,如果在 c.set 之前使用 c.get:当 ContextVar 和 get 中都没有指定默认值,会抛出 LookupError;只要有一方设置了,那么会得到默认值;如果都设置了,那么以 get 为准;如果 c.get 之前执行了 c.set,那么无论 ContextVar 和 get 有没有指定默认值,获取到的都是 c.set 设置的值。所以总的来说还是比较好理解的,并且 ContextVar 除了可以作用在协程上面,它也可以用在线程上面。没错,它可以替代 threading.local,我们来试一下:1234567891011121314151617181920212223242526import threadingimport contextvars c = contextvars.ContextVar("context_var") def get(): name = threading.current_thread().name value = c.get() print(f"线程 {name}, value: {value}") def set_(): name = threading.current_thread().name if name == "one": c.set("ONE") elif name == "two": c.set("TWO") get() t1 = threading.Thread(target=set_, name="one")t2 = threading.Thread(target=set_, name="two")t1.start()t2.start()"""线程 one, value: ONE线程 two, value: TWO"""和 threading.local 的表现是一样的,但是更建议使用 ContextVars。不过前者可以绑定任意多个值,而后者只能绑定一个值(可以通过传递字典的方式解决这一点)。c.Token当我们调用 c.set 的时候,其实会返回一个 Token 对象:12345678import contextvars c = contextvars.ContextVar("context_var")token = c.set("val")print(token)"""<Token var=<ContextVar name='context_var' at 0x00..> at 0x00...>"""Token 对象有一个 var 属性,它是只读的,会返回指向此 token 的 ContextVar 对象。123456789101112import contextvars c = contextvars.ContextVar("context_var")token = c.set("val") print(token.var is c) # Trueprint(token.var.get()) # val print( token.var.set("val2").var.set("val3").var is c) # Trueprint(c.get()) # val3Token 对象还有一个 old_value 属性,它会返回上一次 set 设置的值,如果是第一次 set,那么会返回一个 <Token.MISSING>。12345678910111213import contextvars c = contextvars.ContextVar("context_var")token = c.set("val") # 该 token 是第一次 c.set 所返回的# 在此之前没有 set,所以 old_value 是 <Token.MISSING>print(token.old_value) # <Token.MISSING> token = c.set("val2")print(c.get()) # val2# 返回上一次 set 的值print(token.old_value) # val那么这个 Token 对象有什么作用呢?从目前来看貌似没太大用处啊,其实它最大的用处就是和 reset 搭配使用,可以对状态进行重置。123456789101112131415161718import contextvars#### c = contextvars.ContextVar("context_var")token = c.set("val")# 显然是可以获取的print(c.get()) # val # 将其重置为 token 之前的状态# 但这个 token 是第一次 set 返回的# 那么之前就相当于没有 set 了c.reset(token)try: c.get() # 此时就会报错except LookupError: print("报错啦") # 报错啦 # 但是我们可以指定默认值print(c.get("默认值")) # 默认值contextvars.Context它负责保存 ContextVars 对象和设置的值之间的映射,但是我们不会直接通过 contextvars.Context 来创建,而是通过 contentvars.copy_context 函数来创建。12345678910111213141516171819202122import contextvars c1 = contextvars.ContextVar("context_var1")c1.set("val1")c2 = contextvars.ContextVar("context_var2")c2.set("val2") # 此时得到的是所有 ContextVar 对象和设置的值之间的映射# 它实现了 collections.abc.Mapping 接口# 因此我们可以像操作字典一样操作它context = contextvars.copy_context()# key 就是对应的 ContextVar 对象,value 就是设置的值print(context[c1]) # val1print(context[c2]) # val2for ctx, value in context.items(): print(ctx.get(), ctx.name, value) """ val1 context_var1 val1 val2 context_var2 val2 """ print(len(context)) # 2除此之外,context 还有一个 run 方法:1234567891011121314151617181920212223242526272829import contextvars c1 = contextvars.ContextVar("context_var1")c1.set("val1")c2 = contextvars.ContextVar("context_var2")c2.set("val2") context = contextvars.copy_context() def change(val1, val2): c1.set(val1) c2.set(val2) print(c1.get(), context[c1]) print(c2.get(), context[c2]) # 在 change 函数内部,重新设置值# 然后里面打印的也是新设置的值context.run(change, "VAL1", "VAL2")"""VAL1 VAL1VAL2 VAL2""" print(c1.get(), context[c1])print(c2.get(), context[c2])"""val1 VAL1val2 VAL2"""我们看到 run 方法接收一个 callable,如果在里面修改了 ContextVar 实例设置的值,那么对于 ContextVar 而言只会在函数内部生效,一旦出了函数,那么还是原来的值。但是对于 Context 而言,它是会受到影响的,即便出了函数,也是新设置的值,因为它直接把内部的字典给修改了。小结以上就是 contextvars 模块的用法,在多个协程之间传递数据是非常方便的,并且也是并发安全的。如果你用过 Go 的话,你应该会发现和 Go 在 1.7 版本引入的 context 模块比较相似,当然 Go 的 context 模块功能要更强大一些,除了可以传递数据之外,对多个 goroutine 的级联管理也提供了非常清蒸的解决方案。总之对于 contextvars 而言,它传递的数据应该是多个协程之间需要共享的数据,像 cookie, session, token 之类的,比如上游接收了一个 token,然后不断地向下透传。但是不要把本应该作为函数参数的数据,也通过 contextvars 来传递,这样就有点本末倒置了。
-
各位老师好,FI版本8.1.2.2,集群需要扩容,在填写集群配置规划中的IP规划有一些疑问,希望能够得到解答疑问:集群需要扩容3个CN节点以及1200个DN节点,如何编写配置规划文档中的IP规划【功能模块】集群配置规划文档【操作步骤&问题现象】1、按照3ms上的一篇帖子(http://3ms.huawei.com/km/blogs/details/9738513)基础配置填写如下基础配置: Ø 集群名称:自定义 Ø 安装模式:选择“集群扩容” Ø 认证模式:局点自行决定,建议使用“安全模式”Ø 是否自定义套餐:YES Ø 扩容节点数量:根据真实情况填写IP规划与进程部署: Ø 类型:根据局点情况填写要扩容的节点:MN(管理节点)/CN(控制节点)/DN(数据节点)2、以上步骤操作完,IP规划详情如下,所有节点均为DN节点(实际想要达到的效果是需要有CN节点)疑问:如果要添加CN节点是请问是直接把14行的DN改成CN吗?然后再勾选相关进程?我通过这样的方式填写配置规划文档,生成的preinstall.ini 中新扩的三个CN节点对应的host0.ini host1.ini host2.ini 中并没有对应配置3、注意到9行到14行中有隐藏的行,手动拉出如下显示疑问:不是改第14行,而是改隐藏起来的这里吗?如果是改隐藏行,那么MN需要填写吗?总的来说就是不懂扩容CN+DN节点,集群配置规划要如何填写3ms上只找到了这一篇内容,但是没有详细说明http://3ms.huawei.com/km/blogs/details/9738513
推荐直播
-
华为云码道 × 仓颉编程:工程化AI编码探索2026/05/27 周三 19:00-21:00
刘俊杰-华为云仓颉语言专家/李炎-华为云码道技术专家/王智鹏-OpenCangjie开源社区发起人
本场直播围绕华为云仓颉语言与华为云码道的深度结合,展示华为云智能编程从零基础到高效落地的完整生态能力。以华为云码道为引擎,仓颉语言为载体,带给大家日常提效、趣味创新到极速量产的开发体验。
回顾中 -
一个AI团队帮你写代码:华为云码道Agent Space实战2026/06/25 周四 19:00-21:00
张翰文-华为云码道工程师/郭英旭-青软创新科技集团股份有限公司 软件架构师
本场直播聚焦华为云码道Agent Space两大模式:研发办公、代码开发,亲身体验从需求到代码的AI自动化能力。实操演示基于华为 CodeArts CLI,依托 OpenSpec 规格体系从零搭建业务项目。
回顾中
热门标签