夜天之书 #105 开源孪生:商业开源的模式实践

ops/2025/1/18 16:13:26/

最近一个多月没有发布新的文章,我把时间大多投入在实践验证自己在多次演讲中都描绘过的开源孪生模式上。

1fd2b3b1fad99348d7423139569667ea.png


开源孪生模式

本文展开介绍上图提及的各个具体实践,并说明这一模式如何可持续发展。

商业软件无需开源

《大教堂与集市》一书收录了 Eric Raymond 的五篇文章,其中一篇名为《魔法锅》,这可能是关于如何可持续运行开源项目最早的讨论。自然,其中也包括如何在运行开源项目的同时获得商业回报。

尽管《魔法锅》的重点在讨论哪些软件适合开源,并极力澄清大部分关于软件开源的迷思都是不真实的,但是其中也明确提到了软件闭源的合理理由。

4.6 节“闭源的理由”中提到:

如果你希望闭源,唯一合理的原因是你想把这个软件卖给别人,或者防止竞争者使用它。

4.8 节“为什么销售价值问题多多”中提到:

源码开发使得从软件中直接获取销售价值变得更加困难。

软件服务公司通过私有化部署或云服务提供的商业软件,显然属于企业希望获得软件的销售价值,想要把该软件服务卖给客户,并防止竞争者无偿使用乃至修改后重新发布的情况。

我在过往的文章中多次举例说明尝试直接将开源软件作为商业软件或服务销售,最终必然走向改变协议以至少禁止竞争者使用的结局。在当前鼓励创新,并经由允许创新者独占知识产权给予反馈的商业环境下,这是不可避免的。

因此,在文章开头的模型中,商业软件没有开源(Private and Proprietary)。相反,我们从商业软件中获取回报,支持企业研发团队的运行,并鼓励软件工程师通过多种方式回馈开源共同体。

回馈商业软件中的开源依赖

ScopeDB[1] 是我当前正在开发的商业数据库。它直接依赖了超过 100 个开源软件库,加上间接依赖,这个数字超过 600 个。从源代码行数来说,ScopeDB 自身的代码量绝对不超过总源代码量的一半,甚至 10% 都不一定能达到。

由于 ScopeDB 的核心设计之一是用对象存储(OSS)代替本地盘,以及关系数据库服务(RDS)作为元数据存储,因此 ScopeDB 的核心依赖包括了统一对象存储的数据访问层 Apache OpenDAL[2] 库,以及查询 RDS 所需的 SQLx[3] 和 SeaQuery[4] 库。另外,为了高效处理特定数据类型,ScopeDB 的核心数据模型依赖了 Jiff[5] 库提供的 Timestamp 和 SignedDuration 抽象,以及 ordered-float[6] 库提供的全序浮点数实现。

一般来说,刚开始将一个开源库对接到商业软件的时候,总会遇到一些适配问题,这就是商业软件研发团队回馈上游最直接的动机。

例如,当我们从提供 /metrics 接口以让可观测平台通过拉的方式获取指标,转向主动推送指标到可观测平台时,我们就为 OpenDAL 实现了一个主动推送指标的中间件。

  • feat(layer/otelmetrics): add OtelMetricsLayer[7]

我们还向 OpenDAL 贡献了内部用于评估部署环境的性能测试工具:

  • feat(bin/oli): implement oli bench[8]

为了更好的集成 Jiff 和 ordered-float 提供的数据类型,我们需要对上游数据类型做相关拓展。对于通用的改进,我们总是会回馈到上游:

  • feat: optionally integrate with num-cmp[9]

  • feat: integrate with derive-visitor[10]

  • feat: implement Hash trait for structs that has implemented PartialEq[11]

数据在内存传输时,我们借用了 Apache Arrow[12] 的 Array 抽象,因此在集成的过程中也向上游提出了一些改进:

  • Support cast between Durations + between Durations all numeric types[13]

  • feat: add write_bytes for GenericBinaryBuilder[14]

而对于特化的需求,在上游不接受或短时间内不考虑的情况下,我们会通过包装类型绕过,或者 fork 后打上特定的补丁。同时也会在上游分享相关代码,以期后续找到并入上游的方式,或者至少惠及其他有相同特化需求的开发者:

  • feat: bootstrap jiff-sqlx development[15]

  • feat: add Expr::column[16]

  • Return an EnumConverter for GsonConverterFactory#stringConverter[17]

