普通视图

发现新文章,点击刷新页面。
昨天以前DGideas' Blog

紫15旧寓的最后一晚

作者 DGideas
2025年10月16日 23:21

本文的作者是博主的一位发小。

半年前,hl 回清华,我们一同在五道口的意大利面馆吃了一顿,饭后一路走回学校。我动议他回老宿舍看看,他欣然接受。还记得他站在窗前向北眺望夜景——北边是校外,成片的郊野公园与圆明园相接。谁能想到这位公子哥离校那年和离校后发生这么多事情呢?我们在 23 年都受到巨大的精神挑战,其剧烈程度,几近将我们打倒不起。看他眺望窗外的背影,我说:“你果然没有那种重回故地的慨叹”,他同意。这几年,可以慨叹的事情太多了吧!

今天竟然是我在 1303b 过的最后一晚!然而我也并未发出什么慨叹。 22 年住进博士楼,偶然分配至此,与 hl 做了短短两月的舍友。即使他两月后转到楼下的单人间,但是 22 年底那些刷手机信息夜不能眠的日子,每天都到楼下一层去找他交流信息,揣测、断定,激愤得形诸于颜色。然而却见他喝着加冰的威士忌,喝着我推荐他买的鸭屎香单枞,看他点烟、沉思,当然神色也是阴郁的。如果不去找他高谈阔论,想必胸中的块垒之气是不得抒发的。

还记得那天晚上给老师拨了电话,问他是否住在校内,他立刻反应道“需要我现在过去吗?”又听他讲“这次动员的广度和深度”如何如何。今天想来,只有苦笑,还有感叹。每个人的过去都像陈死人的血肉一样滋润从泥土中长出的生命之芽,对于学者这些历史性的存在者来说更是如此!这几年几位 1930 年代出生的思想史家相继去世,他们的晚年无一不孤寂,或自觉或不自觉地与大陆、港台、美国学界切割,随着 20 世纪的落幕而隐退。读张灏的纪念文集,印象最深的便是“20 世纪之子”这个称谓(还有张灏晚年时常慨叹:俱往矣!读之令人不忍),惠我思想的李先生何尝不是如此,我的老师何尝不是如此!最能振发我的思想与心灵的,便是他们了。莫非我对 20 世纪,就像高中二战军迷对苏、德军服、坦克的历史癖一样吗?我想不是的,我一向缺少这样轻松的消遣。历史是多么沉重啊!

前些日子困闷不爽,找朋友高谈阔论(实际上更多是我单方面输出“负面情绪”),说到自己只在 20 世纪活了三年,为何有这么深的 20 世纪情结呢?20 世纪的遗产,能否因应这几年以来的现实,是个疑问,然而我的 20 世纪情结究竟从何而来呢?我不认为它来自于书本的移植,虽然但凡情结之类的东西,都与意淫难解难分。这是个谜,并且是支持我继续现在工作的绝大动力。毕竟,对那种钱钟书式的博雅的学问,我并不感冒。

记忆虽然可以通过编年串联起来,但是印象中深的,在这间小屋中,也就那么几个瞬间。大部分时间,都是懒散地按部就班地生活。23 年四月底五月初,因为喜欢一个人而不得,想到他,除了渴望、遗憾、不解,还有怨恨。知道他喜欢拍鸟,辨识各种鸟叫,不巧我的宿舍朝北,下面正是一大片树林,春末初夏的鸟叫很早,四五点钟便已经吵闹,而我是久矣习惯开窗睡觉的——即使进了蚊虫也不以为意。听到鸟叫便想起他,早早地醒来不能入睡,以至于恨听鸟叫,流下苦涩的泪水。他是来过这间宿舍的,也曾向窗外眺望,为何当时没有从身后搂住他呢?真遗憾!彻底放下这段求而不得“恋情”,已经是在大病初愈之后了。那是第二年的五六月份,从地铁车厢门走出,忽然想到删掉微信,就这么删了。

我在这间小屋也活了三年!而且是全身心地、发动意识地、痛切感受地活了三年!20 世纪那三年我还是个婴儿呢,什么也不记得。23 年那些痛苦的日子不必说了,如此苦心极力、戚戚惶惶,如此神经衰弱、充满戾气,终至于年底病倒,两个月没有回到这间小屋。第二年开学后回来试探性地重新住宿,从一周一两天开始,一到晚上就产生恐慌与气闷,直到临睡前冲了热水澡、准备服下安眠药才缓解。再到一周住三四天,再到工作日全部住宿。也是五六月份,晚饭后在操场散步,一圈一圈地走,手机放在兜里,就透过眼镜全身心地感受周遭,看宿舍楼顶,看足球场上的大灯,看远处的双清公寓,看天空和云彩,真是劫后余生的感觉!经此一遭,看熟悉的场景竟然都有新鲜的感受了,然而这新鲜中带着念天地、古今之悠悠,独怆然而涕下的历史的沉重,劫后余生的庆幸,当然还有怕——怕再遭这么一遭罪,不知自己还能不能挺过来。甚至对于已有的经历而言,都不能说是挺过来,而是熬过来。当然,劫后余生久了,感官重归于木讷,看到熟悉的场景便无所谓感触了。

晚上出门锻炼前,给hl发了条微信——毕竟他也在这里住过,告诉他这是我在 1303b 住的最后一晚,他半开玩笑,带着一如既往地公子哥式的幽默调侃,但又有那么点老年人的沉重,给我回复:“一个时代落幕了。”确实,很快,也许就在 9.1 号,这间屋中的床铺桌板衣柜就要被拆除,墙上由他多年前张贴的古画复制品,以及那张我经常误看作鲁迅的闻一多像,都要被铲除了!也曾住过这栋楼,早我们数年毕业的亦师亦友的hy兄留给他,并由他留给我的席子和小茶桌,也不在我带走的物品之列。它们将被留在这里,当成垃圾,碾碎、消失。而我的那幅徐悲鸿鹰图的木版水印挂轴,和朋友戏仿康有为字体的竖轴,在新宿舍也没有墙面能够安放,只得卷起收走了。接下来这几个月,可以说是反刍在这间小屋中三年来的经历和思考,我很爱这个比喻:就像一头受伤的狼一样,舔舐着自己的伤口。独狼舔舐自己的血,而这血就像乳汁一样滋润它。

四邻都已搬走,在屋中功放布鲁克纳早年的交响曲,实在是莫大的享受,看了一下唱片封面,是罗日杰斯特文斯基指挥苏联文化部交响乐团的布鲁克纳交响曲全集录音(1983-1988),oboe 响了起来,大提琴的旋律之美,堪比柴可夫斯基冬日梦幻那首交响曲的慢板。

2025 年 8 月 26 日夜
于紫15旧寓

记一次信用卡盗刷

作者 DGideas
2025年7月29日 20:18

今天,我发现我的星展银行(新加坡)的信用卡中有一笔莫名其妙的账单。于是我立刻跟星展银行的客服取得了联系,并且协助我撤销这笔 “盗刷交易”。在中国用了这么多年的支付宝和微信支付,从来没有遇到过未授权交易的情况,而有了信用卡的这几年间,这已经是我第二次遇到这种情况了。

第一次被 “盗刷” 是在招商银行用那个 MasterCard 国际全币种信用卡的时候,有一天我突然发现卡片里的账单多出了一笔不认识的交易,于是立刻通过银行的热线与信用卡中心的客服取得了联系,帮我锁卡、换卡,撤销交易一条龙服务,最终更换了一张新卡来解决问题。不过这已经是 2021 年左右的事情了。

在此之后,我在用卡安全这方面一直 “感觉良好”——比如,我每次拿到卡之后,都会做这么几件事情:

  • 在签名区签上名,记下 CVV 之后用贴纸遮住这一块
  • 除非在海外住酒店,否则不会带实体卡出门,而……
  • 绝大多数的情况下只使用手机钱包中绑定的数字卡
  • 只把卡绑定过 Steam、Paypal 之类的受信服务,以及
  • 对于不信任的网站,我会使用 Revolut 上的一次性卡来支付

不过即使是这样,我这次还是被盗刷了。盗刷的账单在我这里看起来是这样的:

Activation Blizzard UK LiLondon GB USD -39.99

动视暴雪?看起来很像。我搜索了一下相关的资料,最终在动视暴雪的官方网站下找到了有关未授权交易的专题页面。根据网站提供的资料,看起来这笔 39.99 美元的资金交易属于其中的 “数字产品升级” 类别(我猜可能是游戏装备?)。

在小红书我也搜索了一下,也有类似的用户疑似信用卡资料泄漏,而被动视暴雪扣款,最终争议成功的案例。在贴子中,那个作者表示自己也经常玩游戏,因此一开始也没有觉得这笔交易很可疑。这和我的情况是类似的,所以我有点怀疑是 Steam 把我的支付信息给泄漏了,也许吧,谁知道呢。

向银行报告之后,第二天我便接收到一封电子邮件,说明商家并没有对此账单收费,因此相关费用已经从账单中消除。除了需要等待几天更换信用卡之外,并没有其他的损失。不过,对于信用卡的用户来讲,还是要时刻关注一下自己的信用卡账单,避免这种未授权交易带来的损失。

vczh:做自己热爱的事情

作者 DGideas
2025年6月26日 06:06

我就是 vczh 口中那个 “中学时花了 100 小时编程” 的孩子。高考后,在物理老师邓华的推荐下,我选择了计算机专业。进入行业后我也曾焦虑:万一哪天干不动、不想写代码了怎么办?vczh 的这篇文章告诉了我答案:如果有一天你变得不喜欢编程了,那就不要编程。人生在世除了让自己开心,没有什么是必须做的。

本文全文转载自 @geniusvczh 在 2025 年 6 月 16 日发布的 X.com 文章,排版格式略有调整

又到了每年选志愿的时候。一个想法是否经得起时间的考验,就要看他是否不会跟着总路线一起摇摆。虽然每次的措辞不一样,但是我现在跟码农行业如日中天的 10 年前保持差不多的想法:一个人只要在中学阶段能花超过 100 个小时编程,我就认为他可以安心报计算机,为什么呢?

