阅读视图

发现新文章,点击刷新页面。

开源嘉年华纪实

上周在北京参与了开源社主办的 2024 中国开源年会。其实相比于有点明显班味的“年会”,我的参会体验更像是经历了一场中国开源的年度嘉年华。这也是在会场和其他参会朋友交流时共同的体验:在开源社的 COSCon 活动上,能够最大限度地一次性见到所有在中国从事开源相关工作的人。

我在本次会场上有两个演讲,分别是 Rust 分论坛上分享的《高性能 Rust 编程:如何减少数据拷贝和并发开销》,以及开源运营分论坛介绍的《商业开源如何重塑社群信任》。本文将简要介绍我的分享内容,以及参会的见闻纪实。

开源社的午后香茗

开源社的所有成员都是志愿者。也就是说,开源社没有任何一个全职员工,所有成员都是依靠对开源的热爱自愿贡献的。我从 2022 年开始志愿参与开源社的工作和活动,从 2023 年开始参与顾问管理委员会提供建议。

今年的 COSCon 正好也是开源社成立十周年的嘉年华,在活动现场作为组织成员有幸收到了开源社准备的一盒普洱茶:

午后香茗

茶客基操

作为一个典型潮州人和饮茶爱好者,这是我本次活动最满意的收获。可以看到,从会场回来三天,每天一颗普洱茶,正好。

主题分享:高性能 Rust

第一天下午我分享的主题是《高性能 Rust 编程》。

我从今年初开始把 Rust 语言作为我的主力编程语言。这主要是我观察到我长期投入的数据系统领域,出现了一个由于云基础设施的崛起带来的创新机会,而这个创新机会的探索者们大多采用 Rust 语言编程和构建生态。于是,我也加入其中。

我的 Rust 码力值

可惜的是,截止我演讲的前一刻,我的 Rust 代码量仍然以 1% 的微弱差距落后于我早年编写 Java 大数据系统时生产的代码。

不过,在过去的一年里,我充分体会到了 Rust 生态的勃勃生机,全面投入到生态共建当中,包括:

本次分享的内容就是在这些工作当中积累的经验,每一页 PPT 都是朴实无华地贴代码讲代码(bushi)。主要介绍的两个核心论点:一个是在 Rust 所有权系统下,如何规避常见的由于绕过所有权挑战带来的拷贝开销;另一个是破除 Async Rust 的性能迷思,介绍其远不及完善的应用现状。

目前,关于 Rust 编程的最佳实践,包括工程组织、接口设计和性能优化,还处于一个激烈讨论的环境中。整个 Rust 生态的繁荣和完整程度,我经常把它比作 Java 1.5-1.7 时代,即当今统治生态的类库和软件尚未诞生或方兴未艾。应该说,Rust 生态的广阔天地,大有可为。任何想要赶上 Rust 主导的软件发展浪潮的人,现在上车还为时未晚。

主题分享:商业开源

第二天下午,我在开源运营和开发者关系论坛分享了《商业开源如何重塑社群信任》的主题。




这个话题其实不太应该出现在“开源运营和开发者关系”论坛,而是比较适合一个独立的“商业开源”论坛,即讨论在商业活动中如何利用开源要素创造优势,还有营利性组织如何做出开源贡献以及背后的原因。

我在做这个分享的时候更多像是对自己近年来的思考做一个总结,从开源项目的分类学,商业开源的方法,到商业开源和“开源商业化”的思辨。这大概会导致信息量太大而听众难以接受。

我目前在实践自己分享里提到的商业开源理念,也颇感有趣的看到我对“开源商业化”的批判接连被印证。或许以后我再讲这个话题的时候,就不会像这次一样做一个总纲式的分享,而是抽取一两个典型的案例和瞬间来传播。

承接上一个主题讲的这一张 PPT 实际不在演讲的 PPT 里🤣

开源社的下一个十年

这个话题其实我讲不了(笑)。只是在这里介绍一下我所认识的开源社,它是做什么的以及未来可能会做什么。

近年来,开源社除了一年一度举办 COSCon 以外,还联合其他社群伙伴推出了《中国开源年度报告》中国开源先锋中国开源码力榜等报告和评选活动,丰富了开源社作为一个公益团体在开源主题上传递出的观点。

但是,相比起开放源代码促进会(OSI)TODO Group 这样,在通用开源和企业开源领域能够提出见解和倡议的组织,开源社在掌握开源话语权这个议题上显得比较弱势。随着中国开源力量的崛起,如何引导各方参与者正确认识开源,在开源环境当中高效协作创新,将是一个无可回避的问题。我热切期望开源社能够发挥自己所在生态位的优势,联合开源社的志愿者,向社会不断输出批判性观点,帮助中国开源茁壮成长。

此外,开源社从去年开始,成立了一系列开源社城市社区(KCC),包括北京、上海、广州、深圳、成都、杭州、长沙、大连、南京和新加坡等。在一年一度的开源嘉年华之外,日常组织起中华开源的 Meetup 活动,日积月累的提高开源理念在群众当中的影响力。如果任何个人或组织想要在本地聚集开源人办活动,都可以考虑联系 KCC 合作落地。

开源嘉年华:交个朋友

最后,我想以“交个朋友”作为本次开源嘉年华参与体验的总结。

这一次 COSCon 的绝大部分分论坛,都是由开源社和一个或多个合作社群共同举办的,所以才能攒成 21 个论坛的盛况。

在主会场上,我能看到开源公益的从业者分享他们的故事,看到青少年开源开发者介绍自己的作品,看到开源人展现自己多彩的生活。

开源从来都是一个广阔的概念,秉持着开放和合作的基本理念在不同的细分领域创新价值。但是,开源也只是每个开源人生活当中的一部分。本届开源嘉年华的主题“开源新生活(Open Source, Open Life)”,传递出的是开源社一直以来的人文关怀。

希望所有参与开源的贡献者,都能在开源共同体当中认识新朋友,开心开源,在开源生活中收获快乐 :D

改良 SQL Interval 语法:一次开源贡献的经历

本文是 GreptimeDB 首位独立 Committer Eugene Tolbakov 所作。

在上一篇文章《GreptimeDB 首位独立 Committer Eugene Tolbakov 是怎样炼成的?》当中,我从社群维护者的视角介绍了 Eugene 的参与和成长之路。这篇文章是在此之后 Eugene 受到激励,从自己的角度出发,结合最近为 GreptimeDB 改良 SQL Interval 语法的实际经历,分享他对于开源贡献的看法和体验。

以下原文。


开发者参与开源贡献的动机不尽相同。

开源贡献本身是一种利用自身技能和时间,回馈社群和造福更广大受众的方式。开源社群是一个绝佳的环境。参与贡献的人在其中能够与高水平开发者自由交流并建立联系,甚至可能找到可靠的导师或合作者。对于寻求职业发展的开发者,开源贡献可以作为个人技能和经验的展示。当然,也有许多开发者参与贡献只是出于个人的热爱。这很好!当你真正投入到一个开源项目当中后,你可能会发现软件的缺陷或缺失的功能。通过提交补丁修复问题或实现功能,不仅可以消除挫败感,还能让每个使用该软件的用户都受益。

成功参与开源贡献的秘诀不仅仅是编写代码。强烈的学习欲望才是內源动力,由此推动开发者才会主动了解本不熟悉的代码库,并应对其中出现的种种挑战。社群的及时响应和其他资深成员的支持,是这种学习欲望转换为真正参与贡献的重要保障。

社群的及时响应让开源开发者感到宾至如归;其他资深成员提供的指导和反馈,帮助新成员改进自己的贡献水平。理想情况下,你应该可以测试自己的补丁,并将修改版本应用到工作或个人项目当中。这种来自真实世界的需求,尤其是本人的需求,为开源贡献提供了重要的使用场景支撑,确保你的贡献是真的有用,而不是闭门造车的产物。只有如此,通过开源参与做出的贡献才能对整个社群产生持久的影响。

回到我本人的例子上来。虽然我已经花了不少时间锻炼自己的软件开发技能,但事 Rust 对我来说仍然颇有挑战。这种“新手”的感觉可能会让一些人不愿做出贡献,担心他们的代码不够好。然而,我把愚蠢的错误当作提高技能的垫脚石,而不是气馁的理由。

GreptimeDB 社群一年多的参与贡献经历,是一段不断学习并获得丰厚回报的旅程。今天,我会向你介绍其中的一次具体的贡献。让我们亲自动手吧!(或者我应该说,爪子?🦀)

动机和背景

这次贡献主要的目的是支持一个 SQL Interval 字面量的简化语法

1
select interval '1h';

SQL 标准定义了 Interval 的语法:

1
select interval '1 hour';

这个语法相对冗长,我们希望支持上面展示的简化语法,让 select interval '1 hour'select interval '1h' 返回相同的结果。

