尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

一个 Spark Driver 跑多人 Notebook:内核多路复用与并发隔离怎么做对

一个 Spark Driver 跑多人 Notebook:内核多路复用与并发隔离怎么做对 数据平台里的 Notebook,难点从来不在编辑器。三个硬要求决定架构:有状态(上一格的变量下一格还能用)、SQL 与 Python 变量互通(SQL 查出的结果直接就是 Python 里的 DataFrame,不用导出再读回)、能上线(页面上调通的东西原样变成每天定时跑的作业)。三条一摆,问题就变成了:执行内核放在哪,算力怎么在人之间共享。「我的数据空间」(datastudiohappy.cn)的选择是一个常驻 Spark Driver 里跑多个互相隔离的解释器。一、三条路线,一张表说完路线典型形态主要代价每人每份一个内核进程Jupyter Kernel Gateway / Enterprise Gateway 一类容器数随人数线性增长;依赖每份各装一遍,环境难复现;算力碎片化,谁都拿不到足够并行度SQL 走 SQL 网关,Python 另起一套Thrift/JDBC 独立解释器两套执行模型各自维护;变量不通,SQL 结果要落盘再读回一个共享 Driver 内多路复用解释器本平台的做法需要自己在一个进程里做对变量隔离与并发第一条最省事,规模一上来就现原形:十个人开二十个 Notebook,就是二十份容器、二十份依赖安装、二十份零碎算力。第二条牺牲掉的恰好是 Notebook 最值钱的特性——SQL 与 Python 之间零搬运。第三条把账算反过来:**隔离的是变量与会话态,共享的是算力与依赖。**引擎按空间创建,同空间成员挂同一个,依赖只在引擎启动时装一次,配额按引擎数计——成本封顶、可运营,而不是按人头膨胀。二、变量互通,靠的是「不分家」关键动作只有一个:SQL cell 不走查询网关,而是在提交前被包装成一句分布式查询,与 Python cell 走同一个内核执行。于是「打通变量」这件事根本不需要机制——SQL 的结果本来就落在同一个命名空间里,下一格直接引用;Python 里算出的变量也能被后续 SQL 引用。对比另一条常见实现(SQL 结果落对象存储、签临时地址、内核再拉回来变成变量),这里整条桥接不存在:没有中间落盘,没有「预览只有 100 行、真跑起来才发现不是全量」这类惊喜。执行模型的分叉也一并消掉:没有引擎路由选项,报错信息、末值展示、魔法命令行为在两种 cell 之间完全一致。三、一个进程放多个人:隔离好做,并发是硬仗**隔离不难,但要切在两层。**每个 Notebook 对应一个独立解释器实例,各有各的 Python 命名空间;同时在 Spark 侧也切一刀——每个 Notebook 拿一个自己的 SparkSession,共享的只有 SparkContext(executors、缓存、元数据客户端)与已装依赖。第二刀不能省。只隔离 Python 变量的话,临时视图、运行期spark.sql.*配置、当前数据库、UDF 注册表这四样仍挂在同一个会话态上:两人都写createOrReplaceTempView(tmp)时后写者静默覆盖,双方查出来的都是对方的数据;session.timeZone被别人改掉,日期结果整体偏移。这类错不报异常,只是数字悄悄变了——比抛错危险得多。切成独立 SparkSession 之后,这四样各自独立,而算力与缓存照旧共用。难的是并发。多人长期共用一个 Driver,若全进程串行,一人跑慢查询全引擎的人一起等——比不共享还差。可一旦放开并发,运行时里那些「全进程只有一份」的槽位立刻暴露:标准输出、末值回显钩子、富输出发布出口,都是进程级单一变量,而常规做法是「临时改写、用完改回」。并发下就是谁最后写谁赢,后果不是输出乱序,而是A 的输出出现在 B 的页面上。Python 侧解法是同一个思路:槽位只在进程启动时被占一次,之后按执行线程分发,或者干脆绕开——末值不再依赖全局钩子,而是执行前把 cell 末行表达式改写成一次显式赋值,执行完从命名空间取回;富输出出口从「全局唯一活跃通道」改成挂在各解释器自己身上,每个会话一份。再加上 Spark 侧按会话划分公平调度池,一个人的重查询不会饿死同 Driver 上的其他人。至此串行只保留在「同一个 Notebook 内部」——那本来就是 cell 该有的顺序语义,不是性能妥协。这三处加上前面那份 SQL 会话态,都不是某套框架的缺陷,而是**「一个进程服务多个用户」的通用税单**——换任何内核实现都要交,只是清单里有几项容易被漏掉:光看住了 Python 层的全局变量,不等于 Spark 层的会话态也看住了。四、长跑的 cell 怎么判死:两个直觉判据都是错的交互式分析里,跑两小时是合法的,照搬批作业的总时长超时必然误杀;而一句半小时的大聚合全程零输出同样合法,「无输出超时」也会误杀。更棘手的是,真崩掉的内核在流量上和安静的长跑一模一样。正确的判据是**「内核还活着吗」,由内核侧主动回答**:执行期间按固定节律发心跳,与业务输出无关;服务端窗口内收到任何字节即续命,一片死寂才判僵死。阈值取心跳间隔的数倍,一次垃圾回收停顿不至于误判。判死之后只断流是不够的——关掉连接不会让内核停下来,那段计算还在跑、算力继续被占,等于只回收一半。顺序是:先请内核中断执行,完全无响应时再强制掐断底层连接;中断请求自己也带超时,判死路径不能反被卡住。配套四道闸把「占住不放」从源头掐掉:每会话单飞(同一 Notebook 的第二个 cell 直接拒绝并提示,不去内核空等锁,线程占用上界从请求数降到活跃会话数)、有界池打满即拒(长连接不排队,排队等于让用户干等还看不到反馈)、客户端断开即释放(关页面只断了浏览器那一段,服务端要主动感知)、空闲引擎自动回收(租约入库而非放进程内存,任一副本都能收拾)。最后一条有个容易漏的前提:正在执行不算空闲,所以恰恰需要前面那套心跳判据兜住。五、页面上跑通,上线就跑通三层版本:草稿 → 发布快照 → 在线版本。交互式跑草稿,调度只认已发布的在线版,改草稿不影响线上——dev/prod 纪律靠机制强制,不靠自觉;同源执行:定时调度起的是一份临时 Driver,跑的是同一份内核代码,末值语义、魔法命令、报错格式全一致,不会「页面上好的,上线就报错」;进血缘图:SQL cell 的读写表被解析进表血缘,发布或回滚时自动刷新引用它的作业血缘,Notebook 产出的表不再断链;失败诊断到 cell:调度失败时,平台的智能诊断定位到第几个 cell、哪一行代码,而不是丢一坨堆栈让你自己读。六、四句话总结Notebook 的架构分水岭是「内核按什么拆」:按人拆进程简单,但成本随人头膨胀;按会话拆解释器,才能依赖装一次、算力共享;SQL 与 Python 的变量互通不是加一条桥接管道,而是让它们从头就在同一个进程里执行——最好的桥是不需要桥;「进程内多用户」的隔离清单要两层都列:Python 命名空间和Spark 会话态,漏掉后者会得到「不报错但算错数」;交互式的资源保护判据既不是「跑了多久」也不是「有没有输出」,而是「内核还活着吗」,且判死之后必须先中断再断流。Notebook 是「我的数据空间」的一部分——一套可私有化部署的数据平台(湖仓 调度 数据治理 智能诊断),支持 OEM 合作。
返回列表