现在已经活了半辈子了,我越来越觉得 you only live once YOLO 非常的有道理。人终归都是要死的,你做的一切努力都终将白费,所以只有活得开心的人,才没有白来地球 online 一趟。如果你在即将死亡的时候觉得,要是能再来一次就好了,那你就是一个成功的人,我认为这是唯一衡量你是否成功的标志。

如果你中学阶段真的花了 100 个小时编程,那我就认为你是热爱编程的。这种热爱通常不会随着年龄的增长而快速衰退,你一定想在开始工作甚至在中年阶段依然可以写自己喜欢写的代码。然而就算你当了程序员,上班写的代码也可能是无聊的,所以下班后还得写让自己爽的代码。

不管怎么说,报计算机专业终会找到的工作钱会多一点,不管是不是当程序员。而且你从进去大学开始就能一直做自己热爱的东西,工作后还能买得起昂贵的台式机,还有底气拒绝加班,下班后写自己的代码不用看用户的脸色,没人用也不会造成困扰,那么你就可以从此刻开始不停享受写代码带来的快乐。

毕业后,每一年都在做自己喜欢的事,哪怕社会经济出了问题你一下子失业了,晚上写自己喜欢的代码,也可以让你忘却烦恼,白天有更好的情绪。一直有工作自然是好的,暂时没有也不会有很大的压力,保持一个好的情绪,甚至能让你多活好多年,在退休后还能比别人多十几年时间享受写代码的乐趣。

当然人也是会变的。年轻的时候觉得自己只喜欢写代码,到了中年发现多了一些别的爱好,看着 GacUI 代码增长的速度远低于10年前就知道。不过没关系,如果有一天你变得不喜欢编程了,那就不要编程。人生在世除了让自己开心,没有什么是必须做的。

所以报计算机是为了什么呢?就是为了能够一直写代码。如果你真的不喜欢编程,却要被迫写代码维持生计,那是一件非常痛苦的事,搞不好还会熬出抑郁症。所以为什么说我只鼓励在中学阶段写过 100 小时代码的人报计算机呢?

过去几十年见过形形色色的人从零开始学编程,最后会喜欢上的只是极少数。如果你没写过代码,那按照统计,你极大概率是不会喜欢写代码的。不过人生是你自己的,你愿意赌博,那我也不会阻止你的🤪

Pushover 的五种消息优先级别

作者 DGideas
2025年2月25日 13:11

在消息推送服务 Pushover 的文档中,介绍了消息共有 5 种不同的优先级别(-2、-1、0、1 与 2)。这些级别对应了不同的消息推送模式,本篇文章对这些消息级别进行讨论。

最低优先级(-2)

根据文档描述,这种优先级别不会发送任何推送通知。在 iOS 系统中,只会使主屏幕中的图标角标的通知数量增加,相当于是一种 “静默通知”。结合单独的 Pushover “应用程序/API 令牌”(Application/API Token)来使用时,这种优先级别特别适合发送一些提示性的信息,比如在任务执行过程中产生的日志,或者重要程度低到主动想起来才有必要看的一些信息。

低优先级(-1)

这种优先级别会产生一条手机通知推送,但推送不会有任何的震动或者铃声。在 Pushover 的免打扰模式启用时,普通优先级(0)的消息会降低为该优先级的推送模式。通常,一些需要引起关注,但如果长时间不关注也不会造成太大负面影响的事件的消息推送可以考虑使用该级别。比如天气更新、日常新闻,或监控的日常股票价格走势等。

普通优先级(0)

这是默认的消息推送优先级别。以该优先级别发送的消息会正常产生推送通知,响铃以及震动。这种优先级别通常适合推送 “希望使用推送服务本意” 的事件类型,如需要引起注意的运维事件、需要响应的消息,以及到某个时间点需要进行何种动作等。

高优先级(1)

以高优先级别推送的消息会绕过 Pushover 用户设置的免打扰时间,并且消息在 Pushover 中以红色底色显示,以引起用户注意。除此之外,消息的推送模式与普通优先级别的消息相同。用户通常会在休假或完成重要任务期间开启免打扰时间。而需要通过这种优先级发送的消息的重要程度足以打断用户正在聚焦的某些工作。通常是需要立即采取行动的事件、重要天气提示以及重要任务执行异常等通知。

注意,高优先级的震动以及铃声通知无法绕过系统的静音设置,除非在 Pushover 客户端中手动设置

紧急优先级(2)

紧急优先级具有与高优先级一致的推送策略,但会重复推送直到用户响应为止。同时,在 iOS 客户端中,用户可以为高优先级和/或紧急优先级的消息设置“重要警告”。在“重要警告”模式下,即使你已将 iPhone 静音或设置了专注模式,也会收到通知。这种模式通常适用于 Oncall 或紧急消息。

在紧急优先级模式下,推送消息时结合 retryexpire 两个选项可以控制重复推送的频率以及总共次数。retry 选项用于控制用户未响应时,消息应当多长时间推送一次。可以设置一个 30 秒或以上的值。expire 选项用于控制消息的过期时间,消息一旦过期,则不会再尝试去推送给用户,其最大值是 10800 秒(3 小时)。Pushover 还有一个总共 50 次的消息推送限制,而无论 retryexpire 的值被设置为多少。

唯有繁星与时光——我的二零二四年

作者 DGideas
2025年1月12日 06:22

又是一个不眠夜。

自从上一篇博客以来已经有大半年没有动笔来写文章了。因为总有许多工作要处理,而除此之外,有更多让我沉迷的事情。但今天是一个好的机会,虽然失眠的日子越来越少(这是好事),但是在这难得的氛围里,我终于又能撰写这一年的年终总结了。

我在忙什么

回顾 2024 这一年,我仍然在我的全职工作中投入了大量的精力。今年一年带给我的成长不但是技术水平的提升,也是 “如何处理事情” 以及 “如何更高质量交付任务” 的能力提升。对我来说这是非常忙碌的一年,但在工作报酬之外,我得到的经验也是十分可贵的。

除此之外,我继续去做了许多有意思的事情:首先便是继续旅游。这一年里我探索了两个新的目的地:泰国以及日本。其实说到日本,它也不算是一个 “新” 的目的地了。早在疫情之前,我便和家里人乘坐邮轮去过一趟日本。当然当时只呆了一天,并没有在日本过夜。并且现在回想起来,说实话,光靠 6-8 个小时岸上旅游的时间,你根本无法感受一个国家的风貌。2024 年的两次日本旅行我分别探索了大阪和东京(以及附近地区),总共在日本呆了多一周的时间,对这个国家方方面面的感受也更加细致。

大阪关西国际机场的摆渡巴士,拍摄于关西国际机场联络桥,2024 年夏季

有趣的是,第一次去日本的时候,我们只办理了单次使用的电子签证。结果从日本一回来,我和 Minda 一商量,对日本的印象都挺好。之后的几天我们又去了趟大使馆,申请了赴日本的多次旅游签证。本来 2024 年 10 月份我有去加拿大的旅行计划,没想到提交加拿大签证申请之后竟然被拒签了(加拿大使馆反馈说我的入境目的不够令人信服,我了解到一些其他南亚裔国家居民甚至更容易申请到加拿大签证),气不过的我和 Minda 一合计:干脆再去一趟日本旅游——于是多次签证就这么被用上了。

这一年我深入地贯彻了几乎 “每个月旅行一次” 的规划,投入了更多时间去研究便宜的机票和有趣的目的地,当然旅游的开支也有所增加。但如果提前规划好行程并比较合适的航线,是能够以非常划算的价钱完成整个旅行的。更棒的一点是,这样的旅行成为了我的月度 “刷新时间”,能让我以更高的效率来完成手头的工作(在旅行之后),因此对我来说是个非常划算的决定。哦对,香港 “九龙餐室” 的咖喱牛腩饭很好吃!

李宗盛 “有歌之年” 演唱会厦门站,2024 年秋季

这一年我继续做了许多探索性的工作,这些工作占用了业余的一些时间(其实是很多时间,也许将这些时间留给写博客的话,我能够写出更多的文章)。我还不太愿意分享这些工作的具体细节,因为其中没有任何事情我认为是达到 “已经做出成果” 级别的。不过我很开心我能够继续保持这种探索的频率,这让我在这一年的每一周都在处理不同的问题。中间踩了很多坑的同时,也收获了一些乐趣和见识。希望新的一年能够继续努力。

最后一块忙碌的事情是阅读和游泳。这两项任务都没有达到我自己的目标。我一年只读了两三本书,并且都是技术类的。我更多的时间花在了在线查阅文档以了解新的内容上,当然也都是技术相关的内容。希望新的一年我能把更多时间花在阅读上,阅读一些人文社科的内容,来 “保持与这个世界的联系”。游泳完成的还算不错,达到了全年十二分之十的目标,因为新加坡雨季的关系,这块的目标并没有全部完成。

当然我还实现了去年的目标的其中一条:“增强英语口语交流能力”。我是通过和 ChatGPT 的 “高级语音模式” 频繁练习来达到这个目标的。与 ChatGPT 语音交流时,我会通过类似下边的指令来开始一段新的对话练习:

我正在做为一名英语学习者来练习我的英语口语能力。我需要你来挑选一个跟之前我们的练习都不一样的主题,扮演与我对话的人,跟我进行多轮英文对话。在这些对话之后,如果你觉得合适,可以对我们对话的内容进行总结,并逐句指出我的语法问题。然后,要求我复述对应正确的英文表达方式。

贯彻这种练习方式,在这个目标上我取得了一些显著的进步。现在,我能够使用英语和我们公司以英文为母语的同事进行比较长时间的对话,或者以英文分享培训材料。

其实我感觉我有很多技术主题想要分享,有一些是没有来得及写,另一些主题可能更适合以公司的名义发布到官方渠道中。不过无论如何,我一定会在新的一年抽出来更多时间,去给大家输出更多高价值的内容。