除了提供直接的代码补丁,作为多个开源项目的维护者,我深知仅仅是用户反馈,也足以让维护者感受到鼓励,并知道有人正在以特定方式使用自己的软件。因此,在遇到上游库缺少的功能时,即使在 ScopeDB 内部我们已经通过其他方式绕过了需求,我也会在上游报告使用案例:

  • Support Postgres Interval type[18]

  • Add Keyword::Default for SimpleExpr[19]

或者,虽然上游库提供了相关功能,但是文档不全以至于我花了很长时间才找到解决方案,我也会在上游库的文档中补充相关内容:

  • sqlx::Type for Enum with String repr[20]

  • docs: base url relative join[21]

  • Deep copy a VectorSchemaRoot?[22]

  • How to construct array of list of list and array of list of struct?[23]

  • RustlsConfig to be reloadable[24]

  • Type for an Acceptor that maybe TLS or not[25]

  • Transform errors when extract parameters[26]

即使没有遇到任何问题,如果上游库的实现非常好,我也会向作者分享我们在 ScopeDB 中是如何使用他们的库的:

  • Feedback to upgrade jiff to 0.1.16[27]

  • Showcase how I use this crate to crate a mapping between (secured) business object and serializable dto[28]

当然,回馈上游并不是单向的。很多时候,上游的维护者更清楚如何解决具体问题,而作为下游的用户,帮助在实际场景中验证解决方案的可行性,也是对上游维护者的重要帮助。

例如,我们在使用 TestContainers 时遇到了需要复用容器的场景,而上游库并没有提供相关功能。由于对 TestContainers 并不熟悉,我们选择直接依赖底层库实现复用容器的功能,并将相关代码分享到 TestConatiners 上游提出需求。上游社群收到需求反馈后,不久就做出了相应的原型,而我们也基于孪生的开源项目帮助上游测试原型的可行性:

  • Reuse containers[29]

作为 Jiff 库的早期用户,我们也向 Jiff 上游提出了不少功能需求:

  • Allow configure nanosecond's precision[30]

  • SignedDuration's Display should use upper case letter[31]

通常,完成开源库的集成后,回馈上游的机会也就变少了,除非出现新的需求,或者我们的部分核心功能涵盖了上游的主要演进方向。在后一种情况下,我们会成为上游的主动参与方,或者直接成为上游的维护者。

商业软件衍生出开源公共库

除了秉承拿来主义开箱即用的开源库,我们在开发 ScopeDB 的过程中也遇到一些通用的需求,这些需求没有现成的开源软件库可以直接使用。在这种情况下,我们会将 ScopeDB 内部的实现抽象出来,形成一个新的开源库。

Fastrace[32] 起源自我们团队成员在参与开发 TiKV 期间做的一个 tracing 库。几经辗转,这个库从 TiKV 的组织中独立出来,并成为 ScopeDB 自身可观测性的基石之一。目前,我们团队的成员积极维护 Fastrace 库。

Logforth[33] 起源自开发 ScopeDB 时自身打日志的需求。我们最初使用了 fern 来完成这个功能,但很快发现 fern 一些不合理的设计,没有处理的历史包袱,以及当时超过一年没有维护的情况。因此,我们快速实现了一个满足 ScopeDB 需求且可以方便扩展的日志库,并将其开源

为了支持数据库系统内部多种定时任务的需求,我们开发了 Fastimer[34] 库来支持不同的定时任务模式。为了实现数据库用户层面的定时任务,我们还开发了 Cronexpr[35] 库以支持用户在 CREATE TASK 语句中使用 cron 表达式指定任务触发规则。

在这之外,ScopeDB 的 SDK[36] 也是开源的。显而易见,将 SDK 闭源没有任何好处,因为 SDK 本身并不提供商业价值,而是用于支持 ScopeDB 的用户开发应用程序。这跟 Snowflake 开源其各种语言和版本的 SDK 库,以及 GitHub 虽然没有开源其 Server 实现,但也开源了一系列 SDK 和命令行工具的做法是一致的。

