更多技术交换、求职机会,欢送关注字节跳动数据平台微信公众号,回复【1】进入官网交换群
背景介绍
Notebook 解决的问题
- 局部工作类型(python、spark 等)在创立配置阶段,须要进行分步调试;
- 因为摸索查问能力较弱,局部用户只能通过其余平台 or 其余路径进行开发调试,但部署到 Dorado 时,又发现行为不统一等问题(运行环境问题),整体体验较差,须要晋升摸索查问模块的能力;
- 目前摸索查问仅反对 SQL,可反对更多语言类型,扩大数据开发伎俩;
总体架构介绍
火山引擎 DataLeap notebook 次要是基于 JupyterHub、notebook、lab、enterprise kernel gateway 等开源我的项目实现,并在这些我的项目的根底上进行深度批改与定制化,以满足 火山引擎 DataLeap 用户的需要。
根底组件方面,次要是基于 TCE、YARN、MYSQL、TLB、TOS。
外围指标是提供反对大规模用户、稳固的、容易扩大的 Notebook 服务。
零碎总体架构如下图所示,次要包含 Hub、notebook server(nbsvr)、kernel gateway(eg)等组件。
多用户治理
Hub
JupyterHub 是一个反对“多用户”notebook 的 Server,通过治理 & 代理多个单用户的 notebook server 实现多用户 notebook。
JupyterHub 服务次要三个组件形成:
- a Hub (tornado process), which is the heart of JupyterHub;
- a configurable http proxy (node-http-proxy): 动静路由用户的申请到 Hub 或者 Notebook server;
- multiple single-user Jupyter notebook servers (Python/IPython/tornado) that are monitored by Spawners;
- an authentication class that manages how users can access the system;
整个零碎架构图如下所示:
用户通过 IP 地址或者域名拜访 JupyterHub,根本流程为:
- 启动 Hub 服务,Hub 会启动 proxy 过程;
- 用户申请 Hub,申请会被打到 proxy,proxy 保护了 proxy table,每条 mapping 记录为用户申请到 target IP 或者 域名的映射;proxy table 不存在以后申请的 mapping 时,proxy 默认把申请全部打到 Hub;
- Hub 解决用户认证与鉴权,同时 Hub spawner 启动一个 Notebook server;
- Hub 配置 proxy,路由该该用户的申请到创立的 notebook server 处;
1、火山引擎 DataLeap authentication Hub 原生地反对 authentication,次要是用来解决多租户的问题。Hub 里次要是应用 authenticator 类来进行 authenticate。
Hub 原生反对的 authenticator 次要有一下几个:
- Local authenticator, work with local Linux/UNIX userst
- PAM authenticator, authenticate local UNIX users with PAM
- Dummy authenticator, any username + password is allowed for testing
思考到计划 1 须要开发量大、保护老本高,咱们采纳了计划 2。
采纳了计划 2 的整个认证 & 鉴权步骤如下所示:
- 用户在 web 页面拜访了 火山引擎 DataLeap notebook,frontend 会带上 session 信息申请 hub post /api/users/{name}/tokens api 获取一个 token,该流程须要 authenticate & authroization,包含:
- 通过 titan 认证该 sessionid 对应的 user;
- 通过 火山引擎 DataLeap backend ProjectControl /project/canedit api 验证用户是否具备我的项目权限;
- 后续该用户的拜访均会带上 token,Hub 会应用该 token 进行用户认证。
- 每次生成的 token 会保留到 db;
- 认证时也是从 db 进行匹配;
- Token 存在 expire time,expired 的会被从 db 清理掉;
2、TCE Spawner Spawner 负责启动 single-user notebook server,其本质是一个过程的形象示意,一个定制化的 spawner 实现上面三个办法:
- start the process
- poll whether the process is still running
- stop the process More info on custom Spawners. See a list of custom Spawners on the wiki.
目前咱们的服务不是运行在物理机上,所以不会通过 k8s 治理 server & kernel。思考到运维 & 扩大,咱们思考应用 TCE 作为 notebook server 的载体,因而咱们须要实现 TCE Spawner。
设计 TCE spawner 时,有以下几点思考:
- Spawner.state 须要蕴含 service id、cluster id、psm、api token 等信息,这些信息会长久化在 db 中;hub 重启 或者 server 敞开后,重新启动 notebook server 时,保障同一个用户映射到之前该用户启动的那个 sever(same user same server);
- 为了放慢启动过程,spawner 确认 tce 实例启动时,一旦发动了 tce cluster deployment 后就开始 sd lookup psm 确认 server 是否失常启动,不通过 poll deployment status 确认是否部署实现,这能够放慢启动过程,因为 tce 部署过程中包含健康检查等步骤,占时较长;
- Stop 中,并不真正 kill tce 实例,这样下次启动根本不耗费工夫;
- Poll server 状态时,须要思考 降级 & migrate 带来的状态变动,一旦发现立即返回 异样状态,这样 hub 就会认为这个 notebook server not running,就会异样 该 spawner,后续新的申请到来时会重新启动 spawner,因为此时曾经非第一次启动,过程极快,用户不感知。
整个 TCE spawner,次要用到了 tce 的两个个性:
- Psm 惟一对应了一个服务;
- 通过 psm 发现 ip & port;
- 通过 tce 的 api 获取 server 状态;
- 不便运维(降级 & 迁徙);
题外话:最近调研了 server on yarn,有点相似 k8s 的感觉,实质上都是走资源调度,然而 yarn 资源调度有个毛病:每个 application 调度到 yarn 时,都须要随同一个 Application Master。尽管 AM 大多数时候次要是用来和 RM 放弃心跳,只须要 0.5 核即可,然而总感觉很顺当,或者说多了一个不稳固的因素。
3、State isolated
(1) Hub migration
原生 jupyter hub 的降级或者实例迁徙时,须要把所有的 spawner & server 敞开掉。这意味着,hub 实例变动后,之前的 server & kernel 都会被敞开。
因为以后零碎采纳了 remote server + remote kernel,且不会被动 shutdown kernel,因而当 hub 实例发生变化时,server & kernel 实例不会被敞开。然而新 hub 实例启动后,所有的 server 都将连贯不到新的 hub 实例上,会产生幽灵 server & kernel。
咱们提供了如下解决方案:
- 在 notebook server 里减少定时查看线程,依据 hub 的 psm 查看对应的 ip & port 是否产生扭转;
- 如果产生扭转,则切换 hub_activity_url & hub_api_url。如此,notebook server 就能够连贯到新的 hub 实例了。
(2) Notebook server migration
如果 notebook server 实例降级或者迁徙了,hub 也须要能及时感知,并能正确敞开 spawner。
这个目前是通过 tce spawner poll 实现,poll 里会 check 对应的 notebook server 的 ip & port 是否发生变化,如果产生了变动则返回非零状态,示意 server 异样,此时 hub 感知到并敞开 spawner。后续,用户的申请到来时,会从新创立 spawner 并连贯到同一个 notebook server。
Resource pool
Pool 的设计有两个思考:
- Tce 资源无奈独占;
- Server 启动慢;
因为 notebook server 是启动在 TCE 上的,TCE 上启动一个 server 须要经验如下几个要害阶段:新建 service -> 新建 cluster -> 部署(构建镜像、部署)-> 一些查看 整个过程耗时较长,预计耗时 3 - 5 分钟,如果每个 server 的启动过程都须要这么久,显然是无奈承受的。
于是,咱们申请了新建了一堆 tce 实例构建成 tce resource pool。每次新我的项目接入,Hub spawner 依照如下流程解决:
- 去 tce resource pool 中查看是否存在未被占用的实例,有则挑一个
- 否则,走原新建流程;
目前 pool 的建设是手动操作的,前期会反对自动检测扩容:
- 定时线程,检测以后 pool 的容量是否少于 30(例如);
- 少于则新建并退出 pool 中;
另一个问题是:pool 里的每个实例均须要反对 psm 服务发现,那么在 server 被调配前,他们处于什么状态呢?被调配后,如何依照 user 对应的配置启动 server 呢?Pool 里的实例,均是启动了一个 idle server(原生的 notebook server)(该形式能够让该实例胜利启动,并且能被服务发现),同时存在一个定时线程,一直去查看 tos 对应的配置文件是否 ready,ready 后 shutdown idle server,依照 tos 配置文件启动 single user notebook server。
这种形式后,启动工夫从 3min+ 降到 8s,8s 为 single user notebook server 启动并稳固提供服务的工夫。
Kernel 治理
book 存储
Notebook 中的代码和输入文本次要是通过后缀为 .ipynb 的 json 文件存储的,因而 notebook server 须要负责 ipynb 文件的新建、删除等治理。
Notebook server 对 notebook 的存储是通过 FileManager 来实现的,FileManager 次要负责 ipynb 的创立、保留、删除、重命名等文件操作,另外还会进行 ipynb 文件的 format 查看以保障格局正确。
FileManger 保留文件是通过 local filesystem 实现的。为了长久化存储 ipynb 文件,咱们在 FileManager 中嵌入了 tos 文件存储的性能。具体过程为:
- 首次创立时,在本地生成 ipynb 后,并往 tos 上 put 一份;
- 每次更新保留时,在本地更新后往 tos put 一份;
- 每次关上 ipynb 时,首先判断本地是否存在对应的 ipynb 文件,如果不存在则从 tos 拉取;如果存在则不做拉取操作;
- 删除操作只是删除了本地的文件,没有删除 tos 的那份。
kernel 治理
当咱们在页面上关上一个 notebook 工作时,notebook server 会尝试启动一个 kernel 来执行你点击运行的代码。火山引擎 DataLeap 上每个 task 都和一个 kernel 对应,notebook server 负责保护每个工作的 kernel。
Notebook server 是通过 KernelManager 来保护 kernel 信息的,KerneManager 负责 kernel 的启动、重启、删除等操作。
默认状况下,Kernel 是启动在 notebook server 所在的运行容器里,这种状况下单个 server 里无奈撑持起大规模 kernel。
代理
如上一节所述,notebook server local 模式不反对大规模 kernel 的扩大,实用于小范畴应用,次要起因有如下两点;
- kernel 都是在 notebook server host 内启动的,单机必然无奈包容大规模 kernel;
- Kernel 间没有隔离,只是过程间的隔离,资源 & 执行环境等没有很好的隔离与定制化;
Enterprise kernel gateway(简称 EG)次要致力于解决上述问题,采纳了 EG 的零碎架构如下所示:
技术上来讲,EG 局部扩大了 notebook server 的性能,而后作出了如下改变:
- 复用 notebook server 中的 API(kernel 治理局部);
- 提供了 WS 的治理;
- 基于 notebook server 中 MultiKernelManager & KernelManager & SessionManager,做出扩大,提供了 RemoteMappingKernelManager;
从图中能够看出,client 并非是 notebook 相干的零碎,也能够是其余零碎,这意味着能够间接把 EG 当成 Code Execution Server,只须要其 ws client 遵循 Jupyter msg protocol。
代理架构
在 火山引擎 DataLeap notebook 零碎中,上图中的 client 即为 notebook server,此时 notebook server 只负责管理 notebook 文件(创立、读写、保留、删除),kernel 局部的操作全副转发给 EG 进行解决(留神这里的转发蕴含 http 转发与 ws 转发)。具体如下图所示:
用户在浏览器运行一段代码,整个交互流程如下图所示:
EG proxy 的具体过程参考:
以后 EG 反对往 yarn、k8s 等业界罕用资源管理零碎提交 kernel。咱们以后只反对 remote kernel on yarn,后续思考反对 k8s。
近程 Kernel
1、Remote kernel on yarn
开源 EG 往 yarn 上提交工作次要是应用 yarn_client,该 client 基于 yarn rm restful api 进行资源探查 & 工作的提交 & 状态轮询 & kill 等操作。公司内并非凋谢相应的 rest api,因而须要基于 YAOP 进行相应的革新。
2、Kernel configuration
开源 EG 往 yarn 上提交工作暂不反对指定动静参数,比方队列抉择、镜像抉择等等 yarn 参数。咱们进行了简略的革新,能够反对用户设置更为丰盛的 yarn 参数,来定制个性化执行环境。
3、Async
开源社区的版本没有齐全异步化,为了单 eg server 反对更多的 kernel,咱们做了齐全的异步化革新。优化前,只能反对 10+ kernel,优化后,可能反对 100+ kernel(下限没具体测试过)。
4、image
反对用户抉择自定义镜像启动 kenrel,该个性反对用户在 kernel 中装置本人须要的环境,极大地提高了 kernel 应用的场景。
定时调度
调度原理
Notebook 调度执行不同于每个 cell 里的人工调试执行,它须要定时主动执行,每次都是间接 run all cell,并且把执行后果保留在 notebook 里。
Jupyter 提供了能够间接执行一个 ipynb 文件的工具:nbconvert。nbconvert 会依据 ipynb 里的 kernel 信息启动对应的 kernel 来执行 ipynb 里的每个 cell,其本质上执行了 notebook kernel 启动 + run each cell 的性能。
然而 nbconvert 只能启动 local kernel,而目前零碎是 remote kernel on yarn,这能够通过把 nbconvert 提交到 yarn 上,而后在 yarn 上运行上述过程。当然,这其中波及到了 pyspark 工作的提交原理,总的来说,notebook 工作具备和 dorado 上其余工作一样的定时调度性能。
更多个性
1、Version control 反对 notebook 的版本控制。
2、Workflow debug 工作流反对 notebook 工作,并且反对整体调试。
3、Parameterized 反对 notebook 参数化。
4、Executed notebook view 反对定时调度运行后果展现。
结束语
Jupyter Notebook 诞生至今,已数年无余,期间一直呈现 Zeppelin、PolyNote、Deepnote。尽管如此,Jupyter Notebook 依然领有最大量的用户群体与比拟残缺的技术生态,因而咱们抉择了 Jupyter Notebook 做深度定制与革新来服务用户。
以后 火山引擎 DataLeap Notebook 曾经根本具备了离线数据摸索的能力,这些能力曾经帮忙了很多用户更好的进行数据摸索、工作开发调试、可视化等。随着平台对流式数据开发的反对,咱们也心愿借助 Notebook 实现用户对流式数据的摸索、流式工作的调试、可视化等性能的需要。置信不久的未来,Notebook 可能实现流批一体化,来服务更加宽泛的用户群体。
点击跳转大数据研发治理套件 DataLeap 理解更多