2025 年的展望

写到这里,突然发现我的年终总结的风格从一开始的天马行空,抒发对生活的情感,越来越变成像绩效考核式的量化总结了。我不知道这样的变化会不会带来问题,但是我愿意去花更多时间,来继续迫使自己将时间投入到对生活的感受上。考虑到每一年的时间都过得飞快,因此我今年对自己只有三个目标:

  • 投入更多的精力分享
  • 多观察这个世界
  • 保持愉悦的心情

这三条都是比以往宽泛地多的目标,对吧?但是借着每年给自己做规划的宝贵机会,我是真的想结合上一年的感受和体验,在新的一年里做出一些小小的改变。通过这些不那么具体的目标,我有了足够的灵活度来思考分配这些宝贵的业余时间,来让自己的每一年都充满不同。

2022 年我将这个博客加入了一项名为十年之约的开放计划,这项计划旨在鼓励更多的简体中文独立博客作者,持续的坚持更新他们的博客内容。写这篇年终总结的时候,我也翻看了 saveweb/review-2024 这个项目,有超过几百位独立博客的作者总结了他们过去的一年。说实话,我觉得大部分人都很厉害,至少比我厉害:他们做了一些很酷的事情或者项目,年终总结的篇幅或者文学素养都比我要更高。我期待自己在新的一年里仍然保持活跃的精力,来持续输出更多的内容,不负时光的约定。

明年见!

回顾与反思

作者 DGideas
2024年5月30日 09:21

本文的作者是费轩

热气透过大敞的窗户涌入,我偏头看向内室的玻璃,觉得脸上干净不少。虽然瘦了,但是下巴显得分明。两道眉毛仍然紧凑在一起,不能舒展开来。宿舍内的书几乎已经搬空,每周返校工作,随身携带一些书,是韦伯和沃格林的。

就在这间屋子,还记得 2022 年刚搬进来时与 hl 畅聊、品茶、尝试咖啡;还记得那年底被封在宿舍楼中,担心不知何时被转运,蹲在卫生间手洗一大盆衣服;还记得 11 月底几乎每天到 hl 屋中激愤地诉说,看他一支烟接着一支烟地抽,然后沉默;还记得 2023 年 3 月的那次甲流,在屋中高烧两日,hl 送来生煎包和馄饨;更记得身心俱疲的 5 月,四五点钟天将要蒙蒙亮时,听着窗外传来的各种鸟叫,从未感到如此清晰、如此吵闹,想到那个人的拒绝和两个月的过往,咽下孤独,觉得喉咙发紧。

以前经常读到某位哲学家在自述中写到某年某月“大病,几死”。当时疑心是作者为了传奇色彩而夸张。有的人也许属于自我感觉不好,如李泽厚和熊十力,从中年开始就不断抱怨自己身体如何如何差,命不久矣,但却都很高寿。如果有幸忝列这一思想谱系,我在自述中大概也可以使用这句话,用来形容去年底的经历。23 年底到 24 年初厚厚的一沓检查报告和病历还留在文件夹中,按时序排列,原本想誊抄下时间节点,留下资料以便回顾,再做销毁。现在终于恢复了健康(甚至比大病之前还要健康),回顾也不想再做了,因为回忆使人痛苦,也因为精神已经松弛从而懒惰。这些病历很快就要去到它们该去的地方:碎纸机。

“西学为体中学为用,刚日读史柔日读经”,这幅联句还挂在老地方。22 年底每天醒来第一个看到的就是这幅联句,其时其境,可以说完全晓彻这两句话的含义,那时,我是以“取今复古,别立新宗”自认的。为什么总谈到一年半前已经“过去”了的事?难道是某种创伤记忆吗?难道就像那天给导师发了现场视频,老师秒回他在 35 年前的经历,一下触发了激情?衰颓固化的时代再也掀不起结构性变革的浪潮,个体自觉到与社会历史行程相切割,对生存处境的改善,只能乞灵于回到自己的一亩三分地,把小日过得精致到登峰造极,在各种分疏但却同一的体验中寻求认同,与世隔绝。这样,历史可真是终结了。

经此一遭,思想发生不少变化,当然,同心圆——那个回心之轴没有变。现在愈发明确,50 年一代学者(知青、文革一代)和在 50 年一代学者陶养下成长起来的 70 年一代学者(文化热一代,特别是在大学期间经历了 89 和后 89 的市场化浪潮),他们的思想不可能成为我精神解困的方法。90 一代在去政治化的环境中成长起来,这就决定了他们的非政治性(把去政治化说成也是一种政治,只不过反映出说者本身的政治性),使得他们面对 50、70 一代所未面对过的个体人生问题。出于智性上的清明,我不得不认为,这种个体人生问题,不是任何重新政治化的动能所能解决的,在当下,重新政治化的激情只能与已有建制和文化保守主义合流。90 一代很难像 70 一代那样受惠于 50 一代学者的思想(70 一代学者做的往往是把 50 一代学者思想精致化、国学化的工作),他们面对的是全新的问题,需要开辟全新的道路,在这方面,不能抱有任何便巧的幻想。

2024 亚航东盟无限飞折腾记

作者 DGideas
2024年3月24日 14:36
一张来自 AirAsia.com 的低分辨率截图,展示了 “亚航东盟无限飞” 的订阅选项,图片中显示 “搭乘亚航班机于东盟成员国随心飞行” 以及 “388 新币每年” 和 “0% 分期利息” 的文字说明
网页截图,来自亚洲航空的 ASEAN(东南亚国家联盟,简称东盟)无限飞行计划

本文最后更新于 2024 年 3 月 29 日

来自马来西亚的亚洲航空(AirAsia)近日推出了他们继 2022 年 SUPER+ 无限飞行年度计划之后的又一个年度订阅计划 AirAsia Unlimited Asean Pass(亚航东盟无限飞)。和上一年度的订阅计划不同,这一次的计划订阅费用变得更贵,并且仅限于在东盟国家的 69 条航线内的指定日期的航班内选择(要知道,去年的套餐是可以全球范围飞行的!)。然而抱着折腾不止的心态,我还是毅然决然购买了他们的年度飞行计划,踏上了一条折腾不止的不归路……

友情提示:这个计划本身有很多很坑的地方,如果没有做好折腾的准备,千万不要购买!

计划限制

在亚行东盟无限飞计划的条款与条件页面中有诸多限制,这里先整理概括如下:

  • 只能乘坐如下亚航所属航空公司的国际航班:亚洲航空公司(代码 AK)、泰国亚洲航空(代码 FD)、印度尼西亚亚洲航空(代码 QZ)、菲律宾亚洲航空(代码 Z2)以及亚洲长程(代码 D7)
  • 仅能预定从 2024 年 5 月 1 日 – 2025 年 4 月 30 日之间的指定航班
  • 以下日期不提供无限飞
    • 诸灵节(2024 年 10 月 30 日 – 2024 年 11 月 4 日):菲律宾亚洲航空
    • 圣诞与新年假期(2024 年 12 月 19 日 – 2025 年 1 月 5 日):全部航空公司
    • 中国农历新年(2025 年 1 月 24 日 – 2025 年 2 月 3 日):全部航空公司
    • 开斋节(2025 年 3 月 27 日 – 2025 年 4 月 6 日):全部航空公司(菲律宾亚洲航空除外)
    • 泼水节(2025 年 4 月 10 日 – 2025 年 4 月 20 日):全部航空公司(菲律宾亚洲航空除外)
    • 圣周(2025 年 4 月 15 日 – 2025 年 4 月 22 日):菲律宾亚洲航空
  • 除此之外,所有 Unlimited 订阅的可用航班仅以查询页面的展示为准
  • 最多有 2 个未成行航段(每个航段可以是单程,也可以是往返),并且至少提前 14 天预定航班
  • 允许“预定但未上飞机”(no show)至多 3 次
  • 某一天起飞的航班最多为 2 班,并且不能从相同的城市出发

看起来是一个还算公平的计划,是这样吗?我算了一笔账,因为我居住在新加坡,并且持有的是中国内地护照,自疫情结束之后中国与东南亚许多国家都签订了互免签证协定。因此包括泰国、马来西亚与(将来很快的)印度尼西亚在内的许多国家,去旅行都会很方便。388 新币(约合 2000 人民币左右)的计划价格,只要平均每个月周末旅行一次,就可以完全值得回本。因此我义无反顾地购买了这项计划订阅。

更多的坑

我自认为完整阅读了计划的条款与条件后,便能清晰了解该计划的优缺点,于是购买计划后,我便开始预定机票,筹划着 5 月份的第一次旅行。然后我就遇到了这样的错误提示:

一张网页屏幕截图,搜索的是 2024 年 5 月 4 日至 5 月 5 日,从新加坡樟宜国际机场(SIN)到兰卡威国际机场(LGK)的机票预定,并且包含一条错误信息
网页截图,促销码无法用于选定的航线或日期

无法用于选定的航线或者日期?我又尝试了许多不同的目的地/时间选项,最后终于发现我忽略了无限飞订阅中的一个重要限制:所有的可用航班均以查询页面的展示为准,并不是除了限制日期外,所有日期都有航班,并且可用日期的航班也是有限的。以新加坡 – 吉隆坡的单程航班为例,可用于 2024 年 5 月 3 日(星期五)的预定航班如下:

一张网页截图,展示了通过预定优惠码在 AirAsia.com 官网预定 5 月 3 日由新加坡飞往吉隆坡的航班信息,其中推荐的航班是 07:55-09:00,以及 09:10-10:25 的两班航班,显示了 100% 折扣的字样,税费与其他费用的应缴纳总价为 394 人民币
网页截图,在无限飞计划下,5 月 3 日(星期五)由新加坡飞往吉隆坡的航班只有两个上午航班适用