一个开源孪生的系统

最后,对于系统工程中耦合在各个实现的部分,我们选择开源一个与 ScopeDB 工程设计大体相同的消息队列系统,来分享我们使用 Rust 语言实现复杂分布式系统的经验。

  • Morax[37]

如前文所述,在验证 TestContainers 的容器复用功能时,我们的最终目的是让 ScopeDB 工程能够用上。但是,ScopeDB 是闭源的商业软件,我们无法直接让上游开发者访问 ScopeDB 的源码测试验证。此时,Morax 作为孪生的开源系统,就能很好地向上游开发者提供一个源码开放的复现环境:

  • test: try to use testcontainers with reuse[38]

总结

本文介绍的所有开源贡献之所以能够持续下去,其根本原因是企业研发团队从商业软件中获得了回报。

为了支持商业软件的发展,回馈其中的开源依赖是理所当然的。对于商业软件中的通用需求,开源这些衍生出来的公共库,既是对开源共同体的回馈,也有助于公共库收获更多反馈,进而提升商业软件的稳定性和可靠性。

开放源代码是软件工程师之间交流分享的最好方式。在开源孪生的模式下,商业软件的开发者既有物质保障,又有动力回馈开源社群。尤其数据处理系统的开发和落地,从来不是一个团队、一家公司就能包揽所有内容。我们会一直积极回馈上游,开源公共库,分享工程实践,以期在交流中共同进步,打造一个高质量的数据处理生态,服务用户不断增长的需求。

参考资料

[1] 

ScopeDB: https://www.scopedb.io/

[2] 

Apache OpenDAL: https://github.com/apache/opendal

[3] 

SQLx: https://github.com/launchbadge/sqlx

[4] 

SeaQuery: https://github.com/SeaQL/sea-query

[5] 

Jiff: https://github.com/BurntSushi/jiff

[6] 

ordered-float: https://github.com/reem/rust-ordered-float

[7] 

feat(layer/otelmetrics): add OtelMetricsLayer: https://github.com/apache/opendal/pull/5524

[8] 

feat(bin/oli): implement oli bench: https://github.com/apache/opendal/pull/5443

[9] 

feat: optionally integrate with num-cmp: https://github.com/reem/rust-ordered-float/pull/155

[10] 

feat: integrate with derive-visitor: https://github.com/reem/rust-ordered-float/pull/161

[11] 

feat: implement Hash trait for structs that has implemented PartialEq: https://github.com/BurntSushi/jiff/pull/143

[12] 

Apache Arrow: https://github.com/apache/arrow-rs

[13] 

Support cast between Durations + between Durations all numeric types: https://github.com/apache/arrow-rs/pull/6452

[14] 

feat: add write_bytes for GenericBinaryBuilder: https://github.com/apache/arrow-rs/pull/6652

[15] 

feat: bootstrap jiff-sqlx development: https://github.com/BurntSushi/jiff/pull/141

[16] 

feat: add Expr::column: https://github.com/SeaQL/sea-query/pull/852

[17] 

Return an EnumConverter for GsonConverterFactory#stringConverter: https://github.com/square/retrofit/issues/4278

[18] 

Support Postgres Interval type: https://github.com/SeaQL/sea-query/issues/855

[19] 

Add Keyword::Default for SimpleExpr: https://github.com/SeaQL/sea-query/issues/850

[20] 

sqlx::Type for Enum with String repr: https://github.com/launchbadge/sqlx/issues/3630

[21] 

docs: base url relative join: https://github.com/servo/rust-url/pull/1013

[22] 

Deep copy a VectorSchemaRoot?: https://github.com/apache/arrow-java/issues/465

[23] 

How to construct array of list of list and array of list of struct?: https://github.com/apache/arrow-rs/discussions/6631

[24] 

RustlsConfig to be reloadable: https://github.com/poem-web/poem/issues/893

[25] 

Type for an Acceptor that maybe TLS or not: https://github.com/poem-web/poem/issues/872

[26] 

Transform errors when extract parameters: https://github.com/poem-web/poem/issues/814

[27] 

Feedback to upgrade jiff to 0.1.16: https://github.com/BurntSushi/jiff/discussions/174

[28] 

