近日 doom-emacs 的作者 Henrik Lissner 在消失了近半年后于 doom-emacs 的 Discord 频道发言说:
大意是:
大家好!我已经离开了这个项目和社区大约一个月左右,忙于处理家庭问题。如果算上去年我遇到的健康、网络和搬家问题,时间就更长了。更糟糕的是,我没有针对这些事情及时地告知你们(或者提前警告),我想在此道歉。加上过去几个月的几次失败的尝试,我已经开始让人们担心 Doom 的未来和它的 Bus factor 了。
明确一点:我不打算放弃 Doom —— 这只是我不幸而又分心的两年 —— 我希望这已经过去了。
在花了一周的时间赶上进度,合并 PR,并更新模块之后,我会在 Discourse 上发布一个更大的公告,详细介绍目前的情况,项目的发展方向,以及我未来如何解决巴士因子和沟通问题。
在此期间,你可以在 #development 频道找到我;如果你对项目有任何问题,或者最近给我发过私信/邮件(并期待回复),或者有任何准备好合并或需要我关注的 PR,请随时在那里联系我。
暂时抛开 doom-emacs 是什么这一点,Henrik 提到了一个很有意思的词:Bus Factor
维基百科对于这个词的解释是:
Bus Factor 巴士系数是软件开发中关于软件项目成员之间消息与能力集中、未被共享的衡量指标,也有些人称作“货车系数”、“卡车系数”。一个项目或项目至少失去若干关键成员的参与(“被巴士撞了”,指代职业和生活方式变动、婚育、意外伤亡等任意导致缺席的缘由)即导致项目陷入混乱、瘫痪而无法存续时,这些成员的数量即为巴士系数。
对关键成员的诠释为“对项目不可或缺”,即其掌握的项目消息(例如设计、源码、知识)并不被众多其他成员所熟悉、共享。在一个巴士系数很高的项目中,就算某个人突然不参与工作,也会有很多其他个人掌握相关的项目消息而足以接替他的位置。
而低 Bus Factor 其实是许多开源软件的通病,如果你常年逛 Github,应该会发现许多具有较高 Star 数量的项目因为无人维护而遭到了废弃。
最极端的例子是在 2022 年初知名开源工具 Faker.js 的作者 Marak 故意破坏了 GitHub 上的这个开源库,将项目所有代码清空,并在 commit 中留下“endgame”字样,以至于在当天,全世界各地的前端工程师上班的时候迷惑地发现自己的项目不能正常工作并且出现了一大段乱码。
Endgame 乱码
此外,他开发的另一个库 colors.js 也受到了影响。这两个开源软件库都是前端工程中举足轻重的基础,其中 colorjs 仅仅在 npm 上,每周下载量就超过 2000 万次(你可以理解为只要搞前端基本都会用到)。
Marak 老兄在 2020 年曾经面临过公寓失火导致自己无家可归的窘境,他从开源项目中获得的赞助不足以支撑他的日常开支。他的生活愈发困窘。以至于在 2022 年初他做了这个令人深感遗憾的无奈决定。
扩展一下,针对这个事件的有太多不同的看法了:
- 有人认为如果你不想大公司白嫖他的作品,就应该使用 GPL 乃至 AGPL 这样的传染性的 copyleft License 而不是使用 MIT License,MIT 这个 License 从一开始就授权给了所有人随意使用你的代码的权力。只是假如用了 GPL 的话整个项目还有没有如今的影响力就另说了(商业公司商业产品一般会在发布前组织法务来审查项目中的开源依赖以确定没有 GPL License 的依赖,否则全都得跟着开源)。
- 还有人认为项目发展至今,有无数开源贡献者为项目贡献代码,已经是整个开源社区的共同创作了,你没有权力把大家的努力变成 shit
- 支持 Marak 的声音也有,比如说有人说这些吸血虫吸我们的血太久了,至少你让他们感受到疼痛了!
- 还有一些人也从 License 的角度出发,虽然大家的确贡献了代码,但是纠结 License 的话,这本就是 Marak 的个人项目,MIT License 在授予了所有权利之后也相应的免除了作者任何额外的义务,所以 Marak 从法理上讲完全有权利这么做。
_云基础设施的供应链现状 xkcd-2347_
类似的令人惋惜的事情还有不少,比如 Fakerjs 事件的主角 Marak 就提到了 Aaron Hillel Swartz,一位天才程序员,参与了初代 RSS 的开发,参与成立了 Reddit,他提倡知识开源,积极反对互联网盗版法案,为此他知行合一, 透过学院给予的访客账户,大规模系统性地下载 JSTOR 上的学术期刊并公开给大众访问。美国联邦检察官随后对他提起两项网络诈骗和十一项违反《电脑欺诈和滥用法》行为的诉讼, 他拒绝了联邦检察官提议其在联邦监狱服刑 6 个月的认罪协商,两天后,他被发现死在其于纽约布鲁克林区的公寓内,自缢而死。
Aaron Hillel Swartz
回溯历史,开源精神和开源软件可能是这个时代最不可思议的事情之一了:
一方面,开源软件运动自由软件运动,本身就不可思议,一群知识分子主动拆掉自己的专业护城河,很多情况下都是在反商业逻辑地践行着“人人为我,我为人人”的天下大同理念,为了什么呢?因为世界本该如此,为了整个社会的繁荣昌盛,因为这么做很酷,也因为编写软件本身给其作者就能带来无穷的乐趣——我自己毕业的时候也在想:哇,写代码还能 being payed,我的生活也太美好啦!
另一方面从事实上讲,不夸张地说,开源软件支撑起了当今整个软件行业,软件行业被开源彻底地改变了,以至于 MIT 计算机系著名的的开学第一课 6.001 - 著名的 SICP(Structure and Interpretation of Computer Program) 现在都已经停课,教授 GJS 说:“现代软件工程由于变得过于复杂,使得其相比于工程更像是科学,因为没人需要从头开始构建大型系统了——也很少有人有这个能力了”。
你可能会感叹说,事情变成今天这样,一群理想主义者有如此遭遇,真是理想的幻灭啊!
但事实真的如此吗?
理想主义虽好,但理想必须要根植于现实才能生根发芽。让我们请出布迪厄的“场域-资本-习性”社会学理论,来更好地解释这一切。
[(习性) (资本)] + 场域 = 社会实践
简单来说,把某种特定的主题的由人创造和构建的社会空间(场域)当作一种游戏,而我们每个人都是玩家。
身为玩家,尽管在经验层面上讲我们常常觉得我们是自由的行动者,但我们每天所做出的各种决定有往往是基于可预见的、规定的行为以及他者的态度,也即在和这个游戏本身互动的过程中形成的一种惯用策略——一种习惯(习性)。
在这种习惯的影响下根据我们当前的地位(资本)依照游戏的规则规则和我们所采用的策略来进行博弈和斗争,以占取这个游戏中更有有利的地位;
以上的整套主客观一体的有机体就构成了(经由不专业的我总结的极简版的)布迪厄的社会科学世界观。
从游戏的比喻中回到现实世界,同样,没有任何一个人可以在不为了某种利益去行动,而这个行动的目标大概率是希望能够增加自己所持有的资本。
Oliveira 在 IEM 2023 上夺冠,从此人人都知道了李哥
你可能会觉得,如果这样讲的话,那么我们在生产、实践、生活中所见到的利他行为、在艺术、科学等领域的奉献精神,以及这篇文章所讲的开源精神又该作何解释呢?
按照布迪厄的定义,资本不仅仅是经济资本,还应该有包含文化资本、科学资本等资本在内的符号资本。意识到有不同形态的资本之后,布迪厄不认为社会的生产生活实践中存在任何无功利性的行为,他以一种类似博弈论的视角认为人的所有行为都是基于对于某种利益的计算为出发点的,为了攫取某种形态的资本而已——如果不是我们所认为的一般意义上的经济资本的话。
在不同的场域内,不同的资本起着主导作用——简单来说学术圈,就是科学资本,艺术和文化领域自然是文化资本,而不同的场域内的不同资本存在着某种汇率,所以我们可以经常看到一些政治或者文化精英通过发挥自己的影响力来攫取经济资本,反之亦然。
这里我希望荡开一笔,布迪厄理论的进步性在我看来,就是所有的场域都是由人类创造的,而不是涌现于人类社会中的,它解释了许多现象为何如此,同时也暗示了一些看似根深蒂固的思想观念和社会现象不必然如此,也就是说,这里不存在某种纯粹应然的“天理”,如此一来,就有了推翻并进步的可能性,历史没有终结。
就你小子认为历史终结了是吧,现在我就告诉你历史的终结终结了
回到主题,在这套理论框架下看待开源精神,就可以被解释为:在开源软件场域内,黑客们(请注意,黑客 Hackers,喜欢通过智力和创造性方法挑战难题的人,绝非骇客 Crackers,计算机犯罪者,中文互联网常常将这两者混淆,造成了黑客一词的污名化)为了诸如“声誉”等文化资本的积累,来为这个世界贡献开源代码。
这其实和农业社会出现之前,我们的祖先所生活的小型狩猎采集群体中常常出现的一种礼物文化一脉相承。在大自然丰富的物产馈赠之下,社会关系由这种礼物文化所支撑,部落酋长们常常会公开地进行一些慈善行为。正如《大教堂与集市》这本著名的黑客文化文集中说的那样:在开源软件这个场域内,没有非常稀缺的“生存必需品”,早期的软件是自由共享的,这种物质充裕导致了成功的唯一标准就是同侪中的声誉。
但黑客文化和软件终究没有止步于一个物产丰富的小圈子,不同于冯·诺伊曼教授的预言“这个世界只需要大概 5 台计算机就够了”,如今,软件的确在吞噬整个世界,这很好,说明在人类的生产生活实践中软件发挥了无可替代巨大作用。另一方面,在资本主义的作用之下,软件行业对于其他行业的渗透也必然导致场域的交叠和融合,从而使得这个行业必然要在某种程度上服从资本主义世界的逻辑——当计算机技术和软件行业从一群不愁吃喝的美国中产阶级黑客的小圈子文化中走向整个世界,成为人类生产生活实践中不可或缺的一部分时,开源精神的原始内核必将遭到挑战。
因此,开源软件势必要从最初那种乌托邦式的,只属于少部分技术精英的田园牧歌走向大众,开始拥抱支撑着这个世界 70 亿人的资本主义和商业逻辑。
无数的历史已经说明生产关系到另一种生产关系的变化必然伴随着阵痛,但作为历史唯物主义者,我们不认为从古希腊城邦的雅典式民主一路走向资本主义是开历史的倒车,相反,这是一种极大的进步。
自从曾经的微软以它臭名昭著的 “拥抱,扩展,灭绝” 战略绞杀了当时的明星软件 Netscape 赢得了浏览器大战后 Netscape 浏览器以 Mozilla Firefox(就是今天的火狐)的名义重生,同时带来了开源运动起,到开源厂商 RedHat 于 1999 年成功上市,再到如今的 Microsoft Love Linux(TODO:图),以开源软件驱动的创业公司层出不穷,我认为可以说至少到今天为止,即使拥抱了它所“鄙夷”的资本主义商业逻辑,开源运动离失败二字也相差十万八千里。
CNCF landscape,里面列举了云原生领域的一整个生态的各种软件项目
然而开源软件的确已经分化出两种不同的形态:
第一种是原初的,由黑客从自身需求出发,编写软件并开源给更多其他黑客一同共建的乌托邦式的第一代开源软件,像是 Vim、Emacs、Python、Linux、PostgresQL 这类经过时间的沉淀,留存到今天的鼎鼎大名的这类开源软件基本上已经成为了开源软件的符号标识为人们所敬仰和歌颂。
我接触 Emacs 就是因为觉得 Notion 实在太好用了,好用到开始审视自己的文档工具,进而催生了一个问题:我真的要把这些可能伴随我一生的笔记数据托管给这个虽然红极一时但是大概率没有我活得长的商业公司吗?然后转而有意地考察并使用了 Emacs 的 Org-mode 来进行笔记的写作和任务的管理,然后发现对我来说 Ord-mode 比 Notion 不知道高到哪里去了;但是这种需求相比之下还是太小了,人类对于软件的需求更多是更快、更好、更便宜地解决当下遇到的问题,所以诞生了第二种开源软件。
第二种是新生的,有商业逻辑驱动,除了践行“人人为我我为人人”的大同理念以外,将开源作为一种战略考量和市场策略的开源软件(和它们背后的商业公司),像是如今的 MongoDB、ElasticSearch、Akka、RedHat、Ubuntu、GitLab、HashiCorp stack 等等,例子数不胜数,到今天围绕着开源软件也形成了数套经过验证的切实可行的商业模式,如今的创业者甚至不需要说服熟悉开源的 VC 为什么开源创业有可能成功。举例来说:
围绕着上游核心开源软件做发行版或者解决方案,售卖 License 或者软件订阅服务,换取运维层面的方面或者软件可用性保障,这方面的例子有 Ubuntu、RedHat 之于 Linux,各家公有云的 Kubenetes 容器服务,各家公有云的数据库服务(搞得 MongoDB 被白嫖到修改开源协议来禁止商业竞争)。
基础功能免费开源,高级的商业团队特性闭源收费,这方面的例子有 GitLab、InfluxDB、Grafana、Envd/Modelz,HashiCorp Stack。
围绕着 Linux、Kubernetes、PostgresQL、Prometheus 这种复杂的大型开源软件做咨询服务的。
开源的商业模式能够走通的原因在于软件源码并不等于围绕着这个软件的所有知识:包括软件的设计思想、架构演化的历史、以及最重要的稳定运行的运维知识;这些对于需要稳定运行在上面的上层应用而言非常重要的知识往往只掌握在核心的开发者手中,就构成了商业化的基本逻辑。
举个例子,一家主营业务不是公有云各类服务的企业因为需要使用云服务器、数据库、容器服务等技术就自己从头搭建机房开始手搓一套云基础设施而不用 AWS 或者阿里云之类的公有云服务,这显然是不现实的。
你可能会问,既然业务不同,也不关心源码,那么是不是开源软件还有什么要紧?
在这些领域内如果有商业公司想要通过售卖自家的闭源软件,比如说一套兼容了 POSIX 接口协议的操作系统,那么他们必将受到这样的质疑:你的软件相比于经过了 30 年检验的 Linux 到底好在哪里?我们发现在现在,根本没有企业试图去和 Linux 竞争,相反大家都只会去做差异化的部分。
对于开源软件乃至软件行业的这样的变化,我感到十分兴奋。
为什么这么说呢?
在文章开头提到的 Henrik 无疑是那个群星璀璨的乌托邦时代在今天的继承者,是绝对意义上的英雄,是 Henrik 编写的 doomemacs 让我这个 Vimer 在 Emacs 的世界中有了立足之地,甚至获得了比 Notion 还好不少的文档编写体验;是 Henrik 引领我发现了神奇的 NixOS,从此彻底告别无止境的系统配置和痛苦回滚。
虽然针对这次的 Bus Factor 的小小风波 Henrik 表示他绝对不会放弃 Doomemacs,也承诺说会给社区一个交代。但或迟或早,英雄总会迟暮,到了那个时候,追随着英雄的脚步的我们该如何呢?
The net is vast and infinite. 我很喜欢最后素子消失在网络中这个结局。观众不需要作为偶像的素子,素子也不需要用自我来束缚自己
就文档领域而言,从 emacs / org-mode 的繁荣社区中,站起来了 LogSeq 这样的好用的文档工具和创业公司(种子轮 4.1M$),在 Notion 的阴影下也诞生了开源的 AFFiNE(Pre-A 轮上千万美元融资)这样的明星团队。
类似的事情如今在软件行业的各个领域都层出不穷,而这一切都是一代代开源软件和理想主义者前辈为如今的世界铺下的坚实基础设施,这个行业的巨头几乎已经无法像二十年前那样轻松地就能垄断一整个行业。我相信,基础设施的进一步完善,在未来将会打破社会精细化分工这一虚假的神谕,每个人都可以自由地运用这些工具做点什么有趣的事情。
英雄虽然会迟暮,但不同于历史上的一些其他悲剧,开源软件场域内的人民没有背叛英雄,相反,他们沿着英雄们开创的路真正地改变并改造了这个世界,如今,开源软件曾经的“敌人”微软,也成为了开源软件和开源运动的践行者。
开源软件从大学的象牙塔走向资本的拥抱是历史的必然,开源精神的理想没有随着这种转变而磨灭,相反,它催生出一个人们不把全部希望寄托在英雄身上的崭新世界。希望我们的理想都能像开源软件般在坚实的土地上生根发芽,伴着世界的污泥,从中吸取养料,茁壮成长,长成一棵参天大树。
]]>请别担心剧透,这部作品的伟大之处并不在于它的故事性。
如同诗歌一般,诗歌有什么好剧透的呢?
历史也许会随着史料的增多而变得愈发扑朔迷离,我们应该关注表象之下人性中的不变量。
1941 年,德国科学家沃纳·海森堡前往当时已经被德国占领的丹麦哥本哈根,拜访他曾经的老师尼尔斯·玻尔,然而这次阔别四年的重逢却以一种相当不愉快的方式,匆匆地结束了会面,彻底终结了两人近二十年的深厚友谊,顺带着,也许也永远地改变了世界的命运。
这就是著名的“哥本哈根会见之谜”。
几十年来人们对这次事件的真相一致争论不休;在冷战后相关档案解密,更多的来自海森堡、玻尔以及与他们相关的各国科学家的一手资料公之于众后,人们逐渐可以确定在这次会面中大致发生了什么,海森堡到底对玻尔说了什么,以至于玻尔勃然大怒,最终两人一拍两散。
“这次谈话在傍晚散步时进行。我知道玻尔受到德国当局的监视,所以我试图以不危机自己的方式来进行这次谈话。这次谈话可能是始于我的问题:‘物理学家们在战争时期致力于铀裂变的研究是否正确’,因为这一领域的进展可能会使得战争出现严重后果。
玻尔立刻明白了这个问题的意思,他露出有点害怕的神情。我记得他反问道:‘你真的认为铀裂变可以用来制造武器吗?’ 我可能是这样回答:‘在原则上是可能的,但技术上需要付出极大努力,我们只能希望,这场战争中实现不了。’
玻尔对我的回答感到震惊,显然是以为我在传达一个信息,德国在制造原子武器方面取得了巨大进展。尽管我后来试图纠正这种错误印象,但我可能并没有赢得波尔的完全信任,尤其是我只敢小心翼翼地说话(这绝对是我的错误),因为我害怕某些词汇在以后会对我不利。我对这次谈话的结果很不满意。”摘自海森堡在 1957 年写给作家罗伯特·容克的信。
“我现在特别想到的是我们在研究所里的谈话。在那次谈话中,提到了“战争”这个话题,我清楚地记住了我们说过的每一个字。
一开始你就说,你确信如果战争持续足够长的时间,就会由原子武器来决定胜负,这给我留下了非常深刻的印象。那时我对美国正在进行的准备工作一无所知。正当我有些怀疑时,你又补充说,要我明白一点,最近几年你几乎一门心思在思考这个问题,并毫不怀疑它是可以解决的。
因此,你所说的‘曾向我暗示德国物理学家将竭尽所能阻止原子技术的应用’,对此我完全不能理解。在这次简短的交谈中,我非常谨慎,并思考了很多。”1961 年海森堡六十岁生日时,玻尔给海森堡写了一封信,但并未寄出,这封信最终于 2002 年公之于众。
自然科学史学家普遍认为,谈话结果可能对“二战”期间两大参展集团在原子弹研制与付诸实战的走向、现今世界所面临的核威胁、未来科学的发展与人类生存等都产生了深远影响。
然而,史料的解密没能解决这个谜题的核心关窍:海森堡为什么要前往哥本哈根?他的动机是什么?
在我看来,相比于那个已不能确定的历史真相,人在多重身份的叠加态中导致的行为、动机的不确定性具备更大的研究价值。毕竟历史是一面镜子,作为观测者和镜中人的我们自己能够从这面镜子中看到什么,感受到什么,以至于构成何种对于现实世界的认知,以至于如何利用这些认知去反过来与现实世界交互,才是这面镜子对我们最精致而迷人的价值。
对于人本身的复杂性和人类的心智的局限性导致的二律背反的深刻讨论。
为了研究人性幽深处那混沌的不确定性——人类精巧而复杂的动机,话剧《哥本哈根》中,三位主角:海森堡、玻尔和波尔的妻子玛格丽特在一个虚无的世界中以灵魂形态出现在舞台之上。以他们对与这个谜案真相的执念为线索,在死后的世界重新演绎当年那次会面,在复杂而彼此矛盾的身份叠加态中,在人性的不确定性中,抽丝剥茧。
为什么说“动机”是复杂的?
因为现实世界是复杂的,具备无以复加的巨量信息,参与者们在集体无意识中互动;历史是复杂的,在无数的矛盾中演化发展,偶然性和必然性参杂其中。我们不可能以机械唯物论的方式将历史和构成了历史的人们、塑造了这些人的环境、他们所做的决定和想法、以及他们彼此之间的关系一一拆开研究,以期能够分而治之,得到任何有意义的答案。
结构本身就蕴含着巨量的信息,以至于当一个整体的各个部分有机地结合在一起之后,会涌现出无数个出人意料的新现象。
好在人天生对复杂而超过我们处理能力的信息痴迷。以复杂的现实作为原料,《哥本哈根》搭建出它别致的舞台;剥离出现实中人性相关的种种议题分别探讨,《哥本哈根》完成了一次精巧的有关人性的量子实验。
高度凝练地提取了真实的历史中最令人揪心充满冲突的部分作为其坚实的舞台地基:
首先是战争,人类历史上最惨烈的战争将主角三人划分成对立的两个阵营;然后是知识也参与其中,理论的宏伟大厦刚刚建成,由于原子核裂变的发现,核武器的发展成为可能,牵涉出令人窒息的道德挑战;最后是人的连结将这一切连结在一起,父与子,师与徒,将处于风暴的正中心的海森堡和玻尔二人的关系置于其中构造出了它最朴素却又华丽的舞台。
在宏伟坚实的舞台地基之上,三位鲜活的主角的登场了。以极度小心谨慎的态度将,他们的人生,价值,成就,情绪还原在作品中。实验的条件备齐了,于是加入最关键的能量要素——海森堡的前往哥本哈根的神秘动机开启了这次令人叹为观止的实验。在实验中保持科学的态度,任由矛盾肆意发展,任由参与者剧烈的碰撞产生炫目的火花。在这个过程中用那些或随意或日常或激烈或温柔的台词,极其深刻地讨论了诸如伦理、道德、人性、命运等话题,最终承载着几乎溢出的信息呈现给观众一部无与伦比的伟大作品。
老生常谈,“一千个人眼中有一千个哈姆雷特。”
好的文艺作品的作用不在于讲述某个事实,或是给出某类问题的具体解决方案,它的价值和意义在我看来正是以一种诗歌般的模糊意象直抵人的灵魂,让人能够获得某种启示。而这启示也如镜子一般,每个人都能观察到不同的东西,或是对于伦理、道德的讨论;或是对于现实的批判;或是对于人性的拷问;或是情感上的共鸣,会心一笑,颤抖着哭泣,惊惧,惶恐,愤怒,温暖,迷恋······好似无穷的能量之源,由此我们的生命进程多了许多色彩,也多了些许厚重。
2018 年的夏天,我实习的公司位于五道口,正值大学毕业,如同一张白纸;被一起实习学姐拽着去北大看了这场毕业生演出的《哥本哈根》,自那时起,这部话剧在和现实中的生命经验交相辉映下,如同链式反应般越来越剧烈地回荡在我的脑海之中。
这张照片中甚至有我
对于我而言,那诗歌般的启示始于下面的台词:
在回顾“哥本哈根诠释(Copenhagen Interpretation)”的创立时玻尔对海森堡说:
Bohr It works, yes. But it’s more important than that. Because you see what we did in those three years, Heisenberg? Not to exaggerate, but we turned the world inside out! Yes, listen, now it comes, now it comes .… We put man back at the centre of the universe. Throughout history we keep finding ourselves displaced. We keep exiling ourselves to the periphery of things. First we turn ourselves into a mere adjunct of God’s unknowable purposes, tiny figures kneeling in the great cathedral of creation. And no sooner have we recovered ourselves in the Renaissance, no sooner has man become, as Protagoras proclaimed him, the measure of all things, than we’re pushed aside again by the products of our own reasoning! We’re dwarfed again as physicists build the great new cathedrals for us to wonder at—the laws of classical mechanics that predate us from the beginning of eternity,that will survive us to eternity’s end, that exist whether we exist or not. Until we come to the beginning of the twentieth century, and we’re suddenly forced to rise from our knees again.
玻尔 它奏效了,是的。但更重要的是你看到了我们这三年的成果,对吗,海森堡?不夸张地说,我们把世界翻了个个儿!是的,你听着,这就是说,这就是说······我们又将人置于宇宙的中心。有史以来,我们不断地发现自身被放逐。我们将自己流放至万物的边缘。首先我们将自己变为上帝不可知旨意的附属,渺小的众生跪倒在创世纪的大教堂前。而当我们刚从文艺复兴中找回自我,当人们刚刚成为倡导者们所宣称的万物之尺度,我们又一次被自己竖起的理性产物推至一旁!又一次侏儒般地仰望着物理学家们筑起的巍峨高耸的新大教堂——经典力学法则,它不管我们存在与否,先我们之先,开永恒之起始,后我们之后,至永恒之终结。直到进入二十世纪处,我们突然又再一次站立起来。
Heisenberg It starts with Einstein.
海森堡 从爱因斯坦开始。
Bohr It starts with Einstein. He shows that measurement—measurement, on which the whole possibility of science depends—measurement is not an impersonal event that occurs with impartial universality. It’s a human act, carried out from a specific point of view in time and space, from the one particular viewpoint of a possible observer. Then, here in Copenhagen in those three years in the mid-twenties we discover that there is no precisely determinable objective universe. That the universe exists only as a series of approximations. Only within the limits determined by our relationship with it. Only through the understanding lodged inside the human head.
玻尔 从爱因斯坦开始。他指出,测量——整个科学存在所依赖的测量——并非是不偏不倚的、非人格化的举动。它是一项人类行为,受特定的时空观念,及观测者个人视角的影响。因而,在二十年代中期的这三年中,我们在哥本哈根发现了宇宙中并无绝对准确的客观世界。世间万物只是一系列的近似存在。仅仅由我们同它相对关系的限度来决定,仅仅由人类的思维与理解来决定。
我们是万物的主宰吗?
如果人们按严格的可辨量来测衡自身······那我们将需要一种新奇的量子伦理,
作为主宰的我们本身的混乱与复杂导致了人们彼此之间并非总是可以互相理解,即使是自己,有时候也是不可自知的,所以我们总是在寻觅,试图解开留在过去的一个个谜题。
我们尚在寻觅之中,我们的生命便结束了。
世界将不会终结于人类。
一切得以幸免,非常可能,正是由于哥本哈根那短暂的片刻,那永远无法定位及定义的事件,那万物本质上不确定性的终极内核。
如同许多我喜欢的诗歌,我囫囵地吞下这部话剧,然后在人生的某些时刻,它总是会星星点点地迸入我的脑海,我开始细细品尝、咀嚼、玩味起来,吸收一些智慧、或者至少获得一点乐趣。
然而这样就够了吗?
几年来我一直有想写这篇文章的冲动,但在 2019 年的某天,在电脑前写下标题然后发呆了几个小时后我几乎颤抖着放弃了,一个人要有怎样的厚度、智慧和广度才能够针对这部话剧写下一篇不算自取其辱的评价呢——我太年轻了,这些东西一样都没有,我不配写。
但抱着这样的态度退缩就行了吗?然后指望继续肆意、囫囵地吞食各种好东西,不求甚解,暴殄天物,如此一直到 90 岁,指望在这个过程中顿悟然后化腐朽为神奇吗?
不,这是对人生的浪费。让这一切的味道变得太淡了,太淡的生活不值得过。
Verse, Fame, and Beauty are intense indeed.
But Death’s intenser – Death is Life’s high meed.
于是在死去以前,尽可能活得 intense 一些。可什么是 Intense 呢?
是生命的浓烈气息;
是人性的光芒;
也许就是奥林匹克精神,挑战自我挑战极限;
是让自己成为桥梁,通向超人之路;
是山上的树,扎根到最黑暗的地底,生长向最高的天空,然后等待雷鸣。
于是我们迎来了自己生命的最高奖赏,死亡也由此变得浓烈。
生为徭役,死为休息。 浓烈的生才配有更浓烈的死亡。
如今,相比于写下那篇完美的剧评所需要的厚度、智慧、广度,我还是一穷二白,年轻且浅薄;但也许重要的不是“完美”,而是敲骨吸髓的决心,也许正是每一次的敲骨吸髓才能够增加人的厚度、智慧和广度,也许只有这样,生命才会变得更浓烈。
什么是好的软件?
在 80 年代末一篇著名的文章 The Rise of Worse is Better 中,作者提到良好的软件设计应当考虑 简单、正确、一致、完整 这四种特质。
文中作者提到了两种软件设计理念,暂且叫做 the right thing 和 worse is better(有必要指出这个命名并非贬义);这两种设计理念都是围绕着上述的四种特质展开的,区别在于对这些特质的优先级排序。
随后作者举了两者的许多例子来论证为什么 worse-is-better 正在席卷当时的软件行业。
这两种理念不存在优劣之分,如今我们也身边,而实际情况也往往游走于两者之间,我们希望能够做出优秀的,具备美学价值的设计,但也必须考虑成本和人的因素,最终要交付质量达标的,可工作的软件来解决真实世界中的问题,而软件的编写、维护成本绝对不可以大于问题本身的价值。
Golang 几乎可以说是 worse is better 理念的典范:
Kubernetes 大家都很熟悉我就不班门弄斧了,一句话带过:它提供了一套概念上非常简单的 API 设计,辅以来自控制论的调谐机制,使得容器化的软件得以在其中自动化部署,扩展和运维。
k8s 通过开放这套 API 规范和调谐机制允许开发者对其能力进行扩展,开发者得以实现 Operator Pattern:将软件的配置、部署(Day-1)和运维、备份、故障转移(Day-2)知识编写成用于操作软件的软件将这些复杂易错的操作自动化,从而提高可靠性,并降低成本。
那么选择 Harbor Operator 的原因就是它具备非常优秀的设计,在一个优秀的平台上和通过简单语言特性,没有过多的奇淫巧技,通过几个关键的,符合 SOLID 设计原则的设计实现了一套务实且具备美学价值的软件系统,可以被视为是 the right thing 理念的实践者。
作者的代码几乎没有注释,但异常好读,这也有赖于整个系统的
抽象简化问题的同时必然伴随着灵活性的牺牲,而 Harbor Operator 则完全舍弃了灵活性以求带来最大程度的思维减负,使得 Operator 的开发更像是声明一份软件配置。
使用 client-go 手写 operator 得益于 golang 泛型的缺失简直就是找罪受,整个项目中会充斥着巨量的 boilerplate code(模板代码),我相信即使是真的用 client-go 手写也没人会真的从头写起。
于是 kubebuilder 应运而生,它将 Kubernetes Client 的创建,监听 Kubernetes API Server 的请求以及到请求的队列化等操作抽象成公共库 controller runtime,和公共工具 controller tools,并能够为开发者生成脚手架代码专注于处理 API 对象变更请求的业务逻辑开发的工作上。
Kubebuilder 依然为业务逻辑的多样性保留了一丝净土,而 Harbor Operator 在此基础上进一步追求极致,完全牺牲了灵活性以追求概念的一致和简单,而它面临的业务也确实十分适合这种做法。
因此,在本次源码阅读中,我们的主要目标是要学习 Harbor Operator:
此外,我们不会着重研究:
这里只列出了目录
1 | root |
截至 v1.0.1 Harbor Operator 目前主要负责 Harbor 系统的 Day-1 操作
首先让我们来过滤掉一些不是很关键的部分:HarborCluster 这个 CRD 以及其 Controller 的实现。
为什么说它特殊呢?首先观察它在系统架构中的位置:处于最上层,它管理着 Harbor 系统本身和其所依赖的所有有状态服务,这需要从项目历史和它自身在系统架构中的特殊地位两方面说起了。
从系统架构层面看来,这个 HarborCluster CRD 从定义上看和 Harbor CRD 有着极大的相似性,代码存在大量冗余,显得十分不好看,这是由于它作为整个系统中最上(外)层 CRD,直接面向用户;必须能够为用户提供声明 Harbor 部署的所有必要配置项,此外由于 Harbor 自己是一套无状态服务,因此完整的部署也要求 HarborCluster CRD 管理 Harbor 系统所依赖的所有有状态服务,包括 Postgres, Minio 和 Redis。
而 Harbor 系统自身的必要信息已经在 Harbor CRD 中定义好了,因此 HarborCluster CRD 中的冗余部分就是要将这些信息完整无误的传递给 Harbor CRD;此外 HarborCluster CRD 还需要管理那些处于自身职责边界之外的有状态服务的 CRD,因此不能够完全使用 Harbor 以及 Harbor 所有子组件中的 Controller 逻辑。
从历史层面看来,Harbor Operator 最初是 OVH Cloud 的私有项目,后来才被捐赠给 goharbor 社区,因此结合 git history 观察,HarborCluster 的 CRD 定义和其 Controller 的实现与系统中其他 Controller 有如此大的不一致性的原因在于它是社区贡献的后来者,在 Harbor Operator 设计之初并没有考虑到它所肩负的功能。
而 HarborCluster Controller 本身实现和我们平时见到的大多数使用 Controller-runtime 实现的 Controller 没什么太大的区别,因此不做详细研究。
HarborCluster Controller
Harbor Core Controller
依赖图是整个项目中较为独立的一个模块,但它实际上承担着 Harbor Operator 中所有 controller 的执行引擎的作用。本质上是观察到 Kubernetes 中各类资源之间存在相互依赖关系,一些资源的部署和调谐有赖于其他一些资源的部署和调谐,比如 Deployment 可能会依赖 Configmap;最终这些依赖关系构成了一张依赖图,实际上这个图应该是一个 DAG。在这里我们需要接口定义如下:
1 | package graph |
既然是 Graph 总要有 Graph 的数据结构以及 build 这个 Graph 的工厂方法,resourceManager 的数据结构看起来有点生硬,不确定是否就是真正的 Graph。
根据签名可以观察到 AddResource 添加了需要被添加的资源本身和它所有的依赖项以及相应的 runFunc,值得注意的点在于必须先添加不依赖任何资源的资源(即出度为 0)。
1 | func (rm *resourceManager) AddResource(ctx context.Context, resource Resource, blockers []Resource, run RunFunc) error { |
这个包中几乎所有重要的逻辑都在 Run 这个 Method 中,先看它的前半部分:
1 | func (rm *resourceManager) Run(ctx context.Context) error { |
出现了!getGraph,看起来像是要 build 这个 graph,跟进去发现果然 resourceManager 只是个 builder,真正的 graph 藏在这里面,使用邻接链表表达的,乍一看甚至很复杂:
1 | type node struct { |
Parent 和 children 为什么是一个 channel,为什么需要那么多 lock;还是观察一下 graph 的构建过程:
1 | func (rm *resourceManager) getGraph(ctx context.Context) []*node { |
原来依赖关系被倒转了,现在是被依赖项指向依赖方,对于每个资源都构造一个 node,同时对于资源的每一个依赖项,都将这个资源的 parent chan 添加到依赖项的 children 中,最终的 graph 就是 node 的集合。
这么做其实难免令人一头雾水,那么再观察一下执行过程:
1 | for _, no := range rm.getGraph(ctx) { |
非常地简单粗暴,对于每一个 node 都等待其入度 node 执行完毕后执行其自己的 runFunc,并把潜在的错误传播给其链路后方所有等待执行的 node 提前中止。
这里我十分不理解为什么不用一个拓扑排序来解决这种定义良好的经典问题。
1 | GraphManager 的初始化使用的是一个 thread local 的全局变量,初始化之后扔到空中(ctx),是各类 api framework 的常见依赖注入方式; |
除了之前被我们摘掉的 HarborCluster Controller 之外,Harbor Operator 中所有组件的 Controller 都直接组合了这同一个 Controller,抽离出所有 Controller 的公共逻辑,使用同一个 Run,同一个 Reconcile,怎么做到的?
Controller 对于接口的实现除了实现了 Reconciler 之外没什么有意思的;那么先来看看 Controller 的数据结构定义:
1 | type ResourceManager interface { |
我们不太认识的东西只有 BaseController 和 rm
跟过去看 BaseController 基本上是个身份标识符,用于标识 Controller 自身的信息和一些 Label 信息,那么不同 Controller 之间不同的逻辑差异点只可能存在于 ResourceManager 之中,ResourceManager 看上去也是一个很简单的接口,实现了 ResourceManager 的果然就是各个具体的 Controller;我们将在最后研究 ResourceManager 的具体例子。
1 | type Controller int |
那么 Controller 是如何做到只使用一个 Reconcile 就可以 reconcile harbor 全家十一个 CRD 连同那么多系统资源的呢?
根据上面我们已经知道的信息,应该是对于每个 CRD 都使用了一个 ResourceManager 来定义这个 CRD 需要创建和调谐的各种资源,而这些资源通过依赖图来定义其依赖关系,并为每个 CRD 都写一个具体的 RunFunc 来完成最终的调谐。
1 | func (c *Controller) Reconcile(req ctrl.Request) (ctrl.Result, error) { |
很经典的 Reconcile 逻辑,看来花样都在 c.Run 里:
1 | func (c *Controller) Run(ctx context.Context, owner resources.Resource) error { |
果然 rm 在这里出现了,添加某个具体的 Controller 所需要的资源之后,那么 PrepareStatus 看起来是统一的一个,结合这个 method 源码和 CRD 定义发现它假设所有组件都具备同样的 Status,又是一个牺牲了灵活性换取简单和一致性的设计,实际上这个设计遵循了 K8s 提倡的规范,可以在设计 CRD 时加以参考,这里不再赘述。
最后一行:sgraph.Get(ctx).Run(ctx),依赖图来了,从空中(ctx)抓下来,至此这部分逻辑闭环了,还剩下的问题是:实现了 ResourceManager 的各个 Concrete Controller 如何构造依赖图,并构造自己的 RunFunc 呢?
最后一步可以说是水到渠成。
所有实现了 ResourceManager 的 Controller 地位上应该都是平等的,我们借助 harbor core 这一个例子来一探究竟。
除了用来生成具体 K8s 资源的部分之外,应该关注的是实现了 ResourceManager 接口的部分;
1 | func (r *Reconciler) NewEmpty(_ context.Context) resources.Resource { |
好家伙,除了资源要按照顺序添加之外,这就相当于是一个声明式设计了;如果想添加新的 Controller 简直易如反掌:只要知道这个 Controller 要对什么资源调谐就好,不需要管理具体的 Reconcile 逻辑了。
等等,难道 RunFunc 不需要我们亲自实现吗?这就是我们本次源码阅读的最后一个问题,看看 harbor operator 如何将任意 Resource 的 Reconcile 过程泛化并抽象成单一函数。
那么比如说这里有一个 r.Controller.AddServiceToManage(ctx, service)
1 | func (c *Controller) AddServiceToManage(ctx context.Context, resource *corev1.Service, dependencies ...graph.Resource) (graph.Resource, error) { |
有意思的地方有两处:
Resource 定义,这个我们之前没见过
Graph AddResource 的调用出现了,我们关心的 RunFunc 的构造就在这里,稍后做研究。
先来看这个 Resource,记得在 Graph 中,由于不关心 Resource 具体是什么,因此在那里给了一个 interface{},这里对于 Resource 比较关心,因此比较重要:
1 | package controller |
看得出,本质上是对 K8s generic resource,在此之上 Harbor Operator 做了一些额外的封装。
这些额外的封装分别是 checkable 和 mutable。
其中 checkable 定义了 resource 合法性的检查,比较简单。
Mutable 看着很花,实际上就是简单的 Monad Composition;
Mutable 构成了一个 Monad,和函数 composition 不同的是,每一次 compose 之后计算结果都会产生一个额外的结果用来表达计算副作用,因此无法直接连续应用 function,在一些函数式语言中提供了 fish operator >=> 来实现 Monad Compose。
使用 scala 语法描述的 fish operator 签名如下
1 | def >=>[A, B, C](f: A => M[B], g: B => M[C]): A => M[C] |
讲人话就是 mutable 数据结构允许将许多个 mutation function 以一定顺序组合起来,在遇到报错时,直接退出。
至此,研究如何构造 RunFunc 的前置条件已经齐全了,最后让我们看看 harbor operator 是如何利用这些前置条件为各个毫不相同的 Resource 通过通一套逻辑构造出 Reconcile 的具体实现。
ProcessFunc 本质上是一个高阶函数,利用闭包捕获一个 depManager,主要是用于计算和更改资源本身和它的各项依赖的 Checksum。
构造出来的 RunFunc,先检查资源以及其依赖是否改变,如果没有改变直接检查一下 ready 就完事;否则更新 Checksum 供下次检查之后再去调用 c.applyAndCheck 真正 Reconcile Change。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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71func (c *Controller) ProcessFunc(ctx context.Context, resource runtime.Object, dependencies ...graph.Resource) func(context.Context, graph.Resource) error { // nolint:funlen
depManager := checksum.New(c.Scheme)
depManager.Add(ctx, owner.Get(ctx), true)
gvks, _, err := c.Scheme.ObjectKinds(resource)
if err == nil {
resource.GetObjectKind().SetGroupVersionKind(gvks[0])
}
for _, dep := range dependencies {
if dep, ok := dep.(*Resource); ok {
gvks, _, err := c.Scheme.ObjectKinds(dep.resource)
if err == nil {
dep.resource.GetObjectKind().SetGroupVersionKind(gvks[0])
}
depManager.Add(ctx, dep.resource, false)
}
}
return func(ctx context.Context, r graph.Resource) error {
res, ok := r.(*Resource)
if !ok {
return nil
}
span, ctx := opentracing.StartSpanFromContext(ctx, "process")
defer span.Finish()
namespace, name := res.resource.GetNamespace(), res.resource.GetName()
gvk := c.AddGVKToSpan(ctx, span, res.resource)
l := logger.Get(ctx).WithValues(
"resource.apiVersion", gvk.GroupVersion(),
"resource.kind", gvk.Kind,
"resource.name", name,
"resource.namespace", namespace,
)
logger.Set(&ctx, l)
span.
SetTag("resource.name", name).
SetTag("resource.namespace", namespace)
changed, err := c.Changed(ctx, depManager, res.resource)
if err != nil {
return errors.Wrap(err, "changes detection")
}
if !changed {
l.V(0).Info("dependencies unchanged")
err = c.EnsureReady(ctx, res)
return errors.Wrap(err, "check")
}
res.mutable.AppendMutation(func(ctx context.Context, resource runtime.Object) error {
if res, ok := resource.(metav1.Object); ok {
depManager.AddAnnotations(res)
}
return nil
})
err = c.applyAndCheck(ctx, r)
return errors.Wrapf(err, "apply %s (%s/%s)", gvk, namespace, name)
}
}
实际上的 Reconcile 逻辑就在 applyAndCheck 中,这就使用了之前在 Resource 中注册的 mutable function。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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53func (c *Controller) applyAndCheck(ctx context.Context, node graph.Resource) error {
span, ctx := opentracing.StartSpanFromContext(ctx, "applyAndCheck")
defer span.Finish()
res, ok := node.(*Resource)
if !ok {
return serrors.UnrecoverrableError(errors.Errorf("%+v", node), serrors.OperatorReason, "unable to apply resource")
}
err := c.Apply(ctx, res)
if err != nil {
return errors.Wrap(err, "apply")
}
err = c.EnsureReady(ctx, res)
return errors.Wrap(err, "check")
}
func (c *Controller) Apply(ctx context.Context, res *Resource) error {
span, ctx := opentracing.StartSpanFromContext(ctx, "apply")
defer span.Finish()
l := logger.Get(ctx)
l.V(1).Info("Deploying resource")
resource := res.resource
if err := res.mutable(ctx, resource); err != nil {
return errors.Wrap(err, "mutate")
}
err := c.Client.Patch(ctx, resource, client.Apply, &client.PatchOptions{
Force: &force,
FieldManager: application.GetName(ctx),
})
if err != nil {
l.Error(err, "Cannot deploy resource")
if apierrs.IsForbidden(err) {
return serrors.RetryLaterError(err, "dependencyStatus", err.Error())
}
if apierrs.IsInvalid(err) {
return serrors.UnrecoverrableError(err, "dependencySpec", err.Error())
}
return err
}
return nil
}
在所有操作都做完之后,调用 c.EnSureReady 调用之前注册的 checkable 完成 Reconcile 的的过程。
至此,Harbor Operator 代码中我们关心的重要设计已经研究完毕,令人叹为观止。
]]>I’ve been very busy with my new work at Bytedance, and why am I write this lil blog post in English? I’ll explain.
I always had this strong impulse of record my day, to see what I was doing, make a statistic about it.
After a while I found two things: record my day and have a reflect base on that is very very useful, what I reflected during June eventually helped me to make that decision to change my work. Yet for fine-grained time tracking, I kept failing every since.
Maybe it was something about my tool, I tried excel, self-defined JSON schema, Time Rescue App for auto-time tracking (turns out I never really reflect on that because of the noise, It literally record every thing on you computer, and people like me don’t really need that..), Several Apps on my phone… And I almost gave up about it.
But I managed to make it a habit to record notes for my day, and one day I was persuading one of my good friend about how important it could be to make notes, he questioned me about the tools, that the first time I considered emacs
and org-mode
seriously.
He said: “man, we are talking about software! Could any tool survive for like 5 years?”
I said: “oh on that occasion, maybe you’d consider org-mode
.”
I know he was jokingly being picky about my recommendation, but it occurred to me: “what about give it a try?”
I am a vimer for two years, I love Vim and got fever on Vim for a long while (I always ran into something and thought it was the silver bullet, for vim, for Scala, etc.) Now I know it’s just a tool managed to survive over the last 30 years. a lot of it’s design is for historical reasons and not because of it should be like that (for take away, maybe you are interest of why, it’s a very nice video telling you why emacs and vim are designed like this from a historical perspective).
And what would a vimer do when he was considering something from the emacs world? Naturally something like Spacemacs: emacs with vim key bindings, yes, they could be in harmony.
Then you could guess how I started using Spacemacs(here are my config), and org mode. I am still learning, getting used to the tool, and started record my daily notes using org mode, yet one thing I could not resolve: it unnatural to type Chinese with Vim key binding I have not find a good solution for that. That’s why I’m writing this using English, Also it feels differently when you’re speaking another language, feels like a different person, you probably could never see me muttering things about myself in such a explicit flavor.
Still, it’s nice to talk to you all :)
Also I have not find a good solution to replace markdown with org in my hexo blog…
]]>本文的目标读者是刚刚开始接触监控系统,以及对 Prometheus 知之甚少的弱势群体(一如写就本文时候的笔者)
本文中用于搭建 Prometheus 的环境:
⚠️ 请注意:本文列出的命令行参数需要根据当前环境稍作调整(比如 Prometheus 二进制包版本等)
这里列出了一些推荐的前置阅读项:
既然是在 K8s 上手动搭建 Prometheus,那我们在这里有两点规约
刻意不去使用 Helm-Chart,Prometheus Operator 之类的快捷部署方式,这里列出来供参考:
那我们开始吧!
首先第一直觉是在裸机上做概念验证,先跑起来再对进一步的配置做实验,最终,当我们理解了 Prometheus 的各个配置项之后,重新部署到 K8s 上应当是信手拈来。
我想尝试偷懒,搜索教程博客之后发现都不清不楚并且大部分都已经过期了,结果浪费半天时间还得老老实实去官网读文档。
1 | curl -LO "https://github.com/prometheus/prometheus/releases/download/v2.22.0/prometheus-2.22.0.linux-amd64.tar.gz" |
查看一下目录,发现自带一个配置文件 prometheus.yml:
1 | # my global config |
这时候我们通过运行刚刚下载的 Prometheus 来监控它自己,获得一个小小的满足感反馈闭环:
1 | ./prometheus --config.file=prometheus.yml |
可以看到这个时候 Prometheus 已经启动了,访问 http://devbox:9090 看到它的用户界面,这时候随机点点,可以对 Prometheus 提供的功能有一个大致的感觉,让我们对 Prometheus 正常运行时候的表现有一个认知。
此时我们裸机运行一个 Node Exporter 观察一下本机的各种指标。
1 | curl -LO "https://github.com/prometheus/node_exporter/releases/download/v1.0.1/node_exporter-1.0.1.linux-amd64.tar.gz" |
接下来修改配置让 Prometheus 从中采集指标
1 | # my global config |
打开 Prometheus 的 web UI,观察到已经新增了一个叫做 node-exporter
的 target,查看一下工作负载(运行了一个可以占满所有核的永远计算斐波那契数列的程序):
到此,概念验证阶段圆满完成。
注意:作为概念验证环节,这里不推荐直接使用裸机部署的 Prometheus 监控 K8s 集群,原因在于集群外访问 K8s 组件需要配置证书和具备相应的访问权限的 ClusterRole(此处省去笔者非得尝试用裸机部署的 Prometheus 监控 K8s 集群以及其中各种组件的时候踩的各种坑)。
接下来我们要通过 Prometheus 去监控我们的 K8s 集群。
在对 Prometheus 的介绍中可以了解到,Prometheus 主要是 Pull based 数据获取方式,因此需要有服务发现,也就是让 Prometheus 知道从哪里去拉取数据,以便用户去查看。
那么首先就要解决一个问题:对 K8s 集群的服务发现——秘密一定隐藏在配置之中。
在文档中有对于 Prometheus 配置的详细描述。
对其中如下几个配置项稍作描述(彼此之间并不一定正交):
<global>
:其中的配置对任何其他配置项都有作用,并作为其他配置中项目的默认值。<scrape_config>
:定义了一个监控任务,描述了 Prometheus 应该从哪以及如何监控这个目标等信息。<tls_config>
:描述了 TLS 配置。<*_sd_config>
:Prometheus 通过这个系列的配置项提供了对一系列预定义监控目标服务发现的配置(sd 代表 service discovery)。<static_config>
:对于 Prometheus 没有预定义的监控目标(比如裸机手动部署的任意服务),可以通过这个配置项去做服务发现,上面我们在做概念验证的时候就用到了这个配置项目。<relabel_config>
:在开始拉取监控目标的各项指标前,可以通过这个配置项对于一些 lable 进行改变,Prometheus 提供了一些预定义的 lable 规则,relable 可以多步进行,在 relable 结束后,以 __ 为前缀的 lable 会被删除。看起来 Prometheus 中最核心的配置项就是其 <scrape_config>
了,每一个都定义了一个监控任务,类似 namespace 的概念,主要是提供了一个监控目标的聚合,在其中我们通过定义 <*_sd_config>
或者 <static_config>
来告诉 Prometheus 具体从哪些端点去拉取数据,以及如何过滤这些端点。
接下来通过实战来对这些配置项加深理解!
部署的核心工作在于思考清楚为了在集群中部署 Prometheus 都需要哪些资源,笔者直接在这里公布答案:
在应用了 RBAC 的 K8s 集群上,我们需要为 Prometheus 定义一个具备足够权限的角色,可以读取集群状态以及各项指标,因此需要第 5-7 项。
这里给出了一份笔者自己搭建过程中积累的资源声明集合,除了上述的资源还多了 kube-state-metrics,按顺序操作就可以得到一个部署好的 Prometheus 了。
对于 Node-exporter,由于是对于机器本身的监控,因此需求是每个 Node 一个,由于我们同时希望享受 K8s 的生命周期管理,因此 DaemonSet 是最好的选择。
由于跑在容器之中,不做配置时并不能收集到真实的 Node 指标,因此需要在容器中挂载 host 上的特殊位置以便 Node-exporter 收集指标。
1 | args: |
然后通过 Service 暴露出 Prometheus 可以长期访问端点即可。
Prometheus 则使用 Deployment 完成部署,在部署 Prometheus 之前,需要给它配置足够的权限使得它可以访问必要的端点来收集指标,在配置了 RBAC 的 K8s 集群中通过 ClusterRole/ServiceAccount/ClusterRoleBinding 来完成这一目标,在配置完成之后,Prometheus 通过 ServiceAccount 来完成相应的鉴权从而访问需要的端点。
1 | apiVersion: rbac.authorization.k8s.io/v1 |
到现在为止,我们已经拥有了实现监控目标的一切前提条件了,那么怎么去驱动 Prometheus 这个强大的引擎充分利用好我们布置好的环境实现监控呢?
结合前文中对 Prometheus 配置的介绍,四个监控目标用四个 <scrape_config>
定义:
对于 node-exporter:
1 | - job_name: 'node-exporter' |
由于是在集群内部,因此不需要额外的认证,也不用开 https 访问。
这里通过 node-exporter 的例子对 <relabel_configs>
做进一步的解释:
label 就是有关某一个端点的属性,而不同的端点可能在同一个 label 下可能有不同的值,<relabel_config>
所做的事情,就是针对这些 label 进行一些修改和过滤的操作,使得我们能够过滤/修改出所需要的端点。
可以看到,在上面的 config 中,有三个 relabel 动作,其中第一个的意思是,从 __meta_kubernetes_service_name
这个 K8s 服务发现预定义的 label 的所有值中,按照给定的正则表达式 “node-exporter” 进行过滤,根据 action
,保留匹配到的目标端点,丢弃掉剩余相同标签的值。而后面两个 relabel 动作是为了给将 node 和 host_ip 这两个语义标签通过改名的方式保留下来。(还记得吗,双下划线开头的标签最后都会被删除)
对于 prometheus 自己:
1 | - job_name: 'prometheus' |
使用同样的套路过滤出端点
对于 kubelet 和 cadvisor,情况变得稍微复杂了一些:
1 | - job_name: 'kubelet' |
注意到 role 变成了 node,因此 Prometheus 会默认从 <node_ip>:10250/metrics
收集指标,这里多了一个 bearer_token_file
配置项,由于 kubelet 默认不允许匿名访问其指标数据,这里就是用到前面配置的 ServiceAccount 的地方了,这里我们为了方便使用 insecure_skip_verify: true
的方式跳过 TLS 认证。
对于 ApiServer,又变得稍微复杂了一点:
1 | scrape_configs: |
在这里我们通过 <relabel_config>
完成对于 ApiServer 自身端点的过滤,在提供 token 鉴权的同时要多提供 CA 文件认证身份,这样我们就可以访问到 ApiServer 啦。
至此我们就完成了 Prometheus 的部署以及对目标端点的监控配置。
感兴趣的读者可以进一步修改 config,来观察不同配置下 Prometheus 的行为来加深理解,这里留下一个小作业:我们如何通过裸机部署的 Prometheus 监控 K8s 集群呢?
前文中我们探讨过时间的形状,它的力量,它的顽皮和嘲弄;其中有一点特别需要强调:那就是时间流逝的速度由于人的不同状态因此也是不同的——我和帮主观察到最近它似乎是指数级变快:以至于今年的一半似乎是一瞬间就过完的;然而仔细想想,似乎去年也是如此。我总是认为这才从上次的文章写完之后没过多久,却已经一年有余。
这一年虽然快,却也丰沛,然而除却丰沛,还多了生活的气息,和对生活的见证。
而这一切也的确始于一年多前,始于时间还未加速的日子,始于一本书,一位建筑师将自己写进了书中。
在这一年的2月11日,daidai发了我一张图,是一本书的封面,上面写着:一部自传:弗兰克·劳埃德·赖特
。
“你会喜欢的。”
于是这位痛恨多愁善感却又细腻丰沛的建筑师活在了我的脑子里。
通过重温他的生活,他教给了我两件重要的事情,用以见证这个世界:
第一是风格,风格的形成需要时间来催化,我们首先需要扎实地做好那些基本的事情,达到从心所欲而不逾矩的境界,拿骑自行车来打比方:到最后你不需要用双手去操控方向,而是用身体,或者是用心,心里想着从非机动车道上的实线上压过去便绝不会偏航。在基础搭好之后,就是一个具体的创作过程了,面对一个问题领域,提出那些个在关键路径上困扰着我们的问题,然后运用之前打好的基础去解决问题或是抒发情感。
在我们画上句号的那一刻,风格则会悄然到来,此时要做的便是迎接和欣赏,这是一个喜悦的时刻。
风格是是什么呢,像是触碰的到的客观世界中的色彩,拿梵高的画作来说,他会夸张地表达这个世界中存在的一些现象,毫不吝惜自己的油墨,在某些部分也可以说是相当潦草,形成了强烈的辨识度,这种辨识度应当说是风格的体现。然而每个人都会以不同的方式去欣赏这种美感,去欣赏他的画,或者甚至不以之为美;是否对于风格的理解是一种完全的主观感受呢?
什么是好的风格呢?这就引出了建筑师谈到的第二件重要的事情:品位。谁具备这样好的品位,使得他可以欣赏某件具体的艺术作品,并尝到其中呼之欲出的风格?
像是梵高笔下的柏树和星月夜;济慈诗里夜莺令人魂牵梦绕的歌声;贝多芬如诗如画,生意盎然的第六交响曲;这些被公认优秀的作品,每一项都别具一格,却都具备着一股坚实曼妙的力量,可以跨越时空,可以跨越人心的屏障,使人坚定;给予人以意义,赋予生命以连贯性。
然而欣赏这些作品无疑需要某种程度的训练,拿忧郁颂举例子:Aye, in the very temple of delight, Veil'd Melancholy has her sovran shrine
——在欢乐的殿堂中,隐藏的忧郁有着她至尊的神龛。当我第一次读到的时候,正是刚刚拥有了成年人的心智却又涉世未深的年纪,只觉得这句诗想表达的意思很隐晦;而随着我了解到济慈的生平,以及自己也慢慢开始像生命中的忧郁神龛祭拜了之后,它才开始以一种独一无二的方式生长在我的心中。
或者拿戏剧举例子:“为什么海森堡会在1941年回到哥本哈根?”
,为什么在那次哥本哈根波尔家中的短暂会面会终结海森堡和波尔长达二十年的友谊?这个萦绕了大众半个多世纪的问题,在话剧《哥本哈根》中,作为线索,构成了整个话剧的背景和框架基石,实则以巧妙的方式演绎着人的内心究竟有多么复杂,在单个塌缩于现实空间的具体决定和行为背后,可能藏着十数个或者数十个处于叠加态的动机。
海森堡是波尔一家的老朋友,也同时是一个分享着战争罪行的德国人;他像是波尔最优秀的儿子,也同时自己肩负着丈夫和父亲的角色;而他的动机则比他的身份还多:是因为海森堡想阻止核武器的诞生,是因为海森堡想肩负起复兴整个德意志科学界的重担,是因为海森堡成为了整个德意志最年轻的教授,还是因为海森堡单纯地像个想家的孩子,或者海森堡自己根本就不知道为什么要来,而这种迷茫混沌的状态则随着链式反应指数级地扩大,最终拯救了所有没有被轴心德国用核武器轰炸的城市。人啊!是一组矛盾的集合!
在2018年的夏天,小狮带着我去北大校园看了这部话剧,从此它就在我心头挥之不去,并随着我一遍一遍地重温变得更加丰富。这也许就是构建品位的过程:先是进入我的记忆中,并伴随着生活的进行,话剧中的一幕幕在现实中以各种不同却又相通的方式演绎,于是我便能够进一步欣赏它;同时它也指导着我的现实生活,对于其他人的决定背后的那十数种动机给予同等的尊重。
那么什么是品味呢?
你可以说品位完全是教条的,是一套逻辑自洽的虚拟系统,从而在人与人的心中搭起一座座桥梁,而这些桥梁的目的地则完全不受限制,你也可以说品位应当是客观的,它超越了人的好恶,只应是现实世界在人心中的映像。
建筑师没有给出答案,他只是默认品位存在。教条也好,还是客观的存在也好,其实是矛盾而统一的,它必反应了现实中的某种属性,几乎不与现实相悖,也同时没有止步于现实和机械唯物主义。它为人类的理性和审美作出指引,它于现实的环境之上构造了文明。这些丰富的作品给我提供了原材料,如同诗歌一般,初次见面也许并不理解,但时常回想,时常回响,也就构成了我的品位。
风格和品位如同apply
和eval
一样,道法自然,构建了建筑师坎坷而丰饶的一生。
在我每次翻开那本自传的时候,无论心情几何,最终都会归于一种和谐的恬静。一个人应当如何度过他的一生?希望像建筑师一样丰沛。
当我读到弗兰克最终决定加入沙利文的事务所,向从前的老板说再见的时候,他在自传中有一段这样的描述:
这一幕在我的记忆中存着良久,直到我也有了类似的经历,才知道做了的准备全无用处,阵痛来的出乎意料。就如同窗帘上的小爪印那样,葡萄在我不在的日夜里,无数次走到飘窗上,用小爪子挠着窗帘,认真地希望着在她挠下去的一刹那我会出现在窗帘后面回击回应,她挠着挠着,因为太长时间没有修剪而尖锐的爪子勾住了窗帘,拉出一道道小爪印。
]]>没什么证据能够证明Bryan的离去,我们不需要告别,所以他的离开也没有确切的时间。
意识到这一点的时候,是在下班的路上。那晚我走在清华对面,十点多车棚应该已经锁了,所以我决定把车子留在单位,执行晚下班的routine:橙汁,和足以覆盖掉街上嘈杂声音的音乐,再加上一点点悠闲。大概二百来米之后,走过清华东路上唯一能够看到满月的地方,这个念头钻进我的脑海——Bryan好像离开了。
下楼的时候我看到两个人随性地盘腿坐在大楼门口石狮子旁边的大理石台子上聊天,好像拿着酒。我只敢瞟一眼,直视的目光也许不太礼貌,也许即使我真的盯着看他们也不在乎,但至少我得呵护好自己心中这两个难得的人的镜像。他们在聊什么,是否有关于当下、过去、和未来?是不期而遇呢,还是在赴一个不算太久的约定?我无从得知,转头走进了便利店用橙汁和他们遥遥干杯。
清华东路据我观察是有坡度的,或者据S的说法仅仅是因为上班总是让人有点抗拒的原因,回家的路走起来要轻松一些。我的注意力转移到脑海深处,思量起另一个我没有得到的告别,思量起室友不辞而别的原因。我们一早商量好了续租房子的问题,结论是他要搬走,没想到在那天早上我醒来之后,头天还一起玩植物大战僵尸的室友连同他生活过的痕迹一起消失了,你很难不去想是不是自己做错了什么,开始想象如果对于他真正的朋友,他会怎么做呢——但从不辞而别这一个小前提看来我显然已经是不够格的朋友了,而这一点十分令人难过。
但内心深处我没法不注意到大前提——我们的确是真正的朋友。因为人心不是石头做的,即使是室友那样子那么内向性格的,永远怕给人带来麻烦所以那样内敛的人,也会被生活所打动,那些深夜里从技术发散而来对技术本身、人和世界的讨论,那些有趣的逻辑陷阱和谜题:“不到长城非好汉”的逆否命题是什么;我们假设自己是机器尝试以机械的逻辑出发最终会得到什么有趣的东西,这经常会让决定早睡的我俩熬夜到三四点,然而第二天依然不吸取教训;或者是简单的问候,或者是散步。种种这些至少已经实现了我自己对于朋友的定义,虽然你永远也没有办法知道任何另一个人在想什么,但是根据以往经验,对方只有在有相同想法的时候才会有我上述的种种行为。
我想,也许我会得到一封信,或者是一个可以预见的永别。
没什么不确定的,我会等待这封信直到永远。
这时候我开始确信起来Bryan作为一个独立的人已经彻底消失了,我的脑海中寻不见他的踪迹。
有关Bryan的事情也模糊起来,他身上似乎总是带着一些忧伤,与我的不同,他的忧伤更加纯粹,并且没什么具体的理由,也许他享受这份月亮一样的忧郁,享受那种如同文火慢炖胸腔的孤独感,又也许这是他的某种仪式,通过忧郁来荡涤自己的灵魂。
Bryan最后的故事是在前几天,楼下的电磁门打不开了。当时他提着一袋垃圾准备出门扔掉,再去面包店给自己买一袋粗粮面包作为晚餐——我在家呆了一下午了。Bryan反复按下门开关,没用,用身子撞门,也没用,甚至声音都不大,只是一声闷响。
道理很简单,比如我们在物资充裕的情况下在家里待上十天半个月不出门都没问题,但是一旦当我们知道家里门被从外面反锁之后,自由的滋味就会瞬间变得无比诱人,此时在家一个小时都待不下去,就是跳楼,也要出门,这是没有商量的。Bryan也是如此,他脑子里闪过回家随便吃点的念头,但是仅仅是因为门坏了,他就一定要出去,出门成为了唯一的理由,动机和目的,至于晚饭,则不那么重要了。
于是Bryan听到外面的乌鸦叫,好像自己也成为了其中的一员,他来到一楼和二楼之间的楼道,打开通向自由的窗子,窗子很小,只够他蜷缩着出去,但Bryan感到自己如同飞起来一样开心;来到了二楼的平台上,抓着防盗网,Bryan想象自己是一个熟练的探险家,伸出脚寻找一个落脚点;最后一步是踩到单元楼的信箱上,然后跳下来,呼吸着如同空气一样真实的自由。我拿出钥匙,试探性地从外面开了一下门,门开了,拿上垃圾,踏上了寻找晚饭的征程。
那晚我见到了如此壮丽的晚霞。
我最终收到了室友的那封邮件,他的歉意和感谢,整篇都是我们友谊的证明,做了211天室友我被认为是一个温暖的人,是一个合格的朋友。而经过一年左右的反思和修养,和最近的种种迹象表明,Bryan作为我用来逃避自己不良情绪的替身已经消失了,多年前那个令Bryan诞生并长久以来一直折磨着作为Bryan的我的事情,最终让我成为了一个更加真实和坦然的人。
弗兰克·劳埃德·赖特的话可以作为整个Bryan事件的盖棺定论:
下意识的虚伪已经足够可怕,而对于任何富有创造力的艺术家而言,清醒的虚伪会更无情地侵蚀他的灵魂。没有任何懦夫能创造出作品,我曾经这样认为,今天依然坚信。
最后做个总结,之前的文字提到过我看不清自己的道却在雕虫小技上多有徒劳,现在发现无论自己有什么样的目标,如何努力都是为了成为一个更好的人,一个情绪稳定、清醒、自知、专注、丰腴的人。毕业时候的总结也依然准确:
接下来的生活我预感仍然充满了犯良性错误的机会以及不犯任何错误的时刻,过程并不轻松愉快,但绝对引人入胜。
而生活也的确就像是滑雪,转左,转右,或思考它并死去。我们不仅拥有生活,更被生活所拥有。
]]>等离子蛋蛋和Bryan语言不通,事实上,在他们两个中只有一个人会说话。虽然讲得有些离谱,但Bryan说的至少是实话——谁不需要喝水呢?
时间后来被证实并非像人们想的那样,是一维的。或者说仅仅用“一维”不足以表达时间错综复杂的网状结构。
如果把这个秘密公之于众,那么大家一定会认为Bryan疯了。
但真相的滋味一旦尝到,就一发不可收拾,即使当作秘密藏在心里永远也不给另一个人类敞开,忍受极大的孤独,Bryan也要顺从自己的好奇心,继续寻找证据。毕竟这真相诞生在他的脑子里,诞生在他的回忆里,确切地说是诞生自他心底另一个不能说的秘密,那是一场可怕的思维实验,足足让Bryan担惊受怕了六个月又40天的时间。
可是有关于时间的现象也同时与人心有关,众所周知,人们将自己作为牢房(或者是处于有意的,或者是处于无意的),让自己的心永远不见天日,观察别人的心更是绝不可能。我们只能借助语言将自己的心从丰富多彩结构复杂的状态投射在一根直线上,塞给另一个人,然后祈祷他/她的天才可以将我们被笔直地扭曲了的心复原。
不过也会有这样的时候:我们的心会不经意地闪烁到别人地心里,或者反过来,这时候表情会出现在人的脸上,大多数情况是笑,有时候是幸福的酸楚。因为心关在自己所设计/构成的牢房里,最残忍的心理是好奇和羞涩,Bryan深知好奇另一个人的心所带来的痛苦,无论是晴是雨,一个人只能凭借着微不足道的证据去猜测。而羞涩则加深了好奇所带来的痛苦。
好在大多数时候事情与痛苦无关。
Bryan有时会尝试向人讲解他对于时间的理解,比如说人心对于语言的所承载的另一个人心的构造过程,实际上也并非像一般人所想象的那样是只存在于时间线上的一个孤立的点。有时候一个人个一句话要跨越数年的时间突然起作用,如同终于获得了解开谜题的钥匙一般。于是语言加上时间的作用,在这个世界自由穿梭,人由于没有办法预知未来,对于这种作用是很难感觉到的,即使感觉到了,也绝对无法向另一个人描述——方程已经被引入了太多未知数。
对于长期讲一种语言的人来说,他们有时会用同一种并不一定正确的模式来构造和解读语言中隐藏的人心。如果这时候突然讲起另一种语言,由于对语言本身的不熟悉,会让人不由自主地小心对待,从而让语言更加直通心灵。Bryan有一个一起烤过篝火的外国朋友,他们之间的交流时常能让Bryan意识到这一点,但相较于语言本身的作用,人的心的相似性才是友谊的坚固基石。
正是由于这种对于未来的不可知,对于过去的掌握和记忆以及存在于时间之网的不同位置成为了人与人之间巨大的差异与同质。因此Bryan会嫉妒时间,或者说嫉妒时间在一个人身上的印记。这种嫉妒实际上是由两种截然不同的情感构成的:
一是前面说的好奇,好奇在不同的时间里这个人究竟解开了多少语言的谜语,好奇时间带给另一个心以怎样的恩惠与苛责,从而变得丰腴(Exuberance),好奇自己是否能以相同的时间作为度量,体验另一个心的苦难与欢愉,是否,也能变得丰腴?
从而就引出了另一种情感:那就是深深地自卑,大而化之缺少时间的作用,就是缺少无数把钥匙,无法打开从前的人留下的语言中的秘密,也无法有力的形成新的词语构成新的语言将自己的心以新的形式编码;小而化之由于时间的不同步,通过非语言的方式去影响另一个心的概率也大大降低了,——Bryan问自己是不是幼稚的可笑,我事到如今也能理解一二他这种感情了。
终究Bryan决定不做一个懦弱的人,他开始学着分辨在时间中变化的事物和不变的事物,并且相信时间也终究会让他自己变得丰腴。
在铁面无私的时间面前,相对作用开始显现它的威力:”隐藏的忧郁有着她至尊的神龛“,这几乎是Bryan反复咀嚼最久的诗句了,并且随着时间的流逝,会有越来越多的钥匙可以解开它部分的谜题,从而可以品尝到不同的气息。
Bryan发现在快乐中持久地品尝忧郁的滋味是一种一致性的体现,因为只有在同一个参考系下,才会因为对前者的留恋而诞生后者,在Bryan最近看过的一本书里,这种一致性的时间跨度竟然可以达到五十三年之久。
不过一致性终究是一种可贵的品质,更多时候则是随着时间的流转,人的心会变得不一样,简化到我们所说的这种二元相对作用上,就体现为快乐会随着快乐的增加而变得平淡,而忧郁会随着时间的流逝而减少。最可悲的情况则是曾经的快乐变成了痛苦,而曾经的忧郁统统失去了意义,Bryan将这种变化称之为背叛。
他决不会允许这种对自己的背叛再次发生,但只要节奏恰当,平衡自然会显现,就如同一个人第一次看到杨树的眼睛一样,那些大大小小的眼睛!没有丰腴的心就很难看得见。
那么究竟什么是在时间中变化的事物呢,什么是在时间中不变的事物呢?假如曾经相濡以沫的友谊在如今看来已经不值一提,是否可以说存在于那段时间之上的人心已经走向了死亡?
或者每一个人每一天都是新生,承载着昨天的心的记忆,将这项发现作为拖延症堂而皇之的理由。
等离子蛋蛋最终还是接受了她的命运,开始安详的打起呼噜,Bryan这才发现时间已经停下来了。
一个嫉妒时间的人如此敏感,时间也需要他成熟且勇敢。
]]>如果要现在的他形容一下悲哀的形状,他会说像流体,在胸口中盛着,走路时候会晃着,撒出一点来腐蚀他的心。躺下的时候开始平稳地流淌,唯有随着心跳一起颤动。
至于温度,绝对算不上冰凉,是温热的,比胸腔的温度稍高一点,不断缓缓释放热量烘烤着心脏,这感觉就像…文火慢炖,和着痛苦熬煮,却总也煮不熟,活跃的神经持续提醒着他痛苦的滋味。
这个世界上只有一种超距作用——那就是在他后脑勺操控着这锅深渊之汤的回忆们,在与雨打浮萍般的命运一同酿制发酵了一年有余的前一段时间,在他打开这件装满废弃回忆的地窖门的时候,酿好的鬼混带着温暖甜蜜的愉悦与怨恨气味,一下子涌上来让他犹如被一击重锤击中——这表现在我看来实属羞愧,或者说明白一点,羞愧到无地自容,我从来没见到他这样过。真诚自信却又年轻坦荡,现在却迷失在时间的之中,他分不清哪些事情发生在现在,哪些事情发生在两年前,哪些事情他以为发生在昨天却其实已经过去了一年。这些他在过去的一年里以为自己已经早已丢失的回忆,最终以一种如此清晰可见的形式弥散在所有有序的其他回忆之中,如同癌症一般感染。更早些的干净回忆如今也开始有了严厉的棱角,游荡在他的脑子里。
可惜可怜的Bryan只是一位可悲的一维时间生物,不知道自己的脑子在某些时刻中只充当着传音媒介的作用,将久远的信息传递给无数次迭代后的自己,同时做着带着淡淡悲哀的自信蠢事。这一刻他终于意识到了,夹在神与野兽中的人性的悲凉,然而仍然不受控制,要为这螺旋悲剧做祭品。
1 | Ode on Melancholy |
此刻他满腹凄惶,可怜的Bryan。
]]>适才和大班长聊天,我们心里都清楚这大概是毕业前最后一次聊,聊的无非是前程,最后止于一个暗号似的短语,我发出去,他又发回来:为世界之光。
它出自西安交通大学校歌。
美哉吾校,真理之花,青年之模楷,邦国之荣华,
校旗飘扬,与日俱长,为世界之光,为世界之光。
美哉吾校,鼓舞群伦,启发我睿智,激励我热忱,
英俊济跄,经营四方,为世界之光,为世界之光。
美哉吾校,性灵泉源,科学之奥府,艺术之林园,
实业扩张,进步无疆,为世界之光,为世界之光。
美哉吾校,灿烂文明,实学培国本,民族得中兴,
宇土茫茫,山高水长,为世界之光,为世界之光。
毕业在即,很多事情很多人都来不及处理和告别,自己就要随着时代的洪流匆匆离去,或是被拍打在更广大的命运的岸堤上,或是乘风破浪,砥砺前行,成为时代的领航者。且不说有句说烂了的漂亮话:”我们的征途是星辰大海“;雄心壮志总是有的,尤其是这个时候——分别的时候——结束的时候——新开始的时节。这种心情,我想惟有我这又爱又恨并且即将要离开的母校的校歌中的这句“为世界之光”能表达。
天地交而万物通,上下交则其志同。
我终于毕业了,可以大方地说母校这词了。
不知道从哪个师兄或者师姐那里听来的:“西安交大的人都有一股戾气,甚至可以说‘考败来交’”,这印象对于四年前初入大学校园的我来说是非常深刻的:还未开学,就从新同学的qq空间中窥见考北大失利的抑郁,原话是“向死而生”;在学生会社团中感受到的这种情绪尤为明显——大家都不开心,认为自己来到了这里是受到了亏待;未来的“室友”也在一次夜谈中向我倾诉自己犹如丧家之犬,打乱了计划、摔碎了梦想、有辱师门啊!
缩小到本专业:空气中更是弥散着一股绝望的味道。由于录取分数低,本专业成了调剂人才的好去处,再加上所有新生来学校报到时候都有机会无代价转专业进来,以吸引学生。看起来十分廉价,是一个鬼都嫌弃的专业,来者何以不嫌弃自己。军训时候隔壁电子与信息工程学院来领导问候,在田径场上的三千五百位新生起立者八百人众,将田径场一角围得水泄不通,场面一度非常壮观。而软件学院仅有软件工程专业一个系,领导前来,只有淅淅沥沥八十余人,凄惨寂寥。不过我那时天真烂漫没想到这些,倒是趁机观察了一下之前从来没见过的女生们。
以上心态是交大和交大软件工程专业的同学的一大共通之处,我可能是个例外,因为我和同学们都是凭实力考上的大学,凭什么你们能凭实力考败,我不能凭实力超常发挥喜大普奔来到大学校园而绝无忝列门墙的感受?但同学们的失落,我终究是感受得到的,显然也受了影响。所幸这种心态在大多数人那里只持续了最多一年。待我们熟悉了新的根据地,建立新的友谊,甚至舔舐伤口以至于赢回了新的梦想后,往昔的失落和遗憾都变得不足为道,也才开始模糊的意识到我们所拥有的新的使命和视野,还真就如同校歌中所唱的一样,该“为世界之光”。
大一时期教计算机基础的老师笑道我们学校是“工程师的摇篮”,意思只培养的出工程师,却培养不出领袖,成为社会的中流砥柱,却多是埋没在洪流之中为上层建筑做坚实基础。他点评了一下校训“精勤求学,敦笃励志,果毅力行,忠恕任事”;我看来他是有苦说不太出来,可歌又可气,不能说不赞同,却又兴叹没有教学生立鸿鹄之志。
倒是很符合我自己的特点,不适合做leader,又不甘于做一个follower,不过称得上是一个observer,人不傻但校训中的四点每样都是我缺少的,需要学习一个的。至于其他的,道术体用之类的则需要在生活的细节中去品味,不耽误往前走,另外我还有个很奇怪的能力——能够在不经意之间说自己最想说的、做自己真正想做的,有时候这种误打误撞会让人磕碰的很惨,不过在剪去枯枝败叶后,人真的会更精神一些。
于是我就在前文中所描述的戾气的影响下,在自己无心的真诚选择中,步子凌乱地徐徐前行。生活中的种种事件串在一起教人事后想起来那些巧合和千钧一发只会觉得不可思议,要是再上第二次大学肯定是全然不同了。这些事件包括在竞选部长前一天晚上四点钟突然决定弃了学生会,觉得还是在自己喜欢的微软学生俱乐部大家庭里奉献,一待就是四年;之后从梧桐苑吃饭午饭看到心心念的机器人队摆摊便欣然走上去报名,一整年的“除上课时间和周五下午以及过年有十天假期以外其他时间一律来队里签到”之后对于技术的整体感受力和自信提升;抱着试一试的心态报名了大二暑假的微软学生夏令营认识了一位可敬的朋友,这位朋友顺带还在毕业前夕举手之劳帮我安排上了现今的去处;报名去了实验室实习,对于本校的研究生生活进行了绝无仅有的提前感受并下定决心决不考研;在实验室实习期间接了一个让我事后气馁到如此的外包项目,请注意以下言语中的时间均是过去完成式:
去年九月因为手头紧,跑去接了个做网站的私活,约定的10月底上线,但是不曾想这竟成了我一个学期也未能完成的、名副其实的
焦油坑
,于是就有了这人月鬼话,作为我不虚心学习前人的谆谆教诲与宝贵经验、自以为是,最终狼狈不堪,写出了丑陋的代码后才回过头来明得事理的一个教训。也希望作为对个人学习生活的一个总结,尝试着提炼出一些感悟,最终让自己的境界提高一点点。
以及最后的、我早有计划却又始料未及的半年匆忙从零学起,却又在紧要关头急转弯的出国继续念书事宜。我最终坐在床上津津有味地写这篇随笔。
头一年的跃跃欲试、第二年的艰苦积累、第三年的幸福与无助、第四年的迷惘与淡然,是我在这摇篮中的血与火。
因此我没因为被培养成一个工程师而感到气馁,西方的谷歌不也提倡“去问问工程师”吗?前年母校百廿校庆时我自问“我能为世界之光吗?”,一位诤友言:“码农耳。”
我只希望能做那种不会被轻易替代掉的码农,从思维的田野中孕育出不朽的逻辑果实。
校园中曾经发生过一件轶事:两男子在课堂中的教室打架,引得班级中一红衣男孩拍案而起:“这里是西安交通大学,世界顶尖学府,而你们却在这里张牙舞爪,你觉得合适吗?!”在当时轰动整个校园,一股思潮随之而起…
我又想到校庆日子里在朋友圈刷屏的一篇文章《交通大学≠上海交通大学》。全国同根生的好像有五所交大,分别是上海交通大学、西安交通大学、西南交通大学、北京(方)交通大学、新竹交通大学。其中上海交通大学和西安交通大学关系最密,来源于1896年北洋政府创办的南洋公学,南洋公学后来几经改名改为交通大学,再到国立交通大学,最后在建校的六十周年大树西迁,主体部分来到了广袤的大西北支援发展建设,生长为西安交通大学;而留在上海的部分又经三年独立办学成为了现在的上海交通大学。由于种种因素,上海交通大学十分争光,是同源五兄弟中的翘楚,于是“交通大学”这响亮的名字在海内外学者百姓心中与上海交通大学默默的划上了等号。西交学子于是被那“考败来交”的心态又激发了自尊心,开始为心爱的母校鸣不平,甚至江学长后来只光顾上交而不来西交也引人发恨。这种情绪我也有,前几天他校的师兄见了我和江学长大头照的合影惊说“江学长也是交大的?”,还以为自己口误,纠正道:“西交,不是交大”,我隔着屏幕吐了吐舌头。
有趣的是五所交大中都有一尊几乎一模一样的“饮水思源”碑,最上面是立体的校徽,碑上刻有“饮水思源”四字。真正来到了现实的修罗场,只要听说是交大的同学师兄,我们都倍感亲切。更有甚者要真正贯彻落实上的是交通大学而不分西交上交,于是纷纷去了上交读研。玩笑之后,英雄不问出处,大家各凭本事办事,爱校则是另一番情感,我们记住饮水思源即可。
起这个标题好像我沉溺于空想而罔顾现实,实则是也不是。
如本文开头所说“离开甜睡梦乡,再度踏上现实的土壤。”自今年一月半赴京实习起我就悟出了学校生活的宝贵。我曾经一度心中呐喊“我干嘛不在毕业前回学校当咸鱼啊!”实际上实习生活十分新奇有趣令人充满干劲并且有机会可以猛然发现竟已夜半十一点。我想说的是校园生活如同一场美梦,每次回校,都是续上了这酣甜的梦乡。
我于是要在三月初打游戏到凌晨四点,四月初酣睡至中午后点一盒烧鸭饭然后在学院混迹一下午,去康桥吃晚饭,在温泉浴室洗澡后去小黑店买柠檬红茶,五月初除此之外还要在晚上去东南门买卷馍豆腐凉菜和面筋——我们称之为“味觉巅峰 top of the taste”以度过自己在学校里“典型的一天”。在校园里某个楼梯上挥洒汗水都觉得无比幸福,好比世外斧柯烂后回到家中发现一切如旧。
不过六月回校答辩的高列列车上我竟然心如止水。仿佛美美的睡了一个长觉后阳光照射在被褥上,睡眠浓郁还未散尽,身心得到了休息,从睡海底下浮上水面,睁开眼睛,安详而满足。而美梦虽美,终究是深睡眠的梦,在梦中只有眼睛和耳朵,去学去听,现在醒来,才有力气去施展。乾,元亨利贞,一爻变二爻三爻,潜龙出水,是时候寻见大人而后终日乾乾,朝夕警惕了。
大学的前两年的新鲜还没尝够,后两年就像乘上了火箭一般过得飞快,这其中有不少故事我都告诉了日记本,更多的故事我因为犯懒没告诉它。但原始未受扰动的环境让我有许许多多的机会犯一些不会使自己出局 (knock out of game) 的良性错误,并学习不犯错误的知识,鉴别有些一旦犯了就会出局,绝无弥补机会的恶性错误。犯错误的过程以及恶果的滋味最不好受,我得以加深印象,舔舐伤口,从中学习。在话剧哥本哈根中海森堡说了一句话:“向左、向右、或思考它并死去。”我深以为然并发了朋友圈以警示自己,但细细回想品味之后发现这句话适用的上下文是滑雪,在这种紧张刺激的运动中人只能凭着从前摔出来的肌肉记忆转左转右,若是这个时候再去思考,就会错过时机,一命呜呼,被命运判出局;要点在于需要在缓和的环境下不断思考、不断摔跤、不断形成新的肌肉记忆然后才能在真正险恶的环境中敏捷机智地转左、转右,并在这个过程中享受极限运动带来的刺激与快乐。
接下来的生活我预感仍然充满了犯良性错误的机会以及不犯任何错误的时刻,过程并不轻松愉快,但绝对引人入胜。
]]>抖了抖擞精神竟然写到天都快亮了。
这里不可多写因为还未盖棺不能定论,
但我还是要说一句——
为世界之光!
‘Tis not through envy of thy happy lot,
but being too happy in thine happiness.
我终于过上了梦寐以求的独居生活,开始了一段温室中的北漂生活。
人们大概都是在不经意间改变了自己的轨迹,而且要到事后很久才会发现。我们能够体验的就是当下,所以暂且不提Gap这个决定到底改变了什么,但那天晚上真的很自在,不是因为酒和肉,而是和Vicious Master的脑残英语对话。有点像冷风中的夜晚坐在篝火旁煮汤——虽然也许第二天就会被冻死,但烤着火的时候你看到的是银河绚烂而不是冰冻荒原。
我一直认为最高明的杀人方法就是站在当事者的角度去质问他为什么没有把握住之前应该把握的一个好机会。这个方法的要点在于这个机会已经无可挽回,并且这个逝去的机会约弥足珍贵效果越好。在当事者崩溃后你可以大言不惭的说我是为他好的。
但我最终没有崩溃,并且一如既往地,把它当作了旅程中的台阶。
紧接着就跑去实习了。头两天,我对将要负责的工作完全一头雾水,感觉自己是个骗子,什么都不会就跑来搞破坏,在新环境中不停的思考为什么我要折磨自己,当个咸鱼不好吗?但这些压力是有益的,有非常浓厚的生活气息。于是很快我就融入了新的环境。
我们常说生于忧患,死于安乐,请注意这不是在写高考作文,我意识到自己特别喜欢的另一句话与之有着类似的内涵:
“生者的地狱是不会出现的;如果真有,那就是这里已经有的,是我们天天生活在其中的,是我们在一起集结而形成的。免遭痛苦的办法有两种,对于许多人,第一种很容易接受:接受地狱,成为它的一部分,直至感觉不到它的存在;第二种有风险,要求持久的警惕和学习:在地狱里寻找非地狱的人和物,学会辨别他们,使他们存在下去,赋予他们空间。”
我太喜欢这种隐喻了以至于有时候说话晦涩难懂,用抽象而简单的符号表达丰富的内涵。这样做的好处在于你可以用自己的生活为符号赋予意义,从中学习到新的知识。温故而知新?
我想通过这句话告诉自己的事如果没有清晰的自我意识 (Self-awareness),,盲目地过日子,时间很快就会过去,届时世界扑面而来,撞上山头才发现一切都太晚了,给予别人用我所说的高明手段杀你的机会。又或者日子平稳地过去,成为“地狱的一部分”。
所以,承认失败的风险存在,了解它的后果,然后清醒地度过每一天,警惕,并持续学习。顺利的话我大概会在每个阶段都会问自己为什么要自我折磨,但实际上日子过得充实,并且毫不后悔。
就如同我的一个非常阳光的朋友在总结自己一年来的生活时候所说的:
“So sometimes you don’t need to worry about the things that have not happened, just be yourself and hang in there, make sure that when the opportunity comes, you won’t let it go!”
那我们加油。
]]>济慈曾经见过一位年轻的女士几分钟,为其着迷甚至神伤。
1 | TIME’S sea hath been five years at its slow ebb, |
后来发现,这首To - ["Time's sea"]
中最后两句
1 | Every delight with sweet remembering, |
和莎士比亚sonnet CXIII
中
1 | Even so, being full of your ne'er cloying sweetness, |
有些韵律上的相似。难怪曾经看到济慈这首诗的脚注中提到 “在情绪构架的韵律处理上,在逐渐增强的乐感上,都极似莎士比亚十四行诗。”
]]>前途未卜对于那天的我来说是遥远的。
说白了这是长期的酝酿因为我幡然醒悟因而看起来不可接受。想到这一点只觉吾生之须臾,羡长江之无穷。最终坦然地连同牛肉与啤酒将它咽下了肚子里,留在脑子里的只剩下多年前看到的乾卦六爻中最下面一个:初九,潜龙勿用。
回顾我这迷茫地四年,我看不清自己的道,前功尽弃可以说是常事,但却依然以一种近乎残忍的方式为自己老早就迷迷糊糊选好的道路赋予了一个个理由,我奇迹般地没偏航。
如果要做一个总结的话,首先我得讲一个道理。
人们永远不会为自己所缺少并且不自知的东西感到心慌,所幸知识一旦知晓就可以说拥有了。因此在某种程度上我们都对自己当前的智力水平感到满足。
但我们大体上还是进步的,对未来生活的美好期望使得一个人要使出浑身解数才能克服现实生活中的一个个难关,有时候甚至愿意自找麻烦,一般我们把这个过程叫做探索。于是我们精神水平在苦难中一步步提高,并且再也不愿意回到过去。
因此无论走不走得到终点,我对于已经走了的这九十里地是感激的。
至于盖棺定论,是不可的。还没有盖棺,我还有好多个一百里地要走,所以就让细节故事留在日记里吧。
这次剩下的十里地,我希望能用乾卦第二爻来概括:九二,见龙在田,利见大人。
总而言之那天晚上我和一直以来鼓励我的Master聊了整晚,最终安然确信自己选择了最适合自己的道路,第二天和回西安开会的爸爸一起度过了一个安静而惬意的下午,我惊喜地发现自己越来越喜欢和父母相处了。
接下来,我希望自己能够接着探索,并且永远也不会失去自己的好奇心。
]]>一粒豆大的汗滴落在地上。我注意到另一粒落在安检机的传送带上。
因为我必须在半小时内从北邮到北京西检票上车:一路狂奔中我只有在排队的时候停下来喘息一口,再次注意到地面。
一年为期啊。
有些问题只要在脑子里从想法具象为语言,就已经有了答案。这篇文章一个月以前就起了头,是因为有着零碎的念想。在吃饭时、等车时、走路时、睡觉时,只要稍有分神,它们就会趁机从世界的各个角落跑来钻进我的脑子里;可当我真的想把它们抓住时,却又背叛我,从我这里溜走,似乎在刻意与我保持距离。
但不管怎么样,一整年过去总是有些想写的。
这一年的两端——起点与终点都是MSC Summer Camp,就从这里说起吧。
比之去年,这次明显地有一种怅然若失地感觉。个中原因除了精神上有包袱,就是内心里将保存这种珍贵的回忆的位置留给了去年,有所保留。这么一想自己都觉得有些矫情——可这是真的。于是也没有怎么参与讨论,除过有一次本组的小胖提出的想法实在太值得攻击导致我张口就怼。
Day2大家一起去南锣鼓巷逛,这个眼熟的地铁站的栏杆上没有勇哥倚靠着,这条热闹的小巷也对我而言失去了新鲜感,我在其中一家小店更换手串的绳子时血祭了一颗珠——在店主隔断旧线时弹飞到外面的街上再也找不到了,在愤怒之外我隐约感到这其实是天意。
于是翘了Day3的City Tour去找小岱,我估计一年的光阴并不能从外表上改变一个人很多,所以我一眼就认出了从智能化大厦方向健步如飞冲出来开门的她。着实令人开心啊!没有带着什么包袱去拜访一位朋友。我暗自下决心要把智能化大厦的图书馆和中关村那些朋克风格的高楼们记在心里。在两天之后在这些执念中加了一条:北邮的黄桃牛柳我今生还要再吃一次。
短暂的京城之旅已然结束。
回归到我平衡但不稳定的生活之中,平衡在于我安于其中不去寻求改变。而不稳定在于有潜在的压力令人试图去做一些改变或者是变得颓废。在很久以前的语文课上学到“颓废”这个词语的时候我还不知道它会成为我的大敌。
说白了我就是对自己太好了,不忍心让自己雷厉风行。需要一些刺激来让自己前进,在梦被打破的一瞬间体会到无比的怅然,从而去寻求改变。
Forlorn! the very word is like a bell
To toll me back from thee to my sole self!
如同济慈在目送夜莺远去直到它的歌声也消失不见之后那种怅然。只不过我的压力并非来自绝症,比这位命途多舛的诗人更加朝气些有希望一些,可是你说我怎么就越来越颓了呢?
大概这就是我在经历大学中最艰难的一环。
但谁知道呢
何以解忧呢?曹操的答案我觉得很普通。喝酒永远都是那个Easy choice,借酒浇愁大家都会,第二天醒来除了头痛以至于让自己反思人生以外也没什么好处。这个问题的答案对于每一个人来说可能都不一样,对于我而言好像所有问题的病根都在于静不下心来;焦虑和无忧无虑都不是一个正常人应有的情绪。所以我们都必须从这两个极端中求存。
想到自己最近看到的一部电影:Bright Star直接用了济慈送给芳妮的一首十四行诗作为片名,而整部电影所讲述的也就是济慈客死他乡前三年与芳妮的爱情故事,整部电影安静而富有美感,值得静下心来看。我想说的是济慈这个人本身。在电影的结局后有文字写到:Keats died at twenty five, believing himself a failure. Today he is a recognized as one of the greatest Romantic Poets.(济慈直到25岁英年早逝之前都认为自己是一个失败者,但今天他被认为最伟大的浪漫诗人之一)。 我想说的是,想想济慈的经历,是什么成就了他,是什么造成了他的英年早逝?刨除掉他是一个天才这个可能不是一个原因的原因放在一边。因为我们都认为时势造英雄,英雄成时势。人与人的世界是相互影响的。一个人在什么情况下才能写出那样的诗句呢?我想可能只有在那时的英格兰,在那样的窘迫困境下,济慈才有可能成为济慈。那时的人们崇尚艺术——诗歌当然算在其中,那时的生活艰苦不堪。诗人只能通过思维去挣脱现实的枷锁正如夜莺颂中所说的:
Away! away! for I will fly to thee,
Not charioted by Bacchus and his pards,
But on the viewless wings of Poesy,
所以何以解忧呢?什么值得追求呢?
这个问题答案没那么简单,但是从我们所见到的所学到的东西中还是能找到一些线索的。
在这篇文章的开头提到了我的北京之行,那就从这里继续吧。
Day5晚上和Cutuy在宾馆里几乎聊了一晚上,很有启发。我之前有提到过这个世界是分层的,我们靠什么向上爬呢?我们靠的是什么爬的更快呢?我听过一句话,并对此很以为然:We can change, because we are educated.教育带给一个人知识,并通过这些知识赋予人以更为广袤的视野。通过我们每一个人的视野,我们将看到的东西总结为方法论并用来在这世界上立足。但观察的角度不同,的出的结论也就不会相同。这么想想就很有趣了。
后来我又跑去不要脸地去北邮蹭了小岱一顿饭,然后拼命地跑(骑)去了西站赶车。所以才有了开头那一幕汗滴落地,于是从上火车就开始写,直到今天已经写了十天了。幸甚至哉,歌以咏志。
]]>隐藏的“忧郁”有她至尊的神龛
。另外一部分原因是因为未竟的长诗Hyperion以及济慈本人贯穿了整个《海伯利安》四部曲中。神往已久,于是今天就抄下这首《忧郁颂》。中文采用屠岸译版。1 | No, no, go not to Lethe, neither twist |
1 | But when the melancholy fit shall fall |
1 | She dwells with Beauty—Beauty that must die; |
1 | 不呵!不要到忘川去,也不要拧绞 |
1 | 但一旦忧郁的意绪突然来到, |
1 | 她与“美”共处——那必将消亡的“美”; |
这是建立在并发只是提高系统吞吐率的基础上,在这种假设下每一个worker所做的工作都一样,因此可以共享一个消息队列,谁拿到下一条指令都无所谓。
假如我们需要实现一个各个worker各司其职异步工作的系统(例如我们的IP-phone)该怎么办呢?假设我们还使用上面的方法,所有的worker共享一个消息队列,这时候就会产生问题:消息没办法发送给特定的worker。
怎么解决这个问题呢?一个简单粗暴的解决方案是给每个消息加上标识,以标识出这条消息所要发送的对象:
1 | queue = Queue([('Send_to_worker_A', 'do_something1'), |
看起来问题迎刃而解了对吧?但我们引入了一个新的问题。
如果送给worker A的消息被worker B接收到了,他得到条消息其实并没有什么用,而本该得到这条消息的worker A却没得到它,从而这条信息就丢失了。既浪费了worker B的时间,由消耗了work A的青春,甚至有可能因为跳步而使得整个系统陷入到什么诡异的Bug中。
这个问题又怎么解决呢?从直觉上看,给每一个worker添加如下策略就能解决问题:得到别人的消息就把这条消息塞回消息队列中。
1 | class Worker_B: |
看似修修补补解决了问题…实际上又引入了一个新的问题。
第一,消息队列是一个队列,这意味着FIFO,就算引入了具有优先级的消息队列,本质上这一点也并没有改变。这样一来,本该第一个交给worker A的消息经worker B一倒腾,成为了当前消息队列中最晚递交给worker A的了,可能使得系统陷入诡异的Bug中。
第二,即使我们设法让分发错了的消息重新插回消息队列的队首,如果不引入锁机制,使得错收了消息worker B的收信-检查-放回
操作成为一个原子操作的话,那么在work B执行放回操作的时候,可能其他worker已经在继续进行接收消息操作了,仍然有可能造成时序上的大问题,而引入锁机制索然能解决逻辑上的错误问题,但是这样会使得每次轮询中只有一个worker真正能够工作,而其他得到错误消息执行收信-检查-放回
操作的worker白白浪费自己的时间片,效率低下。
所以,看起来针对为了异步工作而引入并发的系统来说,每个worker共享同一个消息队列并不是一个好办法。
根治这个问题的办法就是针对每一组执行同一任务的worker都建立一个专门的消息队列(邮箱),甚至每个worker都设置自己的消息队列,完全放弃了线程间共享内存的能力。后一种方法就是我今天要讲的主角:Actor模型(参与者模式)。
首先我们来看看维基百科对Actor模型的定义与概念:
在计算机科学中,参与者模式(英语:Actor model)是一种并发运算上的模型。“参与者”是一种程序上的抽象概念,被视为并发运算的基本单元:当一个参与者接收到一则消息,它可以做出一些决策、创建更多的参与者、发送更多的消息、决定要如何回答接下来的消息。参与者模式在1973年于Carl Hewitt、Peter Bishop及Richard Steiger的论文中提出。
参与者模型推崇的哲学是“一切皆是参与者”,这与面向对象编程的“一切皆是对象”类似,但是面向对象编程通常是顺序执行的,而参与者模型是并行执行的。参与者是一个运算实体,回应接受到的消息,同时并行的:
以上操作不含有顺序执行的假设,因此可以并行进行。发送者与已经发送的消息解耦,是参与者模型的根本优势。这允许进行异步通信,同时满足消息传递的控制结构。消息接收者是通过地址区分的,有时也被称作“邮件地址”。因此参与者只能和它拥有地址的参与者通信。它可以通过接受到的信息获取地址,或者获取它创建的参与者的地址。参与者模型的特征是,参与者内部或之间进行并行计算,参与者可以动态创建,参与者地址包含在消息中,交互只有通过直接的异步消息通信,不限制消息到达的顺序。
Actor足够简单,为并发而生,并且具有足够好的封装性可以隔离变化(例如:不关心是多线程还是多进程)。
让我们来看看一个典型的Actor的python线程实现:
1 |
|
简单地解释一下,这个Actor(我起名为BaseWorker,在真实的项目中作为父类使用),维持两个数据结构:Queue
作为邮箱以及Event
作为阻塞主线程的杀器。
这里作为外部接口的核心操作只有一个send()
方法。请注意,我们不限制能够传递的消息类型,这意味着无比的灵活性。
在Actor内部我们使用一个线程来运行run()
方法结合recv()
方法执行所指定的具体工作。值得一提的是,我们设置了一个哨兵信号WorkerExit
用来停止任务,请注意WorkerExit
的运行原理是当他被识别后作为一个异常被抛出,而在异常处理这里我们甚至做更多事情,当然这里我们只是在捕获到这个异常时停之线程运行。而这一异常处理的实现有赖于用来包装run()
方法的_bootstrap()
。
在Actor模型的哲学下我们将这个简单的例子拓展从而走得更远。
并发编程需要一种较之平常更为抽象的思维方式,也可以作为“高内聚,低耦合”这一思想的良好实践。
而我在这片对我而言的全新领域的探索中Actor的确是一盏指路明灯,算是领着我真正入了门,因此在这里分享。 :)
]]>我无意将Demo做的无比庞大,我的目标在于说明问题,所以依然”麻雀虽小,五脏俱全。“
那么这个Demo的目标是什么呢,他都可以做些什么呢?
为了有趣并充分反映问题,我将在自己的Demo中模拟一个大幅简化的、每个单位都奇迹般地拥有了主观能动性的星际争霸 (StarCraft) 战局中一位星灵玩家所面对的场景。
一个好的Demo需要一个好的名字,因此这个Demo的名字叫做:为了艾尔!(艾尔是星灵的母星,每个狂热者在被传送到战场时都会说这样一句慷慨激昂的话:”为艾尔而战!“)
在为了艾尔中,你要带领着己方队伍生产出足够的狂热者战士,消灭毁灭者埃蒙,拯救整个宇宙,如果你的狂热者不够,你将会失败,群星低语,万物湮灭。
这一切都体现在一个由python的flask框架所编写的RESTful的服务中。
其实真实的场景中除玩家外每个主体都可以被创造出来,因此都可以被视为资源,而为了体现RBAC,我给他们添加了如下约束:
- 本Demo所提供的几个主体唯一并且始终存在,就如同他们是独一无二的一样,因此也除去任何主体可以被递归地创造出来的可能(例如:探机可以生产星灵枢纽,而星灵枢纽可以生产探机)
- 事实上传送门是需要水晶的能量支持才能运作的,但是在这里没有体现出来,我将其解释为我们的传送门受到了位于同步轨道上亚顿之矛这艘星灵的传奇母舰的能量支持,从而只把水晶塔看作提供人口上限的资源。
这个战局中拥有以下对象:
由于针对资源的每一个操作都是一个权限,因此这里我们不单独把操作列出,而直接给出权限及其描述
主体-角色以及角色-权限的多对多关系,使用python的多元祖数据结构表示,在实现中亦是如此,因此本应用中不使用数据库。
1 | subject_role = (('thrimbda', 'archon'), |
1 | role_permission = (('archon', 'get_status'), |
线上部署(速度慢)
总的来说我使用了python的flask框架编写了一个RESTful风格的服务,整个应用不涉及前端部分,因此也不存在绕过前端等安全问题了。
首先这个Demo的一个特点在于没有使用数据库,RBAC并没有强制使用数据库,且在RBAC中使用数据库是符合直觉的意见很自然的事情,但在为了艾尔中我们不使用数据库,而是使用文件的形式体现RBAC的主体-角色-权限
关系。数据库本身就是在文件系统的基础上发展而来的,这里采用文件是因为系统足够简单,为了说明问题而进一步降低系统的复杂度。具体的文件形式见上述 SA, PA 关系说明。
这里简单地提以下RESTful(Representational State Transfer)
顾名思义,(资源的)表现层状态转化
在一个Web服务中,提供的服务即为系统的资源,以URI的形式体现,而服务的形式为对资源的操作(状态转化),以HTTP动词的形式体现。这其中的几个概念可以很好地跟RBAC中资源、操作对应起来,因此我要做的就是将RBAC中的权限管理应用在REST中对资源的操作上。
可以看到在这两个配置文件中,除了SA和PA之外,我们可以隐含地求得S、R、P:
1 | # 根据上述元组 subject_role 求得S、R列表 |
而SE可以很好地和web应用中的session对应起来,作为一个主体在一次登陆中的一个临时对象:
1 | # 主体用来登陆亚顿之矛战术管理系统的API,这里session作为flask的一个全局对象,其实现细节不再赘述。 |
由于主体-角色的建模最终是为了将权限隔离开后分配,使得系统中的资源能够被妥善使用与保护。
在为了艾尔中,我将权限作为web API的内部属性,例如:
1 | # 用来传送狂热者的API |
而在上述用来举例的两个API中每个类都作为系统中的一个资源而存在,而提供的HTTP方法则是对资源的操作。
至此,RBAC中的几种对象都到齐啦。
由于为了艾尔是一个真实可玩的在线即时战略类游戏API,因此有必要讲讲它的业务逻辑:
玩家的目标是:收集资源,建造基地,然后创造一支令你的敌人闻风丧胆的部队打败黑暗者埃蒙。
打败埃蒙的唯一条件就是要拥有足够数量的狂热者(zealot),而这个数量为系统随机生成的一个20到100之间的整数,同时系统会根据这个数据生成刚好够你打败埃蒙的未采集晶矿。
为什么是刚好够?
由于传送狂热者需要足够数量的水晶能量以及晶矿,提供能量的水晶塔也需要消耗晶矿来生产。因此假如你建造了太多的水晶塔,那么虽然水晶能量够了,但你会因为没有足够的晶矿来传送狂热者而输掉这场决定整个宇宙命运的战役。
而整套逻辑由一个生命周期跨越整场战役的对象提供,为了防止问题,我加入了线程锁来确保每个操作都是原子的。
1 | # 由于它是整个游戏的核心,我将它称之为枢纽-Nexus |
其实星际争霸二这个游戏在每场战局中都是一个典型的DAC模型:玩家主宰一切,而游戏中所有的操作都可以看作是将水晶矿和高能瓦斯(在我这里被简化掉了)这两种基础资源进行状态转化,成为玩家所需要的资源(生产单位,作战单位)并去消耗敌方的资源从而赢得战局。这说明RESTful服务的思想非常普适。
在为了艾尔这个小游戏中,我将几种角色固化,构造了一个RBAC模型。
在这次实践中,理解了RBAC在一个系统中的应用,并且进一步学习了flask这个超赞的框架,更加深入地理解了RESTful思想,收获良多。
]]>RBAC (Role-Based Access Control) 以角色为基础的访问控制模型是一套较强制访问控制以及自由选定访问控制更为中性且更具灵活性的访问控制技术。
首先是RBAC中的几种对象:
其次各对象间具有如下关系:
主体和角色是多对多关系。
${\displaystyle SA\subseteq S\times R}$
角色和权限是多对多关系。
${\displaystyle PA\subseteq P\times R}$
在了解RBAC相关概念之后,在此谈谈自己的理解。
RBAC是一种MAC (Mandatory Access Control) 即由系统管理员统一配置管理个用户角色。
S(主体)R(角色)、SE(会话)的关系如上图所示,即虽然一个主体可以拥有多个角色,但主题在登陆系统后(即在与系统的一个会话期间)只能行使一个角色的权力,
而P(权限)由系统所提供的服务决定,实则是系统提供的资源以及对资源所进行的操作的的组合,也正是资源与操作的组合才使得权限这个概念有了意义。
我对RBAC的应用场景的定义:一个资源由系统共有且具有多种分层的角色的责任分立的系统。
资源由系统共有,且具有多种、分层的角色,这表明系统中的资源对于每一个角色的意义都不一样,因此每个角色对资源承担的责任也因此而不一样,因此对于这样的系统,RBAC是一种十分合适的访问控制模型,可以有效地分配权利与责任,从而更好地保护与利用系统中的资源。
例如:商业公司、以及遵循最小权限原则 (Principle of least privilege) 的操作系统。
而对于一个资源由个人所拥有的系统来说,每个角色都是平等的,每个角色对于自己的资源具有完全的控制权,此时RBAC模型显得力不从心,而应该使用DAC (Discretionary Access Control) 模型,这在社交网络系统中十分常见。
实际的场景中不可能存在纯粹的共有资源与纯粹的独享资源,因此常常将这两种模型混合使用。例如:微信中普通用户可以对自己的“资源”(朋友圈)具有完全的管理权限。但普通用户无法对其他用户进行管理,“举报”这个功能的实现则有赖于系统中其他具有更高权限的角色进行干预。
]]>于是使用python的queue
和signal
还有time
模块做了一个不算真正多线程的示意用的小小demo:
1 | # -*- coding: utf-8 -*- |
解释一下三个模块的作用:
queue
:作为消息队列,这里显然有点大材小用。signal
:用来捕捉KeyboardInterrupt
之后将停止信号置入消息队列。time
:用来让主线程能够捕捉到这个KeyboardInterrupt
。多线程的引入使得非主线程在启动后就很难控制,所以迫使人们使用消息队列这样的方式实现线程间通信。
但其实不论是否是多线程,系统各个模块本就应该将细节封装起来,而使用事件是他们协同工作,这样对于降低系统耦合有很大帮助,这也是一个好的系统的设计思想:为变化而设计。
]]>什么意思呢?
alive and thrive.
尾巴取自希腊字母英文发音lambda,这个字母代表着大智若愚。
所以
Thrimbda!
我又不甘心这篇这么短就写完了,就再扯点别的吧。
总结了一下,这世界上的学问无非分成两个层面:道和术。
道是概观;
是框架;
是胸有成竹。
术则是细节;
是实现;
是奇淫技巧;
我这个人
看不清自己的道
却在术上下了不少功夫。
多有徒劳啊!
]]>当然,为了有趣和有意义,这一切都必须建立在这个日子对我而言产生了他的纪念意义。而对应于张小弟的生日,这意味着从我认识他并且开始想要为他庆祝生日开始。好在我们认识地足够早,这九年的时间足够让我回忆很多事情。
首先我要在这个他无法看见的犄角旮旯发自内心地祝他生日快乐,希望我的这位朋友健康快乐,并且不会被生活的艰难吓怕,能够目光坚定地走在自己脚下的路上,反思一下自己好像自从大学以后我们的联系就渐渐变少,但却不是关系疏远,相反我认为我们的关系并没有收到距离的影响。大学之大,在于文化。
我见证了他近年来因安逸而踌躇,因背叛而悲伤,因心有所念而前进,因可爱而被爱,最终因为缘分而让我失去了两个朋友,多了一对虐我的狗。
人呐,顺意时候少,迷茫时候多。但是回忆起来当时认为的不顺意只是因为肚量还不够大= =
我何尝不是这样?
语言是一种奇妙的能力,能够让巨量的信息在两个个体间迅速传递——决不是拷贝,是跃然,之后消逝于一种介质中,重生于另一种介质之上——在字句里,颦蹙间以及一切可能的方式里把永恒嵌在每一个转瞬即逝的动态之中。
这是怎样一个过程呢?
——腾跃而起,穿过封杀烟尘,越过飞禽走兽,离开了尘埃,离开了巨石,看着这些文字的你最终抵达了——月亮?
说实话我不知道你会到哪里去。
从前我认为人生就是一条条四通八达的道路,人们选择自己的路,志同道合则同路而行,人各有志则分道扬镳。
后来发现不是这么简单的,人生并不是一个连接了所有可能节点的完全图,只需要选择一条自认为的合理路径走过去这么简单,事实上,它是分层的,那么多的路啊,会随着距离的拉长而坍缩成模糊的痕迹,在这错综复杂的巨大有向图中,你很容易迷失方向。
幸运的是足够年轻使得一个人有足够的时间在这些边中跳跃穿梭,练习中总结出规律、信条、与方法论。
我们称之为拨云见日。
]]>一般地,面向对象分析与设计中存在三种事件处理的机制,除了普通的函数调用外,常常用到回调函数,而J2EE中还提供了一种基于监听方式的事件处理机制,请查阅资料,对Action以及ActionListener的机制进行分析,完成一个分析实例。
首先了解观察者模式
观察者模式又称发布订阅模式,例如RSS(微信推送的爸爸)订阅,某博客(被观察者)的博主在更新博文后,订阅者的阅读器(观察者)上就会自动能够收到更新。这种发布-订阅的套路我们称作观察者模式。
(Gang Of Four)对观察者模式的描述:
意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
别名
依赖(Dependents),发布-订阅(Publish-Subscribe)
动机
将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了他们的可重用性。
…
参与者
subject(目标)
目标知道他的观察者。可以有任意多个观察者观察同一目标。
提供注册和删除观察者对象接口。
Observer(观察者)
为那些咋目标发生改变时需获得通知的对象定义一个更新接口。
ConcreteSubject(具体目标)
将有关状态存入各ConcreteObserver对象。
当他的状态发生改变时,向他的各个观察者发出通知。
ConcreteObserver(具体观察者)
维护一个指向ConcreteSubject对象的引用。
存储有关状态,这些状态应与目标的状态保持一致。
实现Observer的更新接口以使自身状态与目标的状态保持一致。
可以发现观察者模式常常应用于:
相比于普通的函数调用以及回调函数,观察者模式的使用能够做到低成本维持对象间一致性,大大降低了对象间的耦合关系程度,同时可以达到广播的效果,这是前二者做不到的。观察者模式的使用大大提高了代码的可重用性**。
而J2EE中的Action和ActionListener正是观察者模式的一个鲜活的例子。
Action&ActionListener 可以很好地应用于MVC范式用来降低model和view的耦合程度。
找到一个很好的例子,代码如下所示:
1 | import java.awt.event.ActionEvent; |
两个类分别担任了Action/ActionListener的角色,frame.dataTextField
在addChangingTextField
方法中绑定了三个监听器(observer1
, observer2
, observer3
),在frame中文本框被编辑后(状态改变),触发Observer中actionPerformed
方法,显示相应文本。
在这个例子中,frame.dataTextField
作为ConcreteSubject
,observer1
, observer2
, observer3
作为ConcreteObserver
。而他们所实现的定义了Action/ActionListener方法的接口,则是相应的Subject
和Observer
。
在绑定观察者后目标并不关注观察者具体的行为,而只是在自身状态发生改变时通知观察者,由观察者自身决定做什么动作,因此这种方法就很nice,比显式调用函数或者执行回调函数的方式触发动作不知道高到哪里去了,值得学习一个。
]]>首先摘抄一段来自《人月神话》中有关项目管理举的一个典型的失败例子。
巴比伦塔的管理教训
据《创世纪》记载,巴比伦塔是人类继诺亚方舟之后的第二大工程壮举,但巴比伦塔同时也是第一个彻底失败的工程。
这个故事在很多方面和不同层次都是非常深刻和富有教育意义的。让我们将它仅仅作为纯粹的工程项目,来看看在管理上有什么值得学习的教训。这个项目到底有多好的先决条件?他们是否有:
清晰的目标?
是的,尽管幼稚得近乎不可能。而且,项目早在遇到这个基本的限制之前,就已经失败了。
人力?
非常充足。
材料?
在美索不达米亚有着丰富的泥土和柏油沥青。
足够的时间?
是的,没有任何时间限制的迹象。
足够的技术
是的,金字塔或锥形的结构本身就是稳定的,可以很好地分散压力负载。对砖石建筑技术,人们有过深刻的研究。同样,项目远在达到技术限制之前,就已经失败了。
那么,既然他们具备了所有的这些条件,为什么项目还会失败呢?他们还缺乏些什么?两个方面——交流,以及交流的结果—— 组织。他们无法相互交谈,从而无法合作。当合作无法进行时,工作陷入了停顿。通过史书的字里行间,我们推测交流的缺乏导致了争辩、沮丧和群体猜忌。很快,部落开始分裂—— 大家选择了孤立,而不是互相争吵。
作者分析后发现,这个失败的项目所以失败的原因在于以下几点:
因此,良好的需求管理是两方面的:面向需求,面向需求的管理。
在结合亲身使用经历后我选择分析Worktile这个项目管理工具。
那么Worktile是如何解决上述问题的呢?
官网的介绍:
Worktile看板式任务协作,通过轻松拖拽任务卡片,团队成员可实时同步看板变化,敏捷协作。轻量级团队协作工具,让你的团队5分钟内即可快速展开协作,效率倍增。它简单、直观、易用、免费,提供无限制的成员、项目和存储空间,和你的团队一起飞
整理之后我发现它有如下特性:
面向项目的看板式任务管理
能够清晰地将任务分工,及时把控项目情况,每个任务包括检查项、分配人、截止时间、附件、评论等。
具有文件同步功能
有点类似GitHub,但不如其功能强大,个人认为有点鸡肋
共享团队日历
可以宏观上了解团队工作计划安排。
消息通知
分配新任务、提醒关注某个文件、或者有了新评论等时候可以得到通知。
分析统计视图
以可视化方式量化任务,以图表的方式直观看到项目进展、项目成员任务分配、项目热度趋势等。
在了解他的特性之后,接下来关注如何使用Worktile进行需求管理。
针对需求管理的流程,分析如何在worktile中体现:
提出需求
详细记录变更需求,可以把它看成是一个备忘录或者收件箱,需求提出者要能够非常容易的记录下需求的详细信息。
审核
确认是否要实施该变更需求,在这个过程中需要对变更进行评审,确认是否需要变更需求,以及需求对已有部分所带来的影响。
在Worktile中通过任务列表展示不同的变更流程。
收件箱:在该任务列表中任何人都可以向这里提交变更需求请求,当你觉得有需要解决的需求事可以添加在这里;
要做:确定要做的需求,可以把它统一放在该列表下,并添加相关的负责人;
进行中:在该列表下,可以明确正在进行的需求,了解产品的进展;
已完成:对于已经完成的需求,可以专门放在一个列表下,方便之后对产品的追踪。
实施
根据需求的详细要求进行具体的实施,其中可能涉及到产品、开发、设计人员的参与。
确认
对需求的结果进行质量保证,确认需求的实施是正确的。
度量
对需求的过程做度量分析,这个过程对于需求管理是非常重要的且有意义的,通过对这些需求的分析,就能知道当前项目的进展情况,以及存在的问题。
用标签来定义任务属性、通过任务优先级来定义需求处理的优先级
如果确认不做变更需求,直接把该需求归档即可,这样方便以后查询。
Worktile项目的分析统计视图中,可进行标签统计,而这对于需求管理的价值在于,我们可以通过需求属性的统计分析,了解到现有产品的进展,用户的多少痛点得以解决,并且可以通过列表统计来统计需求的完成情况:
在对Worktile进行分析后,现在就可以回答最开始的问题了:Worktile是如何解决之前提到的问题的呢?
我们可以使用Worktile灵活地实施需求管理的流程,它任务直观、提供讨论平台以及多种项目视图的特性,可以让我们方便地对定义系统,结合良好的管理,可以做到避免系统边界定义含糊等问题。
使用worktile,我们可以方便地变更需求记录,结合讨论、通知功能,可以起到一些简单的协同和沟通作用,每个成员都可以参与进来。
同时我们可以利用它的统计分析功能,方便地对项目的需求进行度量分析,分析整个项目当前变更状态。
至此Worktile解决了需求的管理、人员组织与交流等问题。
最后,需求管理作为整个项目管理中的一环,不应该项目管理的中的其他要素分割开来。
而需求管理只是Worktile可以发挥作用的一个方面,Worktile除此之外通过上述功能,可以实现对于整个项目生命周期的管理,提供了概念的完整性。
]]>逃避。
这的确是我来不及抽象成语言的想法,我害怕自己任何自欺欺人的可能。
所以我得想想:问题到底出在哪。
我们将之前一直在做的,令我神往的在线评测平台Light列入申请大创的计划。为了得到支持,甚至也可能为了给我们自己一点压力,总之我们这样做了。阿铮要我来做这次大创的主持人,负责处理大创相关的事务。在匆忙提交了申请书之后,我得知立项答辩时间定于今天中午,而我有两天来准备本次答辩。
于是今天中午,我惨遭失败。评委们给我的感觉就是他们完全不知我所云,认为我是异想天开,空穴来风,想搞一个子虚乌有的扯淡玩意来骗钱。
问题出在哪里?
今天总计进行了8组大创答辩,而我是最后一个。评委的确不专业,甚至有一种倚老卖老的感觉。但这个锅能由他们来背吗?
不能,如果我们的产品要面向大众,就必须足够简单,足够易于理解和使用。因此假如连身处相关领域的评委都不能够消化与理解,那么这一定是一个没有定义好的产品。
那么接着想想,问题出在哪里。
我的PPT风格显然是出了什么问题,以至于在答辩报告结束后的提问环节,评委老师花了一半时间来指出PPT的问题,想必这个问题最直观,也最令人不爽。
这是由于我最近几次答辩以来我越来越坚信的一种理念导致的,即:PPT提纲挈领,而内容要以讲为主。
这本身没什么问题,但我这次几乎陷入到了一种病态地偏执:只有标题,没有正文,连内容都不全。
简约不是问题,问题在于简陋。
没有数据,没有图表,没有示例,一切可以让说明问题并且能够使演讲变得更加清晰的东西我都没有。
但仅仅是PPT毁掉了这次答辩吗?恐怕还不是这么简单。
我深知自己的弱点,表达能力严重不足。但是我真的清楚这一点吗?
其实即使以我这样的PPT,予以妥善的表述,对于本次答辩而言,还是有可能讲清楚的。
语言缺乏连贯性,逻辑缺少连通性,表达没有完整性,除此之外我还结巴,忘词,全都是致命伤。
因此表象的PPT问题暴露了我在表达力上的缺点。
但是这些缺点能够改变吗?我相信是可以的,但我在明知道自己有这些缺点的情况下没有采取适当的措施去避免这些问题的暴露——我本可以的。
还有什么?
其实这样的类似的答辩在成都实训时候也有过一次。那次的PPT没有这此这么夸张,表达上也比这次要好很多,我自认为那次的秘诀在于我作为项目经理掌控全局:整个系统的详细设计都是我做的,我了解API服务器的每一处设计,以及他们为什么如此,我明确了解自己是在做一个什么样的系统,面向什么样的人群,以及未来可能的发展空间。
但这次不一样,我认为自己没有完全理解阿铮对整个系统的设想:对用户潜在需求的分析,对系统的定义,以及它的未来,尤其是未来的发展。
归根结底,问题出在哪里呢?
不走心。
如果我对整个项目进行深入的思考拥有我自己的深刻理解;
如果我在答辩前进行排练;
如果我PPT多点内容;
想必这次答辩都不会丑陋到如此地步。
总而言之,我感到羞愧。
勤能补拙,而非宁静无以致远,我希望自己能够记住。
晚饭后提着眼镜和一瓶子豆奶在校园里散步,在图书馆北侧发现了一个让我得以安静思考的小花园。
我在那里总结了为什么自己的答辩成了一团浆糊,同时想了想一个好的产品应该是什么样的。
牛顿讲他自己只是在真理之海前捡贝壳的孩子,而我们是一个对知识有着无穷渴望的物种。
我认为在探索的旅途中有两种先驱者:
第一种高屋建瓴,值得被铭记,值得被载入史册,作为他那个时代的骄傲与自那以后历史的道标,提供了进步的可能。而第二种人,思考的足够深入足够彻底,将技术与人通过一种自然又和谐的方式联结在一起,真正地实现了进步。
——法拉第发现了电磁感应现象,爱迪生和特斯拉创造了整个电气时代。
因此我对好的产品定义如下:
满足人的某种基本需求,足够方便使用,成为联结技术与人文的纽带。
我抬头望着参天的梧桐树,一切都是有结构的。
我们如何对事物进行抽象、建模并加以分析出内在的联系,如何从表层现象看到本质。
人组成的社会也是有结构的,思考的角度深度以及对信息资源的掌握也是一种划分结构的方式。如果每个人在出生的时候都是一样的,那么教育就是提供结构变动的梯子。然而这也无法解决信息不对称带来的矛盾,举例来讲站在这个结构高层的人与低层的人几乎只能通过媒体,类比C/S架构,的方式交流,而目前没有一种合适的办法高效匹配相关信息的索取者与供应者的办法(peer to peer)。
也许Light可以解决这类问题?有待探索。
]]>这篇是今天听课听到酣处有感而发,这大概是我一年来听得舒适的一天课了。
我上课不听讲尔来一年有余,原因在于:睡觉太晚,上课太困,久而久之破罐子破摔。但今天灵机一动就决定跑去第一排听讲,想不到感觉很好。
也不知道是因为近来积攒了一点点经验还是别的什么缘故,我认为今天《软件系统分析与设计》和《软件测试与评估》两位老师都提到了一些我很认同的观点。属于吃了亏才跑回来亡羊补牢。
好了我不想写了,因为我把我想讲的问题忘了。
悲伤。
]]>然而现在坐在实验室里的我感到无所适从——说实话,每当我坐在实验室里的时候都会如此。
这种现象是因为对自己的生活没有计划。
所以我要做个计划。
宁静致远。
]]>作为项目组组长,我有关于软件项目管理的唯一知识来自于寒假里读过一遍的《人月神话》。经此一番实践,发现自己还是太嫩。但是仍然,我认为自己还是学到了一点东西。
相比起两年前写C语言作业时候二话不说开VS写作业,今天的自己显然有了一些改变。
首先VS我已经很久没打开过了。
总之这两年我经历了在天真纯粹的学习python的那段时间的练习,还是机器人队编写机器人上位机时候的历练,又或者是编写教务处网站师生端爬虫的经历、参与橙汁项目时候写的文件管理工具这几个集中编写代码的历程。最开始当然还是莽上,但我估计前后矛盾的苦头吃多了就知道要提前设计了。入了设计的门,接下来就是程度的问题了——因此这次把控整个项目的经历算是一次非常有益的练习。
首先明确要做什么,这个问题对于一个有心想做产品的团队来说至关重要:定义自己的产品,是战略层面的;但是遗憾这次没有什么有趣的想法,而责任在肩,认真做一个靠谱的、简单的项目是自己对这次实训的期待。
简而言之就是一个以微信小程序为主打特性的传统Web应用——具体要做的是一个目标管理系统。
这个东西做的人实在是太多了,因此对所要面临的事务的建模能够找到很多成熟的方案:诸如面向项目的Worktile;面向个人生活管理的WonderList;面向学生的My Study Life;甚至还包括Apple的提醒事项
以及其他一些我听过但没有用过的工具。综合这些工具的特点,总结出一个经典的、微信特色目标管理系统——这就成了我们要做的Seed
。
那么Seed
应该能做一些什么呢?
项目
类似。由此看来,Seed
应该是一个小而精简的实用性服务,这点和微信小程序的初衷不谋而合。
在这里必须要强调“小而精简”这个特点。为什么要小而简单呢?一是人手不够时间有限。二要避免来自《人月神话》中提到的第二次项目
容易犯的错误:盲目自大,疯狂地给一个项目添加无穷无尽的冗余。这是一种出力不讨好的蠢事,尤其——我们不涉及广告收入。举个简单的例子:Mac OS版迅雷和Win 版迅雷,显然大家都会喜欢精简使用的Mac版。
同时也要对微信小程序有良好的兼容性,为了提供良好的用户使用的平滑性,应该在支持email作为用户登录名的同时支持微信openId作为登录凭证。
上文提到我们要做一个以微信小程序为主打特性的Web应用,这就是说Seed
具有兼容其它前端的能力与野心。因此RESTful风格的API就成为了最佳选择:前后端完全解耦,结构清晰,消费方便。
因此Seed
应该是一个由微信小程序和其他一些潜在的形式作为前端,通过HTTP协议消费后端提供的资源
。
后端通过API来提供安全可靠的服务。
为此也学习了一些,接触到HATEOAS(Hypermedia As The Engine Of Application State)(超媒体即应用状态引擎)的思想,虽然不成熟,但却是一种可能的未来。
由于只参与了API服务器的编码工作中,因此这里只谈后端的实现。
我喜欢把设计划分到为战略层面(道),剩下的工作划分到战术层面(术)因此,选用什么技术、如何具体去实现一个系统可以算在这里。
出于实训的要求,也出于对Java语言的合理使用的渴望,选用Spring Framework
作为后端框架,顺带使用了Spring全家桶:
此外使用Git、TravisCi、Heroku等工具提升开发体验。
之前也提到Spring的Controller编写体验与Python Flask Web框架体验十分类似,也许Flask从Spring中得到了一些灵感?面向资源的控制器类我认为比起针对每一项资源都要写一个Servlet的传统Java Web显得要紧凑很多。
Spring提倡测试驱动开发,真要这么做起来到不是一件容易的事情,毕竟测试不是一向讨喜的工作。真要分配一个专人去编写测试代码,一学习成本很高,其次还有一些人情方面的问题。所以在开发中我决定让让每个人负责测试自己编写的代码。最终由我去审阅代码。
有关Seed
本身的值得一讲部分已经讲完了,接下来谈谈别的。
在实训中学到了很多,但仍有问题。
作为项目经理我算是菜得抠脚。对进度的把控、人员的分配以及其他一些细节的把握都有大把不足之处。
举例来讲:
显然我还是需要进一步锻炼的。但同时作为项目经理和开发人员引出了我对另一问题的思考。
对整个系统的架构与把控能力和实现单一功能的算法选择、裁剪、使用能力。
我校的软件学院的课堂教育似乎只专注于后者,这也不算是一件怪事,毕竟对算法的掌握很容易度量,可以说是能力优劣差异显著,就好比解数学题,严格的前提条件与限制下人们的创造力十分便于评测。但合理制定这个所谓的“严格前提条件与限制”的能力虽然重要,却难以衡量。
至于为什么说自我矛盾,举个简单易懂但可能不十分恰当的例子,大家都知道家长以怕耽误学习为由严禁孩子早恋,却又希望孩子在毕业后立马能构成家这样的想法是幼稚而不切实际的。而隐含地期待学生在仅仅拥有良好学习成绩的情况下毕业后成为一个全才也是一种略微扭曲的想法,这两种情况的区别在于后者的矛盾不如前者那般尖锐。
我认为这样的实训的确是针对课堂教育的不足而作出的补偿,是有机会避免作为培养手段的成绩与作为培养目标的能力这两种符号所代表的事物之间的矛盾的。
]]>之前吐槽过这里的网以及网速:宿舍完全没网,上课的地方网速较差。这样一折腾让我突然发现自己网瘾很大,不过私以为这没什么问题,网络完全可以成为启迪人们心智的“卡拉”,继而让我们整个物种升华成为一个高度发达的……
言归正传,这两天在学习Spring framework,让人有种耳目一新的感觉,初来乍到的我不禁感叹道Java代码竟然也可以如此优雅。
这样一感叹就暴露了我有多菜,转念一想什么语言不能优雅。优雅是缜密思考过后精心设计的智慧结晶,和载体好像并没有多大关系。
使用Spring框架现阶段主要是为了构建Web应用,跟着官网的教程走了一遍下来,顺便也温习了RESTful架构的好处,总结下来有如下几点:
&
分开的QueryString,体现了HTTP(超文本传输协议)的设计思想,而不仅仅是把它当作底层传输协议,而上层瞎搞。严格定义了对资源的CRUD操作,并利用HTTP verb实现。我也是才从古代Web开发思想中穿越过来不久,理解的大概还不很透彻,如有误区,请不吝赐教!
从大二 Java Web 课程大作业到现在为止正好一年的时间,已经接触过几个Web开发技术了,我还能想起来当时第一次把应用部署到服务器上从手机访问并成功那时候激动的心情 :) 用有好奇心,追随着好奇心开拓未知的领域,这的确是一件美好的事情。
从Java的JSP
到Python的Django
再到
Flask最终回到Java,我遇见了
Spring`。
JSP是一个真正的古代技术,用起来真是太繁琐了,尤其是在开发后期为了某项功能而改来改去的时候,整个Servlet就会变成一个巨大的冗长的恶魔,而JavaBean遇到有关数据库连接并且要求分页的时候,想要做到优雅简直就是痴人说梦,而本意是为了体现高可复用性的JSP页面会因为各种<%if(...)%>
语句而变得面目全非,以至于我能够写出让自己怀疑人生的代码。我应该还会写一篇专门反省自己犯下这些滔天罪过的文章,话说回来,我甚至不知道怎么用JSP写RESTful风格的Web服务
事情到了Python这里变得清晰明了了起来,不论是Django
的能力强大还是Flask
的简明清晰,总之区别于最基础的开创道路的技术,有关框架还是显得要简明许多。如果把开发者分个级,第一层是技术的开创者,最后一层是应用程序的使用者(就算是完全拿来主义的开发者了),我认为框架(framework)的开发者处于第二层的位置。他们将某项常用技术的开发流程加以总结,从中抽取出最常用、最基础、最精华的部分编写成可复用的构件,就成为了一个优秀的框架。正因为如此,优秀的框架也都是从真正的应用项目上剥离出来的,Django
如此,Spring
也是这样。
但归根结底,我觉得Spring
跟Flask
更相似一点,他们都十分轻盈,几乎不会给人什么限制。也许Flask
的开发者从Spring
这里汲取了一些灵感也说不定。
@
在学习的过程中我注意到Spring的控制器路由是由Java的语法特性”annotation“实现的,对于这个特性我有待进一步学习,巧的是Flask
中也是使用了样的形式,只不过在Python里@
乘坐装饰器,一个用来包装函数用的语法糖,浏览过一些资料,这个特性好像确实是python借鉴Java但功能更复杂的一个语法特性。
因此,本就对Flask
很欣赏的我看到这一点的时候不禁对Spring
好感度大增。
总而言之,我的水平目前还很不到家,所以也没有提及Spring
的一些核心特性诸如Ioc、DI等,但它让我意识到Java这个看似垂垂老矣的语言为什么仍然经久不衰,同时也让我对Java好感度大增。
期待着能够学习更多内容,真是Exciting。
就用一个经典的日志结语:“今天真是有意义的的一天。“
]]>怀着怎样的心情,驮着怎样的包袱,打算施展什么样的抱负。
早在大一的宣讲会上就听说软院大三有实训,会去昆山或者成都。时间飞逝,紧接着就在大三的寒假里得到了要来成都的消息。
寒假里的学习计划根本没有得到落实,到现在就只是看完了《人月神话》,剩下的绝大多数有效工作时间都在搞自己那个恼人的项目,到现在也没彻底完结。算是陷入到了所谓的“焦油坑”之中。但话说回来,时间也没有完全被浪费掉,做项目总比打游戏然后懊悔强,我是这么想的。
说到成都,给我这个外地人的印象就是一个吃货的圣地,一个休闲之都,天府之国。但我又不是一个吃货,所以这样看来成都对我而言是没啥吸引力的。但是高三暑假答应了同学要一起出来玩,成人之美嘛——于是就一起来了。当时查过机票以后发现去重庆的机票十分便宜,我们就决定现在重庆玩两天之后坐高铁前往成都,一路吃吃喝喝,觉得这里和预想中的没什么不同。倒是真的见识到了成都人们安逸的生活状态,有时看到街上的老成都闲庭信步,自己的心情也跟着放松了下来,但可惜那时候心不在焉,总是想着别的事情,来的这一趟也不真实。
于是现在也没了新鲜感,没有私下里给这次实训加上什么不真切的期待,唯一的期待就是希望自己能够静下心来。
除了差点搞错是哪个车站出发(一个我从来没去过的叫做西安南站的地方),一路上都还算顺利,我们一行6个人就这样来到了这个安逸的城市。
目的地在成都西北方向,在郫县的郊区里,据说坐车要两个小时,我满心以为是个怎样的荒凉地方。六点多下了车,火车站有个大叔从我们出站就一路跟着我们想做那天第一笔拉客生意,我们不胜其烦于是如了他的愿——6个人每人25把我们拉到站,还算合适。
于是我领教了成都司机的厉害。
十几个红绿灯,司机大叔趁着天还没亮愣是一个也没停,一个七座商务车,一路八十公里每小时开过来毫无畏惧真是比西安的的哥路子还野。
抵达以后发现比我想象的要漂亮很多,完全没有县城的感觉,倒像是一个大城市的样子,高楼林立。
生活几天后觉得这个地方很奇怪,有一种瘆人的感觉——人如此少,处处都充斥着鼓励创业的鸡血:马云、雷军、李彦宏甚至乔布斯、比尔盖茨等人的照片绘制成巨展宏幅挂在路边的高楼上,一个街区里竟然有3家咖啡厅。真是煞有介事。
于是我怀揣着十足的好奇心去知乎搜索了一下,得到这样的结果:
总结下来就是响应国家“全民创业”号召,在富士康遗留地产之上拔地而起的一座现代化小镇。目前处于发展初期,政府配好基础设施,之后提供相当好的福利招商引资,发展成一个创业人才/公司孵化基地,这和成都新市长的新发展政策有关,最终目的是成为下一个中关村,甚至下一个硅谷。
但我也觉得创业有环境应该也有方法论,不能一味打鸡血,具体这个地方将来会发展成什么样子,拭目以待吧。
我以为过来就会有一个网速极佳,环境舒适,每人一个工位的工作环境,谁知道竟还是要上课,而且宿舍没网,上课的地方网速差劲。
在这样的环境下我完美地错过了概率论的重修以及软件项目管理的报名 :) (就晚了一个小时)
至于为什么有这一个重修一个补考,前一个是因为缓考瞎逼不认真复习导致挂科以至于连不考的机会都没有,后一个是因为缺课次数太多而被禁止考试。不过往好里想,我的大学圆满了 :)
说到底就是我自己一天脑子不知道都在想些啥,就是不操心自己的事情,因此吃亏也算是家常便饭。
错过了报名在今天学校管理日益严格的情况下意味着大概就是下学期见。
总之,吃一堑长一智吧。
这次来成都最主要的目的是要做实训,意在学习一点实际的知识;以标准、规范的流程开发软件。
有了之前不算成功的做私活经历,这次我是带着耳朵来的,真的想虚心学点东西。
但是按照要求只能做Java开发,这就导致不能继续之前和同学合作的Orange-Juice项目,因此也就重新分组了,室友们盛情难却要我留下来,我就压下自己的那一点遗憾吧——毕竟做什么不是做呢,重要的是要认真。
所以——摒除杂念,犯下的错误都过去了,是时候静下心来坐住这一个月了。
听说西安又下雪了,成都呢,则是下了雨。这里的空气很潮,夹杂着一丝寒意。
我希望自己能够安静地一步一个脚印地向前走。
]]>已知A与B互斥
1 | if(A) { |
那我真是觉得自己大概脑子出了什么问题。
]]>峰峦如聚,波涛如怒,山河表里潼关路。
岳渎相望,八百里秦川尽收眼底:
君不见——阡陌交通,鸡犬相闻,风调雨顺,人杰地灵!
此地北眺黄土高原之壮阔,南临秦岭华岳之雄浑;承渭水浩浩汤汤之恩泽,育华夏璀璨磅礴之文明!
愿闻鸡起舞,继往开来!
幸甚至哉,歌以咏志。
]]>在1月25号回家以后,喜气洋洋地过了个年。
我这个人有个毛病,就是放假回家以后绝对学不了习,以前高中时候还偶尔有作业能在周末的时候约束一下自己,现在则是无法无天。但是毕竟也不能放任自己天天在家里胡作非为,生活习性是不好改,人是活的嘛,于是趁着初八年过得差不多了立马从自己的魔爪中逃回了学校。实验室有一个好,环境再舒适我也是能拿出不切回windows玩游戏的毅力的。
回学校以后改了改自己那个做的半死不活的网站,便念想起自己觊觎了很久的个人博客。以前因为简书支持markdown,我就兴冲冲跑去注册。在写了一两篇随笔之后,还是觉得限制太多,太不自由,说是鸡蛋里挑骨头也没什么问题。看到阿铮的博客弄得风生水起,我立马感觉自己图漾还是得学习一个。
其实阿铮是写过一个教程的:优雅地使用 Hexo;写的很详细,照着一步一步来很快就能搭好。但是我最开始眼瞎没看见,于是就带着十足的劲头自己捣鼓。
也因为上述原因,我决定不再自己写一个教程了,就简单地把自己的心路历程记录下来,该有链接的地方加上链接。
首先注意到了hexo这个博客框架。经过一晚上的研究,搭好了hexo博客,安装了NexT主题,自己配置一番,部署到GitHub Pages上并且设置了域名解析。
至此博客就搭建完毕了,到这里也就可以止步了。但是心里还是觉得这个静态博客每次更新都得手动生成、发布有点麻烦,想看看有什么方案能使这个本身就比较方便的方案更加方便一点,说白了就是懒,懒的一逼。但话说回来这个世界不就是因为懒人们为了让自己能够舒服地犯懒所以才能进步的吗
于是发现了NexT(就那个主题)的发起人置顶的一篇博客,讨论是否能应用Travis Ci的自动构建持续集成这种造福人类的服务去自动更新博客,但第二天早上要早起,于是就先移植了自己简书上的两篇文章过来填上这个漂亮花瓶然后早早睡觉了。
昨天去玩了一整天,没动这个,今天回到实验室接着捣鼓。结果突然发现了阿铮的那篇教程,使用Travis自动构建,不知道高到哪里去了,令人心悦诚服:
快速部署
根据 Git 的特性,尽量让纳入版本控制的文件小而多,只包含源码。
自动部署
不需要手动使用
hexo deploy
进行部署。只需要将源码传到 GitHub 上,它就能自动部署。免安装
在没有 node.js 环境与 hexo 命令行工具,甚至没有 Git 时,也能编写/发布博客。
到这里我心满意足地搭好了自己的博客。
之后,作为博客搭建好了用来祭天的文章的结语,我想用这个我一个小时前自己造的这个听起来就很傻的词作为这段的标题,表达自己心里得到了一点点体会。
新年新气象!这也是农历新年后的第一篇文章。
之前为了线性代数考试疯狂从零学起学了三天时间,每一天我都在感叹:如果自己能够在平时的生活中每天都能像这样用功该多好。所谓“骐骥一跃,不能十步。驽马十驾,功在不舍。”这大概就是一个例子,何况我也不是骐骥。
其实这应该作为我一生追求的目标:锲而不舍。
心中有所希冀,并且踏实地走。
]]>前几天聚会碰巧碰见一个大学也跟我同校的师兄,闲来聊天就问我高中在那个学校,我说黄河。他惊叹:“那你一定是你们学校状元吧!”
我上大学之后,这个问题被西安土著同学问过很多次,回答无非是上面这样的惊叹或者表示不知道这个高中。
其实我宁愿自己能讲:“我高中是倒数第一。”
标题中带有两个很口语化的词:活宝、绝活。虽说是懂者自懂,但我还是觉得有必要解释一下,也算是负责任。
这两个词绝对带有很浓的个人色彩,那是我高中的班主任——老曹的口头禅。师出老曹,许多这样的词语你在悠悠三年里恐怕不知道能听上多少遍,但即便是毕业三年后的今天,我也不敢说自己是出师了的,可以说是站在门口,伸进脑袋张望了一番罢了。至于是什么门,什么是门,希望能够仔细思考。
仅以此篇赠来自高中的诸师弟妹。
出于某种未完全的行当习性,我不想对这篇文章的读者有太大的限定,因此也不会对其有比师弟妹们更多限制性的假设——我不应该是大你们三岁的师兄,也不应该是大你们六岁的。
出于这样的考虑,我希望你仅仅是把我当做是我所叙述的那样基本的、不加任何多余标签的人来看待——我希望你能够清楚地理解我写这篇文章的目的。
至于为什么写这篇文章,我心里纠结了很久,却总也找不到一个正当的理由作为一篇赠序送给别人。我们如此矛盾:一个人有多大能耐才能有这个资格去站在某个高度上去教育别人呢?他既不需要有这个资历又永远也不可能达到有这个资历的程度。
那我就索性不去考虑这个资历的问题,写是因为有一种暗藏在我自己心里很久的一种冲动去讲述,是分享人生的经验。但我骨子里是觉得通过向你们讲述,从而对(从前的)自己有所总结,有所交代:互惠互利,未尝不可。
我在高中时候自认为是一个聪明绝顶的人,因此到现在为止,我也从来没有因为某个人有多聪明而去佩服他。但仍然,我喜欢和聪明人打交道,原因在于交流方便。但我那时没搞懂过聪明与年轻的区别。
高一有一同学,我们叫他J。
J的脑子十分好使,以至于那时候我也觉得他聪明。有闪光必然惺惺相惜。高中课本知识片面,漏洞百出(知识的漏洞),尤其是理化生这三门,几乎在每一页纸上都能够找得到书中有所暗示却没有讲解的问题。我们于是天天带着这些问题去隔壁办公室去烦老师——这是一种十分值得赞扬的学习态度——有时候老师思考片刻将答案告诉我们,有时候他们则会以“高考又不考”
这样的理由来搪塞我们——现在想来可能我们问的问题确实太过刁钻,但在当时我们立马将其上升到为“中国教育制度就是一个制造垃圾的工厂”
这样的高度上,真的是太聪明了。
很快我们就膨胀起来,J更甚一步。可能确实是出于这样的原因,我现在回想起来J,就只剩下他说话三句不离“智商”
二字以及带着满满的自负的一句“一群傻×。”
这是思而不学
的确,我常常思考着有关星辰大海的问题一直到晚上十二点。心中有日月,但是却不懂得“骐骥一跃,不能十步,驽马十驾,功在不舍”
的道理,这是我踩过的坑。
J君这样的态度显然是着了所谓“中国填鸭式教育”
的道,而我最终没有和J君一起堕落。
高二转了班,又碰见另一个同学,叫他Y好了。
Y学习十分刻苦努力,是个循规蹈矩的同学。我不知道每天Y要学习到几点,回家后是不是除了学习什么都不做这些我都无从知晓。但在学校我能见到的Y,除开早操这类无法用来学习的的时段,Y都在刻苦努力用功:上课认认真真记笔记,不用去拜读我也猜得出他一定是记下了老师在黑板上的每一个字,也许还包括错别字;下课认认真真写作业,遇到不会的——频率大概是每道题一次(除开那些做过的)——就来问班上那些学习好的同学,或者是老师,而丝毫不管他们是否在与别人聊天或者做一些最好不要轻易被打断的工作。
Y经常上课时问常常对老师讲的某个地方有疑问:不懂事什么意思,而实际上这个地方刚刚讲过,那时Y正在抄之前的板书——如果一字不落,你永远也不可能跟老师同步。
我时常感慨自己如果有Y一般用功那么没有什么我干不成,而Y本身,在我看来则一事无成。表面上看着用功努力,实则乏味死板——我称之为一种高级的懒惰,他的脑子除了用来死记硬背的部分,其余部分大概没有用过。
这是学而不思
“学而不思则罔,思而不学则殆”
,很多这样的句子我们耳濡目染,明白它们的意思,但就是不愿意仔细思量其中蕴含的道理。因为不懂其中的道理,我们多少都会在J和Y中间偏向一方,成为一个具有悲剧色彩的人。要么是怀才不遇,被埋膜了天才,要么内不得于心,外不应于器,三年不成章。
我希望能够以之前所约定好的身份谈谈自己的感想,但又不愿意说教式地举纲列目。正如之前所说,文字是手指,而字里行间蕴含的道理是为明月。以手指指向明月多数时候会让人觉得手指就是月亮,不如引导你们自己去找。
胸有激雷而面如平湖者,可以拜上将军。
这句话出自《孙子兵法》,愿与诸君共勉。
]]>自从昨天下过雪后整个西安都冷了一倍。冷了一倍就是说你要穿原来两倍厚的衣服
盗图一张,衬托一下这篇的主题。
我为什么要取这么个名字,是因为今天在吃过晚饭后从康桥回实验室的路上触景生情,想要抒怀一番。
其实今年1月31日下的雪更大。
前天晚上在实验室过了夜,因为室内暖气空调一应俱全,而且我们几乎从来不开窗户,所以很暖和。只是在出去上厕所的时候觉得比平时冷了几分,天黑,什么都没多想。
——直到第二天早上迷糊中被冻醒,才迟钝地反应过来是下了雪。
雪下了一整天,直到晚上十一点多才停下。
可惜前天在实验室过夜是为了赶今天中午的ddl,于是下雪这天我在屋子里呆了一整天,直到晚上干完活,决定回寝室休息一晚上。卖夜宵的大叔大妈们精神可嘉,如此寒夜还依然出摊,我于是去支持了一下他们的工作。
美妙的雪景就这样被我浪费掉错过了。
上面这句话是在打趣了。
我自己十分喜欢抒发感悟,动不动就要触景生情,感慨一番。
去年被同学叫去大雁塔,在音乐喷泉结束后原本熙熙攘攘的广场很快就空了下来,注视着古塔,心中感动了起来:
它在这里静静地站了一千年,相比之下人生真的是太短了。而且寂寞如雪
一时间一种历史的厚重感扑面而来,我就这样被震撼着,品味着洪水般泛滥的情绪。
我们触景生情大概是因为心里有所感动,马可波罗在向忽必烈介绍城市菲利德时讲:
如果你觉得两个拱廊之中的一个更为惬意,那是因为再三十年前曾有一个穿绣花裙宽袖衣服的姑娘走过那里,或者是因为那个拱廊在某一时刻里的光线使你联想起另外一个地方的什么拱廊。
那天回去后心里很难过,为什么呢?
太喜欢说大话,谈人生谈理想,大快人心!
安逸地坐在咖啡管里高谈阔论伤怀古今当然逍遥自在,但是之后若是不如同师文学琴一样得之于心,应之于器,那么就是不务正业。
我也想学朱自清说一句:今年我二十岁,真的是太聪明了。
没有一点点沉淀又有什么好抒怀的。
高中时候喜欢骂国家骂教育,说他们是工厂,量产出一批傻子。
可转念一想做一个这样的愤青,这样的言论并不能使得国家和教育有一丝一毫的改变。唯一能做到的就是宣泄心中的不满,然后继续苦海沉浮,庸碌不前,变成一个上述的傻子。
波罗在讲完所有自己游历过的城市后向大汗总结道:
生者的地狱是不会出现的,如果真的有,那就是这里已经有的,是我们天天生活在其中的,是我们在一起集结而成的。
免遭痛苦的办法有两种,对于许多人,第一种很容易:接受地狱,成为它的一部分,直至感觉不到它的存在;第二种有风险,要求持久的警惕和学习:在地狱里寻找非地狱的人和物,学会辨别他们,使他们存在下去,赋予它们空间。
其实是上面的两段都是卡尔维诺借波罗的嘴说的。
这是我的高中化学老师口中鲁迅先生的一句话。
英国牛津的主教、牛津运动的发起人约翰·纽曼在他著名的演讲“大学的理念”中讲道:
先生们,如果让我必须在那种由老师管着、选够学分就能毕业的大学和那种没有教授和考试让年轻人在一起共同生活、互相学习三四年的大学中选择一种,我将毫不犹豫地选择后者……为什么呢?我是这样想的:当许多聪明、求知欲强、具有同情心而又目光敏锐的年轻人聚到一起,即使没有人教,他们也能互相学习。他们互相交流,了解到新的思想和看法、看到新鲜事物并且掌握独到的行为判断力。”
虽然西交大是前一种制度,但这里或多或少地闪耀着后一种制度的光辉。
晚饭后走出康桥,哈了几口哈气。这天虽然冷啊,但是正值饭点,食堂门口非常热闹,广播里播放的是轻柔的音乐。
当我穿过人群时,每每听到人们的谈话,他们有的讨论着自己最新的研究方向,有的争论着一个关于力学的问题,还有的在讲关于什么东西的设计…心中竟有些感动,最终意识到我面前的诸位大多是未来的艺术家、科学家、工程师。
虽然是大雪过后,万物凋零的时节,但我却见到了前所未有的生机盎然。
幸甚至哉,歌以咏志。
在ddl后存活下来的我长舒了一口气,写出这么一篇东西。
]]>