🔗活动回放:https://www.bilibili.com/video/BV1wg4y1x79a/?share_source=copy_web
久等啦!上上个周六,我们在“Rust语言中文社区”举办了Dozer 国内首次的技术分享直播(🔎活动详情)。很多参与的伙伴在问答环节互动很积极,提出了许多蛮有意思的问题,欢迎大家来了解~
🔝点击链接即可观看直播完整回放🔝
为了方便阅读,也已将大家的问答提取出来整理成了文字,如下:
Round1
Q: 你好,我第一次了解到这个项目。这个我理解是一个低代码的 ETL 的项目吗?
A:对,我们的目标是做一个低代码平台,当前的主要目标用户应该是全栈工程师。就是希望去同时做出前端和后端的工程师。
Q: 那像数据转换这一块,跟 ETL 有点像。但是我又看到有能够暴露的一些 API,endpoint 这些 。
A: 是,所以就像我们说的,其实我们是一个很 comprehensive 的一个工具,就是它会包含你在构建一个全栈 APP 的时候用到的方方面面的功能。您说的 ETL 我理解更多的是在 transform 这个 component 里面。我不知道理解的对不对啊,然后但是你会看到我们同时还会去做cache,做索引,这个地方又有一点像 MongoDB 或者 elastic search,然后我们又会去提供 API,这个地方又可能又代替了一些 node, js 或者 Python 的工作。所以其实我们的目标是打造一个这样的低代码的平台,能让你很容易的把数据源的数据直接暴露给用户,而不是像以前去思考我到底每一步需要去用哪些工具,然后这些工具之间应该如何的整合。我们希望能 cover 那些最常见的使用场景。
Q: 这个相当于是可以自己部署一个版本然后去用,是吧?
A: 对的, Dozer 的话其实就是一个 binary,现在还依赖 openssl。但是当我们替换到 rusttls 的时候,可能就完全是一个 binary,在任何地方都可以运行。然后部署得话,当然我们也提供 Docker 的container,也可以去用那种 container 的方式去部署。
Round2
Q: 你们那个 query 我看应该是你们自己的语法,为什么不直接用像 graphql 这种方式呢?它其实也很强大,然后语法其实也大家都比较熟了。
A: 嗯,是。query 的前端语法,其实你也可以想象没有那么的重要,对吧?我们可以随时换一个前端语法,只要表达的东西都是一样的。 Graphql query 语法对我们来说暂时还太复杂了,它有点过于强大。当然熟悉 Graphql 的人就会很容易地去用。那如果完全的是新手的话,我们希望给他提供一些更直观简单的语法。当然以后如果我们能覆盖 Graphql 大部分的功能的时候,我们可以考虑做一个 Graphql 的 query 前端,其实整个后端是不用改的。
Round3
Q: 我想问一下它主要的功能是不是这样?以前我们是要把一些查询的业务逻辑要放到一个,比如说 node JS 的服务器上,然后现在我们可能就前端直接连 Dozer,然后 Dozer 连接数据库,然后把数据库通过一些 JSON query 的查询方式暴露给前端,然后把那个查询直接放到客户端上去?
A: 是的是这样的。其实我们之前写前端应该也是经常会有这一步,对吧?这一步可能用 node 或者用 Java 写的去暴露一个 API 给前端代码。那这个 API 其实有时候就很简单,就是增删改查,但是你又不能不写,因为没有办法直接去连 Redis。前端没有办法直接去连Redis,这个还是很痛苦的。所以我们这里也是,就是像您说的一样,我们暴露这样一个 query 的接口,这样的话对前端的小伙伴来说应该会更容易,后端所有的处理都由 Dozer 来做了,只需要专注于怎么写前端逻辑就可以了。
Round4
Q: 我感觉 Dozer 是把后端这一层做了一个相对通用的处理。这样的话,在我们实际的业务中的话,这些业务场景上的一些逻辑是不是应该放到前端去?
A: 对啊,您说的这个问题其实是一个很重要的问题。如果说业务逻辑能用 SQL 表达的话,那其实用 SQL 用 Dozer 就可以解决了。但是我们其实可能是有一些复杂的业务逻辑,未必能用 SQL 表达,这个时候 Dozer 希望起到的作用是沟通这些后端的微服务跟前端,而不是完全地替代后端。比如说你有一个订单系统或者商城系统,它有相当复杂的逻辑,那你不可能通过写 1000 行 SQL 的方式来去表达这个东西。
因为我们现在采用微服务架构也很普遍了,我们的订单数据可能是在一个服务上面,然后我们的用户数据可能又是在另一个服务上面,然后商品的评论数据又在另外一个服务上面。那其实前端页面需要把这些东西综合的展示出来,那这个时候我们运用 Dozer 的话就会比较方便,就是你有一些后端逻辑,但是当你需要跟前端沟通的时候,你通过 Dozer 去沟通,就不需要做架构图的对比。
Round 5
Q:
支持增删改查吗?
A: 首先,增删的话,我们刚刚应该提到我们有一个connector,叫做 grpc 的 connector。这个 connector 其实就是允许你去直接向 Dozer 注入数据的,而不是让 Dozer 去连接到一个数据库。这个 connector 其实就暴露了另外一组 API,基本上就是你说我要增删改这些数据。当然这个就需要你去写代码了,因为这个 connector 本身是不做任何事情的,它就是一个服务器,它就是听一个端口,然后你需要去写代码向 Dozer 发送数据,才能去驱动整个 Dozer pipeline 的执行。所以是支持增删改查的,但是需要写代码,就不是低代码的。
Round 6
Q: 刚才提到的一个复杂的逻辑可能需要连接微服务,那 Dozer 这边是怎么去连接微服务呢?
A: 微服务有若干种方式,首先肯定都是通过 connector,这是必然的,因为这是我们的抽象。如果你的数据都已经被写入到数据库了,比如说写到 Kafka 里面了,或者写到 postgres 里面了,那就直接用那个对应的 connector 就好。
如果说并没有写入这些已经被支持的 connector,你有两种选择,第一个是实现一个新的连接到一个新的数据库的 connector,这个时候就 OK 了,你的微服务往那个数据库写数据,然后 Dozer 从那个数据库拿数据。
然后另一个选择是直接使用 Dozer 的 API connector,你的微服务有新的数据出来了,假如说它就是一个无状态的函数,有新的数据出来就直接往 Dozer 发,然后 Dozer 会帮你去把它 cache 下来,让前端去查询。
Round 7
Q: 那个 DAG 里面每个 node 的数据是需要缓存吗?
A: 这个是实现细节。 DAG 里面的 node,您指的应该是 processor 对吧?因为 source 的话其实没有数据, source 就是一个源数据库,然后往 pipeline 发数据的 node。 processor node 的数据是需要缓存的,因为 processor 内部可能会有状态,比如说我们希望去计算平均值。 那因为 Dozer 的 pipeline 是一个流式的计算, pipeline 在任何一个时刻都有可能会来一个新的insert, update 或者 delete,那这个时候我们就势必要维护一个状态。就是说我们这个 processor 必须要知道当前总的 record 的量是多少,然后当前的总和是多少,这样来了一个 delete 的话就减一个 record,然后同时把这个对应的值减掉,得到新的 average。 之后发送出去一个update,说这个 average 被 update 了。所以说是的,很多 processor 内部都有需要缓存的数据,当然不是所有的了。
Q: 那每一个 node 之间的数据传输的话是怎么传的?
A: node 和 node 之间从当前的架构上来说是通过一个接口去共享数据。每个 node 自己从实现 processor 的角度来讲是不用讲任何数据的。 processor 没有办法说把一个数据传给另一个processor,它唯一把自己数据共享出去的方式是把某个 record 给持久化到它自己的硬盘上。然后这样的话,接下来的 processor ,它的直接的子 processor 会拿到它持久化的数据的句柄,然后会通过读那个持久化的硬盘上的数据来得到前一个 processor 写入的数据。
Q: 也就是说它每个 node 都要持久化一个数据,然后的话后一个 node 去读这个持久化的数据,再做它下一步的计算。
A: 这些被持久化的数据不是在计算过程中的那些数据。在计算过程中的数据是直接通过 channel 在 thread 之间发的。也就是说上一个 processor 会发给一个channel,然后这个 channel 我们在构造这个有向无环图的时候,就是把两个 processor 连好的。这个数据是不会过硬盘的,因为假如这个数据也过硬盘就太慢了。
是那些 processor 的内部状态,就是说我处理了 100 条数据之后,OK,我有一个这样的状态,那么后面的一个 processor 有可能他只拿到一个 update 不够,比如说后面的 processor 是个 join,这个 join 他现在拿到了一个 insert,那他想知道这个新被 insert 进来的行到底跟之前的哪些行能够 join 起来,那这个时候他就会直接去查他的源 processor 里面之前曾经发送过哪些数据,然后来执行这个 join。
Q: 那这里有个问题,就是一个 node 如果有两个输入的话,它是,比如说有一个输入,有新的一个状态,比如说刚才说的插入,那他的另外一个可能就没有输入,这时候是怎么处理?
A: 对,比如说一个join,左边进来一个数据,左边进来一行,右边没有进来,那这个 processor 就会说OK 现在没有任何东西 join 成功。那就很简单了,我不往下一个 processor 发数据就好了,这个东西我就把它放在这儿,等到新的另外一行进来的时候,我再去计算能不能 join 成功。能 join 成功我再往下一个 processor 去发 insert。
Q: 这里最后一个问题,就是那个每个node,比如说刚才说的状态,还是说它刚才如果没有,就两条,它有两个入度的这么一个节点,如果等待另外一个节点的话,它保存的那个数据的,比如说他的状态他是有个时间窗口的,多长时间过期?还是说只要有这个服务一启动,所有的数据都会缓存下来?
A: 没有任何时间窗口,因为假如你考虑 join 的话,实际上只要你设了一个时间窗口就有可能会丢数据,对吧?所以说 Dozer 的原则是这样的,所有输入 Dozer 的数据我们都一定会存下来,不会让它丢。
Q:就是全量的吗?
A:对,全量的。
Round 8
Q: Dozer 是不是提供 metrics?
A:是的, Dozer 提供metrics。有两个选择,我刚刚在那个配置文件里没有展示。你可以选择两种 telemetry 的格式。第一种是 open telemetry,就是你假如你有一个 open telemetric server, Dozer 会自己向那边发送所有的数据,然后你可以去监控它。
然后第二种就是你可以用 Dozer 去监控 Dozer。就是你可以另外起一个 Dozer 的进程,然后在那个工作的 Dozer 进程里面说我要发送 Dozer 格式的 telemetry 数据,然后另一个 Dozer 就会自动地处理那些数据,计算一些指标来供你来查询。
Round 9
Q: 我看到那个 processor 这边其实是把任务直接分配到那个操作系统的线程上的。我想问的是就是性能这块是怎么考量的?包括这个并发还有异步。我看刚才说异步这边支持的会比较少,那比如说我现在有一个场景的话,是我要用这里的整套的系统,我大概能够知道它的一个承载的一个业务量是多少?性能这块可减少一些。
A: 我们现在的 profile 结果,首先 connector 速度会有限制,大部分 connect 的实现本身是异步的,只不过暴露了同步的接口。单纯的处理,然后写入 cache 这一块,我们的处理的数据量级大概在 100K record 每秒。这个是在做 partition 之前,我们现在还没有实现 partition,所以现在的整个 processor 本身是非常非常快的。如果不考虑 cache 的话, processor 可以跑到可能一个 million 每秒都可以,就是你可以想象去做 SQL 计算不是那么难的一件事情,真正的瓶颈是在写入 cache 的时候,因为需要不停地去持久化到硬盘,然后目前没有做partition,所以大概在 100K 个 record 每秒这个样子。做完 partition,你可以想象这就是一个 linear 的事情,如果你的数据能够被 partition 的很好的话,应该足够应付绝大部分的场景这个吞吐量。
这是写入这边的性能。读的那边的性能我们现在测的是 rest 跟 grpc 的响应时间都是在毫秒级的,因为读的所有的进程不会被写的进程 block。所以读就是很快速的一个查询。然后因为它是可以 scale 的,所以你不用担心单个服务器就是吞吐量太大导致 serve 不了,你可以直接按需的去起新的 instance 去 serve 读的需求。
Round 10
Q: 我看这个 storage 这边它是没有使用上什么同步锁啊这种东西来避免数据竞争。我想问一下这个是怎么实现的?
A: 您是说 cache 那边是吧?就是读写互相不锁这个事情?
Q: 对,就这个事情的本身,我想问它是怎么实现的?
A: 这个事情挺有意思的。lmdb 是一个挺传奇的数据库了,它是非常非常高效的。它的高效就是来源于两个地方,第一个是它是 memory map,就是他去读数据的时候不是从硬盘去做 io 的,而是直接从内存做 io 的,所以读是非常非常快的。然后呢,读写互相不锁这个事情的实现是这样。每个 lmdb 在同一时间只能支持一个 writer,这个 writer 本身有一个锁,然后 writer 在写数据的时候,它会首先把这个 page 标记成 dirty page。 就是它的 b tree 是组织成一个个 page 的,就是操作系统的 memory page,然后 writer 会把这个 page 标记成一个 dirty page。在有 reader 要来读的时候,如果说这个 page 是 dirty 的, writer 不会去立即去写它,它会拷贝一份,把这个 page 写成脏的。reader 这时候读的还是老的干净的数据,然后只有在 commit 的时候会一次性的替换所有的 page。所以这里面提到这个 writer 的数据只有在 commit 之后 reader 才是可见的,所以这样是这样来实现互相无锁的。
Q: 噢,那我的理解就是说这种保护或者说这种限制,它其实是在这个 lmdb 上面去做的,对吧?
A: 对的, lmdb 实现我们基于 lmdb,所以整个架构就自然是无锁的。
Q: 那现在我们目前用的是 lmdb,后面会换一些其他的数据库吗?
A: 可能会,因为 lmdb 也是挺老的一个数据库了,我们可能会换他的继任者,但是我们会保持这种特性不变,因为这是 Dozer 高效的来源,因为这样你才能支持大并发的读。
Round 11
Q:你们怎么看待现在GPT 对低代码的影响?
A:GPT can't fully replace data platforms. Even though some of the code can be generated and augment developer experience. For Dozer, we are really excited about how GPT can be used in conjunction with Dozer. We are thinking about a blog series on using GPT to generate API connectors to Dozer and deploy end to end functionality very easily.
(GPT 无法完全取代数据平台。尽管它可以生成某些代码,提升开发人员的体验。我们非常高兴探索如何将 GPT 与 Dozer 结合使用。我们正在考虑写一些博客,介绍如何使用 GPT 生成与 Dozer 相连的 API 连接器,并轻松部署端到端的功能。)
Round 12
Q:解决了什么痛点? 跟阿里巴巴的 DataX 是同类工具吗?
A:DataX and Dozer have different objectives. We are focused on "Data Consumption". We are focused on building data experiences without integrating several platforms and serving data APIs directly so customer experiences can be built faster.
(DataX 和 Dozer 的目标不同。我们专注于“Data Consumption”,旨在构建无需整合多个平台、直接提供数据 API 的数据体验,,以便能够构建更快的客户体验。)
Dozer will mainly focus on READs for fast and efficient queries. Dozer will soon introduce a write back functionality through lambda functions written in something like WASM.
(Dozer 将主要专注于 READs 操作,以实现快速和高效的查询。Dozer 很快将通过用类似 WASM 写的 Lambda 函数引入写回功能。)
- ⬇️下面是我们的 GitHub 地址,欢迎大家去 star,提 Issue,提 PR。我们有很多很有意思的,可以做新的贡献的地方。
- 如果在使用的时候遇到了任何的问题,欢迎立即去提 Issue。当然也可以加入我们 Discord,直接及时聊天。
- 如果你希望实现新的 SQL 的操作符,希望实现新的 connector,非常欢迎,我们会有专人来去 mentor 整个过程。
- 谢谢大家阅读和观看~
Contact us
- GitHub: https://github.com/getdozer/dozer
- Discord: https://discord.com/invite/3eWXBgJaEQ
- Twitter: https://twitter.com/GetDozer
- LinkedIn: https://www.linkedin.com/company/getdozer/