深入研究代码后,我发现处理转换的核心功能已经存在。为了实现上面的效果,只需针对 Interval 数据类型添加一条新规则:把简化语法格式的 Interval 将自动扩展为标准语法。让我们仔细看看代码中执行相关逻辑的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn visit_expr(&self, expr: &mut Expr) -> ControlFlow<()> {
match expr {
Expr::Interval(interval) => match *interval.value.clone() {
Expr::Value(Value::SingleQuotedString(value)) => {
if let Some(data) = expand_interval_name(&value) {
*expr = create_interval_with_expanded_name(
interval,
single_quoted_string_expr(data),
);
}
}
Expr::BinaryOp { left, op, right } => match *left {
Expr::Value(Value::SingleQuotedString(value))=> {
if let Some(data) = expand_interval_name(&value) {
let new_value = Box::new(Expr::BinaryOp {
left: single_quoted_string_expr(data),
op,
right,
});
*expr = create_interval_with_expanded_name(interval, new_value);
}
// ...

代码评审

上面是我第一次尝试写成的代码。GreptimeDB 的资深 Rust 开发者 Dennis 很快发现了改进空间

Dennis 发现了不必要的复制

代码评审是一个优秀的学习渠道。

除了简单地接受建议(因为“减少复制”的理由很明确!),我决定深入研究。通过分析建议并尝试自己解释,我巩固了对 Rust 代码和最佳实践的理解。

避免不必要的 Clone 和所有权转移

起初,我直接在 interval.value 上调用 clone 来取得一个具有所有权的结构:

1
match *interval.value.clone() { ... }

这里的 clone 每次都会创建一个新的数据实例,如果数据结构很大或克隆成本很高,那么这可能是一个性能的影响因子。Dennis 建议通过使用引用来避免这种情况:

1
match &*interval.value { ... }

匹配引用(&*interval.value)避免了 clone 的开销。同样的手法可以用在对二元运算符 left 的匹配逻辑里:

1
match &**left { ... }

这个稍微复杂一些:它使用双重解引用来获取对 Box 内部值的引用。因为我们只需要读取数据结构中的部分信息,而不需要转移所有权,因此只获取引用是可行的。这也减少了 clone 的开销。

清晰的模式匹配

在模式匹配中使用引用可以强调仅读取数据而不是转移所有权的意图:

1
match &*interval.value { ... }

只在需要所有权的时候进行克隆

在第一版代码当中,opright 总是被复制一份:

1
2
3
4
5
let new_value = Box::new(Expr::BinaryOp {
left: single_quoted_string_expr(data),
op,
right,
});

但是,其实克隆只需发生在匹配左侧变量是 Expr::ValueSingleQuotedString 变体,同时 expand_interval_name 成功的情况下。Dennis 建议把 clone 调用移动到 if let 块内,从而只在需要所有权的时候进行克隆:

1
2
3
4
5
6
7
if let Some(data) = expand_interval_name(&value) {
let new_value = Box::new(Expr::BinaryOp {
left: single_quoted_string_expr(data),
op: op.clone(),
right: right.clone(),
});
// ...

直接引用

在第一版代码当中,我用 expand_interval_name(&value) 显式借用了 value 的值。

然而,valueString 类型的值,它实现了 AsRef<str> 特质,所以它可以被自动解引用成 &str 类型。修改后的版本直接写成 expand_interval_name(value) 不需要再手动 & 取引用。

译注:

这个说的不对。其实是因为 String 实现了 Deref<Target=str> 所以不 clone 以后 &String 可以被自动转成 &str 类型,但是之前 clone 的时候传过去的是带所有权的 String 类型结构,这个时候 &value 取引用而不是把带所有权的结构整个传过去是必要的。

Deref 的魔法可以查看这个页面

总结

在这次贡献当中,代码的“效率”体现在以下三个方面:

  • 避免不必要的克隆,减少运行时开销;
  • 让借用和所有权转移的模式更清晰和安全;
  • 提升整体的可读性和可维护性。

最终版本的 visit_expr 大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fn visit_expr(&self, expr: &mut Expr) -> ControlFlow<()> {
match expr {
Expr::Interval(interval) => match &*interval.value {
Expr::Value(Value::SingleQuotedString(value))
| Expr::Value(Value::DoubleQuotedString(value)) => {
if let Some(normalized_name) = normalize_interval_name(value) {
*expr = update_existing_interval_with_value(
interval,
single_quoted_string_expr(normalized_name),
);
}
}
Expr::BinaryOp { left, op, right } => match &**left {
Expr::Value(Value::SingleQuotedString(value))
| Expr::Value(Value::DoubleQuotedString(value)) => {
if let Some(normalized_name) = normalize_interval_name(value) {
let new_expr_value = Box::new(Expr::BinaryOp {
left: single_quoted_string_expr(normalized_name),
op: op.clone(),
right: right.clone(),
});
*expr = update_existing_interval_with_value(interval, new_expr_value);
}
}
_ => {}
},
_ => {}
},
// ...

开源贡献是我找到的加速 Rust 学习的绝佳方式。参与贡献 GreptimeDB 只是一个例子,说明了我如何通过开源贡献获得知识。根据读者反馈,我很高兴在未来的帖子中分享更多这些学习经验!

非常感谢整个 Greptime 团队,特别是 Dennis 提供的帮助和指导,感谢他们在我贡献过程中的支持和指导。让我们继续贡献和学习!

用 Blender 制作一个可循环沙漠公路绿幕

偶然间看到有人发了个《奔跑的阿塔尼斯.GB》,我瞬间联想到的竟是《GO BROLY GO GO》这个梗。

那为什么不干脆自己做一个呢?

然后就遇到了一大堆大坑。其中的一个大坑就是这个奔跑用的绿幕背景该怎么搞。本文就围绕这个大坑随便嘚吧几千字。

(注1:本文非教程)

(注2:本文本该引用的图片,因为我懒而直接截图贴到 Discord 了,所以本地没有保存,我也懒得去 Discord 往回抠,直接文字描述了。对付看吧)


素材与原梗

《奔跑的阿塔尼斯.GB》

《GO BROLY GO GO》


首次尝试

在网上随便搞了一个绿幕背景,然后就做了一下。

[StarCraft] Go Go 大主教 Go Go !!!「旧版」

非常的不满意。

主要是这个绿幕背景不能循环,背景高度在开头和结尾不一致,所以每次到循环的位置时都咯噔一下子,看起来就非常的粗糙。

当然在现在这种大多数都是垃圾视频的大环境下,每天都产生大量漏洞百出的垃圾视频,平台和用户已经亢奋到巴不得直接脑子上插根安卓充电线接手机上在人脑内播放垃圾视频了,没人会在乎这种小细节,甚至都没人看我做的这种视频。


重新搜寻绿幕素材

在网上随便搜索了一下,发现几乎没有我需要的 「可循环」 的绿幕素材。

screenshot_on_b85m_by_flameshot_at_2024-02-16_22-32-40

大多数都是随便一个奇怪的片段,长度都很短,而且全部都是头尾不相同的,简单点的咯噔一下,复杂点的干脆连方向都变了,完全不知道这种限制性极强的素材在什么地方能用得到。

总之在网上找这种可循环绿幕素材这个途径行不通。


尝试自己做

算是要做个3D动画。掏出 Blender。

「路」,好解决,画个面,然后找个路的纹理贴上去就行,然后根据纹理做个首尾相接,循环就搞定了。

难点在路边,我想整个沙漠,那单纯的平面纹理就不够了。

在网上搜了一下关于 Blender 沙漠 的教程。

screenshot_on_b85m_by_flameshot_at_2024-02-16_22-32-51

screenshot_on_b85m_by_flameshot_at_2024-02-16_22-32-56

第一个,测试渲染完了,细节很好,但是主要用法是展示大沙漠里微观场景的,就和其缩略图一样。在大场景下做大背景效果很好,但是缩放小了之后发现作为沙地的效果却很差。只能说纯当练手了。

第二个,测试渲染完了,作为沙地的效果很好,但是沙地的效果是完全随机生成的,边缘不连续,接不上,没法做循环。

很明显遇到了非科班出身的人常遇到的困难。


寻求帮助

找熟人

我目前能联系的人当中,目前没有任何一个人是会使用 Blender 的。

可以说是真正意义上的 人脉匮乏,找不到 关系

中文社区

至于所谓的中文社区,QQ群,那都是吹逼饭的窝点。当年我3DS变砖时寻求帮助,顺着3ds.hack的路先加了中文区的QQ群,结果和想象中的一样,中文社区无非就是 吹牛逼 晒有钱 互相诋毁,一点帮助没获取到,还浪费了我不少时间。最后是把 3ds.hack 改成英文模式然后进了 Nintendo Homebrew 的 Discord 频道,我提出问题之后立刻就有人一步一步手把手帮忙解决问题,而且解决途中还发现我遇到的并不是很寻常的基础问题(不然怎么能卡住我)。

screenshot_on_b85m_by_flameshot_at_2024-02-16_23-41-58

screenshot_on_b85m_by_flameshot_at_2024-02-16_23-42-59

从提出问题到得到响应,用时1分钟。从提出问题到完全解决,用时43分钟。此时中文QQ群还一个屁都没崩出来。

本身被自己不懂的有一点难度的问题卡住就很难受了,网上却又存在着各种能力远比你低下的人在那不懂装懂,很可能导致你离解决方案反而越来越远。你看像 segmentfault.com 现在都是乌烟瘴气,都臭成什么样子了。

(光是当时3DS变砖后的解决办法,我当时没写博文出来,我都觉得奇怪)

总之,在中文区甭想找到什么求助。

英文社区

也不知道算是走运,还是巧合。

我的 Discord 频道列表里刚好有一个 TF2 相关的 Blender 社群。

screenshot_on_b85m_by_flameshot_at_2024-02-16_23-51-17

我还真就想不起来是为啥加了这个社群了,可能是因为看了 Pootis Engage ?

然而事情并没有像解决 3DS 变砖那样顺利。

我提出问题之后,首先跳出来的一帮人问我「为什么要做这个东西?」

哈?

然后我解释了一遍我需要做一个「可循环的沙漠公路绿幕」:其核心思想是,渲染一段以plane为底面的沙漠;沙漠上面有一个公路样式的几何图形,最好是 Cube 这种有高度的,看起来像是沙子上的路;视频结尾帧与视频开头帧相同,这样我可以无限循环这个视频,其最简单的办法是镜头最后的图形边界与开头的边界相同(其他方法亦可)。

然后得到了这么几个答案:

  • 随便渲染一下就好了,循环的时候咯噔一下,没人在意。
  • 渲染一个足够长的道路。
  • 你为什么要做绿幕,你直接在 Blender 你做完你的项目不就好了?

基本上,面对一个 目标极为明确的问题时 ,得到的答案是 不要做?

什么玩意……


继续摸索

阶段1

总之得到的帮助极为有限,多数人都是喷子状态。因为我的电脑配置是 i7-4790K + GTX1080 ,10年前的配置。虽然这个配置称霸了非常多的年份(10系显卡GTX1060霸榜一直到去年年末为止),但是放在 Blender 这种纯正的生产力场景下是很虚的。所以又冒出来一堆开喷硬件配置的,直到有人发现我在 Linux 环境下没有正确启用 Cycles 的硬件加速。

作为个生产力工具,整个社区竟然没几个人用 Linux 这一点也是够搞笑的。不过有了这个场景,我立刻就能分辨出哪些才是真正的有能力的 Blender 用户了。

搞了一宿,最后还是没搞定如何 循环 沙漠纹理。

这个时候终于有人提出新的方案了:不要用随机去渲染沙漠纹理,用内置的 海洋 去渲染。

我试了一下,海洋的渲染是基于一个固定模式的,所以其首尾的形状可以首尾相接。然后用颜色噪音把海洋渲染成土黄色。仔细一想其实海洋和沙漠是一回事,还挺有道理的。

然后这个是阶段成果。

[Blender] 在电脑前坐了一天,干到后半夜3点,就整出来2秒钟这玩意

问题出来了,因为我的目标是公路 正向/反向 的效果,所以「地平线」需要是垂直的,这就要求这个海洋要 足够宽 ,那么这个海洋的纹理就会被拉伸得,没啥效果,看着更像是 泥水 了。

此时已经是后半夜三点了。

然后我决定睡觉!

然后刚准备躺下的时候,脑子里冒出来了一个极其有意思的主意!导致我一晚上没睡好觉!

阶段2

首先,「海洋」的办法不是不行,就是实现起来可能要调教很久,效果也很一般,尤其是为了水平线而拉宽之后,效果肉眼可见的差劲。

毕竟我基础就已经很差了,就算形状搞定了,到纹理的大坑也得摔倒。

screenshot_vlcsnap-2024-01-30-19h05m30s064

screenshot_vlcsnap-2024-01-30-19h05m36s007

但是之前的方法我又搞不定边界连接的问题,无法做到循环。

然后我就想到了,或许改变一下前提。

我目前遇到的问题是,沙漠plane的边缘接不上。那假如,代表沙漠的图形没有边缘呢?

screenshot_on_b85m_by_flameshot_at_2024-02-17_01-10-41

Ta-Da!圆柱!

没错,这个就是兴奋得让我一宿没睡好觉的点子。

放弃平面的设计,做两个圆直径超大的圆柱体,一个代表沙漠,沿用旧的渲染方式;一个代表公路,嵌在沙漠里,只要位置恰当,看起来就像公路一样。

这方法甚至都不需要再移动镜头,只要让俩圆柱体旋转起来就行。

[Blender] 用了新的思路,编辑2小时,渲染1小时。

当然,缺点也是有的。圆柱体的直径必须足够大,不然看起来的确不像是平面,而是圆柱,露馅了。直径太大就导致周长特别大,那么转一圈的时长就特别长,渲染压力立刻就上来了。而且圆柱终究是圆柱,边缘怎么看都会觉得并不是地平面,而周长太大,大过头了 循环 的实用性就没了。

不过目前也就只能这样了。

阶段3

第一个大坑算是过了。

然后是给公路贴纹理。

这 TMD 作为一个 Blender 用户,应该是最简单的一个功能。

我TM不会啊!

screenshot_on_b85m_by_flameshot_at_2024-02-17_01-25-20

screenshot_on_b85m_by_flameshot_at_2024-02-17_01-25-37

我其实是要给一个圆柱的侧面贴纹理,而且还是要重复贴相同的纹理。我哪会这个! 你让我写个 CSS 还差不多!

screenshot_on_b85m_by_flameshot_at_2023-12-29_00-34-18

screenshot_on_b85m_by_flameshot_at_2023-12-29_00-49-32

总之这一块也卡了好久才搞定。

这种最基础的玩意,真的是,最好有个人能手把手教一下,把流程缕清晰了最好。

我这种瞎蒙出来的,完全不知道自己是怎么蒙出来,下次让我做一样的事情,我照旧做不出来,还是得瞎蒙。尤其是我也已经上岁数了,记忆力也差,蒙出来也想不起来过程,非常难受。

阶段4

至此坑就算是填了,把天空改成绿幕,然后速度和其他参数什么的,调整了一下就能出成果了。

然后初次渲染的时候,一遍渲染,一边尝试微调些细节。结果渲染出来的结果一塌糊涂。

我这才意识到 Blender 的渲染不是沙盒模式的,即它渲染的不是我在执行「渲染」瞬间的副本,而是正在编辑的正本。

也就是说 Blender 渲染的时候,最好完全不要动 Blender。切出去干别的,或者最好把电脑放那不要动。

这都2024年了怎么软件行业里还有这种玩意啊!


成果

随便剪了一下,做了两个示例,就发B站了。

[Blender] 可循环沙漠公路绿幕

我甚至懒得发布纯素材。

其实渲染有 Alpha 通道的视频也不是不行,但是指望我这个老电脑渲染 VP8/9 H.265 AV1 实在是太困难了。而且现在所有视频网站依旧以 H.264 为主流。

(其实主要还是我电脑带不动)

这玩意到这个阶段时,我把片段发到之前的 Discord 频道里,然后一堆人(就是之前那堆帮倒忙的)冒出来问我这这那那都是怎么实现的,尤其是对绿幕这块甚至都不知道怎么实现怎么使用。

好家伙, NLE+VFX 最基础的东西都不知道,竟然还试着在那指教我?

然后看到我在用开源的 Shotcut 作为 NLE 时竟然开喷我用不起高贵的 Adobe,这一看就是小孩没参加过工作啊,我在家用不起 Adobe 我大不了去公司用公司电脑上的 Adobe。

(原来国外社区跟国内也没什么两样,都 Toxic


使用例

除了上面那个视频带的 Senator 和 国际靶场 两个示例,我还用这个素材做了另一个视频。

Funky Town ⧸ Eurobeat Remix (Deamoz Eurobeat)

当然最初为了做这个绿幕素材的目的也没忘。

[星际争霸2] Go 大主教 Go Go !!!


结尾

这事就算了结了。

经验基本上是没累积到。我现在这个年龄,好多新知识,摸了不进脑子,除非天天摸。我现在开始担心自己以后会不会糊涂得像家里大人一样,到了岁数后连手机怎么接打电话都不会,但是刷短视频乱花钱比谁都猛。

国内国外社区都这么 Toxic 这事看来我是可以确定了,国外的月亮不比国内圆,但国外的屎一样臭。

The post 用 Blender 制作一个可循环沙漠公路绿幕 first appeared on 石樱灯笼博客.

GreptimeDB 社群观察报告

GreptimeDB 是格睿科技(Greptime)公司研发的一款开源时序数据库,其源代码在 GitHub 平台公开发布。

https://github.com/greptimeteam/greptimedb

我从 2022 年开始知道有 GreptimeDB 这个项目。2023 年,我注意到他们的 Community Program 是有认真写的,不是无脑复制所谓成功项目的大段规则,于是开始跟相关成员探讨开源治理和社群运营的话题。后来,我读过 GreptimeDB 的源代码,发现他们的工程能力很不错,于是就开始参与贡献

经过这几个月的参与,我对 GreptimeDB 的社群有了初步的了解。我认为,这是一个值得参与的拥有巨大潜力的开源社群。于是写作这份社群观察报告做一个简单介绍和畅想。

GreptimeDB 的社群量化情况

两年前,曾有人半开玩笑地说 Rust 和时序数据库都快成开源世界的一个梗了,因为当时有大量的 Rust 语言写作开源项目和定位在时序数据库的开源项目出现。GreptimeDB 也算其中一员,它同样是用 Rust 语言写成的。

不过两年过去,回过头看能够坚持下来不断发展的项目,GreptimeDB 就是为数不多硕果仅存的一员。哪怕跟主流时序数据库项目社群相比,GreptimeDB 的活力也可圈可点。

上面两张图展示了 2023 年,即 GreptimeDB 开源运营第一年,从 OpenDigger 数据集生成的每月 OpenRank 和活跃指数(Activity)数据折线图。

可以看到,从 OpenRank 的维度看,GreptimeDB 显著超越了近年来发展乏力的 InfluxDB 项目,跟 TimescaleDB 和 Prometheus 不分伯仲,相比起战斗民族出品的商业开源产品 VictoriaMetrics 仍有差距。从项目活跃指数的维度看,GreptimeDB 则与 InfluxDB 之外的主流项目同属第一梯队。

OpenRank 是同济大学赵生宇博士定义的一个开源价值流分析指标。相比于容易受先发优势影响的 Star 数和 DB Engines 分数等指标,上面展示的每月 OpenRank 和 Activity 变化情况更能体现出项目当前的发展情况和未来趋势。

GreptimeDB 的社群运营情况

前面提到,我真正开始关注 GreptimeDB 社群的契机是发现他们的 Community Program 并非船货崇拜,而是明显经过思考,有一定可行性的。事实证明,确实如此。2023 年 GreptimeDB 按照 Community Program 的设计发展了两名公司之外的 Committer 新成员:

这两位 Committer 都是通过代码贡献被提名的,提名前都提交了大约二十个代码补丁,且质量被项目团队所认可。此外,这两位 Committer 从开始参与项目发展到成为 Committer 都经过了若干个月的持续投入。可以看到,这个标准下筛选和认可的两位 Committer 在上个月仍然有活跃参与。

应该说,目前 GreptimeDB 的项目功能已经初具规模,能够达到线上交付的标准。这也意味着开荒阶段的大量初创工作已经完成,新加入的社群成员可以在一个坚实的工程基础上发挥自己的创造力。同时,GreptimeDB 在实现优化上还有很大的进步空间,倒排索引、WAL 和存储引擎等技术方向上还有很多未解决的设计实现问题。现在仍然是参与 GreptimeDB 成为 Committer 的机遇期。

不过,GreptimeDB 的 Community Program 距离成为一个商业开源标杆还有不少可以改进的地方。

例如目前邀请新的 Committer 只在 Biweekly 上简单提及。Community Program 设计的结构上并没有即时体现出它正常运转,以及社群存在公司之外的 Committer 的事实。对于活跃参与者和 Committer 的介绍和成功经验分享,也尚有欠缺。目前社群基本处于给代码写得好的人一份权限的朴素运营阶段。

此外,Community Program 虽然已经相比其他船货崇拜的同行删减了许多内容,以保证它能够务实地运作,但是仍然存在一些空洞的组织结构。例如设计出的 Steering Committee 做技术和社群发展决策,但是实际上当前阶段大部分工作就是公司团队商议决定后公开;例如还是定义了 SIG 乃至 OSPO 的组织,但是根本没有人力填充运营这些机构。

我认为,Community Program 应该继续依托当前社群实际运行的状态,结合期望达成且有能力达成的下一个状态,来做修订。例如,提高成为 Committer 的标准和路径的透明性,积极分享案例和邀请 Committer 说出自己的故事。例如,精简冗余和虚假的组织架构的同时,保留在社群征求意见和决策结果向社群公开的关键动作。例如,强调社群成员参与渠道的多样性,鼓励在不同渠道帮助他人使用 GreptimeDB 和参与贡献。这部分是 Ambassador/Advocate 的核心。

除了这个堪称开拓性探索工作的 Community Program 之外,GreptimeDB 社群还有两件事情让我印象深刻。

第一个是 GreptimeDB 社群积极参与开源之夏这样的务实的开源活动,今年释放的三个挑战项目都实现了不错的开源导师传帮带效果:

第二个是 GreptimeDB 的 good-first-time issue 流转速度极快,大部分容易上手的工作往往在一周甚至两三天内就会有人认领,并且完成的情况也还不错。实际认领实现过程中,只要你能够主动更新进展和提问,项目团队成员大多能及时回复。这个体验还是很难得的。

GreptimeDB 的未来发展期望

前面介绍 GreptimeDB 的时候,提到了开源、Rust、分布式、云原生、时序数据库等关键词。虽然这些 buzzword 确实也是 GreptimeDB 能力或特点的一部分,但是从注重实效的程序员(The Pragmatic Programmer)的角度来说,我们还可以做一个具体的展开。

即使当初市面上已经有“恒河沙数”的时序数据库存在,GreptimeDB 的创始团队还是发现了这些现存解决方案没能处理好的问题。对于时序数据当中重要的三个分类:指标(Metrics)、事件(Events)和日志(Logs),大多数时序数据库都只能最优化其中一到两种分类的存储和访问。

GreptimeDB 的创始团队认为,这三类数据可以共用同一套查询层和对象存储层能力,只需要针对各自的数据特性实现各自的存储引擎即可。其中大部分 DB 的架构和能力,例如数据分片、分布式路由,以及查询、索引和压缩等都可以共享。这样,GreptimeDB 最终能够成为同时提供所有时序数据最优化的存储和访问体验的单一系统。

开源应用监控项目 Apache SkyWalking 自研数据库 BanyanDB 也是基于相似的挑战和思考,不过它作为一个监控项目的子项目,更多是以相当特化的方式在实现。但是这反应了时序数据可以统一存储逐渐成为业内共识,所有的通用主流产品都将朝这个方向发展。

在仰望星辰大海的期许之外,GreptimeDB 也有脚踏实地的挑战。

例如,虽然我前面夸赞 GreptimeDB 的工程化水平不错,工程师做功能扩展和代码重构都能找到一个相对整洁的切面,但是软件工程是一个即使知道了原理和最佳实践,真正做出来还是有相当长的必要劳动时间的领域。在快速原型迭代的过程中,GreptimeDB 对内存和抽象的使用是相对奔放的。随着线上应用逐渐增多,GreptimeDB 团队也能收到用户上报的各种性能问题。这就要求重新关注到在快速开发过程里被刻意忽略的细节,精打细算关键路径上的内存使用,针对性能修改抽象以充分利用机器资源。这部分工作都是细致工作,讲究一个 benchmark 发现性能瓶颈并逐个优化。目前的 GreptimeDB 应该有相当多这样的优化机会。

例如,之所以过去时序数据库没能同时服务前面提到的三种不同数据,除了数据建模上的差异,更主要还是因为在数据量暴涨之后,特定数据类型的特定访问形式的读写性能会骤然降低。目前针对此类问题,业界提出了一系列索引方案进行改良。GreptimeDB 目前正在实现其中倒排索引的方案,也将探索结合倒排索引、基于 Cost 的查询优化器和 MPP 查询引擎的自适应方案。这些工作存在许多参与机会,目前 GreptimeDB 团队成员也有不少精力投入在此。

例如,系统层面数据一致性和性能之间的取舍依赖 WAL 模块的实现。目前,GreptimeDB 仅提供了本地的 RaftEngine 实现和 Kafka Remote 的实现,其中 Kafka Remote 的实现发布还不足三个月。这部分工作现在跟进来,参与到现有实现的完善和优化,以及可能的自研 WAL 设计实现过程当中,对任何数据系统开发者而言都将是一段宝贵的经历。

例如,GreptimeDB 在部署形式上支持云端同构部署,时序数据从设备端到云端都是同一套技术栈在处理。这时,如果 GreptimeDB 能够支持一些高级的分析能力,那么时序数据分析的成本将大大降低,体验也将进一步提高。目前,GreptimeDB 已经支持通过 SQL / PromQL / Python 脚本等形式执行分析,正在设计实现基于 Dataflow 技术的分析功能。分析的需求无穷无尽,这一部分对于熟悉数据分析的开发者来说,是一个很好的切入点。

核心数据库系统代码之外,GreptimeDB 还开源了完整的 Dashboard 方案和多语言客户端。再加上本身 GreptimeDB 就支持 SQL 和 PromQL 等业内通用接口,从 GreptimeDB 与生态集成的角度入手参与到 GreptimeDB 的发展,也是一条不错的道路。就在几天之前,我还看到有位同时使用 EMQX 和 GreptimeDB 的开发者向 GreptimeDB 的 Erlang 客户端提交补丁

软件开发参与之外,Greptime 社群维护的两个重要渠道:GitHub Discussions 主题讨论平台Slack 即时通信工作空间都欢迎任何对 Greptime 开源和商业产品感兴趣的人加入。

GreptimeDB 的商业与可持续

我曾经表达过一个观点:商业化不是开源项目可持续的必要条件。因为许多开源软件是个人开发者兴趣所为,这些个人开发者可以有其他经济收入。即使不基于其创造的开源软件做商业变现,也不影响这些开源项目持续维护和发展。

不过,GreptimeDB 是 Greptime 公司研发的开源软件,而公司要想存续下去,就必须以某种形式取得盈利。Greptime 公司投入了不少资本和人力在 GreptimeDB 的研发上,那么 GreptimeDB 总要为 Greptime 的商业成功创造价值。

Greptime 公司目前有两条商业产品线:

  • GreptimeCloud 提供了全托管的云上时序数据库服务,其内核是 GreptimeDB 系统。这个服务可以免费试用,其 Playground 和 Dashboard 做的技术品味都很好。

Greptime Playgorund

Greptime Dashboard

  • GreptimeAI 是为 AI 应用提供可观测性的服务。不同于其他数据库在赶上 AI 浪潮时采用的 PoweredBy AI 增强自身产品的思路,GreptimeAI 是 For AI 增强 AI 产品的思路。其实本轮语言大模型带动的 AI 浪潮对 Database 服务本身的提升还十分有限,反而是这些 AI 应用自身产生的数据需要 Database 来存储和管理。

这两个产品的底层都是 GreptimeDB 的开源代码提供的核心能力,而云控制面、企业管理、安全集成等功能,则是商业代码实现的。

至于为什么要开源 GreptimeDB 数据库核心代码,而不是干脆全部都是私有的商业代码,前几天 Meta 的财报上介绍的 Llama 开源的理由帮我省去了很多口水:

LLaMa 开源的理由

应用在 GreptimeDB 的情况,在 Greptime 团队决心做这个产品的时候,先发的主流时序数据库已经取得极大的优势,且它们几乎全是开源的。这种情况下,就算 GreptimeDB 存在没有历史包袱的优势,直接朝着正确的方向飞奔,但是软件工程的固有复杂度和必要劳动时间并不能无限减少,所以开源是追赶现有主流产品和赢得用户信赖的必选项。

当然,开源软件允许任何用户免费使用,因此构建商业价值不能直接基于开源软件本身。关于 Greptime 如何设计开源模型,或许我会另写一篇文章做对比介绍。目前而言,其开源模型接近 Databricks 的策略。虽然 GreptimeDB 是从头开始写的,不像 Databricks 直接基于开源的 Apache Spark 构造解决方案,但是其核心功能实现重度复用了已有的开源软件:

  • Apache Arrow DataFusion
  • Apache OpenDAL
  • TiKV RaftEngine
  • Apache Kafka

而且,Greptime 团队对于什么功能应该开源是谨慎的,而不是 by default 开源。只有存在这样一个踌躇推敲的过程,才有可能做商业可持续的战略开源。

DISCLAIMER

在社群参与过程中,我跟 GreptimeDB 的核心社群成员有深入的交流,并于近期加入了 Greptime 团队,因此我的观察和评价可能存在一定的主观误差。欢迎各位留言或私信交流意见。

英文独立博客社群

背景

如果你想要一个「去中心化平台」,那么最好的方式就是创建自己的网站。

前段时间参加了一个小活动: App Defaults,这是一个博客作者们的主力软件清单。我使用 OPML 文件订阅了所有博客,发现其中大部分都是 Micro. Blog 的用户,而在这个社群中类似的项目是会定期举办的,是博客作者们了解彼此的桥梁。

最近回到 App Defaults 主页,发现他们还有新更新,收集了所有博主写的文章,并把其中互相关联的外链可视化。每个节点都代表着一篇博客文章,而每条线则表示该博客文章中包含的链接,指向其他的博客文章。花了点时间找到了自己(红圈处),并在附近看到了小胡同学

独立博客就像一块块孤岛,飘在互联网中。连接起来,才是它真正的模样。

CleanShot 2023-12-15 at 13.27.11@2x.png

如何找到英文独立博客

想到中文独立博客也有很多类似的项目,但我对英文独立博客的了解还很少,大多是以内容为导向了解到新的博客网站。Eric Murphy 最近的一期博客 Independent Websites and Where to Find Them,刚好讨论了如何如何找到有趣的独立网站。

他提到了 Webring(网环),这是互联网早期阶段寻找同类型网站的一种形式。先将自己的网站提交到一个 Webring 社群,再将一个特殊的导航链接放在网站底部,浏览者点击此链接即可跳转到一个社群内的随机网站。国内也有类似的优秀平台:开往 和  十年之约

Eric 推荐的 Webring 社群:

浏览这些 Webring 社群的过程中,发现国外不仅有综合类的 Webring,还有很多细分领域的 Webring,比如 Psycho Helmet Webring,很有趣。国内我所知的都是综合性的 Webring,内容更多样,但经常会看到自己不感兴趣的内容,比如开往中有高比例的程序员群体。如果我对该领域不感兴趣,阅读体验就会比较差。

CleanShot 2023-12-15 at 15.55.25@2x.png

Wiby 是一个小众网站检索引擎,但仅收录提交页,检索效率较差。

Blogroll 提供了博客的检索服务,并展示最近更新的文章,中文博客类似的有 BlogFinder川流积薪

Eric 最喜欢的方式还是 Links Page,即建立一个网页,然后收录那些你感兴趣的网站,可以是独立博客,也可以是资源网站。我们常用的 「友情链接」应该也属于这个类别。个人最喜欢是:Everything I Know,内容非常丰富。

image.png

网上冲浪真的好杀时间,看完 「Independent Websites and Where to Find Them」 之后,我就跟着这些 Webring 和 List 到处逛,几小时一下子就过去了。

全票通过?同侪社群无须整齐划一

近几年,国内开源项目捐赠到 Apache 软件基金会(ASF)的案例很有一些。几乎每个在进入孵化器和从孵化器当中毕业时发通稿的项目,都会选择在标题中加入“全票通过”的字样。

诚然,大部分项目在 ASF 孵化器中茁壮成长,实际上投票结果也是没有反对票,使用这一标题无可非议。然而,对于把同侪社群(Community of Peers)作为社群核心价值之一的 ASF 来说,追求全票通过并不是必须的。

在 ASF 孵化器当中,近些年来由于孵化器主席 Justin Mclean 个人风格的原因,许多项目遭受了无端的审查压力。我认为有必要在国内营造出人人都可以,甚至都应该“全票通过”的氛围时,阐明 ASF 同侪社群的理念和工作方式,以减少项目在面临不合理的挑战时遭受的挫败,尤其是当它来自于某个看起来权威的成员时。

理念与制度支撑

The Apache Way 当中即包括同侪社群的理念:

  • ASF 的参与者是个人,而不是组织。ASF 的扁平结构决定了无论职位如何,角色都是平等的,投票权重相同,贡献是基于志愿的(即使有人因为 Apache 代码的工作而获得报酬)。Apache 社区期望成员之间相互尊重,遵守我们的行为准则。我们欢迎领域专业知识,但不允许有终身仁慈独裁者。

也就是说,ASF 当中所有人在原则上都是平等的,所有的 PMC 成员在投票表决议案时具有相同的权重。

进一步地,ASF 关于投票的专门文档中写到:

  • 对于流程问题的投票,遵循多数原则的常见格式:如果赞成票多于反对票,该问题被认为已通过,而不考虑赞同或反对的具体票数,即反对票不构成否决。
  • 对于代码补丁的投票,反对票构成否决。为了避免否决权被滥用,投票人必须在行使否决权时提供技术理由,没有理由的否决是无效的,没有影响力。
  • 对于版本发布的投票,要通过至少需要三票有效赞同票,且有效赞同票多于有效反对票。反对票不构成否决。

实际操作中,行使技术否决时,如果其他 PMC member 不认同否决者提出的理由,否决也不成立。

因此,全票通过当然是一件值得开心的事情,但是 ASF 的运作方式并不要求需要全票通过。

面对反对意见

我在指导 ASF 孵化项目的过程中遇见过多次反对意见。

先看一个压力没那么大的。StreamPark 的孵化提案在提交表决时,最终是以 8 票有效赞成票,12 票其他赞成票,两票其他反对票通过的。

两票反对票来自 Apache StreamPipes 的项目成员,他们没来由地觉得 StreamPark 跟他们的项目“很像”,所以不应该进入孵化器。

且不说 ASF 并不禁止定位相似的项目进入孵化器,例如复数个消息队列和功能相似的大数据软件,StreamPipes 定位是物联网的工具箱,而 StreamPark 是为流计算系统 Flink 打造的作业管理平台(现在也部分支持管理 Spark 作业)。

所以,这种反对意见,既不是有效票,更没有什么可靠的理由,忽略即可。

再看一个比较搞笑的。Doris 的毕业提案在 2022 年 4 月 27 日以 12 票有效赞成票,13 票其他赞成票“全票通过”。但是孵化器主席 Justin Mclean 在 5 月 15 日找了一下存在感发了一个反对意见

显然,时间已经过去了,而且赞成票远多于反对票,因此 Doris 毕业是既定事实。

面对傲慢的审查

既然是同侪社群,那么允许不同的意见存在就是合理甚至必要的。有人提出反对意见,有人行使投票权投有效反对票,这都是正常的。我在本文开篇所反对的,是通过投反对票带给项目无端压力的傲慢的审查。

上面 Justin 给到 Doris 连续的负面意见,虽然对毕业结果没有影响,但是实际上作为 Doris PMC 整体处理起来的负担并不小。Justin 不停地抛出各种链接,要求 PMC 对此做出解释,其中各种无厘头或者过分的要求层出不穷。

例如,他提到,搜索 Baidu Doris 或者 DorisDB 会出现可能模糊 Apache Doris 品牌的内容,这些内容都需要 Doris PMC 去处理解决。

这根本就是扯淡的。

今天,你主动搜索 Baidu Doris 或者 DorisDB 还是会有各种导向非 Apache 品牌的内容,难道 PMC 整天啥正事儿不干,就陪你做因特网警察?这还是在 Doris PMC 对当时的品牌侵占大户,如今的 StarRocks 有较大影响力,且 Doris PMC 中不少成员受雇投入时间解决这些问题的情况下。

另一个例子来自于几乎全员志愿者的 OpenDAL 项目。

OpenDAL 自进入孵化器以来已近一年,在这段时间里,OpenDAL 提名了 9 位新 Committer 和 3 位新 PPMC 成员,发布了十几个版本,且分别由近十位 Release Manager 主导,不同语言的版本被多个下游软件所依赖。以任何开源社群的标准来看,这都是一个蓬勃发展且做出成绩的项目社群。OpenDAL 的作者 Xuanwo 信任 ASF 的社群发展理念,把 OpenDAL 捐赠到 ASF 当中,其本身就是对 ASF 品牌的认同。

那么好了,在上面链接对应的孵化毕业讨论中,OpenDAL 遭受了怎样的审查呢?

第一次回复,Justin 表示 OpenDAL 的一些引用最好改成 Apache OpenDAL 并带上商标标记,一些第三方的网站提到 OpenDAL 的时候也没有 Apache 的品牌。Xuanwo 看到以后及时的处理,甚至到第三方项目中提交 PR 将 OpenDAL 改成 Apache OpenDAL 的字样。

一般来说,到这里我们就可以认为 OpenDAL PMC 认真对待商标问题,尽力展现 Apache 商标,这已经很足够了。

足够吗?Justin 认为还不够呢。

Justin 进一步提出,按照 ASF 品牌政策的字面意思,所有 OpenDAL 网站的页面,都要用 “Apache OpenDAL” 来指称项目,而且都要带商标名称。最为离谱的是,这个要求连带要执行到 API 文档的每个页面上。

这个真的是保护 ASF 品牌吗?我要打个大大的问号。且不论 OpenDAL 的网站明晃晃的是在 opendal.apache.org 域名下的,根本就没有任何一个 ASF 项目,能够做到在所有网页和材料里都用 Apache ProjectName 指称项目,还要带上商标名称。还是那句话,PMC 整天啥正事儿不干,就陪你搞这些?

说到“任何一个 ASF 项目”,就不得不提 ASF 孵化器讨论里某些人的 360° 立体防御体系。其运作方式如下:

  1. 顶级项目不能作为参考,原因不明反正就是不行。你说某个顶级项目也是如此,他们不会解释为什么顶级项目那么做是有问题的,甚至为什么很多顶级项目都没管这些破事,只会说顶级项目不能作为参考,其回答模式就像低水平 AI 一样。难道孵化器项目毕业,不是为了成为顶级项目?怎么顶级项目反而没这么多破事,到你这就有了?
  2. 其他孵化项目不能作为参考,因为它们反正也没毕业,有问题是正常的。
  3. 基金会以外的项目不能作为参考,因为我们是 ASF 孵化器,别人爱咋咋地。

你发现了吗?这样一套操作下来,一个孵化项目要 argue 自己的做法的时候,不能援引任何其他项目做参考,建设性讨论几乎无法进行。

不能参考其他项目,那怎么界定合理性呢?那就要回到 ASF Policy 及其解释了。

例如,Justin 援引 ASF 品牌政策和自己写的 Incubator Distribution Guideline 说,政策规定项目正式名称是 Apache ProjectName,所以你的 NPM 包名应该是 apache-projectname,PyPI 包名应该是 apache-projectname。下面一众项目发出问号:

哦对了,其他项目不能被引用论述。这下无敌了。

哦,也不一定。比如 Justin 自己要证明说这个包名用 apache- 前缀是合理的时候,他就可以说

This is no different to any project that comes to the ASF via the incubator. Many of them need to change names, often before joining the incubator, and all need to change their name to be in the form “Apache Foo”.

这又可以了。

双标。

当然,没有 ASF Policy 支持,Justin 也可以创造出一些村规来审查你。

例如,Justin 表示 opendal.databend.rs 被重定向到 opendal.apache.org 上,那么 OpenDAL PMC 就要能控制 opendal.databend.rs 这个域名。

哈?所幸 databend.rs 是捐赠 OpenDAL 的企业 DatafuseLabs 控制的,这件事情可能还没那么离谱。换个思路,任何人今天就可以搞定 opendal.vercel.app 重定向到 opendal.apache.org 上,其他服务只要想找肯定能找到,是不是 OpenDAL PMC 还得买下 Vercel 啊?

不过我依稀记得 Justin 自己 mentor 的项目 Answer 也有过 answer.dev 的旧域名吧?这个怎么说呢?

Answer 域名的问题还是我提出来的,我也是 Answer 的导师之一。在这里,Justin 明确说:

redirection would be best

这又可以了。

双标。

再来看另一个莫名其妙的审查。

上面说到要用 Apache OpenDAL™ 来指称项目的事情,OpenDAL PMC 觉得也不无道理,一些显著的引用改改也行的。于是 Python API 文档的首页就用 Apache OpenDAL™ 来指称了:

opendal-python-apidocs

Justin 说这不行,你第一个 opendal 是包名,没有 Apache 字样。所幸我强忍恶心,耐心问了下商标团队的成员这个问题。商标团队的成员是个正常人,曰:“如果工具限制就是这样的,那也没事”。我补了一刀,说你非要说那 PMC 高低得自己做个 API 文档工具来解决合规问题。

当然,只有这个怎么够呢?这首页行了,没说其他页不行啊。pdoc 生成页面是按 Python 模块生成的,Justin 找来一个模块的文档页,指着说:你看,没有 Apache,不行。

opendal-python-apidocs-layer

真要较真,合着以后大家搞网站全别分页了,塞成一个大单页,就像 Kafka 这样

kafka-single-page

合规只要做一次,岂不美哉?哦,Kafka 这个大单页也不符合 Policy 呢。

这种想要做事的人反而莫名其妙多了很多繁文缛节要搞,可不就是官僚主义么?

小结

开源社群存在的首要目的,包括 ASF 自己写的第一愿景,是支持开源开发者生产开源软件。

所谓的政策、指南、规则,其目的应该是保护社群成员免收意外风险侵扰。本身它们是一种非强制性的指引,有道理不遵循也是可以的,更不要说违反了就等同于违法。

我在 ASF 当中得到过很多人的帮助。OpenDAL 作为一个支持多语言的库,为 ASF 在很多发布方面的共识提供了讨论的基础。例如,在我和 Mark Thomas 以及 Drew Foulks 等人的合作下,OpenDAL 搞定了所有 ASF 流程以支持包括 Maven Central 在内多平台自动发布。

Justin 本人愿意花费大量的时间检查孵化项目的发版和提案,我个人对这一点本身是尊敬的。他实际上也指出过很多项目实际存在的合规或品牌问题,而且确实应该被合理的解决,包括上面一开始点出 OpenDAL 的品牌问题,OpenDAL PMC 是有可以改进的地方,也确实改进了。

但是,Justin 把 Policy 苛刻成一种对内进攻项目的武器,用一种非常令人头疼的语气攻击项目,实际上是对 ASF 品牌和孵化器更大的伤害。

此前,这种苛刻又傲慢的审查已经逼退了只有一个核心开发者的 ZipKin 项目:

OpenZipKin 本是监控领域的明星项目,它愿意进入 ASF 并宣传 The Apache Way 是对 ASF 品牌的巨大帮助。然而,在这封令人伤心的退出提案中,ZipKin 的主创 Adrian Cole 无不失望的写到:

Process and policy ambiguity has been ever present and cost us a lot of time and energy.
The incubator spends more energy on failing us than helping us.

这其实是一个早该被提起更高优先级的反馈,ASF 的孵化项目居然感觉到孵化器在促使它们失败而不是帮助它们成功。有了本文前面的介绍,你应该知道这是怎么一回事。

关于流程和政策的争论,我想引用 Rust 作者 Graydon Hoare 的博文 Batten Down Fix Later

这里所谓的不专业或不健康的处理方式,我至少见过四种具体的形式:

  1. 通过花费时间争论消耗对手的精力。

离开阶段常常显得有点突然,因为其原因不透明,并且也有几种不同的形式,通常对应到上述处理的模式:

  1. 由于精疲力竭或倦怠而退出。

毫无疑问,Justin 苛刻且傲慢的审查,就走在这个模式上。

OpenDAL 跟 ZipKin 相似,有一位明确的主创 Xuanwo。如果没有几位导师支持,就像 ZipKin 的 Adrian 一样独自面对这些东西,很难想象如何能够坚持下来。不止一次 OpenDAL PPMC 成员和项目导师对 Justin 的雷人言语表示“麻了”。Justin 本人近五年没怎么正式写过代码也让他的很多“意见”显得非常业余。

例如,要求更改发布平台上 OpenDAL 的 README 包括 Apache 商标,PMC 改完以后说下次发版就会更新。Justin 来了一句能不能不发版就更新 … 你说呢?

当然,如标题所言,ASF 是一个同侪社群,孵化器和基金会并不会因为有一个特别苛刻而傲慢的人就不工作。但是 Justin 是孵化器主席,还是 ASF 董事会九人组的成员,身在基金会中的我即使知道这是同侪社群都会感觉到不可避免的压力,更不用说对此了解较少的其他开发者了。

真要说起来,Justin 的表达真像他自己那样较真的理解,并没有这么大的压力。

例如,在我挑战 NPM 和 PyPI 的包名到底要不要非得用 apache- 前缀后,他改口说 Guideline 都是 SHOULD 不是 MUST 所以有理由的话不用也行。但是又不死心的加了一条临时村规说这个要改也得在毕业提案前,提前跟 IPMC 商量。争论村规毫无意义,但我确实有心情,就说 IPMC 在每次发布的时候都会检查,这些内容都是公开的。毕业提案前,导师组都觉得没问题,怎么你不在导师组里,就得跟 IPMC 商量了?我看你在导师组的项目都不怎么商量啊。

再有一个例子是蚂蚁集团捐赠 CeresDB 核心代码的时候,出于保留商标的商业动机,用新名字 HoraeDB 捐赠核心代码。另一个 ASF 老玩家 Roman 都说这种 Dual Branding 很正常了,Justin 觉得不行。

“Daul branding” is nothing new, but recently, some entities have taken unfair advantage of this (including one you mentioned), and I feel the Incubator should take care that others do not also do this.

诛心言论,死了也证明不了自己只吃一碗粉。我就觉得你未来要 taken unfair advantage of this 了,你说你不是,我觉得你是。

Why a company would be unwilling to give up that brand or trademark just because it may be convenient in the future is a concern.

为什么呢?商业行为,甚至都找不到 ASF Policy 来说这不行了,但我不喜欢,我觉得是个 concern,你就要给我解释。

HoraeDB 的提案,最后我就说不剩什么正经问题了,你的这些意见我都听到了,该说的都说了,我们投票表决。最终 HoraeDB 以 13 票有效赞成票,1 票其他赞成票“全票通过”。Justin 没有投票。蚂蚁集团的运营也好险能继续沿用“全票通过!”的标题。

最后复述一遍,我写这篇文章是为了阐明 ASF 同侪社群的理念和工作方式,以减少项目在面临不合理的挑战时遭受的挫败,尤其是当它来自于某个看起来权威的成员时。

Rust 社群何以走到今天?

本文有些标题党,实际想讲的内容,是我从部分 Rust 曾经的核心开发者的自述当中,所发现的 Rust 项目社群开源协同模式发展至今的一些特点。

我会从这些自述发言的内容切入和展开,对比其他社群遇到相似挑战的状况和应对方式,讨论 Rust 项目社群在协同方式维度上走到今天的沿革。

Graydon Hoare 的博文

说起 Rust 语言的核心成员,你会先想起谁?

不像 C# 之父 Anders Hejlsberg 和 Python 之父 Guido van Rossum 这样在很长一段时间里可以代表语言本身的人物,也不像 Brian Behlendorf 早期经常代表 Apache Web Server 项目发言,Rust 项目社群的一大特色就是不仅没有所谓的终身的仁慈独裁者(BDFL),甚至很难找到一个有足够权威拍板的人。

这就是 Rust 社区协同模式发展成今天这样的一个核心影响因素。

Graydon Hoare 是 Rust 的第一作者,但是如果你不对 Rust 的历史感兴趣,在日常讨论 Rust 的对话中,你很难听到这个人的名字或者他的观点。这是为什么呢?

通过查看 Graydon 在 Rust 仓库的提交记录,我们可以看到自 2014 年起,他就可以说不再参与 Rust 核心的开发了。

2014 年起,Graydon 的主要精力就投入在某区块链项目和 Swift 语言上:

apple/swift

apple/swift-source-compat-suite

不过,有趣的是,随着 Rust 在区块链领域越来越火,近一段时间 Graydon 又开始写起了 Rust 代码

Rust 1.0 的发布要追溯到 2015 年,而此时 Graydon 早已从 Rust 的核心开发中离开。这其中的原因可以从 Garydon 今年发布的一篇博文《我想要的 Rust 语言没有未来》中窥得端倪。

文章讨论了 Garydon 在设计 Rust 之初和早期开发过程中对 Rust 的定位,以及他对 Rust 语言当前某些特性的锐评,其中包括他不喜欢的一系列语言特性:

  • 跨 Crate 的内联和单态化;Garydon 引用了 Rust 另一位早期开发者 @brson 关于 Rust 编译时间的评论
  • 以库形式定义的容器、迭代器和智能指针;Garydon 希望这些都作为语法特性从而可以在编译器层面做更加极致的优化,这也是 Golang 的做法;
  • 外部迭代器,Async/Await 语法,& 作为类型,以及显式的生命周期;这些都是如今 Rust 语言基石级别的设计,Garydon 有不同的看法。
  • Lambda 表达式的环境捕获。
  • Traits 的设计,不完全的 Existentials 实现;后者简单地说就是跟 dyn Trait 相关的一系列设计和实现的问题。
  • 复杂的类型推导;这个评论可以参考《对 Rust 语言的分析》中的“类型推导”一节。
  • Nominal 类型;Garydon 喜欢 Golang 的 Structral 类型,这个问题跟 Traits 和 dyn Trait 的使用体验是相关的。
  • 缺少反射的支持。
  • 缺少错误处理系统;Garydon 喜欢 Swift 的错误处理方案。
  • 缺少 quasiquotes 功能,缺少语言级别的大整数支持,缺少语言级别的十进制浮点数支持;这些功能现在部分由生态中的第三方库实现,例如 @dtolnay 的 quote 库实现了 quasiquotes 的能力,但是 Garydon 认为它应该是语言的一部分。
  • 复杂的语法。
  • 缺少尾调用的保证。

可以看到,Garydon 设计和期望中的 Rust 语言跟如今实际成长出来的 Rust 语言大相径庭:关于引用和生命周期的设计,关于 Traits 和类型系统的设计,关于性能和编程效率之间的取舍,Garydon 的思路都不同于 Rust 如今的主流思路。

Garydon 对上面这些异议都分享了他 argue 的历史,实际上,他在博文一开始就对他在 argue 中失败的情形做了分类:

  1. 我计划或初步实现了 X 方案,其他人以高度的热情和坚持支持 Y 方案,包括利用舆论的影响力。最终,他们得到了自己想要的结果,而我失败了。
  2. 我初步实现了 X 方案,其他人更喜欢 Y 方案并做出了原型。Y 方案很吸引人,于是大家转而投入 Y 方案的开发,而我们时间紧迫,没有重新审视 Y 方案的所有缺点。
  3. 我初步实现了 X 方案,但是实际情况(通常是 LLVM 的限制)要求我们采用 Y 方案快速实现。我们暂时开发除了 Y 方案,然后同样由于时间紧迫,我们就一直将错就错的在 Y 方案上持续发展。
  4. 我初步实现了 X 方案,但是实现得明显有问题。于是在正式发布前我们把它从语言核心中移出,免得开发者在错误的基础上构建软件。于是一个语言级别的功能没有实现,后来生态系统或许填补了这一空白,甚至或许有多个竞争者。

对号入座,上面的众多 Garydon 不满意的特性都能分类进这四项中来。Garydon 在介绍这些他不喜欢的特性时多次提到了“我输了”,甚至在关于尾调用的讨论中,他写到:

由于实现尾调用的计划和其他成员对“性能上胜过 C++ 语言”的目标有冲突,我最终被说服不要使用尾调用。于是,我写了一篇悲伤的帖子来表达我对这个结果的不满,这是这个话题中最令人悲伤的事情之一……如果我是“终身的仁慈独裁者”,我可能会让语言朝着保留尾调用的方向发展。早期的 Rust 有尾调用,但主要是 LLVM 的原因让我们放弃了这个功能,而对跟 C++ 在性能上比拼的执著,使得这个结论几乎永远不会被推翻。

这就是 Rust 项目社群不同于其他编程语言项目社群的一个重要特点:它的作者不喜欢语言实际采用的许多核心设计,他没有妥协,也没有说服其他人,最终只能选择离开。

开源世界里有没有作者后来离开的例子,或者朝着不同于最初目标发展的案例呢?其实是有的。

Python 的作者 Guido van Rossum 在前几年也开始输掉不少关于 Python 新语法的争论。终于在 2018 年,他精疲力竭地离开了。但是至少 Python 的几乎所有核心功能都是在他的领导下实现的,同时他也能够以 BDFL 的身份强推社群从 Python 2 迁移到 Python 3 上来,尽管他事后表示这样的事情再来一次他或许从一开始就不会做。而在 Rust 项目社群当中,你很难想象有人能够做出这种级别的 breaking changes 并让社群接受。

C# 的作者 Anders Hejlsberg 在开始搞 TypeScript 以后实际也不怎么参与 C# 的发展了,但是他奠定了 C# 的基础和核心设计,且 C# 总体由思路一致的微软研发团队主导开发,所以这没有带来什么重大的影响。

Apache Flink 的作者最近也去创业搞别的事情了,不过由于社群人丁兴旺,社群发展过程一直是开放讨论达成共识,在早期开发者离开后,现在的开发者持续维护没有什么问题。如果熟悉 Flink 的历史,你会发现起初 Flink 是一个跟 Spark 正面竞争的批处理引擎,是在 2014 年中 Gyula Fora 带着他的实习生在 Flink Runtime 的基础上把整个项目改造成了流计算框架。不同于 Rust 的是,Flink 的作者们愉快地接受了这个改造,并把 Flink 重新定位成带状态的流计算框架全力发展,最终走出了一条不同于 Spark 的竞争之路。

当然,除了作者离开的,还有坚守在原地且不断整合不同人的意见的。在这一点上,做得最出色的毫无疑问是 Linux 的 BDFL 林纳斯·托瓦兹。

《时代周刊》曾经评论到:

有些人生来就注定能领导几百万人,有些人生来就注定能写出翻天覆地的软件。但只有一个人两样都能做到,这就是林纳斯·托瓦兹。

其实,这样的例子在开源世界中实在是少之又少。跟 Graydon 一样离开项目的有没有呢?Redis 的作者或许可以算一个。即使 Redis 的基石是他奠定的,但是后来 RedisLabs 的很多发展明显跟他对 Redis 的期望是有很大出入的,他于是在 2020 年公开声明不在担任 Redis 的维护者。

@withoutboats 的博文

Garydon Hoare 的博文分享了上古时期 Rust 团队的争论,而 @withoutboats 最近的几篇博文补充了许多 Rust “中世纪”的故事。

例如,上一节中 Garydon Hoare 不喜欢的外部迭代器,Async/Await 语法,& 作为类型,以及显式的生命周期这些特性,在 Why Async Rust 一文里 @withoutboats 是高度正面评价的。

那么,@withoutboats 是谁?

他是 Async Rust 的主要实现者之一,主导确定了 Async/Await 的语法,并实现了 Pin 和 Waker 等关键接口。

可惜的是,由于一些 GitHub Commit 和账号关联的问题,我们并不能简单地列出他的所有贡献。不过,就算是所有的 Commits 都能正确显示,@withoutboats 从提交数上看仍然不能排到 Rust 哪怕前 100 的贡献者名单里,从 2020 年开始,@withoutboats 就没有在 Rust 语言相关仓库的新提交了。

如果我们看看 @withoutboats 文章中提到的另外两位 Async Rust 的核心开发者,情况会更加有趣。

aturon-contribution

Aaron Turon 从 2017 年开始就没有任何参与了。

alexcrichton-contribution

Alex Crichton 是非常重要的 Rust 核心开发者。除了 Async Rust 的开发以外,他是 Cargo 项目的核心作者,且以 rust-lang-nursery 为阵地,打造了一批 Rust 早期的关键生态。

然而,他从 2022 年起也慢慢淡出了 Rust 项目社群,投入到 wasmtime 项目的开发中去了。

当然,wasmtime 项目的核心也是以 Rust 语言编写的。Alex Crichton 的这一决定,其实有点像从 2018 年开始淡出了 Rust 项目社群,2019 年加入 PingCAP 开发 TiKV 项目的 Brian Anderson

brson-contribution

2021 年,Brian Anderson 从 PingCAP 离开,关注起区块链公司 Solana Labs 的项目,甚至最近还跟 Graydon Hoare 主持的 Rust 项目 stellar/rs-soroban-env 有些合作,也是一种循环。

这个时候,我们再来看看 @withoutboats 对 Rust 语言演化建议的几篇博文,可能就会有不一样的感受:

《大教堂与集市》第三篇《开垦心智层》里讨论了关于开源软件话语权的问题,里面提到话语权的两种来源:

  1. 代码是你写的,于是你拥有代码的“所有权”,根据“责任背后是权力”的规则,你能够对如何演进这部分代码做定夺。
  2. 争议的双方并没有明确的所有权,但是一方在整个项目中投入更多,也就是在整个项目中拥有更多的领土权,所以他作为资深者胜出。

书中提到,如果这两条规则不能解决,那么“则由项目领导人来决断”。

很显然,Rust 项目不怎么符合这些条件。如前所述,拥有 Async Rust 的代码“所有权”的人都已经离开,且很难说有什么明确的传承。“项目领导人”在 Rust 社群中可以认为并不存在,第一作者 Garydon Hoare 伤心地离开了项目,堪称继父的 Brian Anderson 也从 2017 年起投入到 Rust 写成的项目而非 Rust 语言本身。

如此,Rust 项目社群就进入了 @withoutboats 所观察到的现状:尽管用户对 Async Rust 的后续进展很不满意,尽管 Rust 语言对 Immoveable / Unforgetable / Undroppable 这些能够与编译器深度协作的基础类型的需求是清楚的,但是放眼整个 Rust 社群,对于已经确定要做的事情如何做,争论几乎总是无法收敛,而对于不确定要不要做的提案,尤其是核心假设的进化与兼容方案,更是在未来几年内都看不到达成一致的希望。

@withoutboats 在《关于 Async Rust 的四年计划》的最后一段无不担忧地分享了一个过往的失败经历:

对于那些不了解的人来说,有一个关于 Rust 中 await 运算符应该是前缀运算符(就像其他语言一样)还是后缀运算符(最终的选择)的大辩论。这引起了极大的争议,产生了超过 1000 条评论。事情的发展是,几乎所有语言团队的人都达成了运算符应该是后缀的共识,但我是唯一的反对者。此时,明显已经没有新的论点出现的可能性,也没有人会改变主意。我允许这种状态持续了几个月。我对自己的这个决定感到后悔。显然,除了我屈服于大多数人的意见外,没有其他的出路,然而我却没有第一时间这样做。我让情况不断恶化,越来越多的“社群反馈”重复着已经提出的相同观点,让每个人都筋疲力尽,尤其是我自己。

我从这个经历中学到的教训是:区分真正关键的因素和无关紧要的因素。如果你对某个问题固执己见,最好能够清楚地阐述为什么它很重要,并且它的重要性应该超过语法选择之间微小的差异。自那以后,我试图将这一点铭记在心,以改变我在技术问题上的参与方式。

我担心 Rust 项目从这个经验中得出了错误的教训。正如 Graydon 在这篇文章中提到的,Rust 开发团队坚持只要有足够的构思和头脑风暴,就能找到每个争议的双赢解决方案,而不是接受有时必须做出艰难决策的事实。Rust 开发团队提出的解决这种无休止的争议所带来的 burn out 的方式,是转向内部讨论。设计决策现在主要以 Zulip 线程和 HackMD 文档等未索引的格式进行记录。在公开表达设计方面,主要是在相关开发者的个人博客上发布。如果你是一个开发团队以外的人,那么你几乎不可能理解 Rust 开发团队认为什么是优先事项,以及这些事项的当前状态如何。

我从未见过 Rust 项目与其社群的关系如此糟糕。社群当中存在着无价的知识,封闭自己不是解决方案。我希望看到项目成员重建与社群成员之间的相互信任和尊重的关系,而不是无视目前的敌对和不满的现状。对此,我要感谢那些在过去几个月中与我就设计问题进行交流的项目成员。

Nick Cameron 的博文

对于 @withoutboats 在上一节的最后提出的问题,Nick Cameron 在今年早些时候有几篇文章做出了讨论,直接相关的是这篇《关于开放式协作的一些想法》

非常有趣,在我介绍 Rust 的历史沿革的时候,我大量引用了其核心参与者的博客,这可以看做是 @withoutboats 所提到的“在公开表达设计方面,主要是在相关开发者的个人博客上发布”的习俗对整个 Rust 项目社群工作方式的一个更深远的影响。

那么,Nick Cameron 又是谁?

nrc-contribution

他是 Rust 语言团队曾经的成员,Rust 曾经的核心开发者。跟 Brian Anderson 一样,他在 2019 年加入了 PingCAP 公司,并于 2021 年离开 PingCAP 加入 VS Code 团队做 Rust 语言集成。于是,他在 Rust 主仓库的参与也从 2019 年开始消失,只在做回 Rust 语言层面相关的工作后做了一点工作。可以说,他所写的代码,如今也进入到上一节里提到的困境中:他是作者,但是已经离开太久,且他并未找到明确的继任者,于是这些代码如今是无主的。他对相关代码的演进没有说一不二的话语权,其他人也没有。

其实,在 Rust 项目社群中对编译器或标准库还能有很强势话语权的人,我能想到的大概就是:

  • 语言团队的领袖 Niko Matsakis
  • 编译器团队的成员 Oli Scherer
  • 发布团队的领袖和 Leadership Concil 的成员 Mark Rousskov

其他人就算是所谓 Leadership Concil 的成员或者是某个团队的领袖,从《开垦心智层》里提到的朴素的代码“所有权”的理解方式来看,也不一定是有说服力的。

说回 Nick Cameron 的博文,《关于开放式协作的一些想法》的核心是重申了 Rust RFC 流程的价值,并希望 Rust 在代码以外的治理流程里也坚持相同的开放原则和勇敢做出决定而不是征求所有人意见,这跟 @withoutboats 博文的观点是一致的。

实际上,Nick Cameron 在前两年年强力推进 Async Rust 的部分工作和 GAT 进入稳定版本。后者引起了巨大的争论,这同样让他精疲力竭,写下这两篇文章表达自己对这些争论的看法:

在具体项目的开放式协作以外,Nick Cameron 的其他几篇博文揭示了 Rust 项目社群今日协同格局的另一个重要影响因素:Rust 基金会。

评论

Rust 项目社群发展成今天的样子,其最核心影响因素,就是开发层面没有一个说一不二的领导人,或者一个团结的核心团队。

相信很多人还记得前两年 Rust Mod Team 集体辞职的事情,作为某种后续,实际上 Mod Team 批评的 Core Team 成员包括 Core Team 本身也都从 Rust 社群中消失了。

取代 Core Team 的是所谓的 Leadership Council 组织.该组织于今年六月份成立,起初每周有一次会议,现在减少到每个月有一次会议。讨论的内容主要关注治理、流程和标准问题。

这种情况是否经常发生呢?实际上它也不算罕见。

几乎可以称作 1:1 复刻的例子是 Perl 社群 2021 年的一个故事:致力于激进推动 Perl 发展的项目领袖 Sawyer X 想要推动 Perl 7 版本的发布,结果被其他项目成员联手弹劾。最终项目夭折,他也失望地离开了 Perl 语言团队。Perl 如今也是由一个叫 Perl Steering Council 组织管理。不过不同点是 PSC 尽管在语言发展上相对保守,但确实是领导语言开发的,且工作内容全面公开

作为补充,有心的读者可能已经发现上面列举的三位撰写跟 Rust 发展相关的博文的前核心开发者,如今并不在 Rust 的任何治理机构上。所以尽管没有那么激烈,但是他们的处境和 Sawyer X 是有某种相似之处的。

最后,Rust 项目社群的未来会如何发展呢?我想最有可能的结局,就是像 C 或者 C++ 那样,演化成由标准化委员会主导的语言项目。

实际上,这个新的 Leadership Council 做的一件重要的事情就是开始搞所谓的 Rust 标准

Rust 不像 C# 和 Golang 那样,语言本身就是某家公司的独占软件;也不像 Java 那样,虽然有 JCP 和委员会,但是 Oracle 以模块化提案为契机,奠定了自己几乎说一不二的地位;更不像 Ruby 或者 Elixir 这样的个人作者可以作为 BDFL 拍板。

Rust 基金会的成员就像 C++ 标准化委员会里的成员那样,哪个不是行业大鳄,哪个不是已经有或者打算有海量 Rust 生产代码。为了保护自己生产代码不被 Rust 演进制造出庞大的维护迁移成本,这些厂商势必要尽己所能的向 Rust 项目社群发挥自己的影响力。

由于 Rust 的第一作者和绝大多数早期核心作者已经长期离开项目社群,即使现在回来也不可能再建影响力。唯一有足够长时间和技术经验的 Niko Matsakis 又只关心语言技术发展,甚至 Leadership Council 的语言团队代表也让其他成员参与。这种情况下,Rust 项目社群的个人开发者,是不可能跟基金会里的企业有对等的话语权的。

实际上,如果 Rust 真能发展到 C++ 那样的状况,即使 C++ 有公认的第一作者 Bjarne Stroustrup 存在,他也无法在 C++ 委员会中强力推行自己的主张。

如果你对这样的未来感到好奇,推荐阅读 Bjarne Stroustrup 的论文 Thriving in a Crowded and Changing World: C++ 2006–2020 的第三节 C++ 标准委员会。我预计是已有之事,后必再有。

Apache Pulsar 的社群指标

去年十一月,我成为了 Apache Pulsar 社群 Committers 的一员。

成为 Committer 之前和之后,我都积极参与了代码仓库上 Issue 和 Pull Request (PR) 的处理回应和评审。去年十二月期间,我把未解决的 Issue 和 PR 数量分别从接近 2000 个和 400 个,在动态增长的情况下缩小到了小于 1000 个和接近 200 个。

关于如何高效地、分门别类地处理社群积压的议题和补丁,我可能会用另一篇文章来讨论。本文想讲的是在这个过程里我产生的另一个想法,那就是如何分析在这类过程中产生的活动,从而判断社群的健康情况呢?

tisonkun activities in 2022

数据源与展示

要想做数据分析,首先应该确定使用什么数据源。不同数据源的数据质量和数据口径有很大的差距,可能导致同一个逻辑概念最终算出来的结果之间大相径庭。

我们的目标是分析社群成员的在 GitHub 平台上的活动指标和趋势,而 GitHub 本身提供了 Event 接口获取平台上的所有公开活动。

不过,这个接口返回的数据只有最近九十天的数据,要想获取完整的数据,就需要依靠一个从近十年前已经开始爬取 GitHub Events 数据并提供开放下载的项目 GH Archive 了。关于如何从这个数据集构建一个自有的数据源,可以参考《基于 ClickHouse 的 GitHub 事件数据库》这篇文章。

GH Archive 提供的是 GitHub 全平台所有活动的数据。由于 GitHub 日益活跃,这个数据集的总体大小在几个 TB 的量级。虽然对于一个具体项目社群比如 Pulsar 来说,筛选出与己相关的仓库的活动最终大约只有 MB 量级的数据,但是使用 GH Archive 的数据始终要过一遍全量的活动数据。

另一种方案是把 GH Archive 的工作也集成到数据源制作的工具集里,直接从 GitHub 的 Event 接口里仅拉取相关仓库的活动。这样做可行的原因是往往最近的数据才是最有价值的,一个仓库五年前是什么活跃水平,很大程度上只是一个图一乐的数字。不过,要想这么做,数据源生成的流水线也不简单,需要维护一个定期任务的调度,以及数据去重和补偿的逻辑。

最终,我选择了国内 X-Lab 开放实验室维护的、基于 GH Archive 数据的、存储在 ClickHouse 当中并提供相同查询接口的 GitHub Event 数据集。该数据集的数据模式定义是公开的,相比 ClickHouse.com 提供的 github_events 数据集,X-Lab 的数据集包含了实验室在开源数据指标工作中发现的有效提高数据可用性的字段,包括 repo_idactor_id 等等。此外,X-Lab 的数据集本身是其开源数据指标工作的数据基础,维护投入力度和问题响应速度更可靠。

在数据展示层面,我选择了 ClickHouse 去年官方支持的 Apache SuperSet 系统。你可以选择 follow 下面两个文档自建系统,或者直接使用 Preset 提供的 Free Tier 服务,其中已经预装了连接 ClickHouse 所需的组件。

配置好数据源并连接上展示系统之后,就可以开始探索社群活跃指标了。

评审人数人次

我们想要评估的活跃指标是参与 Issue 和 PR 处理与评审的状况,可以从最基本的指标开始做起:数数。

把活动范围限制在 Pulsar 主仓库当中,我们可以用如下 SQL 来取得一段时间内评审 PR 的人数:

1
2
3
4
5
6
7
8
9
10
11
12
SELECT
toStartOfMonth(toDateTime(`created_at`)) AS timestamp,
COUNT(DISTINCT actor_login),
COUNT(DISTINCT actor_login, issue_number)
FROM `opensource`.`gh_events`
WHERE `created_at` >= '2022-01-01 00:00:00'
AND `created_at` < '2023-01-01 00:00:00'
AND `repo_name` = 'apache/pulsar'
AND `type` IN ('PullRequestReviewEvent',
'PullRequestReviewCommentEvent')
GROUP BY timestamp
ORDER BY timestamp

其中第一个 COUNT DISTINCT 结果表示的是在给定时间段内有多少个不同的 Reviewers 参与,而第二个 COUNT DISTINCT 的结果则是计算人次,即只把同一个人在同一个 PR 当中的 Review 动作合并,而不是把同一个人所有的 Review 活动都做合并。

在 SuperSet 的 UI 上做一些配置,我们可以把上面的 SQL 对应到下面的折线图:

Pulsar Reviews Count in 2022

可以看到,Pulsar 社群在去年一年的 Reviewer 数量维持在 60~70 人之间,而在 10 月之后 Review 人次计数有所下降。

对这个数字的解释,人数上,Pulsar 社群的活跃 Reviewer 群体没有大的变化。人次上,在年中开始发布 2.11 之后,出现了一系列测试出来的回退,核心开发者专注于解决这些回退,而解决回退最好是尽量少的改动,因此 PR 的总体新增数量少了。同时,cheery-pick 和处理回退的 PR 相对直观,往往只需要一两名 reviewer 赞同就已经合并,而不会像新功能一样有大量的成员发表意见。

交叉对比 PR 创建数量的图,可以印证这个想法:

Pulsar Pull Request Opened Count

最后,我们以 DISTINCT Reviewr 的人数为指标,比较一下知名的消息系统项目的活跃情况:

Reviewers Comparation

可以看到,在这一维度下,Pulsar 的活跃度是最高的,Kafka 次之;RocketMQ 和 RedPanda 是下一个梯队;而 ActiveMQ 和 RabbitMQ 的活跃度就快见底了。当然,这个数据只计算和主仓库的数值,而没有把所有仓库群加总起来算,但是加总的结果应当也是大同小异的。另一方面,核心能力的绝大部分开发工作还是发生在主仓库上,计算主仓库的活跃度可以见微知著。

平均响应时间

除了最基本的计数工作,我们还可以计算跨越一定时间的指标。例如,在工程效能乃至服务保障中都常包括的平均响应时间。结合我们这里想要评估的 Issue 和 PR 处理与评审的状况,我们可以分别计算 Issue 和 PR 从创建到首次被响应的时间,以及从创建到关闭的时间。

数据口径和挑战

不过,不同于简单计数指标,在事件粒度的数据集上做具有时间跨度的指标必定涉及数据之间的关联。如果不是类似 Apache Pulsar 活跃参与者每月看板这样,在取出数字后再经过程序处理,而是类似上面直接使用 SuperSet 作为一站式数据分析展示工具,那么关联数据的需求映射到 SQL 当中就是 JOIN 的语义,这会导致查询复杂度陡然上升一个量级,写出正确或者说合理的查询面临诸多挑战。

第一个是数据关联本身的复杂性。

要想知道具体一个 Issue 从创建到首次响应,再到被处理完毕一共花了多久,最准确的方法是通过 GitHub API 直接查询 Issue 的元数据,首先看它是否被关闭,如果被关闭,是何时被关闭。进一步地,列举 Issue 上的 comment 并取得最先提出的那个的时间,与 Issue 创建时间求差。

然而,这类数据是变化的,即使每天定时任务同步,也只能给特定的一个查询使用,从数据治理角度来说成本太高,最好是就写一个程序加上缓存硬算。这不仅脱离了 SuperSet 提供的框架,本身也会被 GitHub API 的 rate limit 限制。

从 GH Archive 数据集的事件信息出发,要想做这样的计算,就得首先取得 Issue 编号,再和其他 Issue 系列事件关联,适当排序后才能得到正确的结果。为了减少关联的复杂性,往往分析存储的每行会冗余信息。ClickHouse.com 和 X-Lab 的数据集都是一张大宽表,而 X-Lab 的数据更是每行都有 issue_created_atissue_closed_at 这样的预聚合信息。

第二个是数据关联对数据质量的高要求。

SQL 查询要想得到正确的结果,能不能正确关联上多个事件是非常重要的。而实际数据集至少会面临以下问题:

  1. 数据丢失。GitHub 本身的服务就是不稳定的,API 上能查到服务恢复后的状态,但是事件信息说丢了就是丢了。很多基于 GH Archive 数据集计算的结果在涉及 2021 年 10 月的数据时就容易出错,正是因为 GitHub 那一个月丢了近三天的事件数据。
  2. 异构社群。例如,有些社群会用机器人来做首次回复,计算社群活跃度计入和不计入机器人的行为显然是不一样的。而不同的社群有不同的自动化工具和流程,进行数据关联时需要处理这些细节问题。
  3. 不单调的数据。计算 Issue 创建到首次响应的指标相对还是简单的,因为第一次响应是个确定的指标,一旦得出结果就不再变化。但是从创建到关闭并不是,因为还有 Reopen 的情况。如果要把 Reopen 的情况考虑进去,数据口径和计算方法就需要重新评估。

第三个是关联时间的指标的口径差异。

OSSInsightOpenDigger 对上述两个时间数据的算法是以结束时间为基准的,即它们所说的 2022 年 12 月 Pulsar 社群 Issue 创建到关闭的平均时间,指的是在 2022 年 12 月被关闭的 Issue 从创建到关闭的平均时间。这样说你可能还没感觉出问题,我举个实例:我在 2022 年 11~12 月关闭了一大批年代久远的 Issue 和 PR 的行为导致 OSSInsight 上的指标在这两个月突然变差:

OSSInsight Pull Request Time to Merge

OSSInsight Issue Time to Respond

但是,实际上这与这两个月新创建的 Issue 和 PR 的处理实效关联不大。

同时,可以注意到 OSSInsight 只给出了 Issue 首次响应,以及 PR Merge 的时效。PR 关闭后可以重新打开,但是合并后则不再能转变状态。这一点,加上从结束时间倒算时间跨度,都是为了好算而做的选择。

下面,我会说明我所采用的指标口径、结果及社群之间的对比。

指标结果与对比

我所计算的时效指标,时间分区的逻辑是以开始时间作为基准的,即 2022 年 12 月 Pulsar 社群 Issue 创建到首次回应的平均时间,指的是在 2022 年 12 月期间新建的 Issue 被首次回应的平均时间。这个首次响应可能发生在 2022 年 12 月以后的任何一个时间。

不过,马上你就会发现两个问题:第一个是如果 Issue 迄今为止还没被响应,那这个时间就是未定义的;第二个是按照这种算法,计算出来的时效一定不会超过从创建到当前的时间,换言之,这个月的时效一定不会超过一个月。

为了辅助以开始时间作基准的指标逻辑,这里需要额外计算 Issue 和 PR 被处理的比例。我先展示最终计算得到的图表:

Pulsar 社群 Issue 和 PR 响应时效

右边一列就是区分所有在给定时间段内创建的 Issue 或 PR 的响应情况占比。可以看到,如同上面说明的,Issue 和 PR 创建的时间越近,其得到处理的比例就越小。而处理时效仅考虑被处理的情况,近期的数据总是相对较好。

实际在指导社群开展工作的时候,可以使用类似:在未处理 Issue/PR 比例不超过 N% 的前提下,平均响应时间不超过 M 小时。

不过,由于后来响应会导致原先的平均值被离群值拉高,所以在执行时可以采用分位数的维度来减少离群值的影响,同时仅在每月一号当天查看上个月的情况,而不是一直监控。

具体的计算 SQL 语句,以 PR 的回复时效为例,写作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
SELECT issue_number,
groupArray(b.actor_login)[1] AS author,
groupArray(actor_login)[1] AS reviewer,
dateDiff('hour', opened_at, commented_at) AS time_to_comment,
groupArray(b.created_at)[1] AS opened_at,
groupArray(created_at)[1] AS commented_at,
groupArray(type)[1] AS type,
groupArray(action)[1] AS action_type
FROM
(SELECT actor_login,
created_at,
type,
action,
issue_number
FROM `opensource`.`gh_events`
WHERE repo_name = 'apache/pulsar'
AND type IN ('PullRequestEvent',
'PullRequestReviewCommentEvent',
'PullRequestReviewEvent',
'IssueCommentEvent')
AND actor_login NOT IN ('github-actions[bot]',
'codecov-commenter')
ORDER BY created_at) a
JOIN
(SELECT actor_login,
created_at,
issue_number
FROM `opensource`.`gh_events`
WHERE repo_name = 'apache/pulsar'
AND type = 'PullRequestEvent'
AND action = 'opened') b ON a.issue_number = b.issue_number
WHERE a.actor_login != b.actor_login
GROUP BY issue_number
ORDER BY issue_number

由于 X-Lab 的数据做了一些预聚合,计算 PR 合并时效可以不用 JOIN 语句:

1
2
3
4
5
6
7
8
9
10
11
SELECT issue_number,
MIN(issue_created_at) AS opened_at,
MAX(pull_merged_at) AS merged_at,
dateDiff('hour', opened_at, merged_at) AS time_to_merge
FROM `opensource`.`gh_events`
WHERE repo_name = 'apache/pulsar'
AND type IN ('PullRequestEvent',
'PullRequestReviewCommentEvent',
'PullRequestReviewEvent')
GROUP BY issue_number
HAVING notEmpty(groupArray(pull_merged_at))

计算比例的 SQL 查询结构相同,但是在最外层 SELECT 仅保留 issue_numberopened_at 两列,并用 CASE WHEN 来区分类别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- Respond --
(CASE
WHEN COUNT(DISTINCT actor_login) > 1 THEN 'respond'
WHEN notEmpty(groupArray(pull_merged_at)) THEN 'selfmerge'
WHEN notEmpty(groupArray(issue_closed_at)) THEN 'selfclose'
ELSE 'norespond'
END) AS status

-- Merge --
(CASE
WHEN notEmpty(groupArray(pull_merged_at)) THEN 'merged'
WHEN notEmpty(groupArray(issue_closed_at)) THEN 'closed'
ELSE 'open'
END) AS status

Issue 的情况大同小异,需要注意的是也需要从 IssuesEvent + opened 的查询里获取 issue_number 的值,而不能直接用 gh_events 里的 issue_number 字段,因为后者还包括 PR 的情况。

最后是同类社群之间做一个指标的比较。由于 Kafka 的 Issue 用 JIRA 来管理,同时不同体量的社群在时效上会有较大的区别,例如线下集中开发的为主的团体几乎总是能立即响应团队内部的需要,因此这里只对 PR 的响应和合并时效,针对 Apache Pulsar 社群和 Apache Kafka 社群做一个比较。

PR 响应时效的比较结果如下:

Pulsar vs. Kafka PR 响应时效

PR 合并时效的比较结果如下:

Pulsar vs. Kafka PR 合并时效

可以看到,在去年一年的时间里,Pulsar 的社群活跃度在这个维度上全面超过了 Kafka 社群。

小结

应该说,Pulsar 社群的活跃,一方面得益于其丰富的功能集带来的海量开发需求,另一方面也是社群多样化的成员组成带来的活力促进的。不同于 Kafka 上游主要依靠 Confluent 公司的雇员开发,一旦公司技术转向就会活力骤减,Pulsar 社群从提供商、集成商到应用企业,再到喜欢完整的 MQ 语义的个人开发者,不同人的需求在这里交汇和实现,与参与者同等数量的 Reviewer 参与评审,为软件长青提供了坚实的社群保障。

应该说,Pulsar 目前的开发者社群活跃度,还能够接纳不少寻找分布式消息系统的架构师,以及想要学习消息系统概念与实现的开发者。我在过去的半年里,逐步撰写了 Pulsar 社群的开发者指南,以期能够帮助到有意愿的开发者快速搭建一个本地开发环境,并参与到典型的开发活动当中来。此外,我还会在接下来的一两个月里,梳理出 Pulsar 社群生态的全貌,从而展示 Pulsar 生态的价值与其目前需要帮助的部分。按照《共同创造价值》的思路,这样就可以回答“我能为社群做什么”以及“应该怎么做到”的两个问题。

开源社群需要什么代码贡献?

几天前 @Xuanwo 的一篇文章《开源运营当论迹不论心》讨论了 TDengine 代码灭虫计划活动中不尊重开源协同的规律,只是通过市场运营手段强行把开发者推进来的误区。

本文从这个例子出发,进一步举例讨论开源社群需要什么样的代码贡献。

代码贡献应该拒绝形式主义

TDengine 这一活动最大的问题,在于它违反了软件开发的常识。

  1. 可以通过 linter 彻底解决的问题,反而“养寇自重”以不断在主分支产生风格问题以“等待解决”。
  2. 可以随手解决的问题,例如 fix typo 等,反而专门建立 issue 甚至做成活动找“外部”成员来做。

我在不同场合都坚持这样一个观点,开源协同的目的是生产高质量的软件。源代码开放带来的软件自由不提,我们运营一个开发者社群,是为了共同创造出解决实际问题的软件,跨越不同公司、组织和国籍的边界,在整个行业的范围内协同开发者做好一个软件。

TDengine 的做法反其道而行之,目的很明显都指向 issue 数量和 contributor 的数量,而不是一个更好的软件。开源软件不会因为参与的人变多就自动变好,为了追求数量刷出来的 issue 甚至降低了开发效率。

《社区运营的艺术》反复提到,必须时刻防范形式主义。形式主义不仅让社群运转变得低效,并且会让关注问题解决,努力参与其中的成员感到挫败,因为他们不能以最优的解决方案处理问题,或者眼看着社群无意义的消耗却无能为力。

同样是处理 Linter 的问题,TiDB 的做法就正常得多。

Make some linters really happy 这个 issue 首先引入了 golangci-lint 工具,然后由于工作量较大,分拆成不同的子任务欢迎其他参与者共同完成。

不过,TiDB 也有过形式主义的问题。为了显得“开放”,没有协调开发工作流,而是一次性地发布每个季度的工作计划,随后置之不理。这样的操作是没有意义的。

例如 Call For Participation: SIG-Planner 2020/Q1 Plan 这个两年前的 issue 至今仍然没有解决,且不知道其中的子问题解了没解。

TiDB 2.4k 个 open issue 当中,有相当部分是各种“开放”运动创建的,事后就成了孤儿。这样试图激活代码贡献的手段,一而再再而三的烂尾,最终消磨了所有参与者的耐心。

代码贡献应该源自实际需求

软件总是被它的使用者定义的。闭门造车做的功能和优化很容易脱离实际,飘在天上。开发开源软件,讲清楚软件是怎么用的是很重要的。因为软件本身就是为了解决问题,优化和功能如果不能更好的解决问题,或者甚至不知道问题是什么,那就变成空中楼阁,华而不实了。代码贡献除了避免形式主义,还应该源自实际需求。

比如,Apache BookKeeper 提案并实现了基于 etcd 的元数据管理,是因为 etcd 真的被广泛使用了,并且在云端部署环境下,很有可能有一个现成的 etcd 服务。集成 etcd 能够服务这类用户的场景,并且可能减少额外部署一套 ZooKeeper 集群的开销。

比如,Apache ZooKeeper 讨论过 Watches 不能够看到所有事件,而是单次触发的语义是否需要改进。Ted Dunning 给了一个非常经典的回应。

If you want to see all events, use Kafka.

这个回应也得到了项目作者 Patrick Hunt 的认可。每个软件都有自己的定位,解决它所要解决的问题。用户永远不可能在一个软件当中解决所有问题,适当的组合不同的软件形成解决方案,是应用工程师的本职。对于开源社群来说,这也意味着它总是不能期待解决所有问题,而应该创造出可组合的基础构建块,并积极地和其他社群联合。如果你想解决所有的问题,那么结果往往是每个问题都解决得不好。

对于新功能的需求,通常首先应该讨论背景和动机,也就是谁为什么需要这样的能力,并且最好有实际的用户,比如提议人自己。我参与设计实现的 FLIP-85 Flink Application Mode 就立足于当时几家公司内部 Flink 作业部署运维的实际需求,功能实现之后,马上就可以测试投产。

对于缺陷修复,首先应该复现问题,确认问题有效之后定位问题再进行修复。如果问题无效,也就不需要提交代码修复了。例如无法复现、设计就是如此或使用方式有误,只需要分别回应 issue 后关闭。如果是有效而严重的问题,一般会由核心成员直接上手解决。例如 Log4ShellSpringShell 这样的情况,是不可能公开发布等待新的开发者出现并解决的。

即使是不严重的问题,一般来说相应模块的处理人也可以自己解决。对于不算困难的问题,如果社群活跃成员较多,且有新成员需要锻炼,可以推送到对应群体邀请解决。当然,由于是实际的缺陷修复问题,不会阻塞等待其他人,一旦自己有时间,或者发布日期将近,也就自己做掉了。如果其他人尝试解决但是不能如期完成,也应该友善说明并接手。

如果确认的软件缺陷没人主动解决,并且没有同类问题持续出现,那么说明这个问题可能不是一个真实存在问题。SkyWalking 的创始人吴晟在 Twitter 上提到“我们在 SkyWalking 信奉,没有人贡献的特性等于没用的特性”。同样,没有人修复的问题很可能就是不需要修复的问题。

对于重构,首先要问的是必要性。代码总是有不止一种方法写成,同义转换对于软件核心价值的开发没有直接意义。

一种有意义的重构是模块专家主导的重构。实际上,任何一个开发者在开发一个软件的时候,心里都会有一个认识模型。不同人的认识模型是不一样的。对于一个多人参与开发的软件来说,一个人读到另一块代码怎么看怎么不舒服,以至于不能很好的开发新功能或者修复缺陷的情况是很常见的。这也是软件开发人员交接之后,往往后继者会先把原来的代码进行一次重构的原因。只有符合核心开发者的认识模型,软件开发效率才能提升。反过来说,如果提交重构的补丁作者并没有深入参与该模块开发的需要,那么他的重构理由就很难独立成立,因为真正在开发这块代码的人很可能不喜欢这样的重构。这其实也是开源社群 Earn Authority by Contribution 的一个佐证。

另外一种有意义的重构是解决实际问题的重构。例如我在 TiDB 当中发起的 Tracking issue for restructure tests 测试框架重构的主要出发点,就是目前的测试框架无法与 GoLand 集成,本地运行测试代价太大,导致开发者倾向于不写测试或者只写简单的测试,而缺乏测试覆盖率的软件,很难有信心合并代码变更,因为谁也说不准只是编译通过的补丁会不会引入额外的问题。

开源软件的开发过程当中,从一个框架迁移到另一个框架,往往能够产生大量的工作。例如 TiDB 的执行引擎换到基于 Chunk 的执行框架,就产生了数十名新的参与者。

同样的,测试框架从 pingcap/check 迁移到 testify 也产生了数十名新的开发者。其中有的人从这里切入到 TiDB 社群,为 TiDB 站台;有的人重新回到了 TiDB 社群,再度找到自己的兴趣点。

确实,这样的活动未必能够有很好的“留存率”。但是开发者社群为什么要追求留存率呢?我在参与多个开源软件开发的过程当中总结到的是,如果我对这个软件非常满意,没有遇到什么需要解决的问题,那么我不会需要刻意去创造需求解决需求。维持一个开源社群,固然需要核心成员和资深开发者的长期投入也即留存,但是能够为软件解决某个特定问题的人,即使他只解决了这个问题就再也不出现了,难道创造的价值就会被消失吗?

例如,Apache Flink DataStream API 的核心作者 Gyula Fora 在 2014-2015 年完成这项工作后,六年间几乎销声匿迹。最近被 Apple 招募之后,在新的需求和开源领域影响力的驱动下,又开始了 Flink Kubernetes Operator 的密集开发。他所做的代码贡献,出自于自己的需求,也契合 Flink 社群中用户的需要,因为这样的原因提交的代码贡献,才能避免形式主义和为做而做的误区,直面需求并解决需求。

开源社群的参与者来来往往,作为项目维护者在维护项目期间,只要保证项目整体前进即可,不用太过关心某一个人为什么来了又为什么走了。而且,软件开发从来不相信人月神话,不是人数越多,就越能开发出好的软件。换个角度说,即使是 Apache 软件基金会的顶级项目,Committer 数量超过 100 个的也不过 6 个项目。对于现在动辄号称自己数百名开发者,上千名开发者的开源社群来说,核心成员占比极低,不是很正常吗?

我在发起或参与这样的活动的时候,只关注活动所绑定的这个开发任务,在活动的形式下是不是被高效地解决。至于活动结束后参与者能否找到与社群的共同利益,留下来共同成长,这至少不是活动本身所能完成的任务。

不过,以实际需求为出发点的活动,因为与参与者一同实际解决问题的经历,确实有可能为开源社群引入新鲜血液。

@Xuanwo 就是从 CNCF Community Bridge 活动参与到 TiKV 社群当中的,他为 TiKV 实现了 Enum 等多个算子的下推,并且至今仍然关注 TiKV 社群。

近期开源的 RisingWave 流数据库的存储引擎的主要开发者之一 @skyzh 也是通过 CNCF Community Bridge 参与到 TiKV 社群的。他在 TiKV 主力开发了 AgateDB 这个基于 LSM Tree 的存储引擎,虽然这个项目 TiKV 后来没有继续下去,但是在 RisingWave 的存储引擎当中延续了它的精神。@skyzh 最近发布的用 Rust 做类型体操系列,我想也跟为 TiKV Coprocessor 设计 RPN 表达式有关。

Apache SkyWalking 通过 GSoC 的活动吸引到了 @fgksgf 的参与,并在完成相关工作后顺利成为了 Apache SkyWalking Committers 的一员。他强大的带货能力把 Apache SkyWalking Eyes 带到了 TiDB 项目里,也是因为这个原因我才知道了这个项目并且进一步大规模宣传 Apache SkyWalking Eyes 在自动化检查 License Header 的优质体验。最近又看到他主导发布了 Apache SkyWalking CLI 0.10.0 的消息,很为他高兴。

其实,本不需要这篇文章来讲一个浅显的道理,开源协同的目的是生产高质量的软件。为了这个目的产生的代码贡献,才是开源社群需要的代码贡献。让社群成员切身体会到自己在参与制造一个伟大的软件,才是维持社群吸引力的不二法门。

Apache 开源社群的经验

大约二十年前我刚开始进入互联网的世界的时候,支撑起整个网络的基础设施,就包括了 Apache 软件基金会(ASF)治下的软件。

Apache Httpd 是开启这个故事的软件,巅峰时期有超过七成的市场占有率,即使是在今天 NGINX 等新技术蓬勃发展的时代,也有三成左右的市场占有率。由 Linux、Apache Httpd、MySQL 和 PHP 组成的 LAMP 技术栈,是开源吞噬软件应用的第一场大型胜利。

我从 2018 年参与 Apache Flink 开始正式直接接触到成立于 1999 年,如今已经有二十年以上历史的 Apache 软件基金会,并在一年后的 2019 年成为 Apache Flink 项目 Committer 队伍的一员,2020 年成为 Apache Curator 项目 PMC(项目管理委员会)的一员。今年,经由姜宁老师推荐,成为了 Apache Members 之一,也就是 Apache 软件基金会层面的正式成员。

我想系统性地做一个开源案例库已经很久了。无论怎么分类筛选优秀的开源共同体,The Apache Community 都是无法绕开的。然而,拥有三百余个开源软件项目的 Apache 软件基金会,并不是一篇文章就能讲清楚的案例。本文也没有打算写成一篇长文顾及方方面面,而是启发于自己的新角色,回顾过去近五年在 Apache Community 当中的经历和体验,简单讨论 Apache 的理念,以及这些理念是如何落实到基金会组织、项目组织以及每一个参与者的日常生活事务当中的。

不过,尽管对讨论的对象做了如此大幅度的缩减,由我自己来定义什么是 Apache 的理念未免也太容易有失偏颇。幸运的是,Apache Community 作为优秀的开源共同体,当然做到了我在《共同创造价值》一文中提到的回答好“我能为你做什么”以及“我应该怎么做到”的问题。Apache Community 的理念之一就是 Open Communications 即开放式讨论,由此产生的公开材料以及基于公开材料整理的文档汗牛充栋。这既是研究 Apache Community 的珍贵材料,也为还原和讨论一个真实的 Apache Community 提出了不小的挑战。

无论如何,本文将以 Apache 软件基金会在 2020 年发布的纪录片 Trillions and Trillions Served 为主线,结合其他文档和文字材料来介绍 Apache 的理念。

以人为本

纪录片一开始就讲起了 Apache Httpd 项目的历史,当初的 Apache Group 是基于一个源代码共享的 Web Server 建立起来的邮件列表上的一群人。软件开发当初的印象如同科学研究,因此交流源码在近似科学共同体的开源共同体当中是非常自然的。

如同 ASF 的联合创始人 Brian Behlendorf 所说,每当有人解决了一个问题或者实现了一个新功能,他出于一种朴素的分享精神,也就是“为什么不把补丁提交回共享的源代码当中呢”的念头,基于开源软件的协作就这样自然发生了。纪录片中有一位提到,她很喜欢 Apache 这个词和 a patchy software 的谐音,共享同一个软件的补丁(patches)就是开源精神最早诞生的形式。

这是 Apache Community 的根基,我们将会看到这种朴素精神经过发展形成了一个怎样的共同体,在共同体的发展过程当中,这样的根基又是如何深刻地影响了 Apache 理念的方方面面。

Apache Group 的工作模式还有一个重要的特征,那就是每个人都是基于自己的需求修复缺陷或是新增功能,在邮件列表上交流和提交补丁的个人,仅仅只是代表他个人,而没有一个“背后的组织”或者“背后的公司”。因此,ASF 的 How it Works 文档中一直强调,在基金会当中的个体,都只是个体(individuals),或者称之为志愿者(volunteers)。

我在某公司的分享当中提到过,商业产品可以基于开源软件打造,但是当公司的雇员出现在社群当中的时候,他应该保持自己志愿者的身份。这就像是开源软件可以被用于生产环境或者严肃场景,例如航空器的发射和运行离不开 Linux 操作系统,但是开源软件本身是具有免责条款的。商业公司或专业团队提供服务保障,而开源软件本身是 AS IS 的。同样,社群成员本人可以有商业公司雇员的身份,但是他在社群当中,就是一个志愿者。

毫无疑问,这种论调当即受到了质疑,因为通常的认知里,我就是拿了公司的钱,就是因为在给这家公司打工,才会去关注这个项目,你非要说我是一个志愿者,我还就真不是一个志愿者,你怎么说?

其实这个问题,同样在 How it Works 文档中已经有了解答。

All participants in ASF projects are volunteers and nobody (not even members or officers) is paid directly by the foundation to do their job. There are many examples of committers who are paid to work on projects, but never by the foundation itself. Rather, companies or institutions that use the software and want to enhance it or maintain it provide the salary.

我当时基于这样的认识,给到质疑的回答是,如果你不想背负起因为你是员工,因此必须响应社群成员的 issue 或 PR 等信息,那么你可以试着把自己摆在一个 volunteer 的角度来观察和参与社群。实际上,你并没有这样的义务,即使公司要求你必须回答,那也是公司的规定,而不是社群的要求。如果你保持着这样的认识和心态,那么社群于你而言,才有可能是一个跨越职业生涯不同阶段的归属地,而不是工作的附庸。

社群从来不会从你这里索取什么,因为你的参与本身也是自愿的。其他社群成员会感谢你的参与,并且如果相处得好,这会是一个可爱的去处。社群不是你的敌人,不要因为公司下达了离谱的社群指标而把怒火发泄在社群和社群成员身上。压力来源于公司,作为社群成员的你本来可以不用承受这些。

Apache Community 对个体贡献者组成社群这点有多么重视呢?只看打印出来不过 10 页 A4 纸的 How it Works 文档,volunteer 和 individuals 两个词加起来出现了 19 次。The Apache Way 文档中强调的社群特征就包括了 Independence 一条,唯一并列的另一个是经常被引用的 Community over code 原则。甚至,有一个专门的 Project independence 文档讨论了 ASF 治下的项目如何由个体志愿者开发和维护,又为何因此是中立和非商业性的。

INDIVIDUALS COMPOSE THE ASF 集中体现了 ASF 以人为本的理念。实际上,不止上面提到的 Independence 强调了社群成员个体志愿者的属性,Community over code 这一原则也在强调 ASF 关注围绕开源软件聚集起来的人,包括开发者、用户和其他各种形式的参与者。人是维持社群常青的根本,在后面具体讨论 The Apache Way 的内容的时候还会展开。

上善若水

众所周知,Apache License 2.0 (APL-2.0) 是所谓的宽容式软件协议。也就是说,不同于 GPL 3.0 这样的 Copyleft 软件协议要求衍生作品需要以相同的条款发布,其中包括开放源代码和自由修改从而使得软件源代码总是可以获取和修改的,Apache License 在协议内容当中仅保留了著作权和商标,并要求保留软件作者的任何声明(NOTICE)。

ASF 在软件协议上的理念是赋予最大程度的使用自由,鼓励用户和开发者参与到共同体当中来,鼓励与上游共同创造价值,共享补丁。“鼓励”而不是“要求”,是 ASF 和自由软件基金会(Free Software Foundation, FSF)最主要的区别。

这一倾向可以追溯到 Apache Group 建立的基础。Apache Httpd 派生自伊利诺伊大学的 NCSA Httpd 项目,由于使用并开发这个 web server 的人以邮件列表为纽带聚集在一起,通过交换补丁来开发同一个项目。在项目的发起人 Robert McCool 等大学生毕业以后,Apache Group 的发起人们接过这个软件的维护和开发工作。当时他们看到的软件协议,就是一个 MIT License 精神下的宽容式软件协议。自然而然地,Apache Group 维护 Apache Httpd 的时候,也就继承了这个协议。

后来,Apache Httpd 打下了 web server 的半壁江山,也验证了这一模式的可靠性。虽然有些路径依赖的嫌疑,但是 ASF 凭借近似“上善若水”的宽容理念,在二十年间成功创造了数以百亿计美元价值的三百多个软件项目。

纪录片中 ASF 的元老 Ted Dunning 提到,在他早期创造的软件当中,他会在宽容式软件协议之上,添加一个商用的例外条款。这就像是著名开源领域律师 Heather Meeker 起草的 The Commons Clause 附加条款。

Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software.

附加 The Commons Clause 条款的软件都不是符合 OSD 定义的开源软件,也不再是原来的协议了。NebulaGraph 曾经在附加 The Commons Clause 条款的情况下声称自己是 APL-2.0 协议许可的软件,当时的 ASF 董事吴晟就提 issue (vesoft-inc/nebula#3247) 指出这一问题。NebulaGraph 于是删除了所有 The Commons Clause 的字样,保证无误地以 APL-2.0 协议许可该软件。

Ted Dunning 随后提到,这样的附加条款实际上严重影响了软件的采用。他意识到自己实际上并不想为此打官司,因此加上这样的条款对他而言是毫无意义的。Ted Dunning 于是去掉了附加条款,而这使得使用他的软件的条件能够简单的被理解,从而需要这些软件的用户能够大规模的采用。“水利万物而不争”,反而是不去强迫和约束用户行为的做法,为软件赢得了更多贡献。

我仍然很敬佩采用 GPL 系列协议发布高质量软件的开发者,Linux 和 GCC 这样的软件的成功改变了世人对软件领域的自由的认识。然而,FSF 自己也认识到需要提出修正的 LGPL 来改进应用程序以外的软件的发布和采用,例如基础库。

APL-2.0 的思路与之不同,它允许任何人以任何形式使用、修改和分发软件,因此 ASF 治下的项目,以及 Linux Foundation 治下采用 APL-2.0 的项目,以及更多个人或组织采用 APL-2.0 的项目,共同构成了强大的开源软件生态,涵盖了应用软件,基础库,开发工具和框架等等各个方面。事实证明,“鼓励”而不是“要求”用户秉持 upstream first 的理念,尽可能参与到开源共同体并交换知识和补丁,共同创造价值,是能够制造出高质量的软件,构建出繁荣的社群和生态的。

匠人精神

Apache Community 关注开发者的需要。

Apache Group 成立 ASF 的原因,是在 Apache Httpd 流行起来以后,商业公司和社会团体开始寻求和这个围绕项目形成的群体交流。然而,缺少一个正式的法律实体让组织之间的往来缺乏保障和流程。因此,如同纪录片当中提到的,ASF 成立的主要原因,是为了支撑 Apache Httpd 项目。只不过当初的创始成员们很难想到的是,ASF 最终支撑了数百个开源项目。

不同于 Linux Foundation 是行业联盟,主要目的是为了促进其成员的共同商业利益,ASF 主要服务于开发者,由此支撑开源项目的开发以及开源共同体的发展。

举例来说,进入 ASF 孵化器的项目都能够在 ASF Infra 的支持下运行自己的 apache.org 域名的网站,将代码托管在 ASF 仓库中上,例如 Apache GitBox RepositoriesApache GitHub Organization 等。这些仓库上运行着自由取用的开发基础设施,例如持续集成和持续发布的工具和资源等等。ASF 还维护了自己的邮件列表和文件服务器等一系列资源,以帮助开源项目建立起自己的共同体和发布自己的构件。

反观 Linux Foundation 的主要思路,则是关注围绕项目聚集起来的供应商,以行业联盟的形式举办联合市场活动扩大影响,协调谈判推出行业标准等等。典型地,例如 CNCF 一直致力于定义云上应用开发的标准,容器虚拟化技术的标准。上述 ASF Infra 关注的内容和资源,则大多需要项目开发者自己解决,这些开发者往往主要为一个或若干个供应商工作,他们解决的方式通常也是依赖供应商出力。

当然,上面的对比只是为了说明区别,并无优劣之分,也不相互对立。ASF 的创始成员 Brian Behlendorf 同时是 Linux Foundation 下 Open Source Security Foundation 的经理,以及 Hyperledger 的执行董事。

ASF 关注开发者的需要,体现出 Apache Community 及其成员对开发者的人文关怀。纪录片中谈到 ASF 治下项目的开发体验时,几乎每个人的眼里都有光。他们谈论着匠人精神,称赞知识分享,与人合作,以及打磨技艺的愉快经历。实际上,要想从 Apache 孵化器中成功毕业,相当部分的 mentor 关注的是围绕开源软件形成的共同体,能否支撑开源软件长久的发展和采用,这其中就包括共同体成员是否能够沉下心来做技术,而不是追求花哨的数字指标和人头凑数。

讲几个具体的开发者福利。

每个拥有 @apache.org 邮箱的人,即成为 ASF 治下项目 Committer 或 ASF Member 的成员,JetBrains 会提供免费的全家桶订阅授权码。我从 2019 年成为 Apache Flink 项目的 Committer 以后,已经三年沉浸在 IDEA 和 CLion 的包容下,成为彻底使用 IDE 主力开发的程序员了。

Apache GitHub Organization 下的 GitHub Actions 资源是企业级支持,这部分开销也是由 ASF 作为非营利组织募资和运营得到的资金支付的。基本上,如果你的项目成为 Apache 孵化器项目或顶级项目,那么和 GitHub Actions 集成的 CI 体验是非常顺畅的。Apache SkyWalking 只算主仓库就基于 GitHub Actions 运行了十多个端到端测试作业,Apache Pulsar 也全面基于 GitHub Actions 集成了自己的 CI 作业。

提到匠人精神,一个隐形的开发者福利,其实是 ASF 的成员尤其是孵化器的 mentor 大多是经验非常丰富的开发者。软件开发不只是写代码,Apache Community 成员之间相互帮助,能够帮你跟上全世界最前沿的开发实践。如何提问题,如何做项目管理,如何发布软件,这些平日里在学校在公司很难有机会接触的知识和实践机会,在 Apache Community 当中只要你积极承担责任,都是触手可得的。

当然,如何写代码也是开发当中最常交流的话题。我深入接触 Maven 开始于跟 Flink Community 的 Chesnay Schepler 的交流。我对 Java 开发的理解,分布式系统开发的知识,很大程度上也得到了 Apache Flink 和 Apache ZooKeeper 等项目的成员的帮助,尤其是 Till Rohrmann 和 Enrico Olivelli 几位。上面提到的 Ted Dunning 开始攻读博士的时候,我还没出生。但是我在项目当中用到 ZooKeeper 的 multi 功能并提出疑问和改进想法的时候,也跟他有过一系列的讨论。

谈到技艺就会想起人,这也是 ASF 一直坚持以人为本带来的社群风气。

我跟姜宁老师在一年前认识,交流 The Apache Way 期间萌生出相互认同。姜宁老师在 Apache 孵化器当中帮助众多项目理解 The Apache Way 并予以实践,德高望重。在今年的 ASF Members 年会当中,姜宁老师也被推举为 ASF Board 的一员。

我跟吴晟老师在去年认识。他经常会强调开发者尤其是没有强烈公司背景的开发者的视角,多次提到这些开发者是整个开源生态的重要组成部分。他作为 PMC Chair 的 Apache SkyWalking 项目相信“没有下一个版本的计划,只知道会有下一个版本”,这是最佳实践的传播,也是伴随技术的文化理念的传播。SkyWalking 项目出于自己需要,也出于为开源世界添砖加瓦的动机创建的 SkyWalking Eyes 项目,被广泛用在不止于 ASF 治下项目,而是整个开源世界的轻量级的软件协议审计和 License Header 检查上。

主要贡献在 Apache APISIX 的琚致远同学今年也被推选成为 Apache Members 的一员。他最让我印象深刻的是在 APISIX 社群当中积极讨论社群建设的议题,以及作为 APISIX 发布的 GSoC 项目的 mentor 帮助在校学生接触开源,实践开源,锻炼技艺。巧合的是,他跟我年龄相同,于是我痛失 Youngest Apache Member 的噱头,哈哈。

或许,参与 Apache Community 就是这样的一种体验。并不是什么复杂的叙事,只是找到志同道合的人做出好的软件。我希望能够为提升整个软件行业付出自己的努力,希望我(参与)制造的软件创造出更大的价值,这里的人看起来大都也有相似的想法,这很好。仅此而已。

原本还想聊聊 The Apache Way 的具体内容,还有介绍 Apache Incubator 这个保持 Apache Community 理念常青,完成代际传承的重要机制,但是到此为止似乎也很好。Apache Community 的故事和经验很难用一篇文章讲完,这两个话题就留待以后再写吧。

《知识社群》书评

时隔两年,以姜宁老师的分享为契机,我重读了《知识社群》一书。结合这两年在开源社群方向的经验,我从中发现了不少极具实践价值的内容。

《知识社群》一书从知识社群的社会性出发,从知识社群的历史沿革,全球知识经济的发展进程,以及知识社群在知识时代的机遇三点立论,阐述了知识社群的时代价值。进一步地,本书介绍了知识社群的三个结构要素,划分了知识社群发展的五个阶段,归纳了知识社群培养的七个原则。本书对知识社群的定义如下。

知识社群是这样一群人,他们有共同的关注点、同样的问题或者对同一个话题的热情,通过在不断发展的基础上相互影响,深化某一领域的知识和专业技术。

领域、社群和实践

本书提出的知识社群三个结构要素分别是领域、社群和实践。

领域创造共同点和共同身份的感觉。一个明确的领域能够确定社群的目的以及它对成员和其他人的价值,从而说明社群的合理性。这样的一个领域可以鼓舞成员们做出贡献、积极参与,指导他们的学习,使他们的行动具有意义。了解领域的范围和最前沿,使得成员们能够准确地决定哪些东西值得分享,怎样提出想法,追踪哪些活动,还使他们认识到试探性或不完整的想法的潜力。

规划一个新的开源社群,首要解决的就是核心开源软件的定位问题,也就是社群将关注在哪个技术领域。这个技术领域可以由一组用户需求,一个现有软件,或者一系列论文定义。

例如,Apache Kafka 一开始定位在解决日志存储和消费的业务问题上,Apache Flink 实现流式计算 API 后对标 Apache Storm 提出富有竞争力的有状态数据流计算定位,Apache Hadoop 则是对 GFS 和 MapReduce 论文的开源实现。

明确领域定位,才能降低开源社群成员交流的门槛。大部分的开源项目不是一个全新的问题,而是现有问题的解决或者现有方案的改良。因此,明确与现有概念之间的联系,才能够使得已经关注现有领域的人能够迅速理解新社群的领域定位,找到共同点和共同身份。不需要从头接触一个陌生的领域,而是对比与现有概念之间的联系和区别,就可以提出一系列的问题,也能理解什么问题是值得分享的。

例如,设计一门编程语言的时候,定位在静态类型或动态类型,就是完全不同的方向。相关领域的专家完全有可能分别拿 Haskell 和 Lisp 来比对概念上的差别,当前语言还没有实现的,尝试实现、模仿或者论证为什么不能实现。曾经对当前实现不满而因为种种原因无法落地的改进方案,抛出来与社群成员分享并争取在新项目当中实现。

领域的定位不是固定的问题,而是与社群一起演变的知识范围。领域的定位也不是抽象的兴趣,而是由社群共同经历的关键事件或问题组成的。

从现有概念延伸到新的知识社群的领域之后,社群存续下去而不是合并到现有社群的理由,就是自己的差异点。这些差异点不是一成不变的,随着不同人的加入可能发生很大的变化。例如,Apache Flink 一直在分布式计算引擎领域当中,但是在 14 年之前,它还是一个注重批处理负载的软件。直到两位开发者在 2014 年中的时候实现了 DataStream API 以后,才转向流式处理领域。例如,Perl 6 项目最初在设计上大量承袭了 Perl 的设计,并且很大程度受到那段时间的面向对象浪潮的影响。但是在 Pugs.hs 项目带来了 Haskell 社群的新鲜血液和函数式编程的思想以后,不少语言设计开始针对函数式编程的范式做出优化。而 Perl 6 的并发编程模型,则是在来自 Node.js 社群的强力开发者加入领导项目开发以后,利用 Node.js 所使用的 libuv 库搭建起来的。

可以看到,领域的问题都是非常具体的,能够围绕解决现有问题或改良现有方案分享知识和进行实践。领域的定位如果太过抽象,例如书中提到的“技术技巧”,那么社群就更像是一个兴趣小组甚至只是一组非常松散的圈子关系,因为围绕抽象议题提出的观点往往很难落到实处,也就很难激起社群成员的兴趣和创造价值。

社群创造学习的社会结构。一个强大的社群能培养互动精神,培养基于相互尊重与信任的关系。他鼓励人们分享想法,暴露自己的无知,提出困难的问题,并且仔细倾听,这是一种混合的气氛:人与人有着亲密的关系,同时又相互坦诚开放地提出和探讨问题。

社群对有效的知识结构非常关键。知识社群不仅仅是一个网站、一个知识库或者最佳实践集合,重要的是相互影响和共同学习,并在彼此之间建立联系的人。参与社群活动使社群成员形成归属感和相互的承诺。每个人分享对于某个领域共同的整体看法,而又在特定问题上带来个人的观点,这就创造了一种总和大于组成部分的社会学习结构。

虽然一个开源共同体可能为了推动软件的开发会建立相应的组织结构,但是在所有知识社群当中,知识的交换都是自由的,或者说去中心化的。Apache 软件基金会在 The Apache Way 当中强调了它所构建的开源共同体是由平等的个体所构成的。

Community of Peers: individuals participate at the ASF, not organizations. The ASF’s flat structure dictates that roles are equal irrespective of title, votes hold equal weight, and contributions are made on a volunteer basis (even if paid to work on Apache code). The Apache community is expected to treat each other with respect in adherence to our Code of Conduct. Domain expertise is appreciated; Benevolent Dictators For Life are disallowed.

书中提到,知识存在于人类认识事物的实践当中。它不是一种物质,不能像游戏当中双击使用书籍即可获得经验和智慧。只有通过社群的形式将关注到同一个领域上的个体聚拢起来,经过运营和管理有意识地促进知识的流动,才能促进知识的动态演进,并将知识带来的价值传达给每一个社群成员,最终使得社群成员所在的组织以及社群本身受益。

实践是社群成员分享的一套架构、想法、工具、信息、风格、语言、故事和文件。领域指出社群关注的主题,而实践是社群开发、分享和保持的特定知识。社群建立一段时间以后,成员们认为彼此应该已经掌握了社群的基本知识。大家分享共同的知识和资源,使得社群能够高效地处理领域内的问题。

知识社群不是魔法,它不能够直接将一个对领域一无所知的人转换成一个能够直接为社群创造核心价值的人。但是,社群可以通过实践的分享,将最佳实践总结成文档,在社群成员当中建立如何基于情景判断的共识,来提高知识社群交换知识、创造知识的效率。通过分享实践,知识社群扩大了自己的影响力,并且建立了一个隐形的参与要求。在正式成员之间,最佳实践是众所周知的,因此沟通的效率也将显著地提高。

知识社群的发展阶段

本书在介绍知识社群的发展阶段之前,首先讨论了培养知识社群的七个原则,这些原则将会贯穿不同发展阶段的关注点。

  1. 精心设计社群的演化历程
  2. 在内部和外部的不同观点之间建立对话
  3. 鼓励不同程度的参与
  4. 既发展社群的公共空间,也发展社群的私人空间
  5. 以价值为关注点
  6. 组合熟悉和兴奋的感觉
  7. 构建社群节奏

知识社群的潜在期

知识无处不在。当围绕着同一个领域的一群人开始交换知识和实践的时候,一个非正式的知识社群就形成了。从非正式的知识社群转变成为正式的知识社群,意味着它开始强化社群的三个关键要素。

  • 关键的领域问题是定义领域的范围。
  • 关键的社群问题是找到那些已经就这个主题形成网络的人,帮助他们想象网络的扩张和知识分享活动的增加会发挥怎样的价值。
  • 关键的实践问题是识别共同的知识需要。

这三个问题的目的都是找到潜在的社群成员之间足够多的共同点,从而使得即将构建的知识社群能够创造足够凝聚成员的价值。共同创造价值是知识社群的最终目的。社群依靠它提供给成员的价值来推动,社群成员需要看到他们的热情怎样转变成有用的东西。

另一方面,启动社群不是从头开始。如果社群能够为其他人创造价值,那么一定已经存在围绕社群关注的领域形成网络的人。告诉他们你的社群能够就他们关注的领域提供足够多的价值,他们的知识和见解也能够在其中转变为实践。以开源共同体的潜在参与者来说,很少有人能够拒绝这样的诱惑。前提是你的社群真的对他们关注的领域有足够优质的实践。

我在一份规划开源社群的草案当中列出的一个关键问题,就是明确社群期望某种形式的参与,并思考可能的参与者现在在哪。书中写到,启动一个知识社群,需要同时发现你可以在什么基础上组建,并想象这个基础的潜力能够把你带到哪里。如果忽视了目前已经形成的网络,那么社群将很难吸引到最有可能成为早期参与者的人。但是如果只是考虑目前的网络,就不能超越个人的限制而为社群引入新的看法。

对于一个新的开源社群来说,一个常见的问题就是对参与者有过于苛刻的预期,并且在预期被屡次打破以后反转成一种彻底的不信任。实际上,哪怕是最有热情的参与者,也需要在持续的价值创造中找到和社群发展的共同利益。尤其是对于一个公司内部的软件团队,考虑突破组织边界以开源社群的方式运作的时候,不同背景带来的信息差是不可避免的。

这一方面需要建立起对话的渠道,就像我在 Open Discussion 当中介绍的一样。另一方面,也需要认识到社群当中存在不同程度和不同类型的参与。

本书当中也采用了同心圆形式的社群引力模型,核心人员往往只占有 10% 左右,积极参与者也不过 20% 上下,剩下的七成左右的参与者,不会对社群的发展产生明显的促进。当然,这些比例和每个个体的行为是会动态流动的。社群的核心组对这样的情形应该有足够的预见性,避免对每个社群成员都予以核心成员的期望,或者按照市场营销的漏斗模型强迫每个成员选择向核心成员的“转化”或者离开。

这一阶段最重要的就是广泛的接触潜在成员和联系社群成员,通过人与人的联系和知识的交换以及实践的交流,定义出领域的范围、社群的主要意图和富有吸引力的切入点。如果社群能够度过这个阶段,往往能够发现潜在的社群协调员和思想领袖。

社群协调员会关注到社群新人的招募,会见潜在的成员,并联络核心成员以对齐社群对关键问题的认识和增强人际连接。思想领袖是社群关注的领域的专家,他们能够定义前沿问题,或者本身是具备丰富经验、德高望重的从业者。思想领袖的加入为社群提供了强有力的凝结核,试想 Ruby on Rails 的作者为 Ruby 社群带来了多少 web 开发者的参与。

书中分点罗列了这一阶段的典型工作计划,作为书评无法面面俱到的议论,但是仍然值得引用以作推荐。

  • 决定社群的主要意图
  • 定义领域,识别有吸引力的问题
  • 证明行动的理由
  • 识别潜在的协调员和思想领袖
  • 会见潜在成员
  • 联系社群成员
  • 发展社群的初步设计

知识社群的接合期

如果一个社群能够把对现状的良好理解和对未来发展方向的构想结合起来,它就已经具备条件,可以向接合期转变了。

社群启动的时候,正如一家创业公司最初的商业计划,看起来往往是脆弱甚至站不住脚的。如果一个社群的领袖能够有信心地宣传社群的目标,介绍当前的情况,并说明如何从当前的情况逐步实现最终的目标,那么社群就可以开始举办各种活动和正式启动,扩大互相信任的社群成员的范围了。

  • 关键的领域问题是建立在这个领域内分享知识的价值。
  • 关键的社群问题是充分发展关系和信任,使成员们能够讨论实践中真正复杂的问题。
  • 关键的实践问题是明确哪些知识应该分享和怎样分享。

信任在这一阶段极为重要,没有它,社群成员很难发现领域最重要的方面和社群真正的价值。虽然每个人都知道平等的交流,相互请教和寻求帮助能够提升自己和他人的知识水平,解决问题的成员也能积累自己的声誉和经验,但是缺乏信任的环境当中,大部分人会选择沉默或观望,而这种沉默和观望如果没有核心成员和积极的参与者以身作则破除不信任的印象,就会不断恶化,使得社群无法产生价值,进而失去由共同的目标的背景聚拢起来的早期成员。

建立信任,不仅仅是核心成员以身作则带来的形式上的安全感,还涉及到社群成员对社群目标的信任。前面提到,共同创造价值是所有社群的最终目的,不同社群只是在创造什么价值,以及如何创造价值上有所差异。如果不能在聚拢起早期成员之后,持续地回馈知识分享的价值和知识实践的价值,那么大部分参与者将会保持或者变成观望的状态,而不是付出足够的时间精力和热情投入到社群当中来。这也是我常说的开源参与者的思路是“谁赢他们帮谁”。

例如,《大教堂与集市》在总结 Linux 的成功经验的时候,就提到了 Linus 在早期开发的时候经常每天发布新的版本,新的版本当中包括了社群成员提交并被接受的补丁。这种直接回馈参与者,让他们看到自己的参与真的能够赢得回报,能够在社群当中建立起最朴素的信任关系。我在提及自己为什么参与 Perl 6 和 Apache Flink 这两个开源社群的时候,也强调了能够及时得到响应,解决自己的问题,并且提交的补丁能够被合并和发布,在社群当中看到我的努力帮到了更多的人,这种喜悦和认同感是联系社群成员的关键。

我和开源社群维护者交流的时候,一定会问的一个问题就是参与者为什么要进入你的社群,你能提供什么价值。尤其是作为一个知识社群,你在领域知识上的领先性体现在哪里?如果是一个自研项目开源的情况,往往项目的所有人都转过好几手,团队负责人只是接受命令开放源代码,那么他是很少考虑这个问题的。实际上,他本人可能都不太关注这个项目有什么用,为什么存在。

如果一个社群只是纯粹的复制别人做过的工作,那么顺着开源文化将会导向上游优先的结果。Apache 孵化器在接受项目的时候,就会衡量这个项目是否已经有同类已经存在,如果已经存在,那么加入这个社群,比起另起炉灶分裂是要更合理的。

We prefer “Do NOT confuse users” because we accepted projects nearly doing the same thing. We always encourage more people could join together and build a more powerful project and community, rather than building several similar projects.

同样,书中对接合期提供了一个典型的工作计划。这个计划的假设更多是在公司组织当中发起一个知识社群。

  • 向成员说明理由
  • 启动社群
  • 发起定期的社群活动
  • 赋予社群协调员合法地位
  • 在核心组成员之间建立联系
  • 发现值得分享的想法、见解和实践
  • 明智地整理文件
  • 识别提供价值的机会
  • 经理的介入

培育和维持知识社群

书中还介绍了知识社群的在启动和实现正常运转以后,走向成熟和管理的后续阶段。限于一篇书评能够关注到的重点有限,这里不做展开论述,只做重点罗列,以期能够激起各位阅读原版的兴趣。

成熟期的关键问题

  • 关键的领域问题是定义在组织中的角色以及与其他领域的关系。
  • 关键的社群问题是管理社群的边界,因为这时的社群已经不仅仅是从事同一职业的朋友网络。在定义新的、更广阔的边界时,社群必须保证不脱离自己的核心目的。
  • 关键的实践问题不再是简单地分享想法和见解,而是认真地组织和管理社群知识。随着社群形成更强的自我意识,核心成员开始认识到真正的前沿知识和知识社群的差距,感到需要更系统地定义社群的核心实践。

管理期的关键问题

  • 关键的领域问题是保持领域的合理性,在组织中发出自己的声音。
  • 关键的社群问题是保持有活力、吸引人的风格和关注点。
  • 关键的实践问题是保持前沿地位。

《知识社群》一书立论的基础跟开源共同体还是有所不同,它主要关注到企业应该如何发起、维持和培养知识社群,并从知识社群的蓬勃发展中受益。当然,相当一部分开源共同体也遵循这样的模式,由企业当中的最佳实践出发,以开放源代码和开源协同的方式跨越组织边界交流知识和实践以解决领域当中实际的问题。

本文主要的关注点还是在于社群本身的健康发展,原文关于如何将社群的价值和组织的价值结合起来,有更加详细的分点讨论。简而言之,就是社群作为一个独立的结构,如何找到它与公司组织利益的共同点,是它能够获取资源和赢得员工信任的关键。而如何找到社群发展与个人发展的共同点,则是持续吸引参与者的关键。

其中对于企业组织的价值,我在过去几天有过一段论述:

开源运营,对于企业来说有两个主要目的,一个是技术品牌的建立,一个以开源协同的方式打造成为行业标准的软件。这两个做好了,技术公司的技术知识优势,人才吸引力和留存率,还有成为标准以后的赢家通吃的价值,就非常可观了。

最大的挑战是如何协调公司的利益和社群的利益,使得两者尽量互不冲突的往前去走,相互能够合作。这个能够达成好的结果的前提是各方能够坦诚沟通,所以需要一个渠道和真正的去沟通。至于沟通下来仍有冲突的各种情况,做好预案就行了。

现在的开源处于一个相对好的时代,MongoDB 和 Elastic 当初面临的那种激烈冲突不太可能再次发生了。当然最终能够守住自己的利益,还是依赖自己强大的实力,不管是技术实力、谈判能力,还是其他。

我现在感觉到最难找的是有技术能力,且认同开源理念的团队。有技术实力,才能够在运营的时候依靠正道的方法掌握主动权。否则所谓的“运营”,就会变成为了技术不足的团队“善后”社群的技术挑战,而采取排他垄断的一个部门。这样就跟开源的理念背道而驰了。

❌