Showcase how I use this crate to crate a mapping between (secured) business object and serializable dto: https://github.com/Artem-Romanenia/o2o/issues/21

[29] 

Reuse containers: https://github.com/testcontainers/testcontainers-rs/issues/742

[30] 

Allow configure nanosecond's precision: https://github.com/BurntSushi/jiff/issues/92

[31] 

SignedDuration's Display should use upper case letter: https://github.com/BurntSushi/jiff/issues/190

[32] 

Fastrace: https://github.com/fast/fastrace

[33] 

Logforth: https://github.com/fast/logforth

[34] 

Fastimer: https://github.com/fast/fastimer

[35] 

Cronexpr: https://github.com/cratesland/cronexpr

[36] 

ScopeDB 的 SDK: https://github.com/scopedb/scopedb-sdk

[37] 

Morax: https://github.com/tisonkun/morax

[38] 

test: try to use testcontainers with reuse: https://github.com/tisonkun/morax/pull/19


http://www.ppmy.cn/ops/151134.html

相关文章

C++ 搭建一个双向多线程的GRPC通信服务框架

文章目录 功能点服务端客户端服务端线程客户端线程心跳机制服务创建总结 功能点 双向通信:即指程序既有客服端又有服务端,以处理复杂的需求客户端信息线程处理:程序客户端发出某个请求后,应开辟其他线程处理,防止等待…

MarsCode青训营打卡Day1(2025年1月14日)|稀土掘金-16.最大矩形面积问题

资源引用: 最大矩形面积问题 - MarsCode 打卡小记录: 今天是开营第一天,和小伙伴们组成了8人的团队,在接下来的数十天里相互监督,打卡刷题! 稀土掘金-16.最大矩形面积问题(16.最大矩形面积问题…

jenkins-node节点配置

一.简述: Jenkins有一个很强大的功能: 即:支持分布式构建(jenkins配置中叫节点(node),也被称为slave)。分布式构建通常是用来吸收额外的负载。通过动态添加额外的机器应对构建作业中的高峰期,或在特定操作系统或环境运行特定的构建…

【PromptCoder + v0.dev】:前端开发的智能加速器

【PromptCoder v0.dev】:前端开发的智能加速器 在当今快节奏的软件开发环境中,前端开发者面临着将设计稿快速转化为可运行代码的巨大挑战。每一个像素的完美呈现都需要精确的代码编写,这不仅耗时,而且容易出错。幸运的是&#x…

《探秘火焰目标检测开源模型:智能防火的科技利刃》

一、引言 火灾,如同隐藏在暗处的恶魔,时刻威胁着人们的生命财产安全与社会的稳定发展。无论是澳大利亚那场肆虐数月、烧毁约1860万公顷土地、造成近30亿只动物死亡或流离失所的森林大火,还是美国加州频繁爆发、迫使大量居民撤离家园、带来巨额…

浅谈云计算16 | 存储虚拟化技术

存储虚拟化技术 一、块级存储虚拟化基础2.1 LUN 解析2.1.1 LUN 概念阐释2.1.2 LUN 功能特性 2.2 Thick LUN与Thin LUN2.2.1 Thick LUN特性剖析2.2.2 Thin LUN特性剖析 三、块级存储虚拟化技术实现3.1 基于主机的实现方式3.1.1 原理阐述3.1.2 优缺点评估 3.2 基于存储设备的实现…

【JavaEE】Spring Web MVC

目录 一、Spring Web MVC简介 1.1 MVC简介1.2 Spring MVC1.3 RequestMapping注解1.3.1 使用1.3.2 RequestMapping的请求设置 1.3.2.1 方法11.3.2.2 方法2 二、Postman介绍 2.1 创建请求2.2 界面如下:2.3 传参介绍 一、Spring Web MVC简介 官方文档介绍&#xff…

Redis超详细入门教程(基础篇)

目录 一、什么是Redis 二、安装Redis 1、Windows系统安装 2、Linux系统安装 三、Redis通用命令 四、Redis基本命令 五、五种数据结构类型 5.1、String类型 5.2、List集合类型 5.3、Set集合类型 5.4、Hash集合类型 5.5、Zset有序集合类型 六、总结 一、什么是Redi…