本文作者:Klesh Wong
GitHub 地址:https://github.com/klesh
引述
自去年 7 月份 DevLake kicked off 以来,我的项目始终处于被各种 demo / event 追赶的状态,为此,咱们放弃了很多,比方代码格调,单元测试,代码正文,文档的保护,issue 的保护,用户的敌对性等。
当然,也不是说齐全一点没有,只是在大面上,我的项目总体呈一种横蛮成长的状态。大家对我的项目的冀望也始终是能“跑起来就行”,“主流程失常就 OK”,至于一些稳定性,对异常情况的忍受度,嗯,大抵只用重启大法解决。
这阐明了,我的项目整体代码品质存在着根底不稳的状况,须要夯实,架构上也须要演进,帮忙插件最大水平地解决一些共性的问题。
没有一种架构,或者具体到设计,是万能的,能够解决所有的问题。不基于理论状况,凭空去猜测,再做出设计,其后果容易适度优化,甚至更糟。比方,因为事实与现实差距过大,导致整个设计要推倒重来。从这个角度来看,横蛮成长也并不齐全是一件好事,晚期咱们有很多要怎么怎么做,解决哪些哪些场景的探讨,怎么满足用户画像一的前提下,同时兼顾画像二的情景等等。
要思考的状况太多了,要不要队列?要不要多级队列?工作编排怎么做?要不要做 DAG?后果是谁也压服不了谁,因为 A 提出的计划,随即就会有 B 提出一种或多种状况是该计划无奈解决的,而大家又不能确定 A 计划要解决的问题到底有多重大,或者 B 提出的状况会不会呈现?
侥幸的是,咱们最初放弃了各种高大上的架构,抉择在框架级只做了一个简略的工作执行逻辑,把具体的解决逻辑都交由插件自行解决,自由发挥。
通过半年多的倒退,咱们缓缓地发现了一些共通的模式,最终在 3-4 月份实现大暴发。
咱们发现了什么?
1. 数据流的模式
所有的数据源插件,都须要从 api 读取数据 (collection),而后提取 (extraction),再转换(conversion) 到对立的实体层(Domain Layer Data,比方 jira 的 issue 和 github 的 issue 得对立到 issue 这张表外面),在此之前可能还会有一些保护转换的工作要做,比方基于 jira issue changelog 的信息,归纳出 issue 在 sprints 之间的停留时间之类的,咱们称之为 enrichment。基本上,这几个类型的工作就能涵盖大多数数据源集成的须要。
在这一条数据流动的门路中,咱们发现,最大的瓶颈和难点在于 collection 这一步,包含但不限于:
- api 会有 rate limit,比方每秒 10 个,每小时 2000 个之类
- 在 rate limit 的限度下,心愿尽量快地拉取数据,并发是必须的
- 在并发的状况下,必须反对随时能够勾销正在运行的工作
- 在遇到可复原的谬误,须要自动化地重试,超限后变成不可复原的谬误
- 对于不可复原的谬误,须要记录出错的信息,以便 debug
这下面的每一个问题,独自解决绝对简略,但要全副顾及,复杂度间接原地爆炸。
如果每个插件都要本人实现一次的话,工作量极大还不好保护,出问题了要一个个地修,十分麻烦。
尽管具体的 api 是不同的,但下面这些模式却是相通的,咱们就思考,是不是能够由框架提供某种辅助类来对立地解决。
同理,extraction/conversion 一样有不少共性的操作,能够由框架来提供相应的辅助类,使得插件开发的工作量极大放大。这便是咱们的重头戏之一 提供一系列 subtask helpers 来加重插件的开发和保护的累赘。
从 api 获取数据(collection),还存在着另一个问题,就是拉取数据所破费的工夫与数据源的规模成正比,这个过程往往是整个数据流程中的耗时最长的,因为不同于其它的工作只依赖于本身数据库,它依赖的是内部数据源,除了规模,还要要受网络延时的影响,还有 api 的速率限度等因素的影响。
按原有的设计,数据的拉取和提取 (collection/extraction) 是在一个工作外面同时进行的,一步到位存储到数据库。这样的实现尽管简略,但有一个很大的弊病,就是 api 返回的数据,有很大一部分被间接抛弃了,当后续的工作须要重跑时,就须要从新去 api 拉取。
举个例子,JIRA 插件中的 Issue 实体中,咱们须要提取它是属于哪个 Epic 的,而在 JIRA API 中,Epic 这个属于是通过 自定义字段的性能实现的,它在 Issue 构造体中的属性名大略是 customfield_1234
这样,其中 1234
在每个 JIRA 的实例都不一样,这就须要用户进行配置,咱们能力晓得取哪个属性作为 Epic 信息,而一旦用户配错了,就得重跑整个流程,奇慢无比。
为此,咱们又捡起之前曾一度被摈弃的 Raw Data Layer,它是一个 api 返回后果的缓存层,负责存储原汁原味的 api response json。有了这一层缓存,咱们只需跑一下 extraction 前面的工作即可达到数据修改的目标,防止 collection 带来的微小开销。
2. 用户对于数据定制的须要
其实,反对用户对 DevLake 进行定制化,始终是咱们的方针。
晚期咱们更关注的是对于图表的定制化,数据层面的定制性根本没有。
具体地讲,用户可能须要基于咱们现有的表,汇总出一些新的表,或者咱们预置的表构造不够不便,他想要本人转化出一些宽表。又或者,他想导入本人的一些表,而后与咱们的表进行整合?尽管,原则上,这些事都能够通过写插件的形式来实现,但思考到咱们的用户群体可能有不少是是数据分析师,让他们去写 golang 插件还是有肯定门槛。
因而,咱们开发了 dbt 插件,用户能够基于 sql 编写本人的转换逻辑,可编入 DevLake 的 pipeline 承受对立调度,简略地说就是用户能够在原有的工作执行流程中插入他须要的 dbt 工作。
3. 大用户的吞吐
直到 v0.9,DevLake 都只是单机模式,也就是说,所有的工作都只能在一台机器下面执行。这对大部分的用户来讲,也就够用了。但体量大的用户也是存在的,当用户须要同步的数据太多,散布太广的时候,单机的吞吐量是个极大的限度。
为了解决这个问题,咱们反对了 基于 temporal 的分布式执行 模式。整个零碎拆成 server 和 worker 两局部,server 接受任务申请,具体的工作由 worker 执行。这听起来是个简略的事件,然而却对咱们架构提出了重大挑战,起因是在单机模式下,咱们所有的子工作,都间接依赖了一些全局资源,比如说,数据库,日志这些。兴许你会认为,这没啥大不了的吧,全局就全局呗,只有在 worker 启动之前把这些全局变量初始化好不就行了吗?是的,但那远远称不上是一个正当的设计,更加不是一个优雅的模式。
假如,要是这个数据库连贯变了呢?难道得把所有的 server / workers 的配置都手动改一下,而后重启吗?还有太多其它的问题,就不一一提了,都是些分布式系统要思考根本问题。
所以,最正当的做法,是把这些子工作全副进行解耦,所有依赖的资源,在执行的时候再行传入。这样的做法也带来了另外一个益处,后续咱们做 unit-test 或者 e2e-test 也容易实现。
4. 降级居然要 drop database?
是的,在 v0.9 及之前的版本,所有的的降级,须要把原来的数据库给干掉,能力确保零碎能失常运行。
虽说 DevLake 自身没有什么业务数据,只有从新同步一下,总能失去雷同的后果。但操作起来存在两点问题:一是顺当,二是消耗工夫。
DevLake 反对数据库迁徙的难点在于,不同于个别的商业系统,在降级的时候由专门的人员针对特定数据库跑一下 sql 脚本就行。咱们心愿咱们的数据迁徙是全自动化的,智能的,还同时能兼容各种关系型数据库。在通过长期的调研和各种 battle,咱们基于 gorm 的 Migrator 设计了一套适 DevLake 的 db migration 零碎。不须要用户干涉,间接换镜像版本,启动后主动降级。
5. 插件接口的拆分
以前,咱们的插件只须要实现惟一的一个接口 Plugin
,这个接口定义了所有插件可能会提供的性能,比如说插件个别会须要跑工作,对外裸露 api,在初始化的时候做一些事件,或者申明本人的 db migration scripts 等等。
缓缓地,咱们发现,这样很不迷信:如果一个插件它没有 api,也一样要实现这个办法,否则它就不会被零碎认可。那么,接口拆分就是牵强附会的事件。
于是乎,秉承着一不做二不休的理念,咱们把它按性能拆分到最小粒度,别离是 PluginMeta/PluginInit/PluginTask/PluginApi/PluginDbMigration
等接口,而惟一必须要实现的接口是 PluginMeta
,其它的,则是依据插件本身能提供的,或者须要的性能进行选择性实现。
6. 其它
除此当前,还有进度明细的优化,日志的优化,canceling 优化等等,点太多了,也有很多细节因为工夫关系来不及一一笼罩。
总结与瞻望
通过这次重构,我总结了几点教训:
- 对于不确定性太高的零碎,不要焦急进行设计,过多的探讨也没有意义,不如有一个简陋的框架先跑起来再说
2. 站在用户角度去思考,从具体的场景登程,解决理论的问题比什么都重要
3. 优先思考绝大多数用户的须要,但也不能放弃少数派的用户,因为他们的价值很高
4. 有时候现成的轮子,不肯定有本人造的好用,要依据本人的场景认真去剖析,联合工作量进行考量
这两个月以来,咱们实现的事件,在我看来,是平时半年都未必做得完的。能达到这个水平,咱们放弃了很多,比如说流程上,简化了,测试全靠手工,文档凑合着写,等等。最重要的是,整体小伙伴毫无牢骚地全力付出!很感激整体 DevLake 成员的贡献,你们是我遇到过所有战友中最棒的。
当初 DevLake 还是有很多有余,从技术上看,短少测试 / 欠缺的文档是个硬伤; 从用户体验上来看,不够晦涩。
而当初,咱们也正式进入了 Apache Incubator,心愿在不远的未来,咱们能很好地解决这些问题,更进一步,承受更大的挑战!
对于咱们:
DevLake
是一款开源的研发数据平台 : 致力于通过收集,剖析,可视化研发过程中产生的数据, 帮忙开发者更好地理解开发过程, 开掘要害瓶颈与提效机会。目前 DevLake 已进入 Apache 孵化器。
欢送大家扫码退出咱们的 DevLake 用户群
此刻你我的间隔就是“它”
理解更多最新动静
官网:https://devlake.incubator.apa…
GitHub:https://github.com/apache/inc…