新加坡 – 吉隆坡是全球最繁忙的国际航线,仅亚洲航空每天便有超过 12 班定期航班往返两座城市之间。而适用于无限飞的航班却选择“十分有限”。另外,该计划在许多目的地之间的限制,使“周末游”变得不是十分可行,以新加坡往返兰卡威的航班为例,2024 年 5 月 – 6 月期间,无限飞适用于如下日期:

  • 新加坡到兰卡威:大部分是周一、周二、周三与周日
  • 兰卡威到新加坡:大部分是周二、周三、周四、周五与周六

因此,从新加坡到兰卡威的周末游变得就不是十分可行。AirAsia 官网上提供的查询工具非常简陋,仅能查询特定月份的两个目的地之间的单程航班的一般供应量情况:

一张网页屏幕截图,展示了一个允许用户输入出发机场、到达机场和月份的查询器,用于查询可用航班的日期和大致容量
网页截图,AirAsia.com 官网东盟无限飞页面提供的航班供应查询器

抱着折腾不止的心态,我找到了该工具的数据源[Wayback Machine]:这是一个记录了从 2024 年 5 月 – 2025 年 4 月期间,无限飞计划的所有可用航班及其供应量的列表。值得注意的是,似乎该列表是动态增长的。在本文初次写就时,该列表中共有 17955 项可用的航班信息,而如今列表中拥有超过 2.1 万条航班信息数据。我对该数据进行了一些统计,发现一些有趣的数据事实如下:

  • 一共有 68 条航线及其往返航线可用:BKI – SIN, BKI – MNL, BKI – CGK, BKK – KUL, BPN – KUL, BTJ – KUL, BWN – KUL, CGK – KUL, CGK – PEN, CGK – DMK, CGK – JHB, CGK – SIN, CGK – KCH, CGK – PNH, CNX – KUL, CNX – HAN, CXR – DMK, CXR – KUL, DAD – KUL, DAD – DMK, DMK – KUL, DMK – MNL, DMK – SGN, DMK – RGN, DMK – PNH, DMK – SAI, DMK – KNO, DMK – HAN, DMK – VTE, DMK – SIN, DMK – LPQ, DMK – DPS, DMK – PEN, DMK – JHB, DPS – KUL, DPS – SIN, HAN – KUL, HDY – KUL, HDY – SIN, HKT – KUL, HKT – SIN, IPH – SIN, JHB – SUB, JHB – SGN, KBV – KUL, KCH – SIN, KJT – KUL, KNO – PEN, KNO – KUL, KOS – KUL, KUL – SGN, KUL – SIN, KUL – PNH, KUL – PDG, KUL – PKU, KUL – MNL, KUL – YIA, KUL – SUB, KUL – SAI, KUL – UPG, KUL – LOP, KUL – RGN, KUL – PQC, LGK – SIN, PEN – SIN, PEN – SUB, PEN – SGN, SIN – YIA(按字母顺序排序)
  • 最多班次的航线:从苏丹谢里夫·卡西姆二世国际机场(代码 PKU)到吉隆坡国际机场第二航站楼(代码 KUL),订阅期内一共有 288 天提供可用航班
  • 最少班次的航线:从新加坡樟宜国际机场(代码 SIN)到苏加诺-哈达国际机场(代码 CGK),订阅期内一共只有 11 天提供可用航班:2024-06-24, 2024-06-25, 2024-06-27, 2024-11-18, 2024-11-19, 2024-11-20, 2024-11-21, 2024-11-25, 2024-11-26, 2024-11-27, 2024-11-28
  • 星期三的可用航班最多,其次是星期二和星期四。而周日的可用航班最少,周五和周六其次
Python 源代码
import re
import json
from typing import NamedTuple, List, Dict

class Flight(NamedTuple):
    Origin_Country_Name: str
    Destination_Country_Name: str
    Origin_Airport_NameAndCode: str
    Destination_Airport_NameAndCode: str
    DepartureDateMYT: str
    TrafficLightSequence: str

class FlightRoute(NamedTuple):
    origin_code: str
    destination_code: str

def get_airport_code(string: str) -> str:
    return re.findall(r'\(([A-Z]{3})\)', string)[0]

with open("O2availableseat.json", "r") as _f:
    flights: List[Flight] = json.loads(_f.read())

flight_info: Dict[tuple, list] = {}

for flight in flights:
    origin_code = get_airport_code(flight["Origin_Airport_NameAndCode"])
    destination_code = get_airport_code(flight["Destination_Airport_NameAndCode"])
    route = (origin_code, destination_code)
    if route not in flight_info:
        flight_info[route] = []
    flight_info[route].append(flight)

flight_info = dict(sorted(flight_info.items(), key=lambda x: len(x[1]), reverse=True)) # sort by number of flights DESC

如果你需要更直观的查询这些数据,我已经将它共享到 Google Sheet 在线文档中,你可以随时浏览或将这些数据下载到本地。你可以使用这些数据来规划你的行程,比如说找到可以供周末旅游的航线,或者研究是否值得购买亚航无限飞。

在每个人的世界中——我的二零二三年

作者 DGideas
2024年1月27日 23:43

年终总结可能会迟到,但它从来不会缺席。

在过去的一年里,我和 Minda 探索了许多地方:新加坡、马来西亚、香港、马尔代夫、上海、布里斯班、北京。这比我过往任何一年的经历都要丰富。有些地方是之前从没来过,全新探索的。而另一些则是故地重游,顺便感受一下发展的新气象。而在旅途中时常让我感动的,其实大概率不是风景,而是人。有幸能够跟各种人交流,倾听他们的故事,近距离又真实地感受他们的情绪,走近他们的世界,这时常让我在内心热泪盈眶。

在过去的一年中,我很忙。有很多事情需要忙。大多数是工作上的,但也有少数不是。现在回头来看的话,我只能侥幸地说做得绝大多数事情都至少有一些成果,都在向前推进。但是这里也暴露出一个问题,就是我很难像之前一样,轻易有大块的不受干扰的集中注意力的时间(就像今天在撰写年终总结一样)来做我想要做的事情:很多事情交织在一起、相互穿插,一不小心就成了线团。有的时候我在想,为什么会变成这样。现在想来这应该是工作内容、外界干扰、个人心态多方面混合的结果。我从来没有想过专注时间会变成一项个人的奢侈品,我在过去一年也没有意识到这对我有那么重要,因为我从来没有应付过如今这么多事情。现在看来,保持平和的心境、切换环境、维持足够的休息和理智对我来说都很重要。这是我在 2024 年争取能够做到的。

一张酒店房间中拍摄的图片,展示了一个只有面向窗户的书桌被投影灯光的场景。书桌上有餐巾纸、饮料、笔记本计算机和几张卡片
这篇年终总结是我在马六甲宜必思酒店撰写的,像这样的极度专注的环境是非常奢侈的,拍摄于 2024 年初

这一年学到的另外一点是信守承诺——承诺不仅包含要完成哪些事情,更重要的是何时完成。时刻保持跟踪并维持高质量的交付始终是一件具有挑战性的事情。正如李显龙(Lee Hsien Loong)在 2019 年新加坡国庆群众普通话演讲中,引用芬兰石油公司纳斯特(Neste)总裁的一句话:“世界上最宝贵的资源是信用。要赢得别人的信任,我们必须兑现承诺,并付出更大的努力去守护这份信用。”(这里附上这段话的英文翻译,来体会这一点:The most valuable resource in the world is trust. But to find trust one must first earn it. And to keep trust, one must continue to earn it.)

忙里偷闲

某种意义上讲,周末对我来说是另一种形式的工作日。在我意识到这一点之后,适当的休假对我就变得尤其重要。还记得在电影《借刀杀人》(Collateral,台译 “落日殺神”)中,驾驶着黄色出租车的洛杉矶出租车司机麦克斯(Max)的驾驶座遮阳板里,就别着一张马尔代夫的照片。在电影中,这位出租车司机说道:“每次当我看到照片的时候,我就像是在度假”。

休假是一种逃避。在几分钟甚至几天的时间里,远离压力源,避免干扰,高质量地享受难得的宁静时光。你可能很难安排出来时间去制造假期,但这是百分之百值得的。当你必须陷入无穷无尽的交付陷阱时,给自己充足的时间去缓解压力,调整心态,是十分重要的。从这个角度来看,人类一生有限的时间本来也是另一样“奢侈品”啊。

Dota 2 中有一样游戏内道具叫做 “回音战刃”(Echo Sabre),能够在短时间内对敌人进行两次连续攻击。我的旅游习惯也是这样:去了一个好玩的地方,只要感受不错,就一定会有第二次。这一年里,香港、马尔代夫、澳大利亚都是我觉得可以再去的地方。事实上,其中有一些地方已经在我的 2024 年旅游计划中了。

一张照片,图片中央有一只美国硬毛猫(american wirehair),图片背景中还有一则使用繁体中文书写的情人节广告
香港北角码头的猫,摄于 2023 年初

平衡

有很多事情是牵扯平衡的艺术。这一年我们同事之间流行起了一款名为务农非易事(Agricola)的棋盘游戏(Board game),我和 Minda 私下也非常爱玩。这款游戏的核心逻辑就是平衡发展:大多数资源的累积都可以使玩家获得分数,但某种资源累积到太多量了之后,即使再累积更多资源,玩家也不会获得更多分数。我之前不怎么接触这一类的游戏,而无非就是在电脑上沉迷一些沙盒模拟器类的游戏。但在 《务农非易事》中,游戏分数却狠狠打击了我这种 “沉迷于某一些特定方面” 的玩家。其实通过玩这些游戏,你甚至能够体会总结出一些做事情的一般性的道理来。

还有一种平衡是平衡目标与预期。我也常翻看他人的年终总结博客文章,有一些人热衷于 “OKR 式量化地” 总结并且回顾上一年自己的 “小目标” 是否达成。我也会有一些年度目标,但是我并不希望量化它们。只希望在来年的年终总结中,这些方面会做得比现在更好吧:

  • 增强英语口语交流能力
  • 做一些区块链基础技术方面的博客(文章)
  • 更高效率专注的完成自己的工作
  • 去新的地方旅游

那么,明年见吧!

为什么比特币网络中会有空区块

作者 DGideas
2024年1月27日 21:36

当你打开比特币(Bitcoin)网络的区块链浏览器,在少数情况下你或许能够惊讶地发现刚刚产出的一个区块是个“空区块”,就像高度为 825999 的区块一样。严格来说,这样的区块并不完全空,而是除了区块奖励交易(coinbase transaction)以外,区块内不含有任何其他交易。

理想情况下,当矿工获得一个区块的记账权后,矿工获得的收益主要来自两部分:一是区块奖励,即 coinbase 交易;另一部分则是来自在区块中包含交易所获得的手续费收益。对于这种“空区块”来讲,从经济上看起来矿工是损失了一部分收益的,但事实上并非如此……我们今天就来讨论一下这样的区块的产生原理。

矿池是如何与矿工协作的

比特币(Bitcoin)网络的挖矿是一种通过暴力计算符合条件的哈希值来实现工作量证明(Proof of Work,PoW)的过程,其难度如今已经变得很高。就拿上述的 825999 区块举例,该区块产出当时的全网难度约为 73.20 T。这是什么概念呢?如果一个矿工拥有一个 1Ghash/s 效率的计算设备(这比常规的家用显卡效率快得多),那么他需要约 3638791736 天(99693 个世纪!)才能成功挖掘到一个区块。这几乎是不可能独立完成的。

相关代码
python3 -c "print(73.20*10**12 * 2**32 / 10**9 / 60 / 60.0 / 24)"

因此我们有了矿池。矿池按照特定的分配方式(如,按工作量以及最近收益的移动平均值)为参与计算的矿工按照所贡献的算力分配挖矿所获得的收益(与此同时,矿池自己也会获得分成)。为了计算一个区块的目标哈希值,矿工必须首先从矿池获得区块中包含的交易信息等内容后,再通过调整区块中的随机数(nonce)等值,尝试计算出符合目标要求的哈希值。区块中包含的交易数量可能很多(从区块链浏览器来看的话,一个区块中可以容纳 4,000 笔交易,有时候还要更多),因此典型上来讲,矿池可能需要几秒中的时间才能将这些编排的交易内容(矿工待解的“谜题”)整理并发送给矿工(其中还有网络传输时间以及网络延迟)。

空区块的经济学博弈

绝大多数比特币矿工遵循最长链(Longest chain)原则工作:当矿工得知网络上有一个新的有效区块被发现时,矿工会希望基于这个最新产生的区块继续计算下一个区块的哈希。但前面我们提到,完整的下一个区块的信息可能需要几秒钟时间才能传输到矿工本地以供计算。在此期间,为了不浪费矿工宝贵的时间(以及算力),矿池通常会(预先)发送给矿工一个区块的模版信息,该信息是只包含一个区块奖励交易(coinbase transaction)的最小区块数据,以供矿工立刻开始计算。而完整的待计算区块的数据将在之后很快的时间发送给矿工。有的时候,幸运的矿工会在这(短暂的)几秒钟时间内,就基于这个最小的区块模版,计算出下一个有效的区块的结果。这个时候会发生什么?大多数矿工会选择将这个“空区块”广播到区块链网络,以获得该区块中的区块奖励。

在上面的场景中,当矿工计算出一个有效的“空区块”后,矿工虽然无法获得由于包含其他交易所带来的手续费奖励,但是矿工广播交易至少会获得确定性的由新区块带来的奖励。同样地,矿工还面临着其他矿工也计算出有效区块的压力,对手矿工可能会先于自己将另外一个有效的区块广播到网络中。因此,面对一个确定性的(但是可能较少)的奖励,以及另一个有极大不确定性的较多的奖励时,我想绝大多数矿工都会选择广播自己计算出的第一个区块的。

值得注意的是,这样的区块由于不会包含其他任何的交易信息,并且还在区块链中占用了一个区块的位置,会被一些社区成员认为是一种垃圾信息以及对比特币网络的攻击。针对这一点,有一篇发布于 LinkedIn 上的文章很好地讨论了这一点。文章中认为,尽管“空区块”不处理任何内存池中的等待交易,但它们并没有为区块链网络天价更多混乱,反而增强了所有先前区块的安全性,并重申了矿工们维护网络完整性的承诺。“空区块”实际上是对比特币巧妙激励结构的证明。在这个系统中,每个决定,即使是区块中没有交易,也都有助于网络的整体强度和韧性。空区块并不常见,通常不是攻击,也不是垃圾邮件,它们是矿工的战略决策,凸显了比特币架构中内嵌的韧性和远见。

参考链接

Fragments of two months

作者 DGideas
2023年6月19日 10:57

这两天发小儿来催稿,真惭愧,博客文章念叨了几周还没写出,几个月前逢人便吹的写《诸天讲》的文章也没有动笔。现在北京气温飙升,实在有些盛夏难挨的感觉了,文章恐怕一时半会儿更难写出。

三周一小病,三月一大病。去年十二月第一次感染新冠,自然是高烧,而且烧得半睡半糊涂时,满脑袋纠缠什么“框架”“系统”。今年三月甲流,又是高烧,在宿舍挨了三天。甲流后咳嗽了两个月。五月中旬肠炎发烧,反倒泄得治好了咳嗽。5 月 30 号把左脚大拇指搞骨折。六月初第二次感染新冠,不过症状极轻,第三天就恢复了卧推。

多病,就容易迷信,容易虚妄,但我还没有,因为这根本不是能够计算得了的。以前进山找庙,现在除非有极重要的古迹,也不爱进庙转悠,因为观察“信众”行止,觉得那里倒是我相、人相、众生相、寿者相执念更深的地方。

多病,还容易自感脆弱,感到孤独,希望被爱和爱人,这也是无可如何了。

不过感觉最强烈的则是恼火,觉得耽误了工作进展,唯识历史哲学不是要推出吗?在这之前,胡塞尔的《被动综合分析》不是要拿下吗?现在深感德里达的《胡塞尔哲学中的发生问题》和福柯的《康德人类学导论》,关切集中在同一个问题域,于是忽而又有了信心和激情,觉得过关这些,不过花几周功夫而已。

五月份读熊十力在 48、49 两年与徐复观、牟宗三等人的通信,看他抱怨生活条件之不足(不能每天吃鸡),抱怨黄艮庸对他招待不周、行事不靠谱。感到他修养实在很差,且又天真。他在一封信里说,读理学可以使人变化气质,惜乎他一直未能做到,也自感气质不佳,因此惭愧无奈。读到这封信,熊十力的形象一时生动起来,我不能不有些动心。我一向讨厌熊十力的文风:自命不凡、重复啰嗦,体用论更是苦心极力之作,毫无宽和中正之气。既然坚持即工夫即本体,工夫跌落至此,如何见道?他所谓新唯识论和体用论,要解决的问题不过是佛学的老问题:既然是空性,如何开物成务?于是有气论,有力动(force)等进路。自称接着熊十力造论的那位吴汝钧,写出厚厚一大本《纯粹力动现象学》,要解决的也是这个问题,而且,从他那牢骚满腹的《苦痛现象学》可以看出,其气质修养,与熊十力相差无几。

运动过度,不得休息,于是抵抗力差而多病。生活不入正轨,精神不得安顿,于是精力内耗而涣散。多病,容易加重我执,这是无可如何了,但是如何从病的否定性中转出一种绝大的力量,从苦闷中转出一种绝大的力量,横推而进入政教人伦(福柯意义上的éthique,而非当今文化保守主义意义上的),纵贯而进入古今历史,挺立起无我之我?“彼语寂灭者往而不反,徇生执有者物而不化,二者虽有间矣,以言乎失道则均焉。”(张载《正蒙·太和篇第一》)能体无,才有彻底性。但往而反之,建立起自己的回心之轴,锻造革命的、能动的历史主体,这是最难的。

前天读海德格尔《现象学之基本问题》到深夜,再次坚定了历史哲学的理论自信,第二日清晨,竟然梦到阳明,隔着一层纱帐,听他说话,看他写书札……

2023 年 6 月 19 日凌晨

Copilot 使用体验

作者 DGideas
2023年3月17日 02:09

我可能是全网最后一个体验 Copilot 的用户了。由于最新又在做一些好玩的小项目,而其中的很多基础逻辑都跟以前的项目很类似,因此摆在我面前的有两个选择:从以前的代码库中把相关代码抄下来,或者重新写一遍逻辑近似、但不完全相同的代码。

话说回来,虽然在不同的项目中要实现的逻辑是类似的(例如都要实现用户登陆的功能),但由于每个项目的具体应用场景存在区别,因此具体到每个项目中,相似功能的实现也存在较大差别,无法简单地复用先前的类似代码。我想到了 Copilot。

一张来自 Visual Studio Code 代码编辑器的屏幕截图,展示了用户在编写少数上下文的情况下,Copilot 插件能够根据用户上下文提供代码建议
Copilot 能够根据上下文自动提供代码建议,这是基于 OpenAI Codex 实现的

自从 Copilot 结束公众测试阶段、提供给所有开发者使用后,GitHub 对于 Copilot 的功能开始收费:除了特定开源项目维护者、学生以及教师之外,所有用户可以选择 10 美元/月或者 100 美元/年的两种不同订阅选择。这其中有趣的一点是,“开源项目维护者”的标准似乎是由 GitHub 通过一套未知的算法判断的。也就是说,有些用户进入 Copilot 的订阅页面之后,会收到“符合免费试用资格”的提示,从而可以免费使用 Copilot 12 个月的资格。当 12 个月过后,如果用户仍然是一名活跃的开源项目维护者,他/她仍可能有资格获得另外 12 个月的免费资格,以此类推。

而我显然不是上述人士。而不出意料,我点击进入 Copilot 的订阅页面后,也没有任何的免费试用提示。由于涉及到基础逻辑的开发并不是特别多、开发周期也不是特别长,我只订阅了 1 个月的 Copilot 用于体验,对 Copilot 有一些有趣的印象。

Copilot 使用体验

Copilot 能够实现一些简单、常用的逻辑建议。由于 Copilot 的训练数据主要是公开的代码仓库中的代码逻辑,所以对于常见的业务逻辑来说,Copilot 都能给出适当的代码补充建议。举个例子,对于后端应用程序来说,当一个用户成功登陆系统后,系统应当记录用户的登录会话(Session)数据。下次用户尝试继续进行操作时,需要传入对应的会话标识符(Identifier)或会话令牌(Token)以作鉴权。这中间可能出现的问题是,用户可能会传入一个过期或不正确的令牌。而当我写出一个名称为 def check_user_session_token( 的方法后,Copilot 能够根据实际情况生成下一行或者下一大段代码,其中通常就会包括一些对于上述场景的检查逻辑。

对于已有一些开发经验的程序员来说,在实际撰写代码之前,我们的脑海里就已经有关于如何正确撰写下一段代码的蓝图。在编写常见逻辑的场景下,Copilot 能够辅助程序员快速构造代码架构。而对于几乎没有经验的程序员来说,正如产品名字 “Copilot” 一样,Copilot 能够通过从公开代码仓库中学习到的知识来“指导”这些新手程序员撰写符合业务逻辑的代码。

但在我的使用体验中,Copilot 能做的所有的事情也仅仅是“辅助”程序编写。除了能生成一个“看起来正确”的程序架构以外,Copilot 没有考虑到一些常见的安全检查,也没有表现出理解复杂特定业务相关逻辑的能力。在我看来,虽然每个程序都有涉及到数据操作(CRUD)的部分,为计算机编写程序的乐趣在于对于每一个具体的问题,如何在不同条件中进行取舍,编写出能够快速、高质量解决问题的程序代码。人工智能程序学会根据代码库中的代码生成解决问题的“模版”,但对于特定具体业务的理解,以及如何在实际场景下通过代码逻辑来对问题进行思考和取舍,是我认为编写程序的乐趣所在。而对于这些细节的思考显然是目前像 Copilot 这样的工具无法完成的,也是人类程序员的价值所在。

人工智能辅助程序设计

Copilot 是基于 OpenAI Codex 构建的,这是基于 GPT-3 模型的一个变体。在撰写本文的前两天,OpenAI 刚刚发布了 GPT-4 模型。这几年最令人兴奋的事情就是在看似机器学习领域又走向了又一个瓶颈的时期,有几家行业领先的公司通过不断发布的改进模型向大家证明着机器能够实现的智能:从机器视觉、图片生成、对话、翻译,到通用自然语言任务处理,机器能够实现的本领越来越多。而更多的行业工作者愿意在日常的工作和生活中通过这些增强的人工智能模型来辅助他们的工作,比如代替一般目的的翻译工作,或者(像程序员一样)通过机器学习模型辅助撰写代码等。

GPT 系列模型已经向我们展示了机器学习模型能够达到的可能性。我相信总有一天,机器能够通过文本、推理等方式理解更复杂的问题,并且能根据机器对这些问题的理解,构造出更复杂的程序。这样的一天也许很快就会到来。而我正在思考的问题是,当这一天来到的时候,对于人类程序员来说,这个职业存在的价值在哪里?我们会变成给 AI 发送命令来操纵它们写程序的“操作员”,还是将更多时间解放出来,把注意力放在解决特定复杂问题的算法工程师,或者称
“问题解决者”?

这个世界对于技术的更迭速度远比我们想象中要快。当我们还在讨论用户发布内容的短视频平台是否已经代替以文字搜索为主要载体的站点(例如,Google)的时候,很多 ChatGPT 用户已经通过文字互动这一形式与人工智能生成的内容进行对话、学习了。就在几年前,计算机系的博士生论文还在争先恐后地在计算机视觉(CV)、自然语言处理(NLP)以及生成对抗网络(GAN)等领域进行研究时,更大、更通用的人工智能模型就像降维打击一样将这些特定领域用于一般目的的机器学习模型席卷到一丝都不剩:现在的通用语言模型在翻译任务上的表现比前几年最佳的翻译模型的得分还要高。因此保持一颗开放的心态,接纳变化,不断学习,才是重中之重。

WordPress + Nginx 客户端证书验证的坑

作者 DGideas
2022年11月27日 02:17

我始终在关注各种能够提升访客访问本站点体验以及安全性的优化措施。继去年底将博客接入 Cloudflare 后,在最近一段时间本站又将博客源服务器进行了扩容升级。借着本次升级,我们配置了 Nginx 中一项被称为客户端证书验证的安全措施,随之踩了不少坑,遂撰本文以分享,希望能帮助更多人。

tl;dr不要为 WordPress 站点开启源服务器上的客户端证书验证功能

客户端证书验证

互联网站的访问者依赖服务器提供的具备信任链的证书验证服务器的身份,并保证连接的安全性。但这种验证能力不止在服务器端可以提供证书以证明其身份,在 TLS(传输层安全)协议 1.2 版本的 RFC 文档的 7.4.6 小节中,客户端也能在请求时提供相应证书,以向服务器端证明访客的身份。这通常用于访问机密数据,或限制服务器字段的访客身份时使用。

Cloudflare 提供了请求源服务器时夹带 Cloudflare 客户端证书的选项,当源服务器中进行相应配置时,就可以做到仅允许来自 Cloudflare 的流量访问到源服务器。而其他请求到源服务器的未经验证的请求,则会被网络服务器直接拒绝,以提高安全性。

Apache 与 Nginx 网络服务器均提供了用于进行客户端证书验证的配置功能,可以参阅由 Cloudflare 整理的这篇文章

一些问题

配置好客户端证书验证后,我注意到当尝试在管理后台编辑 WordPress 文章或新创建文章,以及自定义网页主题时,访问经常会出现超时的情况(Cloudflare 524 错误),这表明 Cloudflare 无法在 100 秒(预设时间)内接受到来自源服务器的响应,从而导致错误。我尝试通过多种方法来诊断和定位问题,包括开启 Nginx 服务器的调试(debug)级别的日志,以及通过 WordPress 的执行日志进行诊断,但仍未能发现问题。

通过不断试错,我注意到当禁用网络服务器的客户端证书验证功能时,一切都恢复了正常:

...
    # ssl_verify_client on;
...

我上网搜寻了一些相关资料,猜测问题可能是由于 WordPress 在访问自定义主题编辑器或文章区块编辑器时,会通过类似内部请求的方式向本地网络服务器进行 HTTP 请求,而这些请求并未附带客户端证书,导致请求失败。这些仅仅是猜想,截止撰写本文时,我未能通过任何日志获得证据来支持这一点。

不过从观察到的现象来看,当我们临时禁用网络服务器上的客户端证书验证功能,成功加载上述页面后,将验证功能重新打开,上述页面在一定时间(看起来不超过 24 小时)内也能正常加载,所以我也一度怀疑问题可能与 wp-cron 定时任务有关,不过还没有仔细整理这一部分的逻辑。

弊端

我们在此部分更多地讨论与禁用源服务器网络服务器上的客户端证书验证会产生何弊端。首先对于公众可访问的站点来说,客户端证书验证功能通常在访客通过类似 Cloudflare 这样的代理服务器访问网站时比较有用。因为如果访客直接与源服务器建立链接,要求每位访客提供客户端证书是没有意义的。然而,如果攻击者得知源服务器的地址,通过直接向源服务器指定域名进行请求(如下例代码)的方式也是可以访问到对应站点的,这会造成潜在的安全问题:

curl https://DOMAIN.EXAMPLE --resolve 'DOMAIN.EXAMPLE:443:192.0.2.17'

因为这样一来,流量不会经过 Cloudflare 防火墙和 DDoS 防护的预先过滤,会造成潜在的安全问题。但如果源服务器地址不被泄漏,该问题不是十分严重。

由于我仍未完全了解问题的发生原因,所以如果哪位读者有更多信息可以分享,或者希望针对此主题进行更多讨论,欢迎通过博客信息页面中提到的联系方式联系我,不胜感激!

惠普的“打印即服务”——HP Instant Ink

作者 DGideas
2022年8月31日 23:37

来新加坡的这段时间,因为平常有打印需求,所以购买了一台惠普 HP DeskJet 2722e All‑in‑One Printer 打印机。这款在官网上标价 69 新加坡元(约合 50 美元)的打印机具备喷墨打印、扫描与无线打印等基本功能。但最令我感兴趣的是随附的名为“Instant Ink”的订阅服务,这款能够以订阅的方式获得邮寄墨盒的服务在网上能搜索到的中文简介和使用体验较少,遂撰写本文以记录。

打印痛点

像我一样的居家个人用户,平常的打印需求是比较少的。通常是偶尔才想起来使用打印机列印一些文字材料,或者一两张照片之类的。面向家用用途的打印机厂商深谙这一点,于是便面向家庭用户推出了许多价格低廉(低于 100 美元)的一体机款式。这些一体机兼具黑白与彩色打印、扫描等功能,价格低廉,随附小容量墨盒,深受家庭用户喜爱。

而“羊毛出在羊身上”的道理大家众所周知,售价较高的打印机的耗材价格相对便宜,而特别是家用打印机的耗材(墨盒)价格则比较昂贵。堪称“性价比之王”的小米最近几年推出的米家喷墨打印一体机改款自惠普某机型,虽然墨盒较惠普墨盒便宜,但几十元人民币的墨盒价格仍然体现了耗材致富的理念。

另外对于向我一样的家庭用户的痛点是,有时候经常一两个月不使用打印机,而安装在打印机上的墨盒容易出现干枯的现象,导致打印效果下降。这时不得不将之前的墨盒丢弃掉,然后购买新的墨盒。上述两点原因造成了家用打印机“机器便宜,耗材昂贵”的现状。

“打印即服务”

我在想,提出这个概念的设计师真是深得订阅制的精髓。在这个万物都可以被按月订阅付费的时代,我们为何不将打印机的打印功能也作为一个订阅使用的功能呢?惠普在一些地域推出的 HP Instant Ink 订阅服务将打印机的打印服务作为包月套餐提供给所有用户。用户可以按照每月打印量的多少购买相应的打印套餐,惠普会根据打印机实时上报的墨盒用量在墨盒即将用尽时邮寄新的墨盒给用户。特制大容量墨盒根据用户的打印页数(而非墨盒消耗量)为用户提供打印服务。

一张定价简介宣传图片,其中最流行的套餐为每月打印量 100 页,按月收费 5.99 新加坡元
来自 HP Instant Ink 官方网站的定价简介,价格单位为新加坡元(SGD)

用户使用 Instant Ink 订阅服务有什么好处呢?比起传统的用户从网站上购买墨盒来说,使用 Instant Ink 根据打印量订阅的方式能为用户节省一半的打印成本。并且由于该计划是根据用户的打印量来计算套餐的,所以特别适合经常进行彩色打印或每月充分利用套餐页数的用户。如果每月并不常使用打印机(打印页数比套餐用量要小很多),则或许传统的购买墨盒方式会更加划算。

特别地,这项在美国、加拿大、澳大利亚、欧洲部分地区、新加坡、台湾、香港等地区提供的服务为新购买打印机的用户提供了长达三个月 / 1500 页的 Instant Ink 服务使用期,用户在试用期即可获得来自惠普的邮寄 Instant Ink 墨盒,并最多拥有 1500 页的免费打印额度。对我这样的短期内有很大打印需求从而购买打印机的用户非常友好,因为这意味着我在这三个月内的打印成本几乎为零。唯一需要注意的是,激活这项计划后墨盒需要等待约 10 个工作日才会寄送到你填写的地址,这期间你需要使用打印机随附的普通墨盒。

一张包含两个未拆封的惠普 HP 打印机墨盒(67/305)、一份已预付邮资的信封,以及一份宣传材料的图片
我于 2022 年收到的惠普 HP Instant Ink Welcome Kit 墨盒套装:包含黑色和三色墨盒、宣传材料以及用于邮寄回收空墨盒的“邮资已付”信封

惠普会根据打印机墨盒自动上报的用量在墨盒即将用尽之前将替换墨盒邮寄到提供的住址。随附的预付费邮票鼓励用户将用尽的旧墨盒回收给惠普公司(虽然我个人认为墨盒的成本并不是很高)。虽然相比使用第三方墨盒或者连续供墨系统的价格要高,但是按打印量订阅制收费模糊了用户对于按照墨盒更换成本计价的概念,按需寄送的墨盒对一般家用用户来说更加友好,不乏为一个很好的尝试。

一些尾注

在 2020 年之前,惠普在一些地域的 Instant Ink 墨水订阅计划中是包含每月免费打印 10 页的免费层级的。不过这个政策在 2020 年被取消了

和平西桥的只言片语

作者 DGideas
2022年7月17日 23:29
一张拍摄和平西桥的照片
和平西桥,摄于 2021 年冬季,这座三环路上的桥梁位于北京市朝阳区

去年的晚些时候,因为先前租约到期,我和 Minda 搬到了位于和平街附近的老小区,在这里一同度过了几个月的时间。

于我而言,这是一段极不平凡、令我深深难忘的生活经历,它充满了交错、融合、流动以及生活的琐碎。我想,我只能适合以流水帐的形式来记录它,来试图抓住这些稍纵即逝的瞬间(以及三环路上源源不断的车流)。

美食荒漠

要我说,和平西桥一定是美食荒漠的正中心——我们选择住在这里的唯一原因,就是这里离我们两人的工作单位都很近。但平常想在住处周边吃点东西的时候,我们的选择就只剩下了:要么步行十分钟,到我们附近最近的肯德基餐厅;要么就乘坐公共交通工具,去望京、去簋街,或者去遥远的海淀寻觅美食。

驻足观看小区里的《致全市老年朋友的一封信》,我所居住的小区乐龄人口数量占比很高,摄于 2022 年,北京市朝阳区

其实完全可以理解这里成为“美食荒漠”的原因:和平街附近都是些老小区,这些超过 30 年历史的住宅区中居住的乐龄人口的比例都很高,这一代人都习惯于在家烹饪美食,而非像不太会做饭的我一样,习惯于每天以订购外卖为生。在这里,你可以见到街边的主食烙饼摊,拜访小区里的迷你蔬菜市场,以及在每个饭点闻到从楼上楼下飘来的饭香味。

我和 Minda 经常去寻找各种美食。到周末的时候,我们喜欢乘坐 117 路公交车前往簋街,在那里大吃一顿之后,沿着雍和宫一路往北走回来。在这座大城市,我们经常习惯遇到一家好吃的店之后,在未来的一段时间内无数次地拜访这同一家店,直到吃个够为止。在忙碌的生活中,为了过足嘴瘾而进行一些小小的投资(时间和金钱)是值得的。

搬到和平西桥之后,Minda 买了一个多用锅。我们经常用这个迷你多用锅煮一些饺子、汤圆或者面,也有几次用它来炸臭豆腐、涮火锅。在弥漫着的北京冬夜,我们把各种食材和汤底准备好,开上大火,就着麻酱蘸料吃着刚出锅的涮羊肉,漫长而又留恋。

扭着屁股的大公鸡

去年 12 月末的一天,在前往地坛公园附近咖啡馆的路上,我的手机收到了一条摇号提醒的短信,通知我小客车普通指标(汽油车)摇号中签了。

登陆相关网站查询过后,我终于确认这条消息是真的。稍微惊喜之后,我在微信朋友圈里发送了这张截图。而后在不到一个小时的时间内,我收到了不下十条消息,询问我是否愿意借用号牌指标使用——北京小客车普通指标的摇号难度可见一斑。第二天,我们就前往了 4S 店选购汽车。销售对我说,他摇了十年的小客车普通指标,但是包括他和他身边的人在内,都没有摇中过。

这不久之后,我们便落实了车辆购置计划。我拥有了人生中的第一辆小客车,京牌,Volkswagen Santana。出于我偶尔愤怒又不羁的驾驶习惯,Minda 给这辆车取了一个响亮的名字:“扭着屁股的大公鸡”。我欣然接受了这个命名建议,并且将车辆蓝牙也改成了相同的名字。很有喜剧效果。

我与“扭着屁股的大公鸡”,一辆 Volkswagen Santana PA MP19 1.5L MT Trendline C6 轿车,拍摄于 2021 年末

作为一辆合格的代步工具,这辆手动挡轿车坚实而又经典耐用。提车第一天,我开着这辆 Santana 去雁栖湖跑了个来回。当我们发现这辆小轿车默认配置只有收音机,副驾驶座遮光板连梳妆镜都没有的时候,坐在副驾驶位的 Minda 突然发现“我们就像是坐在了车轴上”。疾驰的高速公路上,手握方向盘的我回复说:“还好有电动车窗”。

结婚照

在和平西桥的这段时间,我和 Minda 结婚了。这是继我高考选择专业之后,人生的又一次重大抉择。老实说,直到不久之前我还觉得,我会是那种在 40 岁之后才会结婚的人之一——人生充满了多少偶然和变化。

其实做出这个决定的理由很简单,在和平西桥的这段时间,我和 Minda 拥有了一段非常奇妙且有趣的生活体验。当被问起是否拥有领取结婚证的打算时,我确实思考这个问题很久,然后才回复 Minda 说,我们领证吧。现在来看,婚后生活除了胖了,并没有什么大不了。

作为一个常年穿着 T 恤的技术工作者,拍结婚照的之前几天,我才在优衣库买了一件白色衬衫和黑色裤子,就这样穿到了拍摄结婚证件照的影棚里。领证当天,我又穿着这套衣服到北京海洋馆和 Minda 一起拍摄了一组照片,永远地留住了这一天。

在和平西桥的最后一段时间,我去附近的安贞医院口腔科拔除了一颗智齿。紧接着,因为医院旁边的小区安贞里出现新冠病毒疫情,整个朝阳区的形势变得紧张起来,接踵而来的是附近的封控区和不断的核酸检测。因为房子快要到期,加上担心被封控的风险,我们于是提前从和平西桥搬出来,前往下一个栖身之所。

揭幕无图型设计的神话

作者 DGideas
2022年5月27日 00:59

本文翻译自《Debunking the Myth of Going Schemaless》一文,原作者为 John Page,他是一名苏格兰人,现在 MongoDB 担任工程师;本篇文章的英文原文版本由 MongoDB 赞助

译者按

在面向有状态(Stateful)应用程序开发的过程中,通常会使用数据库对程序的状态进行持久化存储。与关系型数据库管理系统(RDBMS)使用前需要定义数据库图型(Schema)不同,诸如 MongoDB 在内的一系列数据库是无图型(Schemaless)的。开发者基于无图型数据库设计时,通常会有一些使用误区。本文原文作者从一个概括的角度讨论了这些误区,并提供进行无图型设计时的最佳用例。

前言

各地区的开发者都在拥抱文档型数据库。然而在大多数情况下,是出于错误的缘由。就拿现在对于无图型(schemaless)的炒作来说——几乎太容易将任意构造的 JSON 或者 XML 存入文档型数据库中。但是当你希望去高效地筛选、修改与读取数据时,你可能会让自己失望。

尽管文档型数据库允许你存储数据而无需定义它是什么样的,但当你希望去做比简单地依据键值(keys)来读取数据以外更多的操作时,这些数据的形状(shape)就显得尤为重要。

如果你忽略了图型(schema)设计,而只是简单地将已经存在的文档存储起来,你或许需要的不是一个文档型数据库,而是一个简单的键-值对存储。

文档图型设计 vs 关系型设计

关系型数据库设计为用户提供一个预定义的、持久化且安全的,与数据进行交互的方式。你用来组织数据的方式与这些数据被实际使用的方式无关。这并不对。再说,谁能够预测当不同的需求出现时,不同的用户该用什么样的方式访问已经存在的数据副本呢?

因此,通常的图型设计是基于关系(relationships)已经由数据本身定义,而非数据如何被最终使用——而设计的。这使得数据建模(data modeling)更具可预测性。为模型提供同样的数据集,任何创造图型的、合格的架构师都能够得出同样的结果。但可预测性通常也意味着缺乏灵活性。

文档型设计起源于 19 世纪 60 年代,那个年代同时产生的是面向对象编程(object-oriented programming,OOP)。但经过几十年的发展,计算机才发展到可以欣赏文档型模型的灵活性的地步。

在文档型数据库中,图型的设计是基于数据如何被访问的,而非数据本身。开发者是最能够了解数据是如何被他/她们的应用程序访问的人了。在你并没有想好用户将会以何种方式访问数据的时候,是无法去优化图型的。当然,你也可以在没有决定好用户如何访问数据的情况下就存储这些数据。文档模型允许这样的灵活性。但在这之后,你能够,并且应该去优化这些图型。

更好图型的优势

初学者因为可以直接存储数据而无需预先定义它们而爱上文档型数据库。在不需要任何额外训练的情况下,几乎任何人都可以在不了解文档图型设计的前提下构建可用的应用程序。

然而,在有限服务器硬件条件下如何能够实现更多功能时,了解如何正确地去设计图型就变得关键。在当今云服务提供商按量付费的场景下,就更为重要。文档图型能够通过减少计算、I/O 操作以及用户之间的争用来提高在同等硬件上的性能。

认为文档型数据库缺乏前期强制性图型要求的想法是完全不正确的。

文档型数据库能够像关系型数据库一样,强制要求提供图型。无图型化的设计在文档型数据库中也许很常见,但并不是它们的代名词。现代的文档型数据库也有明确的数据类型、丰富的数据操纵语言(data manipulation language,DML),复杂的基于 B 树的索引、ACID 事务以及数据库内的聚合计算等功能。文档型数据库也与 Postgres、MySQL 以及其他的关系型数据库共享相同的存储引擎基础结构。

而文档型数据库与关系型数据库的真正区别在于能够在原子存储单元中共同定位相关数据,因此单条记录连续地存储在内存或硬盘中,而不是被分解成行并独立存储。

简单地说,在文档型数据库中,一个属性的多个值可以存储于单条记录中。如果一个人有多个电话号码,你就不必使用一张单独的表来存放它们。你也不需要为每一个号码单独定义字段。而可以简单地拥有一个电话号码数组或者号码对象。这就有点像在存储层中,将一张表中的一些列嵌入另一张表一样。

{
  name: "john",
  phones: [ { type: "cell", number: 4475566218},
            { type: "cell", number: 4479927716},
            { type: "voip", number: 17035551234}]
}

多年来,这种将数据放在一起以减少 I/O 的想法一直是数据库实现的基础原则。在关系型数据库中,数据库管理员使用“索引组织存储”的概念来共同定位要预期同时访问的行。但这是事后进行的。而且重要的是,它不允许将来自不同表的数据共同定位以减少读取数据的成本。

文档型是如何减少计算与 I/O 的

当对数据库做查询时,实际上是在过滤这些数据的一个子集,然后直接以原始形式返回它们,或者以某种形式进行计算汇总。如果你希望得到的数据在单个行,且对应查询从单个表来获取它的话,那么关系型数据库或者列存储可能会更有效。

另一方面,当你需要将不同的表连接在一起以执行查询时,查询多个索引以及合并结果的额外计算工作会抵消该优势。你需要访问的每一个额外的列,都会增加多余的 I/O 操作。I/O 操作很慢。加速它的成本很高。

在图型设计良好的文档型数据库中,由于单个文档包含了整个一条业务记录——需要读取和过滤的所有数据将会通过单个 I/O 操作来最小化所需的计算量。这样能够使查询以及读取数据快得多。

对于无法放入单条记录中的任何内容,你将需要多种不同的记录类型,并且可能需要查询相关组。幸运的是,成熟的文档型数据库也提供了一种或多种连接选项,尽管它们应当被谨慎使用。

文档型如何减少争用

数据库必须允许多个用户或进程编辑同一条记录,而不会覆盖彼此的更改。我们不能出现这样的情况,即两个用户正在修改相同的数据,而最后一个写入的用户会覆盖另一个用户的更改。如果你处理过 git 的冲突,你应该懂得理清这些更改的重要性。与尝试合并冲突相比,一致性的数据需要更好的方式来处理。

在数据库中的解决方式是通过锁(locking),这需要以下内容:

  1. 找到你明确希望修改的一条记录
  2. 为其加锁,使其他用户不能更改它
  3. 验证在查找与加锁之间该记录未发生变更
  4. 执行修改
  5. 为其解锁

对每个更改都执行此操作,能够序列化更改并保持数据正确性。例如,如果两个进程同时在库存中查找最后一个项目并修改,以将其放入购物车,那么只有一个进程应该成功。在上述情况下,对单个记录的所有修改都发生在单个锁中,并且该锁只需要在应用检查和更改所需的时间内持续存在,这通常是几微秒。

在文档型数据库中,对单条记录的编辑时,只有一篇文档需要被更新,这是一种非常低的争用操作。应用程序可以同时维持大量同时使用的用户。

这种短暂、低争用的更改操作需要一种能够完全在服务器端执行相关更新的丰富查询语言。如果你被迫去查询记录,在客户端进行更改,然后将它们发送回服务器,这就意味着分别创建和释放了一个时间很长的锁,或者承担被覆盖的风险。来自客户端的两次数据库调用之间的时间是产生争用的地方,有时这个问题会变得很严重。

通过发送一条指令去将某一个字段更新成特定值的这种操作是最简单不过的了。然而文档型数据库需要支持远比上述操作逻辑复杂得多的修改。想象一下你正在为一个分数排名表建模,将前五个分数存储为单个文档以提高检索速度。你可能有以下内容:

{ 
 game: "super_kong",
 highscores: [{ name: "joe", score: 118231},
              { name: "amy", score: 75651},
              { name: "chloe", score: 62352},
              { name: "bryan", score: 54524}, 
              { name: "dwayne", score: 41654}]
}

你需要做的事情只不过是向服务器发送一条指令,告诉它“如果分数表里包含一个小于 X 的分数,那么在一条指令中,把 X 加入到分数表中,按照分数排序分数表,然后只保留分数表的最高五个分数。”MongoDB 支持这样的富查询功能。

所以,当需要同时更新多条零散项以更新记录时,会发生什么呢?这对关系型数据库来说太常见了,对记录的编辑操作意味着不止一行需要被更改。

解决方案是 ACID 事务。ACID 事务将要更改的每一个项目上锁。在整个事务被提交时将它们全部解锁。这通常是在多次调用服务器之后,意味着文档会被实际锁定更长的时间,这包括网络和客户端时间。这是在高吞吐量的关系型数据库用例中导致性能和争用问题的典型原因。

在相同场景下,你可以使用文档型数据库来收尾。不过设计良好的文档图型能够避免这种问题的发生。并且如果你确实必须编辑多个文档,像 MongoDB 这样的文档型数据库也提供了相当于 ACID 事务里的 BEGIN TRANSACTION COMMIT 语法。由于这种方式也会引发像关系型数据库里的争用问题,所以最好的方法是创建一个图型,来避免更改多个文档,或者提供一个能够完成编辑但不会引发数据库争用的方式。

文档图型设计中的权衡

对于图型设计来说,没有最完美的答案。文档模型假设比起写入来说,有更多的读取。这通常会多很多。通过一次更新刻意写入很多数据是对于优化读取速度来说的一个很好的方法。除非系统只是简单地记录或者审计数据,这样可以安全地假设写入的每一位数据将会读取一遍或者可能多于一遍。

在文档型设计中,可以对域表(domain tables)进行非规范化,或者拷贝同一份数据的多个副本。如果你有一个很少被更改的国家/地区列表,那么存储国家/地区名称而非域表的键,并且能够处理国家/地区名称的更改的话,就会是一种可以被接受的权衡。

对于其他类型的记录来说,很可能存在数据的最终副本。比如拿一条客户记录来说,每个客户通常对应一个文档。但为了提高阅读速度,某些字段可能会在写入时复制到其他文档中。

举个例子,你也许会想将客户的名称、地址以及唯一识别符复制到它们的每张收据记录中。如果客户修改了他/她的姓名或地址,你可以修改这些发票记录。

文档型数据库同样鼓励你思考幂等性、可重复的操作以及在发生错误时,始终前滚(roll forward)而非回退。想象一下我们需要给每个人加薪 10%。我们可以将其包装到一个事务中,但这会锁定我们的员工表,取决于这个操作需要多长时间。有可能是几个小时。

变通的办法是,我们可以要求数据库仅对于每个人的薪水进行增加,同时作为更改的一部分,添加一个名为 got_payraise 的新的临时字段。如果在执行部分时失败,那么可以再试一次。但是此时,就只应该将那些没有 got_payraise 字段的文档进行加薪,这样就不会有人被加薪两次,重复这样的操作直到所有人都被加薪。这时,我们就可以删除所有 got_payraise 字段了。这样的模型避免了几乎所有的争用操作,并且没有不对某人加薪,或者不知道哪些人已经被加薪过的问题。这就是图型设计真正灵活的,有帮助的部分。

步入实践

不像关系型数据库,文档型数据库要求设计者或开发者思考有关正确性、争用和性能的问题。但是话说回来,它给了你更好的性能和操纵。不像关系型世界中一个人为数据建模,其他人在其上构建程序,而后数据库管理员再尝试将它优化一样,文档型数据库将开发者置于创造更好的数据库的前沿和中心。从关系型数据库进行迁移时,这可能需要组织或者流程上的调整。

当你理解这些基础概念后——你心里就应该知道好的图型以及坏的图型都是什么样的了——然后你就可以从多种选项中进行选择,并且懂得什么会发生,而什么不会发生。在教授这些课程多年后,我经常听到有开发人员说他们不知道可以通过文档型数据库中的图型设计来完成这么多事情。

❌
❌