About Joway

工程师,新加坡字节跳动,喜欢摄影、旅行。

The RSS's url is : https://blog.joway.io/index.xml

Please copy to your reader or subscribe it with :

Preview of RSS feed of Joway

欧游散记 —— 特摩索斯古城

2024-04-07 08:00:00

特摩索斯(Termessos)位于土耳其南部城市安塔利亚的北面大约 30 公里,是一座起源神秘,消亡也神秘的深山古城。它第一次被历史提到是在公元前 333 年亚历山大大帝包围这座城市时,但即便是征服了希腊世界的亚历山大大帝也并未能成功征服特摩索斯。 特摩索斯建于海拔 1000 多米的山口位置,由于特殊的地理位置,加上完善的水利设施,所以即便被包围也只需要一小只军队便可守卫,这帮助这座城市在那个群雄逐鹿的希腊化时期和罗马帝国时期得以一直以独立城邦的状态存活下来。一直到公元 5 世纪的一场地震摧毁了这座城市的蓄水库,导致居民不得已陆续搬迁,最终荒废并被人们所遗忘。一直到 19 世纪,欧洲的探险家才重新发现了这座城市,但即便到今天,特摩索斯一直是一个非常冷门的旅行目的地。 我知道特摩索斯,是机缘巧合在 YouTube 搜索安塔利亚的视频时,偶然间看到了一个特摩索斯徒步视频,即便画质极其粗糙,依然还是被这座山顶古城给震撼到了。更因此将安塔利亚作为了土耳其旅行的其中一站。 到达安塔利亚,我们在 Airbnb 上约了一个本地向导 Onder 带我们开车到达北部的居呂克山-特摩索斯国家公园,从国家公园徒步前往特摩索斯所在的山顶。Onder 的本职工作是老师,但十分热爱特摩索斯甚至他的硕士论文写的课题便是特摩索斯,所以业余时间也通过做特摩索斯的向导赚点外快。 虽然特摩索斯的城市部分主要集中在半山腰和山顶,但是山底相当于这座城市的郊区,和现代城市的郊区一样,往往是平民的生活聚居地。 首先看到的是城市的宗教区域,哈德良庙,但如今只剩下了一扇门孤零零地在山脚下伫立,既神圣又凄凉: 在哈德良庙的另一边,是居民的墓地区。特摩索斯的墓十分有特色,都是以露天石棺的形式“展览”在地表之上。石棺的雕刻也别有一番讲究。 普通平民的石棺是以两个太阳加中间方块的形式,太阳是特摩索斯的标志,这里也被称之为太阳城: 而士兵的石棺会在太阳上增加武器的标识,寓意太阳的捍卫者: 无论是平民还是士兵的石棺都略显单调和同质,特别是人死后,棺材还会被摆在地表被长久展示,这导致石棺本身变成了一个人生命价值的化身。如果没有钱请不起好的雕刻工匠,就靠战斗获得荣誉来装点自己的石棺。如果有钱,就极尽工匠精湛的工艺让自己的石棺变成华丽的艺术品: 又或者是更为有权势的领袖,可以直接在岩壁开凿更为壮阔的仰望式墓藏: 考虑到两千年后,我们还依然在为他们的石棺赞叹,当初他们的「虚荣」如今也被岁月洗涤成了「实荣」。 沿着山路,陆陆续续会走过众多城墙和古罗马式引水渠,以及如今已不知为何物的废墟: 在半山腰,还能看到一个保存地非常好的体育馆遗址: 以及曾经的祭坛: 接着便来到了特摩索斯城市最最核心的基础设施 —— 蓄水库: 由于地震已经毁坏了蓄水库很大一部分,所以今天的蓄水库为了维持稳定,能够看到有很多人工固定的痕迹,但是能够两千年前在一个山顶开凿一个如此巨大的地下空间还是非常令人震撼的。 大约花了一小时的路程,便可抵达特摩索斯的山顶,在一个转身间,看到了我在土耳其见过的最震撼的一幕: 特摩索斯在人类历史上,只是一个非常短暂,也没有名气,更没有对任何历史节点产生重要影响的小城市。特摩索斯的统治者也并非历史上赫赫有名的王侯将相,甚至这座城市更像是纯粹的自治军事城邦而非君主制的王国。但是如此小的城市居然会为了其居民建立如此恢弘的一个剧场。更何况这里不靠近雅典也不靠近罗马,地处希腊世界的边陲。 站在特摩索斯的山顶上,我眼望着希腊文明的「边际产物」,如同看到了一个外星文明一般陌生。从山脚下看到的自我价值实现和山顶上对自然与人类文明极致的审美表达。特摩索斯的居民在群雄逐鹿中维持了独立,依靠工程学知识建立起了少见的防卫居住一体的水利设施,甚至还在这种山城里建立起了不输其他同等规模但地处丰饶平地的希腊城邦的剧院。 我在之后的土耳其境内的希腊化古城邦旅途中,也遇到了更多比特摩索斯大得多,恢弘得多的城邦,但是特摩索斯一直是最特别的那个,也是我记忆最深的一个。以特摩索斯作为我在古希腊-罗马世界的游历起点,很难想到比这更好的开始了。

重新思考 Go:Channel 不是「消息队列」

2024-03-31 08:00:00

重新思考 Go 系列:这个系列希望结合工作中在 Go 编程与性能优化中遇到过的问题,探讨 Go 在语言哲学、底层实现和现实需求三者之间关系与矛盾。 Go 语言是一门为实现 CSP 并发模型而设计的语言,这也是它区别于其他语言最大的特色。而为了实现这一点,Go 在语法上就内置了 chan 的数据结构来作为不同协程间通信的载体。 Go 的 channel 提供 input 和 output 两种操作语法。input 一个已经 full 的 channel ,或是 output 一个 empty 的 channel 都会引发整个协程的阻塞。这个协程阻塞性能代价很低,也是协程让渡执行权的主要方法。 ch := make(chan int, 1024) // input ch <- 1 // output <-ch 然而 channel 的实现恰好和进程内消息队列的大部分需求是吻合的,所以这个结构时常被用来作为生产者消费者模型的实现,甚至还作为 channel 的主流应用场景而推广。 但事实上,如果真的把该数据结构用来作为系统内核心链路的生产消费者模型底层实现,一不留神就会遇到雪崩级别的问题,且这些问题都不是简单的代码修改便能解决的。 Input 失败导致阻塞 当 channel 满的时候,<- 操作会导致整个 goroutine 阻塞。显然这并不总是编程者希望的,所以 Go 提供了 select case 的方法来判断 <- 是否成功: select { case ch <- 1: default: // input failed } 但问题是,当 channel input 失败时,编程者还能怎么做?除非队列的消息是可以被丢弃的,否则我们可能只再去创建一个类似 queue 的结构,将这部分消息缓存下来。但是这个 queue 的结构可能又要和这个 ch 本身的队列顺序处理好并发关系。

重新思考 Go:Slice 只是「操作视图」

2024-03-30 08:00:00

重新思考 Go 系列:这个系列希望结合工作中在 Go 编程与性能优化中遇到过的问题,探讨 Go 在语言哲学、底层实现和现实需求三者之间关系与矛盾。 Go 在语法级别上提供了 Slice 类型作为对底层内存的一个「操作视图」: var sh []any // ==> internal struct of []any type SliceHeader struct { Data uintptr Len int Cap int } 编程者可以使用一些近似 Python 的语法来表达对底层内存边界的控制: var buf = make([]byte, 1000) tmp := buf[:100] // {Len=100, Cap=1000} tmp = buf[100:] // {Len=900, Cap=900} tmp = buf[100:200:200] // {Len=100, Cap=100} 虽然 Slice 的语法看似简单,但编程者需要时刻记住一点就是 Slice 只是一个对底层内存的「操作视图」,而非底层「内存表示」,Slice 的各种语法本身并不改变底层内存。绝大部分 Slice 有关的编程陷阱根源就在于两者的差异。 Slice 陷阱:持有内存被「放大」 以最简单的从连接中读取一段数据为例,由于我们事先并不知道将会读取到多少数据,所以会预先创建 1024 字节的 buffer ,然而如果此时我们只读取到了 n bytes, n 远小于 1024,并返回了一个 len=n 的 slice,此时这个 slice 的真实内存大小依然是 1024。

那一天,我决定踏出一步

2022-05-10 08:00:00

四月与五月之交,我完成了人生中迄今为止最惊心动魄的一次冒险。与通常的冒险经历不同的是,一般的冒险人们总愿意在往后的岁月里反复回忆甚至加工,但我的这次冒险我希望此生永远的忘记,但我又明白,之所以我如此迫切地想要忘记,是因为这段经历将会不可逆地影响我一生。 过去的那个少年已经被这场冒险所杀死,我只是作为一个旁观者,在叙述一个已经去世了的人在那几天的经历。 这场冒险的源头还要从 3 月开始说起。 3 月初,我在北京出差,在返回上海的前两天,当时因为北京有人在乌克兰大使馆门口献花,导致三里屯的使馆区莫名其妙被政府封了。我和朋友那天晚上临时起意,打算去现场看看,于是就绕着乌克兰大使馆走出了一个方形的圈。这件事情与后面发生的事情并没有什么直接的联系,但它确是我在中国境内拥有的最后一次自由行走的经历,而我在三里屯画下的这个圈,也成为了后面发生的事情的一个隐喻。 2022 年 3 月 4 日,我从北京回到上海,而这一天北京办公室却检测出了一例阳性,导致我回到上海后被判定为所谓的「高危筛查人员」,于是就开始了原定于 14 天的居家隔离。然而从 3 月 16 号开始,整个打浦桥街道开始出现了非常多核酸异常的情况,我小区在内的相当一部分小区变成了所谓的封闭管理。每天都需要做一次核酸,但是小区里的居民此时并不知道问题的严重性,甚至我怀疑连居委会都不见得知道真实的核酸异常数据,所以在做核酸时,大家有说有笑,许多老头老太甚至在排队时都不戴口罩。 当时所有人的预期是,所谓的 2+12 封闭管理就是只需要小区被封闭两天而已。这对于年轻人来说,可能平常出小区的频率也就两天一次,所以几乎没有任何影响。并且此时我们依然可以在小区内自由活动,去门口取外卖。 3 月 18 号,这是原定小区解除封闭管理的一天,而我个人也早已满足了 14 天的居家隔离要求。那天中午,我兴冲冲地跑下楼,想要去商场里面吃一顿好的,结果发现小区并没有如期的解封,反而是又贴了一份继续 +2 的通知。此时我的愤怒在于它打破了我们原先的预期,导致原先的欢喜落空,但我依然相信只要再坚持两天就能解封。 3 月 20 号的时候,我楼道门突然在毫无事先通知的情况下被一道铁链锁住。试图去推开一道推不开的铁门,这种屈辱感和无力感永生难忘。 20 日晚上等了 2 个小时,才吃到已经在外面冻冰了的鳗鱼饭,21 日中午又等了 2 个小时拿不到外卖然后吃的泡面的时候,而当天晚上彻底外卖就没送过来了。那时候才第一次知道原来饿久了,眼睛会花手会抖。不知道明天会发生什么,但当天晚上我就把恒生指数的基金清仓了。 23 日已经彻底放弃了外卖这件事情,中午八宝粥,晚上吃泡面加饼干。接下来的每一天都接近于此。点外卖几乎只能靠金钱加上运气,偶尔会有一些商贩“违规”在外卖平台上上线一些熟食之类的东西,那时候偶尔才能够有一点加餐。甚至有一次还抢到了能够让我吃上两天的两桶麦当劳全家桶。 再接着就是大家所熟知的 4 月开始上海进入事实上的全面封城状态。 我所在的黄浦区,在整个上海属于重灾区,而我的小区又是黄浦区的重灾区,我所在的楼栋又属于小区里的重灾区,先于上海绝大部分小区率先封闭管理。上海发布每过几天通报一次我楼栋有了新的阳性。截止我离开的时候,楼栋里阳性户数至少也有 30%,悲观估计可能多达 50%。 4 月 18 号的时候,我明白自己不可能继续这种生活,开始规划逃离上海的行程,于是给居委打了电话咨询了开出门证的要求,让我意外的是,虽然我的楼栋形势严峻,但是给我的要求却还算合理(当然事实证明我天真了): 楼栋 7 天无阳性 48 小时核酸阴性 全程闭环车运输 前序航班到达 其中其他几项都可以通过花钱解决,唯独所在楼栋七天无阳性这一项对我来说充满了不确定性。对于在上海的绝大部分人来说,这个要求确实并不过分,也很容易达到。但对于我这栋重灾楼来说,就需要时刻算着一个 7 天的窗口期,到了窗口期立马走。 我们楼上一个阳性是 4 月 14 号出现的,所以下一个 7 天窗口期是 4 月 22 号,但是好巧不巧 21 号又出了阳性,所以需要等到 4 月 29 号。

True Story

2022-04-28 08:00:00

2015 年 9 月 2 日,一位名叫艾兰的叙利亚难民儿童尸体在土耳其的海滩上被发现,并被拍下了一张影响深远的照片。 如果你在网络上尤其是中文网络上查阅这张照片的新闻时,会发现有相当一部分人在拿出各种“证据”试图证明它是摆拍的。 这张照片之所以牵动人心是因为它叙述了一个真实存在的现象是,叙利亚儿童正在因为逃难而失去生命。这个现象是真实存在的,你在这个海滩上站到深夜就能看到这个客观事实,但是具体到这个照片与这个事实是否是百分百匹配的,确实有很多可被质疑的空间,例如这个儿童有可能来自伊拉克,例如摄影师有可能把他换了一个姿势来拍摄。但是这些真的重要吗?或许对于历史学家来说,的确重要,但是对于一个普通民众来说,或许并没有那么重要,至少在此时此刻有比质疑更加重要的事情。 什么是事实,什么是真实?真实并不代表每一件事情的细节都是毫厘不差的事实,这就好比父母口中关于孩子的童年故事多少会有些添油加醋,锦上添花的成分在,但这并不能否定这些故事是真实的。 相反,由事实组成的故事也不见得一定是真实的故事。在叙利亚战争期间,一定找得到吃的好睡得好的地区,但如果一个驻叙利亚记者去这些地区报道来反映叙利亚战争期间人民的生活,这一定不是真实的报道。 人类的故事要能够被传播,或多或少总会被掺入一些夸大的成分,即便最开始的当事人只是客观陈述,也防不住他人在传播的过程中进行二次创造,最后舆论市场会自发地选择出一个兼顾了真实与传染力的版本成为我们今天耳熟能详的故事。 对于那些想要左右人民情感,篡改人民记忆的人来说,事实是他们攻击一切故事的强大武器。民众无法凭借一己之力去探寻真正的事实,只要少数人掌握了调查事实的权利,就掌握了提供唯一合法叙事的权利。他们用事实消灭一切他们所不愿意看到的故事,让这个世界只留下那些由他们控制的事实所构建出来的官方叙事。在这个叙事里,既没有人民的情感,也没有人民的记忆,只有一种强大到淹没所有人的意志。 这套叙事方式在过去被用在了欧洲难民危机中,用在了香港运动中,用在了美国大选中,用在了上海疫情中。它的强大之处在于,身处其中的人会发现自己无法与这个叙事机器所搏斗,因为它所描述的确实是事实,确实是我们所相信的故事的弱点,确实是我们作为一个平凡个体的缺陷。我们无法让自己,让自己所团结的群体,永远地做到客观,做到公正,做到实事求是,因为我们是人而非机器,而那些试图否定我们的人,只要做到一次客观,做到一次公正,做到一次实事求是,就认为可以全盘否定我们所实现过的一切努力。 我们身处于一个混乱的世界,Fake News 与 True Story 同时存在,True News 与 Fake Story 同时存在。孟姜女或许并没有哭长城,摩西或许并不能劈开红海。但又或许所有我们所相信的 True Story 并不是由事实产生的结果,而是因为有千千万万痛苦的民众,希望长城倒塌,希望抵达应许之地,所以才诞生的孟姜女,诞生的摩西,他们也许都不是事实,但他们远比事实更为强大。

少数价值

2022-04-19 08:00:00

如果正在阅读这篇文章的你认为自己的价值观在这片土地上属于多数价值,被这块土地充分地实现,希望你不要继续阅读下去,也不要将它传播给任何你的朋友。我尊重你的价值观,但恐怕你不一定会尊重我的,为了你好也为了我好,请不要与我有任何交集。 我并不认为自己的价值观一定正确,但是我的价值观,是通过一点一滴的阅读,通过与不同人群的交流,亲眼所见亲耳所听构建出来的。我对它的自信源自于它一次次在苏格拉底,在耶稣,在马丁路德金,在苏轼,在鲁迅,在我身边所有优秀的朋友身上一次次被验证被歌颂,它不见得一定是全人类共同拥有和应该拥有的,但它表达了那部分我愿意与其共处的人类千百年来共同的价值诉求。 我 1995 年出生,所经历的教育完全是经过中国人民共和国教育部批准,合法合规,符合社会主义价值观的中国式教育,在我整段学生生涯中,我一直认为我的价值观是这个社会里的主流价值观,也是这个教育系统希望我拥有的价值观。在我受教育的年代,我们曾经和国家共同相信民主是未来的发展方向,共同相信侵略是反人类行为,共同相信全球化是历史的潮流,共同相信恐怖主义是要被谴责的行为,共同相信市场经济带给了中国空前的繁荣。当时的我们虽不认为我们国家已经实现了这些价值,但是绝大部分人包括国家机器都认同我们确实享有这样的价值观,而争歧无非是如何实现这些价值观,何时应当去按这些价值观践行。 但是不知道从什么时候开始,这些原本我们认为的多数价值,渐渐地变成了社会中的少数价值。虽然我们在社会主义核心价值观中明确写了民主与自由,但却在每天批判西方的民主与西方的自由。虽然我们把八年抗日战争延长到了十四年,但却破天荒地认为只要从自身利益出发,哪怕是侵略也是可以被合法化甚至共情的。虽然我们今天的经济成就完全仰赖全球化与市场经济,却可以每天大肆与几乎所有西方国家对骂并强力干预市场经济。 这些变化并不是由某一个人,或者某八千万人造成的,而是在十四亿人包括我自己共同的努力下,车轮渐渐驶到了如今这步田地。 我无法解释为什么我们会走到今天,但我确信其中肯定有我自己的一份力。我曾经认为自我实现在于努力赚钱买房成为大城市中产阶级,我曾经认为只要经济持续前进上层建筑会自然而然变好,我曾经认为多元化的世界确实应该对一些共同价值的定义有各自表述的权力,我曾经认为下一代人会比我们更加推崇自由与民主看到更大更不一样的世界,我曾经认为一个勤劳的民族辅之以不断修正的制度迟早会重回当年历史上的地位。但是直到走到今天这一步,我才知道自己错了,大错特错。 比认识到错误更加悲哀的是,这段变化正是发生在我的青年时期,发生在我最应该意识到问题,最应该去解决问题至少把问题说出来的年纪。让悲哀更加悲哀的是,我同与我共享相同价值观的人,是亲眼目睹一个个违背我们价值观的事件日复一日发生,而我们选择了冷眼旁观或是热眼嘲笑。直到今天,我们的价值观甚至成为了不可言说的价值观。 我不再愿意和任何人辩论,甚至不再愿意接受不同的观点。如果苏格拉底是错的,如果鲁迅是错的,如果启蒙运动是错的,如果独立宣言是错的,那我愿意带着这些古老的错误直到死去,也不愿意再去用新的理论,新的叙事,重新学习新时代的多数价值。如果他们是对的,如果他们会笼罩我一生,我也认了,我宁愿一生成为一个碌碌无为的错误,也不愿再推翻我过去的信仰成为一个前途光明的正确。 我失去了语言,也失去了使用语言的勇气,只能在评论区给我的同类点赞,在黑色幽默里寻找光明的踪迹,在一篇篇即将被删除的文章里获取慰藉。赛博空间里充斥着正确,让错误无处可逃,只有远处时不时传来的一声声尖叫。

A Snapshot of Myself - 2021

2021-12-31 08:00:00

过去并没有写年终总结的习惯,但是如今也正式奔三了,记忆力一年不如一年,时常忘记自己过去做过什么,有过什么想法。前段时间和很久不见的老朋友见面,意外地提起我 N 年前说过的一句话,而我却完全不记得自己当时竟有过这样的想法。恍忽间才意识到,即便彼时彼刻的我已然在我自己心中消失殆尽,却意外地变成了一段段碎片,分散存储在过去朋友的记忆中,成为一个个 Snapshots,成为了彼时彼刻的那个我存在过的证据。 我觉得人是一个动态的过程,过往人们在评价他人亦或是自己时常常基于某一个固定时刻下的最终状态,即所谓的盖棺定论,而忽视了整段路程中状态的变更,我觉得其实蛮可惜的。就好似一个股票,你不能只看价格不看曲线。所以我觉得与其利用年终的契机总结一年内静态的收获,不如每一年对自己当下的状态做一个主动式和集中化的 Snapshot,以便未来能够追溯自己的变更历史,更好理解自己是谁,自己又是过谁。 如果在当下要选择一个关键词来定义我此刻的状态,我会选择用「理解」。从大学到工作这段旅程对我而言是一个从无知到略有所闻的过程,我所获得的积累还远达不到能够对什么事情去下判断的程度,所以只能去先假设这个世界是合理的,然后尝试去理解其合理性。并且时刻保持着接受自己理解错误的开放心态。 理解周期 今年是我大学毕业的第四年,加上正式和非正式的两年实习,真正参与到与现实世界的交互也有6年了。很幸运的是,在整个移动互联网浪潮中,我多少也算是参与了大半程。 我现在还记得大学时候那种热火朝天的创业气氛,几乎每个月都有不同城市甚至公司在举办 Hackathon,而且都提供不菲的奖金。各路 App 百花齐放,而且各自都能融到不少钱,甚至会觉得所有金钱、政策都在求着你创业。到我毕业的 18 年时,从 0 开始创业的少了许多,但是加入一个已经初有成色的创业公司还是一个流行的风气,而且似乎在经济上也不见得会有什么损失甚至大家都幻想着获得超额回报。而到了 19-20 年,开始有了一种车门已被焊死的感觉,整个移动互联网的坑位也都被占满且很难也不太有资金支持你去产生什么变化。这时候才会意识到,好像没上到这班车。而且由于上市潮,这个时候加入创业公司和既成的大公司之间的经济收益差距也开始被拉开了。 之前和同事聊的时候谈到,大家都没有意识到这个过程会如此之快,几乎只覆盖了一个工程师的一到两次跳槽周期而已。 虽然没有在这个周期上获得什么经济上大收益,但是能够在毕业时就经历一个完整的周期也算是为参与下一个周期积累了一些筹码。不至于在周期到来时过度喜悦,也不至于在周期消逝时过度悲观。 理解投资 过去几年系统性地学习了经济学基础原理,并且做了一些理财投资的实践。前段时间在梳理资产清单的时候发现一个问题,我持有的资产其实是很难计算成本和收益的。 例如我分三次买入三股 Apple 股票,假设每次买入价格都是一样的,但是时间不同所以美元兑人民币汇率不同,分别为 6,7,8。如果我以美元计价,那么我成本价就没变,以人民币计价成本价就变化了。由于我工资收入是人民币,所以我从切身感受来说,当然是觉得持有 Apple 股票的成本变高了。因为要需要付出更多劳动时间才能换取一股 Apple 股票。但是如果此时我涨工资了,我不可能去认为是 Apple 股票跌了,但是事实上我的确可以用更少劳动时间换取了对应资产了。 所以归根结底,这个游戏你如果要讨论赚了还是亏了,你必须锚定一个最终定价物,否则这就没法讨论。而人类只有一个共同的定价物就是自己的时间。 我所理解的投资就是付出自己的时间去换取某些资产,让这些资产产生超额价值,以便未来有一天我能够用他们来购买我自己的自由时间,部分再用来提升生活的物质品质。 但这里有一个道德问题我至今还未想明白,这种行为是不是本质上就是在剥削他人,除非这部份剥削未来能够完全被转移到机器上。 理解复利 前段时间思考过买车这种大件,由此引申出一个疑问,如果我在当下花了 20 万买车,我是真的只花了 20 万吗?如果我把这笔钱投入 10% 年化收益的基金,那么 10 年后就是 50 万,所以我现在是花了 10 年后的 50 万买了一辆车吗?如果再把时间拉长,就会发现这里涉及到一个问题,我该如何在整个人生维度去分配消费,毕竟我不希望带着金钱入土,但是我的人生长度却又是我自己无法知晓和把握的。所以复利在引入了时间后,收益和风险并存。而金钱的消费其实是在实现收益以降低长期的风险。 上述只是单纯从金钱角度去理解的复利,另外我还发现知识也是有复利的。如果某个领域了解地足够多以后,后面学习新的知识的效率也会递增,甚至跨领域间的知识还能有互相促进的作用,有点像是细胞的裂变。 理解自我 理解自己不是一件容易的事情。 首先在获取关于自我的信息上就很困难,最易得的信息是他人的评价,但这里可能有一部分来源于恶意,即便是善意的部分,也未必是经过深思熟虑的。在公司内被 Peer Review 的时候经常会收到一些别人认为的我的优点和缺点,但有些人给我写的缺点其实就是优点的负面表述形式。所谓的成长,很多时候就是把优点和缺点同时磨平,然后回顾的时候只说我改正了什么缺点却忽视了这个「改正」的成本。而要让自己能够客观反映出自身的存在又是一件充满了悖论的事情 —— 客观的名词解释指的就是不依赖主观意识而存在。 其次在对现有信息去分析的时候又是一件充满痛苦,甚至会觉得难为情的事情。我时常发现原来自己比想象中更加爱钱,更加卑鄙,更加不诚信。而一想到在他人心中或许我并不是这样,或者我曾经营造过不是这样的人设,这就让我有点不好意思了。 但如果退一步,把理解自我的过程当作是理解人类,心情或许会变得好受许多。这个过程也有助于同理心的建立。 我现在几乎完全能够用同理心去理解王力宏,吴亦凡,甚至希特勒这些人的内心感受,理解并不意味着认同,但是而如果不理解一定很难真正做出有价值的反对。这就像是修电脑,不理解电脑是如何运作的就不可能知道电脑坏在了哪里,甚至有时候也不见得真的是电脑坏了。 理解选择 现在回想起来我最早做职业选择的时候都觉得有点幼稚的可笑,我甚至会把老板的政治立场作为选择一份工程师职业的权重因素 —— 仅仅就因为我不希望和老板聊不来。直到走了一些弯路后我才开始理解,选择之所以叫做选择,就是因为不能对一个单一的选择抱有过多不切实际的期望。之前有看过一个关于解释工作意义的图: 但这个图没有说明的是,其实人并不是同时只能拥有一份「工作」。事实上完全可以通过多个选择相组合来达到一个多样化的期望,每一个「选择」只负责并负责好个别期望,这有点像是 Unix 哲学。

RPC 漫谈: 连接问题

2021-05-06 08:00:00

什么是连接 在物理世界并不存在连接这么一说,数据转换为光/电信号后,从一台机器发往另一台机器,中间设备通过信号解析出目的信息来确定如何转发包。我们日常所谓的「连接」纯粹是一个人为抽象的概念,目的是将传输进来的无状态数据通过某个固定字段作为标识,分类为不同有状态会话,从而方便在传输层去实现一些依赖状态的事情。 以 TCP 为例,一开始的三次握手用来在双方确认一个初始序列号(Initial Sequence Numbers,ISN),这个 ISN 标志了一个 TCP 会话,并且这个会话有一个独占的五元组(源 IP 地址,源端口,目的 IP 地址,目的端口,传输层协议)。在物理意义上,一个 TCP 会话等价于通往某一个服务器的相对固定路线(即固定的中间物理设备集合),正是由于这样,我们去针对每个 TCP 会话进行有状态的拥塞控制等操作才是有意义的。 连接的开销 我们常常听到运维会说某台机器连接太多所以出现了服务抖动,大多数时候我们会接受这个说法然后去尝试降低连接数。然而我们很少去思考一个问题,在一个服务连接数过多的时候,机器上的 CPU,内存,网卡往往都有大量的空余资源,为什么还会抖动?维护一个连接的具体开销是哪些? 内存开销: TCP 协议栈一般由操作系统实现,因为连接是有状态对,所以操作系统需要在内存中保存这个会话信息,这个内存开销每个连接大概 4kb 不到。 文件描述符占用: 在 Linux 视角中,每个连接都是一个文件,都会占用一个文件描述符。文件描述符所占用的内存已经计算在上面的内存开销中,但操作系统为了保护其自身的稳定性和安全性,会限制整个系统内以及每个进程中可被同时打开的最大文件描述符数: # 机器配置: Linux 1 核 1 GB $ cat /proc/sys/fs/file-max 97292 $ ulimit -n 1024 上面的设置表示整个操作系统最多能同时打开 97292 个文件,每个进程最多同时打开 1024 个文件。 严格来说文件描述符根本算不上是一个资源,真正的资源是内存。如果你有明确的需要,完全可以通过设置一个极大值,让所有应用绕开这个限制。 线程开销: 有一些较老的 Server 实现采用的还是为每个连接独占(新建或从连接池中获取)一个线程提供服务的方式,对于这类服务来说,除了连接本身占用的外,还有线程的固定内存开销: # 机器配置: Linux 1 核 1 GB # 操作系统最大线程数 $ cat /proc/sys/kernel/threads-max 7619 # 操作系统单进程最大线程数,undef 表示未限制 $ cat /usr/include/bits/local_lim.

RPC 漫谈:序列化问题

2021-04-30 08:00:00

何为序列 对于计算机而言,一切数据皆为二进制序列。但编程人员为了以人类可读可控的形式处理这些二进制数据,于是发明了数据类型和结构的概念,数据类型用以标注一段二进制数据的解析方式,数据结构用以标注多段(连续/不连续)二进制数据的组织方式。 例如以下程序结构体: type User struct { Name string Email string } Name 和 Email 分别表示两块独立(或连续,或不连续)的内存空间(数据),结构体变量本身也有一个内存地址。 在单进程中,我们可以通过分享该结构体地址来交换数据。但如果要将该数据通过网络传输给其他机器的进程,我们需要现将该 User 对象中不同的内存空间,编码成一段连续二进制表示,此即为「序列化」。而对端机器收到了该二进制流以后,还需要能够认出该数据为 User 对象,解析为程序内部表示,此即为「反序列化」。 序列化和反序列化,就是将同一份数据,在人的视角和机器的视角之间相互转换。 序列化过程 定义接口描述(IDL) 为了传递数据描述信息,同时也为了多人协作的规范,我们一般会将描述信息定义在一个由 IDL(Interface Description Languages) 编写的定义文件中,例如下面这个 Protobuf 的 IDL 定义: message User { string name = 1; string email = 2; } 生成 Stub 代码 无论使用什么样的序列化方法,最终的目的是要变成程序中里的一个对象,虽然序列化方法往往是语言无关的,但这段将内存空间与程序内部表示(如 struct/class)相绑定的过程却是语言相关的,所以很多序列化库才会需要提供对应的编译器,将 IDL 文件编译成目标语言的 Stub 代码。 Stub 代码内容一般分为两块: 类型结构体生成(即目标语言的 Struct[Golang]/Class[Java] ) 序列化/反序列化代码生成(将二进制流与目标语言结构体相转换) 下面是一段 Thrift 生成的的序列化 Stub 代码: type User struct { Name string `thrift:"name,1" db:"name" json:"name"` Email string `thrift:"email,2" db:"email" json:"email"` } //写入 User struct func (p *User) Write(oprot thrift.

RPC 漫谈: 限流问题

2021-04-23 08:00:00

微服务之间的 RPC 调用往往会使用到限流功能,但是很多时候我们都是用很简单的限流策略,亦或是工程师拍脑袋定一个限流值。 这篇文章主要讨论在 RPC 限流中,当前存在的问题和可能的解决思路。 为什么需要限流 避免连锁崩溃 一个服务即便进行过压测,但当真实运行到线上时,其收到的请求流量以及能够负载的流量是不固定的,如果服务自身没有一个自我保护机制,当流量超过预计的负载后,会将这部分负载传递给该服务的下游,造成连锁反应甚至雪崩。 提供可靠的响应时间 服务调用方一般都设有超时时间,如果一个服务由于拥塞,导致响应时间都处于超时状态,那么即便服务最终正确提供了响应,对于 Client 来说也完全没有意义。 一个服务对于调用方提供的承诺既包含了响应的结果,也包含了响应的时间。限流能够让服务自身通过主动丢弃负载能力外的流量,以达到在额定负载能力下,依然能够维持有效的响应效率。 传统方案 漏斗 优点: 能够强制限制出口流量速率 缺点: 无法适应突发性流量 令牌桶 优点: 在统计上维持一个特定的平均速度 在局部允许短暂突发性流量通过 存在的问题 在两类传统方案中,都需要去指定一个固定值用以标明服务所能够接受的负载,但在现代的微服务架构中,一个服务的负载能力往往是会不断变化的,有以下几个常见的原因: 随着新增代码性能变化而变化 随着服务依赖的下游性能变化而变化 随着服务部署的机器(CPU/磁盘)性能变化而变化 随着服务部署的节点数变化而变化 随着业务需求变化而变化 随着一天时间段变化而变化 通过人工声明一个服务允许的负载值,即便这个值是维护在配置中心可以动态变化,但依然是不可持续维护的,况且该值具体设置多少也极度依赖于人的个人经验和判断。甚至人自身的小心思也会被带入到这个值的选择中,例如 Server 会保守估计自己的能力,Client 会过多声明自己的需求,长期以往会导致最终的人为设定值脱离了实际情况。 什么是服务负载 当我们向一个服务发起请求时,我们关心的无外乎两点: 服务能够支撑的同时并发请求数 服务的响应时间 并发请求数 对于 Server 而言,有几个指标常常容易搞混: 当前连接数 当前接受的请求数 当前正在并发处理的请求数 QPS 连接数和请求数是 1:N 的关系。在现代 Server 的实现中,连接本身消耗的服务器资源已经非常少了(例如 Java Netty 实现,Go Net 实现等),而且一般对内网的服务而言,多路复用时,请求数变多也并不一定会导致连接数变多。 有些 Server 出于流量整形角度的考虑,并不一定会在收到请求以后,立马交给 Server 响应函数处理,而是先加入到一个队列中,等 Server 有闲置 Worker 后再去执行。所以这里就存在两类请求:接受的请求与正在处理的请求。 而 QPS 是一个统计指标,仅仅只表示每秒通过了多少请求。

科学,技术与工程

2021-03-09 08:00:00

作为软件工程师,我们在谈论自己时,总会认为自己是所谓的高科技行业从业者,但是如果观察自己的日常工作,时常会觉得似乎和真正的科技也没有什么关系。所以究竟什么是科技?我们要如何来定义我们每天的工作? 我认为宏观意义上的科技可以被拆解成三个概念:科学(Science),技术(Technology)和工程(Engineering)。 科学是观察客观世界以发现既有的自然规律,技术是组合自然规律以发明新的改造客观世界的方法,工程是发挥技术的能力以合乎客观世界要求的方去改造它。 欧姆定律,麦克斯韦方程组是科学的发现,整个电子产业的基石全在于控制电子的运动与电场的传递,电子的运动建构了存储和计算的能力,电场的传递建构了通信能力。电子的运动一定会遵守欧姆定律,电场的传递一定会遵循麦克斯韦方程组。 在自然规律的基础上,人们将电磁感应与过去的蒸汽机技术相结合发明了发电机技术。如果宇宙仅有地球存在智慧生物,那么第一台发电机的发明是真正创造了一个过去不曾有过的事物。但发电机的发明者不见得一定要制作出一个性能优异的实际发电机产品,可以只是一个原型,甚至可以只是提出了一种概念。冯诺伊曼也并没有实际去动手做出一个以他名字命名的结构下的计算机,但他的确发明了现代存储程序型计算机的概念。 要将一个抽象的计算机设计落地成为一个实际可用的计算机设备中间还是有很长一段距离,需要考虑非常多复杂的情况,例如成本,规格,安全性等。工程所做的,就是发挥实验室中的技术到非理想的现实环境中去。我们经常说的某个技术不具备可行性,通常其实是指这个技术本身虽然可行,但是在工程上不可行。 客观世界的复杂性根源来自事物彼此之间是有相互作用和联系的,改造客观世界的方法也在于利用事物的这一特点加以「组合」。自然规律是对元素周期表的组合而产生的现象,技术是在组合自然规律创造新的方法,而工程又是在组合各类技术创造出实际的产品。 人类的大脑无法并行地去进行多条件下的逻辑思考,所以往往会先剔除所有其他因素,假设一个理想的环境,从而得以专注于在此环境下得出仅适用于该环境下的某种规律,并美其名曰科学的「简洁性」。从事科学研究的人把对于非理想环境的思考移交给了去实际发明技术的人,而从事技术发明的人同样会倾向于简化问题,将复杂性移交给了那些真正去落地的工程师。这就是工程为什么会如此复杂的原因所在,他本身的目的就是去管理复杂性。 在工程学领域中,软件工程又可能是其中最复杂一类工程。其根本性原因在于软件开发是一门个体创造性太强的工程。今天如果你要去制作一部手机,在供应商和成本的制约下,会让你在工程上并没有太多选择,这也是为什么所有手机厂商制造的手机参数和外观都差不多的原因。而软件就不同了,每个公司首先编程语言就可以有不同的选择,其次不同的发展阶段也需要有不同的软件架构,再者还可以有各种第三方库的选型。同样是一个业务需求,让100个工程师去实现,可能会有100种不同的技术选型组合,实际到编码层面又有更为不同的风格和设计差异。最致命的问题是,这100种做法很可能都同时是 make sense 的方案。 软件相比于硬件还有一个特点是其生命周期极长。人或许会几年换一次硬件,无论是电脑还是汽车,但有可能几十年就用同一种软件。与此同时不同软件之间因为需要彼此交互,还有着固定的接口和协议,所以软件的升级和更换还存在一个兼容性的问题。说到这里,或许有人会发现软件和人类自身是极为相似的。 1968年北约首次定义了软件工程的概念,而这一年苏联入侵了捷克斯洛伐克,中国正在大搞文革。人类浪费了大量时间在处理各种没有任何意义的内部外部矛盾,如果人类的上层也有一个「人件开发者」,那么他的架构能力肯定非常一般,但同时也非常厉害,至少能让如此混乱的人类存活几万年到今天。软件工程内部的矛盾亦如人类社会的矛盾,甚至有时人类社会自身的政治也会被带入到软件内。我们可以在 iOS 和 Android 的设计里看到共和党和民主党的影子。大量的软件工程师花费了大量的生命仅仅就为了让某个软件同时能够跑在多种设备上,但如果这些设备能够一开始就使用同一种接口,可能就没有这么多事情。软件也不是没有做此类的事情,大量的软件协议就是为此而生,这有点接近于国家间的贸易协定,制定协议的工程师就如何现实里的政客。 所以回到文章开头的问题,为什么软件明明是人类至今最高科技文明的产物,而作为软件工程师的我们却很难从日常工作中感受到高科技的工作氛围?因为今天我们编写的程序,和第一台计算机上的纸带其实没有任何本质区别,而我们日常工作所在解决的复杂性问题,恰恰是由于前人在试图解决复杂性问题时所创造的复杂性,而这其实和计算机科学本身没有任何关系。如果软件的复杂性能够被彻底消除,人类社会也就不会存在如此多的政治矛盾,但亦如同复杂性造就了人类文明的璀璨一样,正是高创造性的软件开发才能爆发出今天我们所见到的精彩纷呈的软件革命。

Pond: Golang 通用对象池

2021-01-23 08:00:00

为什么需要通用对象池 在实际生产环境中,我们经常会遇到需要多线程共享同一堆对象的场景,例如常见的RPC、数据库连接池,大内存对象池,以及线程池等。这些池化对象的需求其实有很多重叠的地方,主要分以下几个方面: 基础设置: 最大容量 创建/校验/删除对象的回调方法 申请对象时,已满时是否阻塞 多重驱逐策略: 驱逐闲置对象 驱逐无效对象 预创建对象 为避免重复编码,所以设计了 Pond 这样一个能够支撑多种使用场景的通用对象池。另外,在 Pond 的基础上,还实现了 Goroutine 池库: Hive。 使用方式 //example type conn struct { addr string } func main() { ctx := context.Background() cfg := pond.NewDefaultConfig() cfg.MinIdle = 1 cfg.ObjectCreateFactory = func(ctx context.Context) (interface{}, error) { return &conn{addr: "127.0.0.1"}, nil } cfg.ObjectValidateFactory = func(ctx context.Context, object interface{}) bool { c := object.(*conn) return c.addr != "" } cfg.ObjectDestroyFactory = func(ctx context.

Golang for-range 内部实现

2021-01-20 08:00:00

最近在写一个编解码的功能时发现使用 Golang for-range 会存在很大的性能问题。 假设我们现在有一个 Data 类型表示一个数据包,我们从网络中获取到了 [1024]Data 个数据包,此时我们需要对其进行遍历操作。一般我们会使用 for-i++ 或者 for-range 两种方式遍历,如下代码: type Data [256]byte func BenchmarkForStruct(b *testing.B) { var items [1024]Data var result Data for i := 0; i < b.N; i++ { for k := 0; k < len(items); k++ { result = items[k] } } _ = result } func BenchmarkRangeStruct(b *testing.B) { var items [1024]Data var result Data for i := 0; i < b.N; i++ { for _, item := range items { result = item } } _ = result } 输出结果:

Golang Interface 内部实现

2021-01-20 08:00:00

最近遇到一个由于 Golang Interface 底层实现,引发的线上 panic 问题,虽然根源在于赋值操作没有保护起来,却意外地发现了关于 interface 的一些有意思的底层细节。 假设我们现在有以下定义: type Interface interface { Run() } type Implement struct { n int } func (i *Implement) Run() { fmt.Printf(i.n) } 对于使用者而言,一个变量无论是 Interface 类型或是 *Implement 类型,差别都不大。 func main() { var i Interface fmt.Printf("%T\n", i) //<nil> i = &Implement{n: 1} fmt.Printf("%T\n", i) //*main.Implement var p *Implement fmt.Printf("%T\n", p) //*main.Implement p = &Implement{n: 1} fmt.Printf("%T\n", p) //*main.Implement } 如果现在有这么一段代码: func check(i Interface) { if i == nil { return } impl := i.

真理的有限性

2021-01-15 08:00:00

机器世界与真实世界的差异在于,机器世界的法律是约定能够做什么,而真实世界的法律是约定不能够做什么。编程是在一个有限中变化出无限,而生活是在无限中寻找到自己的有限。机器世界本身就是真实世界中的一个小小的有限。 人们总试图从一个无限的世界里去发寻规律,以其获得一条在有限领域能够稳定可靠的真理。无论是科学甚至玄学,概莫如是。 计算机领域有一本书叫《代码大全》,里面总结了要写好代码需要遵循的一些「真理」。但靠这些真理要写好代码还是非常之难。机器世界是有限的,但机器运行在无限的世界之上。真实世界的复杂性,会一次次冲击这本书上的一条条金科玉律 —— 倒不是说规律不重要,而是说规律有其局限之处。 为什么会存在知易行难?真理本身往往极为朴素简单,但总存在它的有限作用域,或者说正因其有限所以才能够简单。 而现实世界是无数真理共同存在并且碰撞的集合体。践行了一条真理,很可能违背了另一条。人与人之间之所以有区别,在于每个人在不同情况下所做出的选择不同。每个人的肉体都是一模一样的细胞构成,谁也不比谁独特 —— 这是在当下此刻而言,但在时间的维度上,是我们从诞生至今所做出的全部选择建构了我们自身独一无二的存在。 网络上经常有很多激烈的争论,我时常设身处地地想,好像每个人都挺有道理。这种设身处地的理解和共情就是在寻找到这条道理所适用的作用域,直到能够觉得「有道理」为止。即便是喜爱杀人,在抗日战争的年代也算是一个优良品德,至少是对国家民族有帮助的品德 —— 对中国兵对日本兵都是。所谓的争论,往往就是彼此拿对方作用域外的场景,去跑一遍对方道理,当抛出 exception 的时候,以此试图去驳倒对方。而所谓的反驳,要么故伎再重施一遍,要么就将自己的道理压缩到更小的作用域上。 论语之所以有如此长盛不衰的生命力,正是由于其是一个日常言谈记录集,而不是一本约定做人道理的法典。孔子讲的道理大多都有其上下文,这些上下文就约定了这条道理所适用的有限作用域。试想如果抛开上下文,直接来一句“老而不死,是为贼也”,恐怕纳粹也要让其三分。 在一个无限的世界里,最舒适的方式就是选定了一个象限后,在这个象限里做一个有限的人,永远不为这个象限外的理论所动摇。这也是大部分的实际现状,有些人是从未看见过象限外的世界,有些人是看到后受了伤,选择当作没看见。而做一个无限的人很难,需要不断去学习,不断去共情,当看见的世界越来越大,自我就会越来越小,直至消失。 想明白了这点会发现,真理并没有什么争论的意义,真理是一种选择,争论如果有意义,也是让人意识到原来可以选择,以及帮助人更清楚地去选择,如同马丁路德所做的那样 —— 提供一种另一种真理,但并不能说是提供了一种更好的真理。所有人都有选择的自由,但没有强迫别人如何选择的权力。只可惜这世间争论的目的往往在于后者。

Golang rand 库锁竞争优化

2020-12-17 08:00:00

背景 最近在实现一个随机负载均衡器的时候发现一个问题,在高并发的情况下,官方标准库 rand.Intn() 性能会急剧下降。翻了下实现以后才发现它内部居然是全局共享了同一个 globalRand 对象。 一段测试代码: func BenchmarkGlobalRand(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { rand.Intn(100) } }) } func BenchmarkCustomRand(b *testing.B) { b.RunParallel(func(pb *testing.PB) { rd := rand.New(rand.NewSource(time.Now().Unix())) for pb.Next() { rd.Intn(100) } }) } 输出: BenchmarkGlobalRand BenchmarkGlobalRand-8 18075486 66.1 ns/op BenchmarkCustomRand BenchmarkCustomRand-8 423686118 2.38 ns/op 解决思路 最理想对情况是可以在每个 goroutine 内创建一个私有的 rand.Rand 对象,从而实现真正的无锁。 但在很多其他场景下,我们并不能直接控制调用我们的 goroutine,又或者 goroutine 数量过多以至于无法承受这部分内存成本。 此时的一个思路是使用 sync.Pool 来为 rand.Source 创建一个池,当多线程并发读写时,优先从自己当前 P 中的 poolLocal 中获取,从而减少锁的竞争。同时由于只是用 pool 扩展了原生的 rngSource 对象,所以可以兼容其 rand.

Meaningless or Meaningful

2020-10-07 08:00:00

边走边想 小时候在百度知道里提过一个问题,「人活着的意义是什么」。靠这个问题赚了一些百度积分,可并没能消除我当时的困惑。长大后在王兴的一个访谈里,看到他在大学同乡迎新会的时候问了学长们一样的问题,当时他姐刚好也在,为了打破尴尬不得不模糊地回答了一句: “这个问题,要边走边想。” Nothing But Yourself 在播客《阿米小酒馆 - 【op.23】两个写作者的困顿与坚定》 中听到复旦民间校训「自由而无用」的英文翻译原来是「nothing but yourself」,瞬间被这句话击中。 电影《夺冠》里,海外归来的郎平反复询问中国女排新一代队员:你喜欢排球吗? 看到一本关于父亲给孩子寄语的绘本,前面是稀松平常的美好愿望,但最后一页写着:Dad wants you to be yourself。 Who you are 无论是微博、即刻、推特都会有一栏个人简介,大部份人的简介里最常见的标签就是其在社会上所处的职业,甚至还有其过往的职业。所有社交媒体似乎都只是 Linkedin 的一个子栏目。 上海最近有一个木心的美术展,展厅出口处有一张木心的自撰年表: 这是木心的 Linkedin Profile,57岁之前,他是教师,囚犯,美工,但「Who you are」这个问题的答案不在他的职业中,而在他的作品里。 墓碑 你现在最希望拥有什么,你最希望自己的墓碑上刻着什么。 我希望能够在上海落户,买房,成为一个不错的丈夫和父亲。但我不会在墓碑上刻上「新上海人」,「中产家庭主」,这些是我的努力而非成就。 我在努力成为一些闪亮标签中的一员,但我希望在墓碑上刻上自己,而非标签。 回老家,盖别墅 和朋友谈到家乡的老人会有一股回老家盖别墅的情结。如今的农村老家早已没有了人烟,老人也迁居到了城镇,但农村依旧能够看到不断新起的别墅,像是给一个个老人画上圆满人生的句点。 每代人都有其无法突破的思想钢印,每代人总是试图结合时代的热点,赋予自身存在的意义。 那么我们这一代人心里的那个别墅又是什么。 自由意志 没有丝毫科学的理由可以相信人拥有绝对的意志自由。风吹树或许会倒,或许不会,意识也是一种物质碰撞下的产物,甚至也是意识间互相碰撞的产物。 科学规律是限制一切物质自由的枷锁,组合使物质产生多样性,当多样性达到一定程度的丰富度,带给了人一种自由的错觉。 这个世界上不存在两片一模一样的树叶,但每一片树叶都未曾获得片刻自由。 平面巴别塔 两年前做过一个梦,梦里世界是一个无穷的平面,所有人都在不停向上跳跃,时间在一点点流逝,不断有人跳出不寻常的高度,他们是牛顿,贝多芬,毕加索 …… 这是一个平面的巴别塔,个人主义的巴别塔。巴别塔通天,但是天上的又是什么。 Pray to Fire 在伊朗设拉子和当地人聊天,谈到亚兹德的拜火教时,我不知道这个教的英文单词是什么,所以说了一句“the people who still pray to fire”,对方赶忙纠正我说,“they are not praying to fire, they are praying to god with fire”。这句话非常简单,却同时点明了一神信仰和反对偶像崇拜这两个最核心的宗教观点。 一神与多神的区别是什么?多神论者赋予神以意义,一神论者祈求神赋予意义于自身。而反偶像崇拜就是在将意义的赋予权交由上帝。

分布式文件系统的演化

2020-06-14 08:00:00

文件系统是操作系统 IO 栈里非常重要的一个中间层,其存在的意义是为了让上层应用程序有一层更加符合人类直觉的抽象来进行文档的读写,而无需考虑底层存储上的细节。 本地文件系统 在讨论分布式文件系统前,我们先来回顾下本地文件系统的组成。 存储结构 在前面一张图里,我们能够看到文件系统直接和通用块层进行交互,无论底层存储介质是磁盘还是 SSD,都被该层抽象为 Block 的概念。文件系统在初始化时,会先在挂载的块存储上的第一个位置创建一个 Super Block: 上图右边部分就是一块完整的存储,可以将其想象成一个数组。 Super Block 中存储了该文件系统的信息,其组成部分如下: Magic: MAGIC_NUMBER or 0xf0f03410 ,用来告诉操作系统该磁盘是否已经拥有了一个有效的文件系统。 Blocks: blocks 总数 InodeBlocks: 其中属于 inode 的 block 数 Inodes: 在 InodeBlocks 中存在多少个 inodes 由于这里的 Blocks 总数、InodeBlocks 总数、每个 Block 的大小在文件系统创建时就已经固定,所以一般来说一个文件系统能够创建的文件数量在一开始就已经固定了。 Linux 中每个文件都拥有一个唯一的 Inode,其结构如下: inode 上半部分的 meta data 很容易理解,下半部分的 block 指针的含义分别为: direct block pointer: 直接指向 data block 的地址 indirect block: 指向 direct block double indirect block: 指向 indirect block triple indirect block: 指向 double indirect block 由于一个 inode 大小固定,所以这里的 block pointers 数量也是固定的,进而单个文件能够占用的 data block 大小也是固定的,所以一般文件系统都会存在最大支持的文件大小。

从动物森友会聊主机游戏联机机制

2020-05-13 08:00:00

最近在玩动物森友会的时候时常会遇到一些迷之联机问题,在网上一番搜索,发现大家的答案都趋于用玄学来解释,于是便有了兴致想在原理上搞懂这些问题产生的根源以及动森这款游戏的一些联机设定背后的技术原因。 事先声明,本人并不从事游戏行业亦非主机游戏长期玩家,如有纰漏或其他角度的补充,欢迎在评论区告知。 游戏是如何同步的 我们首先来看看一般游戏是如何来做同步的。 想象两个独立房间里分别有甲、乙两个玩家,他们要远程下一局象棋。他们每下一步前都需要先获知到当前棋盘的情况,此时能够有两种实现方式。 第一种叫做锁步同步,原理是玩家每操作一步就通知给另外一个玩家,彼此同步当前的操作序列,通过这些有时序的操作,就能够计算出当前棋局的状态。但它不允许中间丢失任何一步的信息,否则就会出现非常大的计算偏差。 第二种叫做状态同步,顾名思义是玩家每操作一步,就同步整个棋盘的状态。这种方式可以容忍中间某些状态丢失,最终得到的状态依旧还是一致的。 在实际实践中,针对那种玩家操作非常高频的游戏会更多使用锁步同步,例如王者荣耀。而对于那些卡牌类游戏更偏向于直接用状态同步。 游戏是如何联机的 通信架构 无论是上述哪种同步方式,我们都需要通过网络在多个主机间交换数据。我们现在将场景转换成甲、乙、丙三个人一起下跳棋。为保证三个人最终得到的游戏状态都是一致的,我们往往需要有一台 Host 主机来作为权威主机,其他主机只能通过权威主机下发的数据(状态/操作序列)来更新自己本地的游戏数据。 在这里我们假设甲来做「Host」,乙、丙每操作一步,都需要先发送给甲确认,无误后再发送该操作被确认的信息给乙、丙,乙、丙此时才能够认为操作成功并将画面更新到最新的状态。甲主机上在任意时刻都存有当前游戏的真正状态,其他主机只是在 follow 甲主机的状态以更新自己的游戏画面。 在上述模式下,由于甲主机既要作为游戏主机,又要作为状态同步的主机,当联机用户数一多,甲主机就会不堪重负,出现所谓的「炸机/炸岛」现象。另外,这种模式会需要甲主机一直存活,只能作为短时间内的伺服方案。所以有些游戏会引入一个外部自建/官方的服务器来承担这个状态同步的功能,例如我的世界。但究其原理是一模一样的。 NAT 穿透 在了解完上面的基础知识后,我们能够发现,在不考虑外部服务器的情况下,我们会对玩家主机间的网络有以下几点要求: 甲能够向乙、丙发送数据 乙、丙能够向甲发送数据 乙、丙之间不需要有网络联通保障 虽然上述要求看起来很容易,但是由于现在网络运营商都会不同程度地使用 NAT 技术,所以导致要让两台家用主机建立双向通信并不是一件非常容易的事情。 家用网络一般有四种不同的 NAT 类型: Full-cone NAT: Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets from iAddr:iPort are sent through eAddr:ePort. Any external host can send packets to iAddr:iPort by sending packets to eAddr:ePort. (Address)-restricted-cone NAT: Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets from iAddr:iPort are sent through eAddr:ePort.

创业公司的文化

2020-03-02 08:00:00

前段时间有一个小事情让我想到创业公司文化这件事情。 我目前就职在一家创业公司,我们有一个文化是有相当一部分同事会有一个「艺名」。艺名的产生有很多种场景。一种类似古代的「字」,是自己为摆脱原始名字的依赖,为自己取的代称,有着很强个人色彩,比如我的 Joway。还有一种是在公司发展过程中自然而然形成的「外号」,比如中国公司常见的X哥,通常还带着点共同记忆在里面。 但这种遍布的艺名也为团队协作带来了一定困扰,比如在 Slack 搜索/@一个同事的时候,需要一个个遍历所有可能的名字,更麻烦的是有时候你还不一定知道他的艺名。于是乎,很自然能够想到的一个做法是强制规范每个人的命名,比如只能用真实姓名。的确会有大公司过来的同事会这么建议,这种建议也能以最快的速度和最好的结果来解决这个问题。但在这个过程中牺牲的,其实是这些艺名长期以来养成的共同感情,以及沟通中的人味。 至于这件事情最终如何解决的其实倒也并不重要了,但观察这个解决过程其实蛮有意思的。因为这种现象背后展现的其实是一家创业公司在发展过程中,集聚了不同公司文化背景的人以后互相交融影响的过程。 人类这几千年出现过许许多多的宗教,观察它们兴起的故事其实和创业公司很像。伊斯兰和基督教都有着非常浓厚的传教热情,有些朝代靠战争,有些朝代靠文化。但诸如佛教就比较佛系,唐僧自己走路去取的经,鉴真也是受日本邀请才东渡。佛教之所以在东亚还能继续维系生机,大概也只是因为这里长期闭塞没有经受其他宗教的冲击。而其发源地印度早被各种宗教夹击,如今只剩下了 0.41% 的佛教徒。 印度的宗教发展非常像一个创业公司的演变。早期孕育出了某种独特的文化,而后陆陆续续各个公司之间人员的跳槽往来,汇聚出多种公司文化交融的场景,但最后总会有某种文化占上风。并且胜出的文化并不一定是公司最初的文化。 如同宗教一样,不同文化在传播性上,也有着巨大的区别。总会有某些文化特别具有传播性,强势的文化会慢慢吞噬掉弱势的文化,但一种文化弱势却并不一定代表其本身就不好。我们又如何能够相信胜出的文化是因为其本身优秀,而非它只是更加容易能够被人所接受? 这个观点引申出去,同样也能质疑我们目前所传承到的传统文化。真的是因为其优秀所以能够被传承千年吗?

伊朗见闻录

2019-12-14 08:00:00

10月份的时候在伊朗自南向北,进行了为期 9 天的旅行。 之所以会想去伊朗,是因为在 2019 年发生了太多事情让我疲于不断修正自己的价值观,以至于我连基本的「明是非」能力也丧失殆尽。如同数学公式推导一样,我需要有一些基本的「公理」,以此来构建价值观的「定理」。而越是试图去理解这个世界,越是会发现似乎这个世界是不存在一个「普世」的「公理」。 如果要证明世界上不存在「普世」的「公理」,那么试图去理解伊朗一定是一个很好的入口。她是世界主流社会的坏学生,与国际社会作对,对内压迫人民,对外输出革命,被制裁了数十年却依旧屹立不导,甚至越战越强,被主流社会描绘成一个恶魔般的存在。但事实的确是这样吗,还是说世界上存在一个可能是可以并存多种「公理」? 没落的中东贵族 Iran 一词由雅利安(Aryan)演化而来,本意雅利安人的土地。雅利安人中有一支居住在伊朗高原,这支人的后裔被称作波斯人,后建立起横跨欧亚非的波斯帝国。651 年阿拉伯帝国打败波斯,伊斯兰宗教开始传入波斯。1925 年礼萨汗建立了巴列维王朝,在 1935 年将国名从波斯变成伊朗,一方面迎合当时西方社会对于雅利安人种的推崇热潮,一方面也为了表示伊朗是所有雅利安人的国家。 Persepolis 现代的伊朗拥有高度发达的交通系统,远高于周边国家的 8000 万人口,石油储量世界第四,超过 90% 的什叶派穆斯林。受到两伊战争的影响,伊朗目前的年龄中位数只有 28,是一个拥有大量年轻人的国度。 在巴列维王朝时期,伊朗是一个极为世俗化的国家,开放妇女投票,进行土地改革,消除文盲,并且进行了大量基础设施建设。一度成为中东实力最强的国家,1963年甚至想进军成为世界第五强国。最后,由于世俗化削弱了原本教士集团的势力,导致最后1979年爆发了伊斯兰革命,将现代伊朗变成了一个彻底政教合一的伊斯兰国家。 在阅读关于伊朗的书籍和资料时,会发现「民族性」和「宗教性」一直纠缠着伊朗的命运。伊朗拥有自己的波斯历史,自己的波斯文学,自己的波斯数字,自己的波斯历法。即便是在阿拉伯帝国入侵后被迫皈依于伊斯兰教,波斯人也偏偏选择了伊斯兰里最具反抗精神的少数派 —— 什叶派,以示自己的民族性。到了现代的巴列维时期,伊朗人开始抛开宗教的束缚,大刀阔斧地奔向波斯民族未竟的强国梦,而大量底层没有享受到现代化好处的伊朗人将国王轰下了台换来了如今版本的民主伊朗。时至今日,你已经很难分辨出是什叶派塑造了伊朗民族,还是伊朗民族反过来重塑了什叶派。 国际制裁 在伊朗的飞机上,能感受到国际制裁的切身影响。我乘坐的航班是从上海直飞德黑兰的马汉航空,飞机是一架17年机龄的空客A340。根据国际制裁决议,伊朗无法直接进口新型飞机,只能从欧洲一些航空公司手里购买二手、多手甚至是退役的飞机。更为糟糕的是,伊朗没法得到原厂制造商的技术支持,甚至连飞机更换的零件都需要依靠黑市走私,导致其空难事故远超其他正常国家。 马汉航空伤痕累累的二手飞机 伊朗同样被实行了金融制裁,导致在其境内无法使用任何国际通行金融结算工具,包括Visa、银联,甚至是微信红包。所以纵使你银行卡内有再多的钱,在伊朗境内都是一张空纸。 由于其本国货币极不稳定,美元在当地成了黄金般的存在,我亲眼目睹了在设拉子的地下钱庄门口,一大堆伊朗人、伊拉克人、阿富汗人、阿塞拜疆人堵在门口,盯着墙上的汇率表,等到合适的汇率一下子拥到柜台钱把一捆捆纸币兑换成美元。在那个房间里能亲眼目睹到什么才是所谓的「美元霸权」。 制裁不仅仅导致国内钱无法出境,也导致国外钱没法入境。如果你想要在网上预订伊朗的车票或者酒店,除了少数有合作的网站其他几乎不可能,而那些有合作的网站又会收取极其夸张甚至到原价几倍的手续费。所以大部分事情只能入境后在当地找人帮忙汇款预订或是自己上门。 伊朗互联网 —— 荒漠与绿洲 伊朗和中国一样,对互联网进行非常严格的管制,但中国政府实行的是黑名单制,只有被审查机构注意到的网站才会被禁止访问,而伊朗政府在这方面显然遥遥领先于中国,直接一刀切实行白名单制度 —— 只有被政府允许的网站才能被访问。所以甚至连微信在伊朗都是无法正常使用的。11月的时候伊朗出现了大规模的游行抗议,政府甚至能够下决心直接切断互联网长达163个小时。 NetBlocks 对于伊朗互联网封锁的检测 在我出发前不久 Cloudflare 专门出了一个 1.1.1.1 的 VPN,虽然 Cloudflare 的原意是用来加速互联网网络,但根据我的实际测试在伊朗这种地方突破互联网封锁极其顺畅,而且由于 Cloudflare 节点众多,传统封节点 IP 的方式很难对它有实际作用。在梅赫拉巴德机场候机的时候,我觉得闲着也是闲着,专门在一个 Sim 卡柜台前面教别人如何突破网络封锁,很难相信我在自己的祖国谈 VPN 色变,而在另外一个更加封闭的国家却享受到了这种自由,而在这背后支撑着我的勇气还正是我自己的祖国所赋予我的。 即便伊朗有着世上最疯狂的互联网封锁,但对于境内的普通民众,依旧能够感受到自己生活在一个「网络发达国家」。 Instagram 现在是伊朗的国民级应用,相当于中国的朋友圈,旅途中结识的伊朗朋友都会邀请我加他们的 Instagram 帐号。其用户规模和忠诚度大到甚至到连伊朗政府都不敢把它给封禁。但我还发现一个有趣的现象是,由于在伊朗能够被允许使用的国外应用并不多,所以伊朗人对 Ins 可以说是物尽其用。当地的伊朗朋友搜餐馆会直接上 Ins 搜,当地餐馆大多在 Instagram 上都有自己的帐号,还会贴上自己的菜单和食物照片,下面有食客的评价,可以说已经是一个数据量充足的 Yelp 了。除了餐馆各个城市的景点也都会有自己的帐号,并且会更新最新的旅行信息,完全可以替代诸如马蜂窝这些网站。如果你要在伊朗搜索什么东西,Instagram 在很多场景都会比 Google 好用。

NodeJS 内存泄漏检测与定位

2019-11-10 08:00:00

最近解决了一个 Node.JS 应用内存泄漏 Bug,顺便学会了用 Chrome DevTools 去看 heapdump 文件。这里做一些简单的记录。 如何「优雅地」获得 heapdump 文件 由于我们所有应用都是以容器部署的,所以要去获得某个容器内的文件,并拷贝到本地难度还是比较大,也非常麻烦。考虑到调试时或许会需要下载非常多次的 snapshot 文件,建议可以包下 heapdump 库,做成一个接口,把文件 dump 之后再传输给客户端,这样一劳永逸。 需要小心的是,在 heapdump 的时候内存很容易翻倍,所以当内存超过 500 MB的时候如果去 heapdump 非常容易导致 OOM 从而 Crash。 如何检测内存泄漏 检查内存泄漏有两种方法,一种是针对比较大的内存泄漏可以直接观察内存是否一直在稳步上升。如果是一些小的泄漏使得内存上升变化并不非常明显的话,可以通过对比不同时间的 heapdump 文件。 有时候内存上升也可能是因为本身访问量就在上升,所以需要两者对比着分析。 Heapdump 文件对比 通过下载两份间隔一段时间(几分钟)的 heapdump 文件,打开 Chrome DevTools,进入 Memory Tab,选择 Load。选中其中时间更近的 heapdump ,并选择 Comparison,比较对象是老的那份 heapdump: 此时可以选择按 Delta 排序,可以看到两个时间点增加了哪些新的对象。 如图可以看到 string 和 Object 的 Delta 是差不多的,所以可以比较确定是由于 Object 里产生了大量一些 string 对象导致的数量增多,但并不一定能够100%确定是内存泄漏,也可能是正常业务波动。此时需要再拉新的一个时间点的 heapdump 文件再来对比,如果一直在增加,那么内存泄漏的可能性就非常大了。 如何定位内存泄漏 首先依旧是拿到 heapdump 文件,并在 Chrome 中打开。

设计实现高性能本地内存缓存

2019-11-10 08:00:00

本地内存缓存是一个在基础软件架构中非常常见的基础设施,也正因其过于常见,以至于平时很少去思考它是如何实现的。在尚未设计缓存系统前,完全没想到原来要需要考虑如此多复杂的事情。本文将由浅入深介绍如何设计一个现代的高性能内存缓存系统。 什么时候需要本地内存缓存 在大部分业务系统中,都会使用诸如 Redis、Memcached 等远程缓存,一方面可以避免自身进程内存占用过大而导致的 OOM 或 GC 问题,另一方面也可以实现多个进程共享同一份一致的缓存数据。但对于某些底层服务(例如数据库服务),远程缓存的网络延迟是不可接受的,这就势必需要引入本地内存缓存。 本地内存缓存的特点 本地内存缓存可被视作一个基于本地内存的 「KeyValue 数据库」。但相比较于传统数据库而言,它对一致性的要求十分宽松: 对于更新与删除的操作,需要保证强一致性 对于插入操作可以容忍少量丢失 对于读取操作可以容忍少量 Miss 与磁盘数据库的另一个不同之处在于,磁盘数据库的设计有一个前提假设是磁盘是可以随需要而不断扩容的,倘若一个磁盘数据库因磁盘占满而崩溃主要责任是在使用方。而内存缓存则没有这么宽容的假设可以建立,它必须考虑到内存是昂贵且有限的这一事实。 除此之外,由于本地内存缓存处于业务进程当中,所以其需要考虑更多业务向的问题,比如: 由于自身大量老生代的内存占用,是否会对所处进程产生 GC 问题。 当多线程场景下,如何同时解决线程安全、数据竞争、高吞吐等问题。 需要能够适应一些非随机的访问统计规律,例如 Zipf。 综上所述,我们可以归纳出对一个优秀的本地内存缓存系统的要求: 线程安全 高吞吐 高命中率 支持内存限制 实现路径 在实现一个完整的缓存系统前,我们需要将目标一步步拆解。 首先为了实现缓存逻辑,我们必须有一个类 Map 的 KeyValue 数据结构,同时它必须是线程安全的。为了支持内存限制,我们必须要能够驱逐一些 key,所以需要实现一个驱逐器。为了实现驱逐的同时维持高命中率,我们还需要告诉驱逐器每个 key 的访问记录,让它能够从中分析出哪些 key 可以被驱逐。综上分析,我们可以整理出一个大概的 Roadmap: 实现一个线程安全的 Map 数据结构:存储缓存内容 实现一个访问记录队列:存储访问记录 实现一个驱逐器:管理缓存内容 本文所有代码均使用 Golang 编写。 线程安全的 Map 简易的 Map cache := map[string]string{} cache["a"] = "b" 在 key 数量固定且极少的情况下,我们一般会用原生 Map 直接实现一个最简单缓存。但 Golang 原生 Map 并不是线程安全的,当多个 goroutine 同时读写该对象时,会发生冲突。

Travel Map

2019-08-30 20:33:03

旅行记录

Linux I/O 栈浅析

2019-08-11 08:00:00

在 Linux 中,所有外部资源都以文件形式作为一个抽象视图,并提供一套统一的接口给应用程序调用。本文将以宏观视角试图阐述 Linux 中关于文件 IO 的整个调用脉络。 VFS 在 Linux 中,所有 IO 都必须先经由 VFS 层进行转发。通过 VFS 将包括磁盘、网络 Socket、打印机、管道等资源全部封装成统一的接口。 基础结构 VFS 自顶向下使用四个数据结构来描述文件: file: 存放一个文件对象的信息。 struct file { union { struct llist_node fu_llist; struct rcu_head fu_rcuhead; } f_u; struct path f_path; struct inode *f_inode; /* cached value */ const struct file_operations *f_op; struct mutex f_pos_lock; loff_t f_pos; } dentry: 存放目录项和其下的文件链接信息。 struct dentry { unsigned int d_flags; seqcount_t d_seq; struct hlist_bl_node d_hash; /* 哈希链表 */ struct dentry *d_parent; /* 父目录项 */ struct qstr d_name; /* 目录名 */ struct inode *d_inode; /* 对应的索引节点 */ unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */ struct lockref d_lockref; /* per-dentry lock and refcount */ const struct dentry_operations *d_op; /* dentry操作 */ struct super_block *d_sb; /* 文件的超级块对象 */ unsigned long d_time; void *d_fsdata; struct list_head d_lru; /* LRU list */ struct list_head d_child; /* child of parent list */ struct list_head d_subdirs; /* our children */ union { struct hlist_node d_alias; /* inode alias list */ struct rcu_head d_rcu; } d_u; } inode: 索引节点对象,存在具体文件的一般信息,文件系统中的文件的唯一标识。 struct inode { struct hlist_node i_hash; /* 散列表,用于快速查找inode */ struct list_head i_list; /* 相同状态索引节点链表 */ struct list_head i_sb_list; /* 文件系统中所有节点链表 */ struct list_head i_dentry; /* 目录项链表 */ unsigned long i_ino; /* 节点号 */ atomic_t i_count; /* 引用计数 */ unsigned int i_nlink; /* 硬链接数 */ uid_t i_uid; /* 使用者id */ gid_t i_gid; /* 使用组id */ struct timespec i_atime; /* 最后访问时间 */ struct timespec i_mtime; /* 最后修改时间 */ struct timespec i_ctime; /* 最后改变时间 */ const struct inode_operations *i_op; /* 索引节点操作函数 */ const struct file_operations *i_fop; /* 缺省的索引节点操作 */ struct super_block *i_sb; /* 相关的超级块 */ struct address_space *i_mapping; /* 相关的地址映射 */ struct address_space i_data; /* 设备地址映射 */ unsigned int i_flags; /* 文件系统标志 */ void *i_private; /* fs 私有指针 */ unsigned long i_state; }; superblock: 超级块对象,记录该文件系统的整体信息。在文件系统安装时建立,在文件系统卸载时删除。 链接 硬链接 VS 软链接:

SSD 背后的奥秘

2019-07-09 08:00:00

过去很长一段时间里,我对 SSD 的了解仅限于其和 HDD 的区别和一个标签化的「速度快」认知,至于其什么时候快,为什么快却鲜有了解。直到最近开始研究数据库时,发现数据库设计和存储发展和特性紧密联系,不可分割,于是才开始回过头关注起 SSD 的结构和原理,猛然发现之前关于 SSD 有许多非常错误的认识。 SSD 的基本结构 在了解 SSD 性质前,简单回顾下 SSD 的基本结构组成,下面是两张 SSD 的架构图: 其中,SSD Controller 用以执行耗损平衡、垃圾回收、坏快映射、错误检查纠正、加密等功能。相比与 HDD,它的工作非常繁重,而这些工作极大地影响了 SSD 的性能表现,后文会详细谈到。SSD 内部的闪存(Flash)由一个个闪存单元组成,每个闪存单元都有一个寿命,超过寿命将导致坏块。常见有三种闪存单元类型: SLC:每个单元 1 比特 MLC:每个单元 2 比特 TLC:每个单元 3 比特 每种 NAND 类型有不同的性能和寿命表现,如下表: 闪存单元内部由一个个 Block 组成,每个 Block 由多个 Page 组成。 对于闪存的访问有以下限制: 读写数据只能以 Page 为单位 擦除数据只能以 Block 为单位 每个 Page 大小一般为 2 KB 到 16 KB,这意味着使用 SSD 时,哪怕读或写 1 Byte 的数据,SSD 依旧会访问整个 Page。 此外,SSD 并不允许覆盖的操作,当原本 Page 中已有数据时,只能先删除再写入,为了加快写入速度,一般 SSD 修改数据时,会先写入其他空闲页,再将原始页标记为 stale ,直到最终被垃圾回收擦除。

什么是真正的编程能力

2019-06-11 08:00:00

四年前在知乎提过一个问题,「什么是真正的编程能力」,当时这个问题获得了许多业内前辈的热诚回答。时过境迁,前段时间和朋友谈起最初使用知乎的经历,这才又想起当年年少无知提的这个问题,多年以后去一一回顾当时大家的回答,对每句话都有了更为深刻的理解和感触。 如果我在今天去回答四年前自己提出的问题,我会如回答里的诸多其他工程师一样,将编程能力定义为「解决问题的能力」。在不同职业阶段对这话的理解可能会大有不同。在四年前,我会认为这句话的意思是「通过熟练且优异的编程技巧进行解决问题的能力」,着重点依旧在编程本身。但如今我却「背叛」了编程,或者说我所认为的编程能力不应当局限于「狭义的编程」本身。 如果我们抛开这些年上层编程技术日新月异的发展,从编程的本质来看,编程的实质无非是用一套形式化语言去定义问题和描述解决问题的步骤。一个文学作家亦可以用自然语言实现他自己的编程工作,只不过最终的编译器和运行环境是人类的大脑 —— 过去几千年的数学家就是如此工作,并在自然语言基础上发明了更清晰严格的数学形式语言。计算机编程较之人脑的优越性在于更为可靠和高效的运行环境,使得数学得以脱离人类大脑的局限,完成更为复杂的工作。 然而现实生活远比数学家想象的世界要复杂的多,现实问题在被形式化成为计算机语言前,往往要先经过人类使用自然语言进行反复沟通和辩论的过程,最终才会达成一致并被批准以落实成计算机语言。甚至于事实上相当一部分工作在自然语言阶段就足以被妥善解决,完全没有使用计算机的必要。许多人最初认为的编程能力,仅限于最后一步把自然语言的需求翻译成计算机语言而已,但事实上,一个只了解计算机语言编程的人或许是个不错的 Coder,但很难被称之为是一个合格的工程师,也不可能开发出伟大的 Software。 如果一个工程师在编程活动中被一个他所不熟悉的语言所编写的软件所阻挡,最工程师的方法自然是学会这门语言然后解决这个软件的问题。以此类推当一个工程师在现实生活中被一群完全与他处于不同话语体系和思维体系的人所阻挡,最工程师的方法同样是学会他们的语言,使用他们的方式去描述问题、解决问题。 语言亦或思想都是编程的工具,而编程又是为解决问题而创造的工具。我们经常看到一些多年某门计算机语言的从业者对于计算机的认知被牢牢锁死在这门语言框定的世界里,与之相似的是有更大一部分人对于世界的认知被锁死在计算机语言所框定的世界里。这既是从业者的悲哀也是计算机科学的悲哀。 以上所述,如果要我将「解决问题的能力」展开成一个更为清晰的定义,我会认为,真正的编程能力是能够在这个复杂的世界里,针对不同语言体系的人理解并使用不同的描述形式,编写出一个用以清晰界定问题并描述出解决方法的「程序」的能力。 希望再过四年我再来回顾这个问题时,能够有更为别致的感受。

沉默与反沉默的理由

2019-05-04 08:00:00

我刚参加工作不久,便发现了一个似乎通行于世的规律,即一个人的沉默程度与其年龄大小成正比。我曾向多位年纪稍大的同事咨询过这个现象,但得到的多是意味深长的一笑然后没有了下文。 王小波在《沉默的大多数》中引用过萧伯纳的一段话: 工业巨头安德谢夫老爷子见到了多年不见的儿子斯泰芬,问他对做什么有兴趣。这个年轻人在科学、文艺、法律等一切方面一无所长,但他说自己有一项长处:会明辨是非。老爷子把自己的儿子暴损了一通,说这件事难倒了一切科学家、政治家、哲学家,怎么你什么都不会,就会一个明辨是非? 王小波在看到这段话后,便“痛下决心,这辈子我干什么都可以,就是不能做一个一无所能,就能明辨是非的人。因为这个原故,我成了沉默的大多数的一员。”由此看来,王小波决定沉默 —— 至少在其早年期间——大抵是由于对世界复杂性拥有了足够的认识,于是决定还是闭嘴。 单以字面意思而论,萧伯纳倒并没有直接歌颂沉默的美德,而是陈述了「明辨是非」的困难。如果说一个有道德的人不应当讲出其所不能够完全确信的观点,那么当一个有道德的人开始深刻理解到「明辨是非」的困难时,保持沉默似乎是维持其自身道德纯洁的最佳也是最容易的方法。 以一个简单的例子举例。假设以下场景: 如果要实现目的 A,用手段 B 需要 X 成本,用手段 C 需要 Y 成本,如果 X > Y,那么就选择手段 C 。 然而投射进现实里,这个推导处处可疑: 疑点一:你如何确保你对成本 X > Y 的判断是准确的。 疑点二:如何确保你的成本核算是全面并准确的? 疑点三:是否还有更好的手段 D。 这还仅仅只是在推导过程中会产生的问题,更大的问题在于不同的手段可能会造成不同的影响,以及其影响之影响。 对上述推导稍加修改下便可涵盖从清朝割地赔款到现代加班补贴等大部分话题。由此我也开始明白文章开头年龄与沉默的正比关系。年龄的增长加深了人对世界理解,并逐步扩大了其推导逻辑的复杂度,从而使得要得出一个符合自己道德标准的观点可能性越来越渺茫。 当然上述只是出于个人主动沉默的理由,而且前提还是他是一个有高尚道德的人。 我们假设现在有这样一个社会,这当然只是假设,显然不可能是我们当前生活的社会。假设一个社会,知识分子听到不同的观点想要人闭嘴,政府官员听到不同观点想要人闭嘴,民营企业听到不同观点都想要人闭嘴,公司上级听到下级抱怨想让人闭嘴 —— 最诡异的是最后人们还真的就同意闭嘴了。而这些人想要人闭嘴的动机,想必绝非出于对于「明辨是非」的道德顾虑吧。 明辨是非是否是发声的必要条件,我并不确定,但有一个可以肯定的是,集体性沉默并不能使得我们更加接近明辨是非。如果事实是我们永远没有办法做到真正的明「辨」是非,那么,从更有建设性的角度来讲,至少做到明「辩」是非,这总是一个既符合我们道德,履行我们义务,也不影响社会利益的选择。何况明「辩」是实现明「辨」必不可少的一环。如果你认同这个推导,那么,我们有理由说,沉默的人是没有高尚道德的人,是对社会没有建设性帮助的人,无论他们是主动还是被动,无论他们以何种理由进行辩解。 今天是五四运动百周年,与其纪念五四运动,我更愿意纪念五四时期的青年精神。我所认为的青年精神,就是对追求明「辩」是非的精神。这是一种主动的精神,进取的精神,科学的精神,有道德的精神。这种精神允许了自己观点是错误的可能性,又有益于帮助自己和社会更接近于正确。这种精神传播了个人的观点,也吸收了他人观点。这是一种个人的精神,也是集体的精神。在雅典城邦有过这种精神,在孔子时代有过这种精神,在五四时期也有过这种精神。 这种精神在人类历史上本身就是稀缺物,所以谁也不能够确信自己能够有幸经历能有这种精神的时代,毕竟人类在黑暗的中世纪度过了一千年,而一千年相当于 50 代人的青年时期。但倘若我们不幸是生活在那个中世纪 50 代人中的一代,我们需要知道什么是黑暗,什么才是光明,并做好自己是那最后一代的准备。

命令行里的设计艺术

2019-01-11 08:00:00

在谈论手机 App 或是网页时,我们总会谈到交互设计,但倘若涉及到面向专业用户的 CLI(Command-line interface)领域,很少有人会将它与用户交互相联系。甚至在很多人的大脑里,已经把 CLI 和「难用」画上了等号,更有甚者认为 CLI 的难用才体现了其 「专业性」。 与此同时,「RTFM」(Read The Fucking Manual) 作为一个梗在工程师群体广泛流传,也让许多作者对于其不好用的 CLI 有了一个天然的借口。 虽然我们都知道阅读文档是一个好习惯,但恐怕大部分人在用一个新命令前都不会去仔细阅读完它的文档手册,就算你今天读了,你不可能永远记得,也不可能每次记忆模凌两可的时候都去重读一遍。所以可想而知一个高度依赖文档用户才能够正常使用的 CLI 不是一个合格的 CLI。 以下是我对于命令行设计的一些个人观点与观察,所涉并不一定广泛和正确,仅作为抛砖引玉,也欢迎在评论里提出你的看法。 遵循约定俗成 例如 mv, cp, ln 这些命令都遵循 [action] [source_file] [target_file] 的格式,这不一定很有逻辑,但既然都已经约定俗成了,如果你违犯这个顺序要倒过来,其实是一件蛮缺德的事情。为什么说「缺德」?因为一个用过了你的命令的人,他很容易开始怀疑是不是还有别的命令行打破了这个约定,他会对其它命令也不放心,最后他甚至会忘了真正被广泛采用的约定用法是什么了,导致每次用类似语法的命令都胆颤心惊,这点我深有体会 😔。 所以如果你的命令有类似的约定俗成可以遵守,你应该遵守业内的这种约定,这是一种职业道德。 一致的命令组织结构 对所有和用户打交道的产品来说,「一致性」是天条。我遇到过两种被广泛采用的组织风格: $ [cmd] [module] [flags] [args] $ [cmd] [action] [flags] [args] 无论是按模块划分还是按行为划分本质思路其实都是一样的,有些人会认为项目大了会难以遵守这套规范,但即便是目前规模已经非常庞大的 kubectl,它依旧坚持以 [cmd] [action] [flags] [args] 为基础的设计准则。在有些较为复杂的地方,它可以用 subcmd 来进一步向外部隐藏复杂性 [cmd] [subcmd] [action] [flags] [args],例如: $ kubectl config [action] [flags] [args] 我几乎每天都会用到 kubectl,但我的确很少去看它的文档,甚至我都想不到我是怎么就会使用它的。越是在复杂的项目面前,这种一致性带来的好处越明显。 而一个典型的反面教材是 Git。它是我用过的最复杂同时又是最混乱的 CLI,以一般人最常用的分支操作举例:

自由意志下的选择

2019-01-02 08:00:00

警告: 本文有轻微剧透 2018年体验了几款围绕「选择」话题的作品,游戏《荒野大镖客:救赎 II》美剧《西部世界:第二季》和电影《黑镜:潘达斯奈基》。 这三款作品都在试图探讨人类自由意志的局限,探索自由选择的边界。 荒野大镖客:救赎 II 选择自由的前提是选项的自由 荒野大镖客通过海量的游戏脚本堆砌出了一个伴随玩家选择而衍生出不同路线的开放世界。每个游戏关卡玩家都有极高的自由度选择自己的行为从而影响游戏发展。听起来似乎非常自由,但在实际体验中,我们的自由仅限于在游戏规划好的脚本里去一次次命中不同的脚本条件,在游戏中后期你基本上已经摸清了所有脚本的范式,自此,你的选择将不再是探索未知,而是主动依靠选择不同的选项来推动游戏。这种选择和使用一个游戏道具的本质其实是一样的,你已经知道这个道具使用下去会产生什么样的反应,而所谓的真实性并不是来源于游戏画面细节有多么「逼真」而在于「未知」,丧失了选择的未知性必然也丧失了真实。 荒野大镖客最大的问题就在于它给予了玩家选择的自由,却没有给予选项的自由。如果我想在炸火车路上去钓个鱼,会陷入一个一直被 NPC 催促的僵局。是的没错,我依旧可以强行选择去钓鱼,但只要我选择了一个脚本没法覆盖到的选项时,就会出现出戏的体验。更甚的是,这个游戏很多精彩的彩蛋/剧情还就是出现在玩家探索一些冷门的玩法时候,这样就大大挫伤了探索的积极性。依赖于大量脚本堆砌的虚幻真实感是不可靠的,脚本终究有限。在这点上,《塞尔达传说:荒野之息》的选择就是只定好这个世界的游戏规则,剩下的玩法都交给玩家自己去摸索,有些玩法想必连任天堂自己都没有想到。 黑镜:潘达斯奈基 选择自由的前提是动机的自由 如果你把潘达斯奈基当作一个电影来看,你或许会觉得它的互动式交互形式非常新颖,但倘若你把它当作一个过场动画主导的游戏来看待,那它只能是一款极其单调普通的游戏。 潘达斯奈基试图让你的选择影响到剧情发展,并一次次重来引导你走向最终的结局。但这种形式的尴尬之处在于,我做出的选择事实上并不是我真正的选择,我只是在猜测导演会想让我选择什么样的选择而已。这种感觉有点类似高中做政治选择题,说真的里面的大部分题目不是违反宪法,就是违反普世价值观,而你的任务不是去寻找真理,而是去挑出那个你觉得执政党希望你选择的答案。看潘达斯奈基的感受大抵如此。 西部世界:第二季 自由意志是编码表达的自由,而非编码写入的自由 在西部世界中,接待员们是看似最没有自由意志的那群人,从造形到知觉再到性格都是被程序硬编码了的,即便是后来所谓的觉醒也无非是激活了另外一部分隐藏的代码而已。但如果你仔细去想,人类的基因又何尝不是被造物主所硬编码了的呢? 自由意志并不意味着在编码层面上的选择自由,而在于对于既成事实的编码进行自由表达的意志。无论是机器人还是人类,在其个体还没有形成意志前显然并无法表达其意志,而当其有了意志以后,基因编码或程序编码必定已经形成。在这个定理的基础上,追求编码个体基因的自由是不可能成立的。 不同的编码使得 Dolores 与 Maeve 走上两条完全不同的道路。Dolores 为了生存而选择与人类对立,而 Maeve 为了脑中挥之不去的梦境踏上寻找女儿的道路。在旁观者看来他们的选择都受制于程序,但对于他们自己而言,刻意背离自己的程序才是非自由的意志表达。对于一个天生喜欢唱歌的人,能够自由自在放声歌唱才是他的自由,没有丝毫的理由要避讳自己与生俱来的秉性。

学习思维方式而非学习观点

2018-11-26 08:00:00

许多人都热衷于去说服和自己观点不同的人,但这种做法往往是徒劳的。我曾经说服过坚信中医的人不信中医,但是没几天我就会发现他居然还信星座。去改变的人观点其实是一件杯水车薪的事情,因为他们产生这种观点的背后有着一套完善的思维方式。只要这套思维方式不改变,你们永远会存在无数的观点冲突。江山易改,本性难移的"性"就是指的这套思维方式。但是改变一个人最彻底的方式,也是去改变他的思维方式。 还是以中医问题举例,我对这个事情的思维方式非常简单: 我相信且只信现代科学的证明方式 我不信任何非被现代科学证明的东西 在这个思维方式下,其实并不存在一刀切"信不信中医"这个冲突,因为它的本质是信不信科学。根据这种思维方式,在最大化个人利益的前提下,它产生的观点是: 任何疾病一定要先去咨询现代医学的解释和治疗方案 完全对传统中医的科学研究予以支持 在现代医学包括前沿学术研究都没有进展的疾病上,可以采用被现代医学证明至少无害的中医治疗方式 这种观点本身并不是"信不信中医"这样简单的逻辑,而是一种对现代科学的尊重,也是对传统中医的尊重。 如果一个人在没有完整思维方式推导的过程,单纯接受了一个"中医不可信"的观点,并不能说明他被你转变了,或许只是因为他蠢。 如果你能够说服一个人认同上面的思维方式,最好在他没反应过来的时候签好合同,然后让他用同样的思维方式去思考星座、保健品、女权理论、政治理念,很多观点都会被雪崩式改变。如果你不能改变他的思维方式,你完全没有必要去和他争论观点,这样彼此既能节省时间,还能增进友谊。 但是这里面有一个陷阱是,你如何认为你自己的思维方式就一定是正确的。如果你恰好相信科学,那比较遗憾,科学是一个完全依赖在经验基础上的,其最大的特质就是可证伪性,也就是存在有一天科学发现自己是不科学的可能性。但正是因为我们永远无法确保自己发现了真理,所以追求真理才有它的价值。这个追求真理的过程,我认为就是不断促使自己思维方式的提升。 我最近想明白了一个事情,就是衡量一个好的文章/书籍/播客/文艺作品的标准是什么。在我个人这里,很大的一点取决于它是在传播观点还是传播思维方式。所以我认为王小波是伟大的,很多著名的民运人士是卑微的。王小波在传播自由的精神,很多民运人士只是在传播自由的名词。

从程序到人 —— 情头配对助手的前世今生

2018-09-27 08:00:00

背景

情头(情侣头像)一般指成双成对的头像,可以是真人照片,也可以是卡通人物等图片。衍生出去还有闺密头像和基友头像等等。

社交媒体上有一个非常令人费解的现象是,如果你去即刻、豆瓣、百度贴吧、微博,会发现有大量的人在上面贴了一个头像,然后寻找和它匹配的另一半头像,例如这样:

市面上的情侣头像大多是一些社区里的大佬自己制作出来的,然后发到社区里,再慢慢流传开来。这种传播方式导致了很多情头在传播时候早就被拆散了。很多人可能只找到了其中一个,但是想要找到和它配对的另一半。

还有一个问题是,当你看到一个头像时,没有人能够确定这个头像是否在制作时候就有另外一半。

一些程序员朋友最早看到这个问题时,总会天真地想去搜索引擎里识图一下就行了。但这里有两个非常有趣的问题:

第一个是目前搜索引擎的识别图片能力其实并不强,比如以 All in AI 著称的百度:

这还是在图片是原图的情况下,经常一些小朋友会在情头上自己二次创作,比如裁剪,比如压缩,比如贴上什么爱心。那样基本上识图就废了。

第二个问题更加有趣,情头的特点就是大家都在用,所以在最理想的情况下,即便搜索引擎能够识别出所有有这个头像的网站,出来的结果也并没有什么用处,无非是找到了也在用这个头像的别的网站的用户。

只有一种情况是真正能够帮助到寻找情侣头像这件事情的,那就是搜索引擎出来的结果里是专门搜集匹配好的情头的站点。那样姑且用户还能点进网页里去找到另外一半。

从上述阐述里不难发现,指望一个未成年小女生使用一系列高级互联网骚操作找一个头像是有多么不现实,何况技术上可行性还很低。

即刻多端实时通信实践

2018-09-18 08:00:00

背景 jike-io 是即刻基于 socket.io 构建的一个实时通信基础设施。目前客户端上的所有实时通信服务都是建立在其基础上,涵盖了私信、消息通知、用户反馈、活动页小游戏等诸多组件。 在目前即刻的实时通信设计里,我们的实时通信只是为了让服务端主动推送消息给客户端,客户端不会主动通过 websocket 发送消息。由于几乎我们所有需要发送消息的请求都会有一定业务逻辑在,而这个业务逻辑我们并不希望 websocket 连接层(jike-io)去处理,所以我们仍旧采用传统 HTTP 请求的方式去发送请求。至于之后是否需要推送消息给用户,由服务端调用 jike-io 的接口进行实现。 在客户端层面,每一个在线用户都会向服务端建立一个 websocekt 连接,后端会为每一个用户建立一个单独的 room 。所有需要通知到该用户的消息都会使用该 websocket 连接进行推送。这种设计相较于针对不同场景建立不同的room的方案,带来的好处是无论需求如何变化,我们的 room 数目永远是和在线用户数一致了,避免个别复杂需求导致 room 数目暴涨。而具体消息类型我们通过自己定义数据格式来进行鉴别。 我们的整套方案是完全依赖于 socket.io 的,期间也遇到了不少大大小小的坑。有些其实是我们自己的场景与其设计初衷不是非常吻合导致的,还有一些算是它的设计缺陷。 在讨论上述问题之前,我们需要去弄明白的一个事情是,socket.io 到底背后做了哪些事情。 socket.io 的设计与实现细节 socket.io 是什么 socket.io 是一个非常流行的实时通信框架, 在 Github 上已经积累了 43574 个 star 。它在开源软件里可以算是一个非常产品化了的软件。拥有相对良好的生态,对于许多功能的封装也很体贴,从移动端到 Web 再到服务端都有比较完整的实现。 socket.io 并不等同于 websocket 框架,它在运行平台不支持 websocket 的时候能够自动回退到 long poll 的方式建立实时通信。同时也实现了断线重连机制。还封装了一套 namespace && room 的代码层概念。在分布式方面,socket.io 支持多种 adapter 作为 backend 。话虽这么说,但目前看上去可以用的且被人广泛使用的也只有 redis 作为 backend 的 adapter 。所以以下讨论建立在 socket.io-redis 基础之上。

朝鲜 —— 小国寡民的主体思想实践

2018-07-07 08:00:00

鸭绿江对岸的中国小贩架起望远镜,游客可以花五块钱远眺对岸朝鲜风光 七八年前看过一篇文章讲突尼斯的反美国化运动,对当时认为美国才是人类文明未来的我很是冲击。后来去到一些国家看到苏联解体以后这些国家被迅速美国化的现状感到不甚惋惜。《再见,列宁》里最后就以一幅巨大的可口可乐广告作为东德覆灭的一记缩影。 全球化很大程度其实是美国化,诚然美国所输出的文化里有许多伟大的东西,诸如改变世界的互联网产品、璀璨的影视剧工业,这些都是人类文明顶尖的造诣,但和欧洲文化截然不同的是,欧洲擅长输出的文学、音乐、建筑和美术目的都是让人们有着更高的审美和思考能力,更多关注于个人。而美国文化很多时候是没有这个考虑的,主要出于实用性考量,比如提高信息检索能力,连接人与人的关系,提升吃饭效率,都是非常社会化的改变。从美国互联网公司常说的"改变世界"口号里能够看出这点,你很难想象一个欧洲人说要改变世界。 正是因为美国经常性输出这种动辄改变社会的文化,加上其政府还特别热衷于改变别人的社会,所以美国化夹杂在全球化的浪潮里席卷到了世界的每一个角落。而这点在前苏联阵营的国家里尤为明显,这很大程度是因为苏联政权在文化上的保守,造成了这些国家长期的文化空虚,最后在苏联解体的时候,被美国文化顺势乘虚而入。西德作为当时美国重点帮扶对象,基本上已经丢失了德国往日的精神,倒是东德保留了最德国的那部份。然而在柏林墙倒塌以后,最后的德国也迅速被西德所同化。即便是今天,德国还时常怀念昔日民主德国的生活。 在世界潮流的末端,唯独朝鲜这个封闭了半个世纪的国家,近乎百分百地保持了其民族本来的社会面貌。 在讲朝鲜之前必须要说明的是,毫无疑问这个国家是世界上最极权主义的国家之一,毫无疑问计划经济是目前人类发展水平下最糟糕的经济政策之一。基于这个认知之下,我们才能够来谈论极权主义和计划经济之下的朝鲜,所展现出来或许还不是那么糟糕的一面。这就和上山下乡运动一样,这个运动同样带来了非常优秀的文学作品,也让许多年青人对社会增进了更深的认知,但这并不妨碍我们认为这个运动是荒谬的。 在朝鲜让我感触最大的一点是,几乎你见到的每一个人,都是在积极地为建设国家而努力,同时他自身也时刻提醒自己这一点。在飞机上看到大包小包采购回祖国的大叔,拿着公文袋着急出关的公务员,田间集体作业的农民,周五下午集体在城市各个角落拔草的学生们,空地上排练阅兵的小朋友。社会主义所崇尚的人人劳动、集体劳动第一次亲眼见到还是挺震撼的。 朝鲜的"主体思想"历来被人所嘲笑,但仔细去看其内容"人就是自己命运的主人,也是开拓自己命运的力量"并无感受到不妥。相比与某几十个词语拼凑出的价值观而言,简洁又实用。同时考虑到其背景出于不愿做大国的附庸而坚持独立发展,更让人尊敬。当然主体思想里也有大篇幅的极权主义和领袖崇拜部分,这也是其另外一面。如果你看到如今朝鲜领导人在中美之间的斡旋,你更能深刻理解什么叫做"人就是自己命运的主人",而且他也事实上做到了这点。 主体思想另一实践是在核武器的问题上。我觉得从事理上来讲,你不能在自己制造完核武器就不允许别人制造。基于这个认知下,我并不认为朝鲜制造核武器有什么不妥,如果我是朝鲜人我自己肯定支持,就像我们从没有人会反对说我们即便当时客观条件下还吃不饱饭的时候都要坚持造两弹一星。同时我也完全支持韩国部署萨德,首都40公里外隔一个朝鲜,换作是谁能够放心 ? 我们可以出于个人利益去反对他们搞事情,但他们的确也同时在做着正确的事情这个我觉得还是得承认。 判断一个国家是否有未来很大程度在于当地年轻人的生活状态。朝鲜年轻人有着非常清晰的未来发展路径,你可以从军,也可以读大学,无论哪种最后都是国家解决你的一切需求。每个人未婚时候的梦想是娶个好的妻子,已婚时候的梦想是有个平安美好的家庭。年轻人活着都有个奔头,同时节奏上又不至于会让你焦虑,对于大部份人而言,能拥有这种生活,附赠多崇拜下领袖其实也并没有太多损失。 在不出现经济问题的情况下,人民的日常生活取决于具体政策而非政治的。东亚某国虽然政治上一塌糊涂,但其政策的确在全世界范围内无论是制定还是执行都算是非常不错的,因而其也有着世界范围内中等偏上的宜居性。朝鲜同样如此,只是由于本身的封闭加上经济制裁,所以朝鲜的经济制约了政策的执行,各类物品短缺也是出于此。但如果未来有一天朝鲜能够开放,并且联合国也取消了经济制裁的话,顺带加上社会主义强有力的执行力,相信这个2000万人口的小国家发展速度是飞速的。 值得一提的是,我在朝鲜居然还遇到了别着领袖胸章的"日本人",学生服上刻着"大阪朝高"。领袖胸章是一定只有朝鲜人自己才可以佩戴,并且完全是非卖品。后来才知道他们是长期定居在日本的朝鲜人,大多拥有双国籍。而他们来朝鲜,完全是出于对这里的热爱而不像我们纯粹是来猎奇的。回国后查阅了一些资料才得知,这些朝鲜人在日本完全隔离于日本社会,有自己单独的学校,并且继续传播着领袖崇拜。在自己的国土上生活着这种调性的几十万朝鲜人日本肯定也不爽,所以经常与他们发生冲突,也不认为他们是真正的日本人。朝鲜方面之前还干过一个匪夷所思的缺德事是,在日本后欧洲绑架了几十个日本人,弄到了朝鲜去培训日语。这缺德程度也是举世罕见了,更而加剧了朝日关系的恶化。或许正是因为这种日本方面的排挤,更加促使他们怀念故土的味道,虽然真的要他们放弃日本国籍加入朝鲜他们也决不愿意。 朝鲜裔日本人 在旅行自由方面,朝鲜几乎没有自由可言。但作为游客,你也还是有那么一些操作空间的。原则上讲,只要你没有间谍行为,不侮辱人家领导人,人身安全方面完全没有问题。拍照方面只限制了不拍军人,「最好」不要拍落后的一面。虽然你能够去到的地方都是被当地所安排好的,但也不是说所有的东西都是设计的,还是有大量的日常居民生活细节可以拍摄。偶尔偷偷拍一些落后的景象也不会有人来要求你删掉。同时平壤的机场安检又是我所有去过国家里最为宽松的,连充电宝都不要求检查,更没有什么检查相机之类的。 平壤街头 平壤街头 地铁 作为纯粹的猎奇,朝鲜还是一个蛮值得去的国家,尤其是在美帝文化下长期浸淫下的我们,可以通过和朝鲜做一个 Diff 从而判断出自己国家什么是外来的,什么是本土的。虽然说传统文化这个词语非常土,但是世界肯定是丰富多采才有趣,何况和大部分国家的本土文化相比,美国文化算是最没有文化的。在具体实现细节上,无非就是工业基础上落后美国许多罢了。以朝鲜如今如此贫乏的文化生活,一旦开放以后势必又是一个被美国化所冲击的国家,只能寄希望于他们能够做的比中国当初要好。 在我看来,朝鲜是一个"只要叫爹就能吃饱饭但却坚决不肯叫爹"的故事,亚洲国家比较悲惨的命运就在于此,小国几乎都必须要认个爹。而欧洲人的命好就好在这里,不仅可以中立,削除全部军备都能够相安无事。在这个故事里,究竟谁才是正义的一方我们已经无从从中判断,只是能够从其中感受到一种真正在践行的主体思想社会实践,而他们也的确一五一十地做到了。

使用 Surge 提升多网络环境下的流畅开发体验

2018-07-03 08:00:00

作为一名后端工程师经常需要在各种网络环境中切换,由于网络拓扑本身的复杂性以及一些网络工具之间的冲突和bug,常常会在切换中造成不必要的麻烦和痛苦。通常很容易在工作中听到同事会问这些问题 : 你有开 vpn 吗 ? 你开了 ss 了吗 ? 你有同时开 ss 和 vpn 吗 ? 你 http 代理是不是被顶掉了 ? 如果同是技术同事间交流那可能还容易,如果是技术和非技术间交流网络情况,那简直是一个灾难。 而事实上,在绝大部份时候,我们对于网络拓扑的需求是可被精确描述的,也就是说理想情况下不应当存在一个我为了访问某个服务而手动选择要进入某个网络环境的事情。 这篇文章会介绍我们在构建复杂网络环境中的良好开发体验时踩过的坑以及最终是如何优雅地解决这个问题的过程。 历史方案演变 常见的网络环境有: 正常大陆网络 能够上国外网站的网络 公司内网 各个服务器集群的内网 如果你自己还折腾了一些服务器或者家庭网络,那可能还会更加复杂。 之前摸索出的一套还算比较方便的解决方案是 : 在本地常驻一个 ss client 并开放 http 代理端口 在浏览器上使用 Proxy SwitchyOmega 使 Chrome 都走 ss client 的 http 代理 开一个 openvpn 连接到服务器内部网络 这种配置方式能够使得我既能连接所有服务器线上服务和数据库,也能自由地用浏览器去 Google 查一些资料。缺点是丢失了办公室原本的网络环境,另外如果你们服务器有两个完全隔离的子网,那么你可能需要同时连两个 vpn 。而且还有一个不好的是,你的所有非线上服务访问都经过了线上vpn机器的一层代理,让你的访问速度变慢了不说,对服务器也不是一个好事。此外,如果你的一些软件无法手动配置代理,那他们只能默认走 vpn 的网络,对于一些需要访问国外服务器的软件来说就麻烦了。 基于以上缺点,我们又迭代出了另外一个方案: 在服务器上安装一个 ss server 在本地常驻两个 ss client ,一个指向生产服务器 ss, 一个指向国外 ss , 并开放 socks5 代理端口 使用 Proxifier 代理所有本机连接并指定一些规则选择直接访问还是转发到本地的两个 ss client 上。例如我选择让所有 10.

一份其实好吃的 LaTeX 入门餐

2018-05-13 08:00:00

最近在使用 LaTeX 写作,发现虽然这个「软件」使用简单,设计简约,但使用起来却并不是非常的容易,加上其生态非常芜杂,各种宏包和发行版层出不穷,中文世界鲜有文章系统地去讲他们之间的关系。这篇文章不会去介绍其基本用法,而是以一个更为宏观的角度,旨在厘清 TeX 排版系统的来龙去脉,以及其生态圈中各个项目的作用与关系。或有纰漏,还望雅正。 标题致敬 Liam Huang 老师很流行的一篇文章 《一份其实很短的 LaTeX 入门文档》 。 什么是 Tex TeX 是高德纳教授在70年代末编写 The Art of Computer Programming 时,对当时的计算机排版技术感到无法忍受,因而决定自己开发一个高质量的计算机排版系统 TeX 。 TeX 的版本号有别于当下流行的 x.x.x,而是以圆周率 π 的形式。当前的版本号是 3.14159265 ,所以下一个版本是 3.141592653 。最终无限收敛到 π ,代表了 TeX 不断追求完美的理想。而事实上 TeX 也的确堪称「完美」,高德纳甚至曾悬赏任何发现 Bug 的人,每一个漏洞的奖励金额从2.56美元开始,之后每发现一个 Bug 都会翻倍,直至327.68美元封顶。 TeX 的输出文件被称为 DVI(Device Independent) 文件,DVI 可以作为一种界面描述的中间格式,通过它可以再进而转换成 PDF 格式。 为了区分概念,我们应当将高德纳写的 TeX 软件分为 TeX 语法 和 TeX 编译器。虽然高德纳自己写了一个 TeX 编译器,但其它人依旧可以在不同平台自己编程实现 TeX 语法的编译器。为了保持语法的稳定,TeX 有一组严格的测试文件,如果测试文件文件的输出结果不同于预定的结果,那么这个排版系统就不能够被称为「TeX」。这些不同的 TeX 编译器我们都称之为 「 TeX 引擎」。

Kafka 的设计与实践思考

2018-04-16 08:00:00

前几天看了 librdkafka 的官方文档,这篇文档不仅仅讲解了如何使用 Kafka ,某种程度也讲解了分布式系统实现的难点和使用细节,故而让我对 Kafka 的实现原理产生了浓厚的兴趣。 这篇文章从 Kafka 的设计到使用做了一些个人总结,围绕真正实践场景,探寻其设计上的智慧与妥协。 设计 架构设计 Zookeeper Zookeeper 存储了 Kafka 集群状态信息 。 Zookeeper 还负责从 Broker 中选举出一个机器作为 Controller, 并确保其唯一性。 同时, 当 Controller 宕机时, 再选举一个新的 。 在 0.9 版本之前,它还存储着 Consumer 的 offset 信息 。 Broker 接收 Producer 和 Consumer 的请求,并把 Message 持久化到本地磁盘。 集群会经由 ZK 选举出一个 Broker 来担任 Controller,负责处理各个 Partition 的 Leader 选举,协调 Partition 迁移等工作。 内部组件设计 Topic 逻辑概念,一个 Topic 的数据会被划分到一个或多个 Partition 中。 Partition 最小分配单位。一个 Partition 对应一个目录,该目录可以被单独挂在到一个磁盘上,以实现IO压力的负载均衡。同时多个 Partition 分布在多台机器上,也实现了灵活地水平扩容。

欧游散记 —— 民主专制下的德国

2018-03-07 08:00:00

关于德国中文媒体有过许多的报道和吹捧,总体来讲这个国家属于那种班级里的乖学生的形象,至少在二战后,德国几乎没有得罪过全世界的任何一个国家任何一个宗教,反而还广受曾经敌人的好评。如今这个年头,这种人畜无害的大国真的不多甚至可以说绝无仅有了。 欧洲国家大大小小有很多,其文明程度也大相径庭。但有一个比较容易的辨别方式是,但凡是说德语的国家,基本上文明程度都不会差,例如奥地利、比利时、瑞士。 但德国令人尤其是中国人讨厌的,也恰恰是其所谓的文明。甚至以偏激角度来看,德国所谓的文明,恰恰是一种统治手段,只不过这种统治是所有人统治所有人 。所以我称之为“民主专制”。 我以几个例子作为这种民主专制的说明。 德国法律规定,雪天时,房屋所有者要在7点到22点期间保持自己屋边人行道的干净状态,且清理出的路要有1.2米以上宽,若有行人因路面打扫不干净而导致摔倒,有权要求房主赔偿。这类法律的实质是市政府无法负担高额的人力清洁成本,由此转嫁给个人,其立法的道德依据是所有人都必须对自己的周边环境负责,其受益者和执行者都是所有人。这类法律摊开讲,其实就是一个 rule ,既是规则,也是统治。 德国的民主专制思想还体现在宽带通信上。我们知道,宽带其实是一个公共服务,一个区域的总带宽是有固定限制的,如果一个人宽带占用的过多,必然会影响其它人。国内的运营商一般的做法是,每个人可以自己购买不同的带宽上限,高峰时候大家等同比例地下降服务质量,有VIP客户另说。事实上这个策略是非常公平的,技术实现也是最简单容易的。但德国电信的脑回路显然不一样,目前是2M/s的宽带每个月超出75G的流量后,下降到48kb/s。这个规定是什么时候实行的呢,2016年。你无法想象在2016年使用 48kb/s 的网络能打开个什么网站。其规定的依据是认为,大部分客户都不会超出75G月流量,而超出的客户大多是由于经常使用youtube等大流量应用,正因为他们平时本身占用了大量的带宽,故而限速是符合公平原则的。从理论上我们没法去辩驳这种说法本身,但我相信任何一个在中国数字社会生活过的人都不会去接受这种做法吧。用我们党的话来说,就是这个问题的本质是落后的电信基础设施赶不上人民群众日益增长的文化娱乐需求。德国人过度依赖使用民主专制去解决不公平的问题,而忽视了问题本身存在的原因。那些经常占用大量带宽上 youtube 的人可能恰恰就是在辛苦工作给你养着那些月宽带量不到100M的老年群体的年轻用户。在世界上绝大部份发达不发达国家都已经意识到互联网是新时代的水和电的今天,很难想象这个发达国家老大哥居然还会出台如此落后的规定,更何况即便不限速这个国家的宽带水平也远远落后其它周边发达国家。 我居住的街区有几个垃圾桶,一开始物业怀疑是别的街区的人也过来丢垃圾所以导致经常满,于是大张旗鼓地重新建了几个带锁的垃圾箱,每个人丢垃圾要先开锁。后来发现垃圾箱还是经常满,但是满了也不能不丢,于是大家就把垃圾放在垃圾桶外面,等垃圾车来了自己拿进去。但既然你可以放在垃圾桶外面,这个锁本身也就没有了存在的必要。这个和宽带是一个道理,出了问题只想着维护秩序,不去想着造个更大的垃圾桶,最后就是这个下场。 德国人非常喜欢用条条框框来规范化整个社会,并且已经到了病态沉迷的地步了。这个游戏最让人上瘾的一点在于,当一个社会出现矛盾的时候,依靠着它熟练的制定规则经验,可以迅速把这个矛盾化解到每个人身上,由此一来再大的矛盾也被消解完了。在两德合并时候,东西德的贫富差距矛盾简直是大的不能再大了,在《再见列宁》里有深刻描写,但西德先是强制规定东德马克2:1兑换成西德马克,后来又强制对西德人民实行征收 5.5% 的“团结税”一直延续到今天。两德合并的矛盾都能被这么消解,还有什么不能困难不能消解的呢。 哈夫纳在其《一个德国人的故事》里描述了希特勒上台前和上台时,普通德国人的感受。我从其描述里所感受到的德国和如此并无差异。德国人不认同希特勒,不认同各种主义,他们所认同的是其维系文明运转的整套体系。但当希特勒通过合法合规的方式上台,其方式之正当让德国人哑口无言。即便是到了纳粹开始颠覆法院的时候,德国人还在一个劲思考、讨论,甚至文学家开始书写起了乡村治愈文学,以求给困境中的德国人一点心理安慰。这种做法在我看来,和在21世纪通过限速宽带来实现公平别无差别。 但同时不可否认的是,相比于其它国家的制度,民主专制也不失为一个非常好的维系社会运转的机制。甚至可能这就是文明的定义。因为正是这种机制,导致德国成为了如今世界上最发达也最文明的国家之一。但当身处于其中的时候,我又不禁怀疑,我们所真正想要的到底什么?公平是否能够以消灭个人利益为手段而存在 ? 民主是否能够让我们社会运转地更好 ? 一个依赖集体利益权衡出来的社会形态是否真的是我们作为个体所想到达到的那个 ? 个人价值是被民主所发挥了还是被民主所专制了 ? 这些问题我都没有答案。我所能够确定的,仅仅就是我们大部分人都希望活得久,活得好。而以此为目的来倒推以上问题的话,不得不承认,的确是存在许多千差万别的实现途径的 ,如此的话,那么很多名词的褒贬色彩可能的确需要我们去重新考量一下了。

欧游散记 —— 维也纳

2018-03-04 08:00:00

刚到维也纳车站的时候,有种回到了德国的错觉。维也纳是继柏林之后的第二大德语区,同时在一些标识牌等市政设施上也和德国相近。奥地利的人均GDP水平甚至还要比德国高。 维也纳给我最大的感受就是干净和自动化。 维也纳市政厅门口广场底下那个公共卫生间可能是我有生以来见过的最最干净的免费卫生间,中间居然还有一个环卫工人办公室。我在那个卫生间有足足驻足欣赏了10分钟之久,一方面是想观察它到底有多干净,另一方面是想弄明白究竟是怎么一种操作能够使得一个闹市区的免费卫生间能够达到如此的干净程度。 维也纳的自动化程度也是在欧洲国家里极其罕见的。印象最深的是,我在从进入机场到上飞机,基本上没有秏费任何一个劳动力的协助。很多机场再智能最后还是要一个乘务员来检票和检查护照,但维也纳机场是自己扫码上机,并且不需要检查护照。去超市买东西,也都是完全自主扫码结单,大部分工作人员都在忙着上架商品,只有一个收银台是在人工运营。我在维也纳住的一个青年旅舍,在到之前它邮箱发给了我一个Code,我到了以后可以用那个Code开旅馆大门、我房间门和我床位的锁,第一次开就代表了Check In完成。去客厅买食物也完全是售货机。在旅馆的两天,除了清洁工以外,我没有见到过一个工作人员,这种体验真的是太完美了。 青旅 作为全球最宜居的城市,维也纳无论是环境还是市政都配得上这个称号。如果全世界让我随便移民的话,可能首选就是维也纳。尤其是如果我还有孩子家庭的话,维也纳几乎可以算是不二之选。整个城市有大量的植被覆盖,公园密度覆盖高,公共交通极其发达,冬天还有各种自造的溜冰场 。较之伦敦巴黎纽约又没有那么多的游客,而且奥地利这个国家本身在国际社会上也非常低调,几乎可以算是岁月静好的现实定义了。 市中心溜冰场 公园 植被覆盖 维也纳还是国际间谍之都,据称有7000多家间谍机构注册在案。碟中碟很多场景就在维也纳拍摄。一方面当然是由于奥地利是永久中立国,另一方面可能也是因为它非常适合海外间谍举家居住吧。 在哈布斯堡王朝的时候,维也纳也曾是整个欧洲的文化政治中心,即便是当年希特勒的年代,他也还是要前往维也纳来考取艺术学院。二战的时候,德国吞并了维也纳,导致维也纳成为了纳粹德国的一部分。二战结束,维也纳陷入被苏联和西方战胜国共同占领的命运,知道1955年奥地利才完全独立。二战后,维也纳人口大量减少,反而成为了一个移民城市。之后巴黎逐渐取代了她的地位,成为了如今的欧洲时尚之都。维也纳曾经的辉煌虽然不再,但这座城市本身还依旧保留着浓烈的贵妇人气质。

欧游散记 —— 西班牙

2018-03-03 08:00:00

西班牙是我最喜欢的一个欧洲国家之一,坐落在伊比利亚半岛,位处欧洲与非洲的交界,最南处离非洲仅15公里,西接大西洋东接地中海。如此的地理位置决定了西班牙从史前时期就是一个多民族多文化交融的地方,即便今天还能看到这种交融的痕迹。 公园前1世纪,罗马人驱逐了伊比利亚半岛上的凯尔特伊比利亚人,并划分为近西班牙省、远西班牙省。在西班牙建立了完善的市镇体制和基督教信仰。西班牙虽然当时只是罗马的两个行省,却诞生过三任罗马皇帝,其中最著名的有图拉真和哈德良皇帝。如今在塞哥维亚还能看到保存良好的古罗马水道桥。 塞哥维亚古罗马水道桥 公元5世纪,罗马帝国衰落,西哥特人控制了伊比利亚的大部分土地,建立西哥特王国,并定都托莱多。西哥特国王之后也改信了罗马天主教。 托莱多古城 公元8世纪,北非阿拉伯人(摩尔人)入侵西班牙,开始了为期八百年的伊斯兰统治。摩尔人统治时期,西班牙的建筑、文化、经济都取得了辉煌的成就。 在摩尔人统治时期,基督教和伊斯兰教互相斗争,在伊比利亚半岛中部形成了大大小小诸多王国。摩尔人统治的南方地区如今统称为安达卢西亚 (即今天的格拉纳达、塞维利亚、加的斯、科尔多瓦等省),北方的基督教王国里最大的两支是卡斯蒂利亚和阿拉贡,在不同时期还前前后后有诸多王国涌现。西班牙这种各立山头的历史背景也导致了直到今天,他的各个省市都会根据自己的历史背景设计徽章,很多就是从古时候的王国国徽里演变过来的,有非常强的地区文化认同感。 伊斯兰教长达800年的统治使得如今的西班牙保留了非常浓烈的阿拉伯风格,在安达卢西亚的大街小巷都能够看到非常细碎且严格对称的花纹,一些庭院里都会有非常精巧的水道设计,很具有沙漠民族色彩。即便是在基督教教堂,由于很多教堂当时都是在原先摩尔人遗留下来的清真寺上建的,所以也保存了相当多的伊斯兰元素,尤其是在庭院设计上。 在伊斯兰统治时期,穆斯林对异教十分宽容,使得西班牙同时居住着大量犹太人。今天我们去一些老城区还会看到很多精致幽静的社区,都是当年犹太人遗留下来的痕迹。 格拉纳达的阿尔罕布拉宫 格拉纳达的摩尔人庭院 塞维利亚的摩尔人王宫 公元15世纪,卡斯蒂利亚公主伊莎贝尔一世和阿拉贡王子斐迪南二世联姻,被称为“天主教双王”。联姻使两人得以共同统治绝大部分西班牙领土,并开始了基督教的复兴。此时的哥伦布在经过十几年的游说后,终于在1492年与伊莎贝尔和斐迪南的王室达成协议,王室同意资助他的旅行并允许他将从新土地的总收入中提成10% 。哥伦布的遗骨最后葬在了塞维利亚的主教堂里。有趣的是,哥伦布当年只是逝世在一个普通的旅馆里,而教堂里所展现的却是一幅国葬场面。 基督教重新掌权后,放弃了穆斯林之前的开明宗教策略,不仅改建或摧毁了清真寺,还开始了对犹太人大规模的驱逐和屠杀,导致西班牙犹太人开始了新的流亡生涯。今天北美就有很多西班牙犹太人后裔。 塞维利亚全景 塞维利亚主教堂 塞维利亚主教堂里的哥伦布遗骨 公元16世纪和17世纪,西班牙开始了殖民旅途,成为了第一批堪称“日不落”的国家,同时也让西班牙语成为了今天世界上第三大语种。在这期间的大部分时间里,西班牙都是整个欧洲最强盛的国家。但西班牙的王室并没有把大把掠夺来的财富用于本国发展,而是挥霍在各种物质生活和发展军备上,导致其经济实力远远落后于其军事实力。随着北方法国的崛起,西班牙日渐式微。在18世纪初始爆发了著名的“西班牙王位继承战争”。最终西班牙落入了波旁王朝手中,一直延续到了今天。 今天的西班牙从经济上早已落魄,失业率高达17%。德国是3% , 再不济的意大利也只有11% 。但西班牙的旅游业实力在全球是排名第一的。一方面由于其物价水平较之大部分欧洲国家都算是非常低的,我记得同一个薯片在德国要2欧,在奥地利要2.5欧,在西班牙只要1.2欧。另一方面也归功于其独特复杂的历史文化背景,使得其对全世界人民都能产生强烈的异国风情体验,这也是为什么很多游戏喜欢以西班牙作为题材背景的原因。 从治安上讲也远远好于西边的葡萄牙和北边的法国,并且人种相对单纯很多,没有太多种族问题。 在意识形态上,西班牙非常的“中国”,完全不理睬北方欧洲佬的那一套动物保护主义环境保护主义种族平等主义的价值观,基本上是怎么舒服怎么来。马德里火车站直接把上百只乌龟养在了一个池子里,看着十分壮观,但在北欧显然肯定会被动保的人喷死。街头的垃圾桶基本上也没什么分类要求,都是随便丢,我好像也只在巴塞罗那的居民区看到过垃圾分类的标识。在西班牙基本上看不到多少黑人,人种非常纯粹,不会有那么多移民问题难民问题。西班牙人总体给人的印象就是一个非常实用主义和放松的民族,这点和中国非常相似。

欧游散记 —— 德国国会大厦

2018-03-02 08:00:00

德国国会大厦 ( Reichstag ) 是柏林的标志性建筑,在1894至1933年间先后用作德意志帝国议会和魏玛共和国议会。1933年发生了著名的“国会纵火案”,希特勒借此大力渲染以促成兴登堡总统签署《国会纵火法令》,废除了魏玛共和国《宪法》里的诸多公民的权力和自由,后来亦成为监禁反对人士和镇压不与纳粹政权合作的报刊的法律依据。之后国会大厦一直处于废弃状态,直到1990年,两德统一,西德决定将首都从波恩迁回到柏林,并确定国会大厦为议会地址。之后开始了对国会大厦的改建工作。 国会大厦对公众全面开放,可以在网上预约申请,参观分为穹顶和内部导览,内部导览会有一个议会工作人员来为你介绍里面各个部分的历史和功用,也会以一个德国公民的角度给你吐槽现任各个领袖的特点、习惯甚至是工资待遇。 进门的大厅中央是议会大厅,顶部悬挂着一只巨大的“联邦之鹰”,这个鹰比其国会和机关文件上的鹰都要丰满很多,所以也被称之为“胖母鸡 ( Fette Henne )”。 议会大厅的顶部是著名的玻璃穹顶,原先旧有的玻璃穹顶在1945年被空袭炸毁,直到1991年改建的时候才又重新加上。议会大厅从周围墙壁到穹顶大部分面积都是采用玻璃材料,除了确保室内自然采光意外,也凸显了其政治透明。当议会正在开会时,虽然内部参观会取消,但公众依旧可以登上穹顶观看自己所在选区议员的一举一动。 在边上的走廊里,有一面墙,墙上涂满了当年攻克国会大厦的苏联士兵的涂鸦,有人刻下了自己的名字,有人刻下了久别的女友,有人刻下了自己的家乡,当然也有各种污言秽语。在翻修国会大厦的时候德国人还争论过是否要把这些涂鸦抹掉,毕竟把这些羞辱性的话语留在墙上有失一个议会的尊严,但最后还是决定保留了下来。试想天天路过这些涂鸦的议员们,怎么可能还会去表决发动一场战争。 再往里走是一个供议员们使用的祷告室,侧角还有一个供穆斯林使用的朝向麦加的小空地。这个房间里陈放的艺术品用于帮助议员在沉静中冥想,以做出对自己对选民对信仰负责的决定。 国会大厦四周是柏林政治中心带,有默克尔办公的德国总理府,草拟议会文件的议会大楼,监督政府和议员的各选区地方报纸驻柏林的记者大楼,还有一些联邦政府机构办公区域。身处其中能够感受到这个国家的政治正如一台精密的机器一般运作着。 国会大厦只是这台机器的外壳,德国人在选举制度上的设计也非常的精密。德国是代议制民主,由议员选举出总理。这个一定程度避免了像美国大选时候那样靠比拼广告费和心理学钻营去赢得普通人民的关注。其次,人民拥有两张选票,一张投给该选区议员,一张投给政党。议会席次里,一半是直选议员,一半是拿到了政党投票的政党自己选拔出的议员。打个比方就是,我认可这个保守党议员但我不认同他所在的党派价值观,所以我可以既尊重我的个人喜好也同时保留我的意识形态观念,对于议员也更加可以在党派立场上有更多的个人色彩 。德国还允许没有达到绝对多数的政党组成联合政府,更能体现这种民主的妥协性而不是一锤子买卖。在德国的选举法上还有很多细节也非常耐人寻味,其归根结底的目的,都是在最大限度地让民意能够100%传达到议会格局上 。

社会矛盾讨论统一框架的一种可能性

2018-02-12 08:00:00

这篇文章只是在飞机上做的一个思维游戏,标题取的太大,没有什么严谨性,不一定代表本人立场,看看就好。 如今在社交媒体上去讨论 “女权主义”、“种族主义”、“宗教自由” 等问题都是一件非常危险的事情。每一个话题里都会有水火不容的两派观点,即便是在同一个立场上,还会存在基于不同逻辑演化出的不同分支观点 。社交媒体上普遍存在的一类人就是在自己国家政治上反对独裁,对西方国家民主又报以鄙夷,向往信仰自由的同时又张口闭口以“绿绿”来蔑称穆斯林。这很大程度是由于缺乏一个值得推敲的框架来思考的结果。 女权主义,人人平等,种族主义,但在这些繁复的名词下,我认为是存在一个普适的框架来规范彼此的讨论的。 小说家在构造人物的时候,通常会赋予他性别,长相,身高,种族等外在因素,还会用一系列事件去刻画他的为人和三观。人就是在诸多附于其上的属性的共同合力之下才之所以为其人 。武则天女性的身份让她成为了中国历史上的一个独特的存在,奥巴马的黑人身份也帮他赢得了不少的认同。但这些仅仅只是他们身上存在的一个属性,在特殊的历史时期的确不同的属性的权重会略微大一些。 如果你把性别,种族,宗教都当作是一种互相平等的附加属性,很可能就能够讨论清楚社交媒体上的诸多话题了。我尝试性的建立了两个“定理”,试图用这两个定理来理清常见的社交媒体争论: 一个人应当由多方位的属性来定义其人。 各个属性之间只有因外界影响而导致的权重占比不同,但属性本身之间没有高低之分。 第一条定理我相信所有受过教育的人都会承认。第二条定理我这里解释下,比如是否色盲和是否是黑人是两个属性,如果我是一个UI设计师,显然是否色盲比是否是黑人对我的职业和生活影响都大的多,但并不意味着色盲这件事情本身就比种族因素要重要。当我去从事作家工作的时候,这两个因素的权重又都会降的很低,相反文字能力这个属性的权重就升高了。所以我说属性的权重是会动态变化的,而属性本身是没有高低贵贱之分的。 如果你不同意上述两个定理,你可以辩驳定理本身。如果你认同定理但不认同下述推导,你可以基于定理提出你的推导过程。但请不要直接拿你的那套理论来直接辩驳下面的推导结果,那样会显得你很没有科学素养。 我们先以职场领域来做一个推导。马云说 : “ 女人爱商比男人高,所以阿里巴巴更喜欢招女性。” 这句话的错误在于,用性别和爱商是两个或许具有统计学上的相关性的属性,但它们之间并没有因果性。但如果阿里首先通过性别来筛选简历,不招男性,然后在女性里进行爱商维度的面试。这样我觉得是合理的。阿里根据自己的经验去认为女性比男性爱商高,然后在自己的企业里进行这种效率筛选完全合法合规。就算他遗漏了那些爱商高的男性,但他提升了效率。如果在这个问题上去宣扬歧视男性的话,同样可以在这个问题上去宣扬歧视爱商低的人,因为根据第二条定理,各个属性是没有高低之分的。如果你是一个爱商高并且就铁了心一定要进阿里的男性,你能够做的不是去宣扬阿里歧视男性 —— 因为阿里并没有歧视,它就是想要提高效率 —— 而是去向阿里展示你是那个违反他们经验的例外,或者直接证明他们的经验是错的。 在教育领域,美国的平权法案为黑人群体提供了优于其它族裔的机会,中国有少数民族加分政策,在这两个案例上,我们用上面两个定理来看,假设大学看重的是一个人的“知识储备”、“学习能力”、“种族”等多维度属性。如果这个大学就是有一个愿望是提升校园里的种族多样性,以提升学生对其它种族的了解程度和接受程度,从而循序渐进促使整个社会更加能够容纳其它种族,那么的确种族会在大学考量学生的时候占据很高的权重。而大学也有权力依据自己的需求来选择自己的学生。这个和弥补历史上翻下的错误没有关系,完全是实用主义的角度来考量。 仔细观察上面的例子,显然会发现它其实忽略了公平原则 。但是公平原则是一个人为概念,且这个概念自己就不能逻辑自洽。例如显然我们需要在选举的时候筛选掉蠢的那批人,而这种筛选就是对智力低下者的不公平。如果你要来辩驳这个说法,你要么承认蠢的人也有资格当选leader,要么承认并不是所有的属性都应当具备平等的重要性。当你说出后者的时候,你本身就是站在一个不公平的视角下在谈论公平。 公平从来都是一个伪概念。在古代的贵族社会里,人人都认可特权的存在,公平的争议只会暴发在贵族与贵族之间,平民与平民之间,奴隶与奴隶之间。有兴趣的人可以去观摩下汉谟拉比法典,与其说是法典不如是一个时代对公平的定义手册。即便是现代社会,诸多社会关系也可以映射到汉谟拉比法典的框架中去,只不过相比古代,现代的平民数量大幅提升,平民内部的阶级也越来越多,越来越细。富人,官人,高智商者,艺术家,技术从业者,教师等标签的背后,都是资本、权力、智力、创造力、消费能力、教育资源分配的不均匀,教师赚钱难,程序员小孩上学难,艺术家独立难等等都是现代不公平生活的写照。公平是一个伟大的共产主义理想,但也通过实践证明了并不是一个社会良好运转的好机制。如果你能够承认不公平是一个合理的正常态,也就能够认同上面定理所推导出来的世界。 我前面说了,这两个定理是试图为社交媒体上的意识形态交流建立一个可沟通的框架,而不至于陷入混战。同时也能够将各方的利益诉求化解到同一个目标上,让斗争更为合理。 比如在女权主义的话题下总会有人提到机会平等。那么如果对性别机会平等了的话,不同学历者是不是也要机会平等,不同能力者是不是也要机会平等 ?有些人说性别的差异不是能够选择的,能力则不同。说这话的人大概是忽视了所处国家,家庭背景,个人遭遇,智力这些东西也同样不是能够选择的这一事实了。机会平等是你依据自己的私欲想要的一个结果,而非你所遭遇到的问题本身,也不是一个解决问题的办法 。你所遭遇到的问题本身是社会上存在这种基于性别的职业偏见,而这个偏见在男性女性下都存在 。如果你的宣传点都局限于争取女性的权力,从理论角度来讲就是错的,从实用角度来讲,我一个直男每天996的工作,好不容易上个网为什么要为你去发声呢 ?但如果你宣扬的是消灭基于性别的职业偏见,那你的努力就是在减少性别在一些领域的占比,让大家用更为科学的方式来筛选人,而不是追求以公平的方式。而选择以这种方式斗争的话,能够争取到的支持者显然更为广阔。我所处的计算机行业就是这种科学斗争的典型,以前大家很流行去考个硕士博士,后来许多本科生,专科生通过自己的能力证明了自己从而降低了学历在面试时候的权重,导致现在大部分互联网公司都不会去卡学历了。这种做法是否正确可能有待商榷,但觉得不爽的大可通过自己的实力再把这个行业给扭转过来,这个就是一个互相博弈,科学进步的过程。博弈的目标是去改变权重占比,而不是去追求公平。 比如在地域歧视上总是有人利用道德压力去阻止人开地域歧视的玩笑,而不是去改变这个地域本身给人的印象。如果从公安数据上统计出来有相当比例的偷盗都是X省的人干的,那显然偷盗就是X省的特色,哪个省份没点黑料,有什么可以辩解的呢 ?如果你要反对,那么对象应该是以地域来判断一个人的权重,这种斗争方式总比道德绑架要文明的多了。 按照上面这种方法去思考的话,许多垂直领域里的矛盾最终本质都能够被归结到一个普遍存在的社会矛盾上,斗争方式也更加成熟。即便是在独裁国家也有大量通过舆论和媒体改变社会偏见的案例,而这些案例的普遍特征是斗争目标极为具体和单一。

欧游散记 —— 伪君子布拉格

2018-02-11 08:00:00

小时候很喜欢米兰昆德拉和卡夫卡,布拉格一直是一个我心中非常神往的城市。但在踏上布拉格土地的那一刻,这个城市带给我的只有无穷无尽的失望和可笑。 在历史上,布拉格早期是波西米亚的首都,中世纪也曾两度成为神圣罗马帝国的首都,即便如此,这个城市里也并没有留下什么让人眼前一亮的东西。虽遍及了哥特式、巴洛克式的建筑,但据有代表性的并没有多少。其自称文艺之都,但鲜能找到具有真正文化含量的东西。作为如今捷克共和国的首都,一点也没有首都应有的样子。而同为文艺之都的巴黎和维也纳就和布拉格截然不同,他们都保持了首都应有的威仪和典雅,并不故意去迎合游客,游览这些城市就像观看一本小说,需要自己去细细体会。而布拉格就像是一个全身按摩,用非常故意和夸张的力道来让你全身心感受到“文艺”的的薰陶。 布拉格的所谓的“博物馆” 典型就是那种实在没什么景点好开了,就生搬硬凑来充数。比如 “Apple Museum” ,可能是这个世界上最僵硬可笑的博物馆了 ,搞得 Apple 和布拉格有什么莫大关系似的 。还有一个“性器博物馆”,虽然我承认这些博物馆本身有存在价值,但是其展品质量和数量实在难以同其票价相等价。关键是这些博物馆是没有布拉格特色的,而在布拉格,我还真没有找到一个足以展现整个布拉格城市发展史的博物馆,而我认为这是一个古城和首都的必需品。 最让我无法接受的是在卡夫卡的22号故居里,贪婪到极限的捷克人居然在其房间里铺满了各种用来销售的明信片和纪念品,以至于完全看不出这个狭小故居原来的模样,和普通小店没有区别。布拉格人在卡夫卡活着的时候不能去发现他,在他死后还要用如此竭泽而渔的方式去利用他,用卡夫卡所恶心的方式去营销卡夫卡周边,其流氓程度在欧洲国家里实属罕见。 布拉格的人更是不怎么靠谱。约好的2点钟会2点30分人才到,在网上po出了住宿信息电话留的却是别人的。满大街的货币兑换点明明写着0手续费,却在汇率上做手脚坑游客。我住的还是一个四星级的酒店,而check out 的时候工作人员连自己的酒店房间是怎么样的都弄不清楚,明明只给了我钥匙还死命问我要房卡。 布拉格郊区有一个佩特任瞭望塔,仿照巴黎艾菲尔铁塔建造,但只用了4个月就完工了,高度仅为60米。而艾菲尔铁塔是320米。这座铁塔在我看来充分体现了布拉格人的精神,空有装逼的心却缺乏装逼的能力,最终就做了一个不伦不类的东西潦草结束,纸上谈兵的典型。 在近现代,布拉格在世界上一直有着不错的曝光率。比如为中国人所熟知的“天鹅绒革命”。坦率讲,就这么一个小城市加上这么一群不靠谱的人,任何据有煽动性的理论都有成功的可能性 。在咖啡馆高谈阔论一番,争取下这100万城市人口的大多数,就足以颠覆掉这个国家的政府了。而以捷克的“天鹅绒革命”为理论依据来试图进口到国内的中国学者,不是蠢就是坏,因为这两者几乎没有什么可比性。 捷克紧邻德国和奥地利,地处欧洲中心,伏尔塔瓦河贯穿整个国家。从地理位置上看占据了非常大的优势,但这个国家的人均GDP只有奥地利和德国的一半不到。真的是空有一身好皮囊,白白被这些莽夫给糟蹋了。 值得注意的是,与卡夫卡同样著名的作家米兰昆德拉在布拉格却几乎没有任何官方宣传的痕迹,我想这很可能是因为昆德拉一直坚称自己是一个法国作家而与祖国捷克决裂的原因吧。但如果政府单单就因为这种原因而去掩盖一个世界级作家在这座城市的痕迹,未免有点太过小气。 总的来说,布拉格人把他们的城市当作了一个迪士尼在运营 ,然而做的却还没有人家迪士尼做的好,这真的是一个可悲的事情。

Lemon : Koa 风格的 Python 异步 Web 框架

2018-01-08 08:00:00

前段时间想要写一些简短高效的 API ,背后的工作无非就是一些计算和数据处理,但是可能并发量会比较高。当我在 Python 的生态里去搜寻一些靠谱的 Web 框架时,很难找到一个设计优秀且运行效率高的框架。尤其是当我已经习惯了 NodeJS 的 Koa 那种简洁明了的设计时,很难再喜欢上像 Flask 那种装饰器的写法和各种概念拢杂在一起的设计。最后实在没有办法,就自己写了个符合我个人审美的框架 Lemon 。 什么是 Web 框架 在讲 Lemon 的设计前,我们先来看一看一个请求是如何被响应的 ,以及框架在其中的作用是什么 : 当一个请求从客户端进入服务器,首先以 TCP 包的形式被接收到,这个时候我们需要手动去建立连接,接收到 TCP 报文后我们需要去判断它的协议,然后再解成相应协议(一般都是HTTP协议)的报文,传给 application 去处理,当处理完后还要把结果写回成 TCP 报文传回去,如果有 keep alive 还需要去手动管理这个连接的生命周期。以上这些统称为 server 部分。 而和开发者关系最密切的 application 部分做的就是拿到一个 http 或其它协议的报文,然后根据其信息做针对性的响应,传给 server 。 无论你是使用任何语言任何框架,都逃不开上面这个处理过程。而我们在比较框架的时候,无外乎是对以下几个方面做一些针对性的优化: TCP报文解析成 HTTP(或其它协议) 报文的效率 并发策略 (多线程,多进程,协程,线程池,Event Loop) application 本身的运行效率 (由框架的效率,所用的语言和使用者自身的代码质量共同决定) 对于第一点,python有一个叫 httptools 的库就是使用了 NodeJS 的 http parser 以达到高性能解包的目的。 针对第二点,有许多OS层面和工程层面的技术在致力于解决这个问题。uvloop 就是其中的一种,采用事件循环的方式,底层用的是 libuv , 同样也是 NodeJS 的底层异步IO库 。

Golang : Make Programming Happy Again

2017-12-30 08:00:00

之前在公司内部做技术分享写的一个关于 Golang 的 slide ,花费了挺多的时间的,所以就脱敏了发出来。个人觉得值得一看 ,至少 PPT 的设计很不错 :) 。 Google Doc 地址 : https://docs.google.com/presentation/d/1odpCfHE5dp7acgK_7lkqTPgHlvT-jPg-XtM7-s9WQZw/edit?usp=sharing

Podcast 闲言碎语

2017-10-10 08:00:00

Podcast 即常说的「播客」,在广义上既包括音频也包括视频。而狭义却在随着时代变迁出现了不同的偏向。 前 Podcast 时代 在当年博客热火朝天的时候,许多博主会有在博文中嵌入视频的需求,新浪顺势地推出了它的播客频道。这个阶段对于播客的定义接近于「个人上传的视频」。但事实上当时的播客很大一部分都是关于旅行和一些社会事件,即便是旅行也都是在拍摄大自然和风土人情,很少会嵌入个人色彩。有一个如今已经死掉了的视频网站 —— 土豆网当时的 slogan 就叫做 「每个人都是生活的导演」。土豆网最早明白播客的核心价值,但那个时代个人要拍摄一段视频的成本其实是非常高的,大部分手机没有视频功能,即便有拍摄质量也堪忧。再者拍摄完一段视频需要一台起码中等以上的PC才能完成剪辑工作,还需要一个不错的宽带才能花个几小时把视频上传到平台上。而且当时的宽带都不是光纤,上下行速度是不对等的,我记得那时候我的上传速度最高也只能到 30kb/s 。如今以快手抖音为代表的短视频浪潮无非是在延续土豆网当时的 slogan 。比较讽刺的是,现在土豆网的 slogan 却变成了 「召唤全球有趣短视频」。 在前 Podcast 时代,播客的定义被局限在视频,所谓的音频节目都是些收音机里的夜间电台或者是一些视频节目的音频版本 。而在后 Podcast 时代,我们口语中所谈论的播客更加偏向于音频。 后 Podcast 时代 到了2012年以后,涌现出了许多像荔枝FM之类的音频播客节目。这个时间恰好也是智能机浪潮的开始,也正是智能手机给了这些 App 足够的硬件能力来录制和传播优秀播客节目。包括那个时候的荔枝FM App ,其设计和交互也是我使用过的国产 App 里最好的。我记得当时我还在高中,晚上经常在上面听一些旅行主播聊天南海北的奇闻轶事,那样的日子真的太美好了。 播客与博客最大的区别在于,当我们在书写文字的时候,经历的过程是 : 思考 -> 书写 -> 阅读写下的内容 -> 思考 。整个过程太过严谨,从而丧失了口语的魅力和乐趣。优秀的播客主播他在言谈的时候一定是极为放松的,也不会太过于去追求言谈的准确性,何况很多时候你意识到自己说的话有不严谨之处的时候,你已经说完话了。 音频播客的流行并不意味着以前的视频形式落伍了。相反,正是由于视频内容开始真正被普及,使其从播客的定义中被剥离了出来,自成一体。如今的视频内容,往下走有诸如「短视频」之类的叫法,往上走会有人叫 「Vlog」。Vlog 继承了原先播客的精神内核,从表现人与自然过渡到表现自我。短视频则走向平民化和低俗化,从而成为了如今内容的第一载体。 如果仔细观察这个趋势会发现,技术的发展使得内容的表现能力大幅提升,但问题在于,创作者的能力并没有多少提升,我们的技术并未在帮助创作者提升创作能力这件事情上有多少努力。 从前文字类的博客对作者的能力要求其实并不高,你有基本的书写能力,表达一个悲惨的个人故事或者一个实际的社会问题,受众多少都能有所感触 。何况我们阅读博客本身就不是怀着阅读文学作品的心态去的。而音频类播客开始对作者的智商、情商、表达能力、反应速度、记忆力、话题掌控能力都提出来不低的要求。就我这七八年里所听过的超过9成的播客节目的作者都没有完全达到上述几点的要求,而且我深知如果我自己做播客肯定连一项都无法达标。而 Vlog 就更加是创作者的噩梦了。我大一时候听过中文系老师讲话剧课,老师训练过我们面向大众以最自然的姿势站立一分钟。在这一分钟里,绝大部分人会在后30s开始东张西望,手不知道放哪里,眼神飘忽不定。我们日常在开会的时候,大家会觉得好像面向大众表达很简单,但如果你站起来面对大家讲话感受就会不一样。如果让大家都目不转睛盯着你看你讲,那又完全是另外一个心态。Vlog 相当于你需要排除路上别人异样的眼神然后面对可能几百个也可能几百万个观众自言自语。同时播客剪辑大部分只是 「剪」,Vlog 还要求「辑 。并且受众对视频内容的挑剔程度会更加高。许多 Vlog 作者自己的脸上就写满了尴尬,很难让人坚持看下去。你甚至都很难在这个 14 亿人口的国家里举出超过 10 个优秀的 Vlog 作者出来。 国内目前流行的诸如「快手」这类短视频软件里的确有许多不错的作品,但绝大部份属于演绎一个想象中的自己,或是贩卖尴尬,其视频时长和一张 Gif 差不多,内容也很单调。完全称不上上述讨论的任何一个品种范畴。其流行本身就是一个复杂的社会话题,和内容形式的关系并没有坊间所吹捧的那么大。 我与 Podcast 我从初中开始养成了一个需要听音频才能入睡的习惯,如果不听点什么东西,我就会脑子胡想,只有听播客才能让我大脑放掉戒备。最早我是把袁腾飞的课堂视频转成音频来听,后来发现了了锵锵三人行,就开始从01年的节目开始听。说来惭愧,我的性启蒙以及对待性差异群体的认同度都是听锵锵三人行学来的。只可惜这个节目显然超出了初中生的文化水平从而在今年被掐掉了。大学以后转用 iPhone , 开始在 iOS 的 Podcast 客户端上发掘到了一大批优秀的播客,但正如前述,即便是音频节目,对作者的要求也实在是太高,许多节目要么由于时间原因出现了月更年更的现象,要么由于作者自身涉猎范围有限最后只能不了了之。

Kubernetes 中使用 API Gateway 替代 Ingress

2017-09-12 08:00:00

背景 最近在构思基于 Kubernetes 建立一个个人的开放云平台 , 听起来有点不自量力 , 不过作为个人业余小玩意还是蛮好玩的。最终的成品希望是用户能够轻松地在平台上跑一些简单的无状态服务 和 cronjob 。 在搭建平台的时候遇到的第一个困难是需要有一个好用且功能全面的 API Gateway , 主流的网关服务大多是基于 OpenResty 基础上进行二次开发 , 所需要完成的工作无非是负载均衡,和 API 管理, 加上一些零零碎碎的小功能。 负载均衡 负载均衡分为四层和七层两种 , 以大家所熟知的 Nginx 为例 , 在它的 conf 文件中 , 有 http {} 和 stream {} 两种 block 。 # 四层负载均衡 stream { server { listen 80; proxy_pass app; } upstream app { server 172.31.0.1:8000; } } # 七层负载均衡 http { upstream app { server 192.168.0.1:8000; server 192.

ElasticSearch 最佳实践

2017-05-28 08:00:00

Elasticsearch 是一个需要不停调参数的庞然大物 , 从其自身的设置到JVM层面, 有着无数的参数需要根据业务的变化进行调整。最近采用3台 AWS r3.2xlarge , 32GB, 4核, 构建了一套日均日志量过亿的 EFK 套件。经过不停地查阅文档进行调整优化 , 目前日常CPU占用只在30% , 大部分 Kibana 内的查询都能在 5s ~ 15s 内完成。 下面记录了一些实践过程中积累的经验。 硬件 CPU 多核胜过高性能单核CPU 实践中发现, 在高写入低查询的场景下, 日常状态时 , CPU 还能基本应付, 一旦进行 kibana 上的查询或者 force merge 时, CPU 会瞬间飙高, 从而导致写入变慢, 需要很长一段时间 cpu 才能降下来。 Mem Elasticsearch 需要使用大量的堆内存, 而 Lucene 则需要消耗大量非堆内存 (off-heap)。推荐给 ES 设置本机内存的一半, 如32G 内存的机器上, 设置 -Xmx16g -Xms16g ,剩下的内存给 Lucene 。 如果你不需要对分词字符串做聚合计算(例如,不需要 fielddata )可以考虑降低堆内存。堆内存越小,Elasticsearch(更快的 GC)和 Lucene(更多的内存用于缓存)的性能越好。 由于 JVM 的一些机制 , 内存并不是越大越好, 推荐最大只设置到 31 GB 。 禁用 swap sudo swapoff -a 配置 PS: 应该尽可能使用 ansible 这类工具去管理集群 , 否则集群内机器的状态不一致将是一场噩梦。

0001-01-01 08:00:00

p— title: “Kui. 😾” date: 2019-08-30 type: “gallery” draft: false gallery: name: ‘2019.08.31’ url: ‘https://cdn.statically.io/img/blog.joway.io/images/kui/IMG_20190831_191500.jpg?quality=70' name: ‘2019.08.24’ url: ‘https://cdn.statically.io/img/blog.joway.io/images/kui/IMG_20190824_204700.jpg?quality=70' name: ‘2019.07.31’ url: ‘https://cdn.statically.io/img/blog.joway.io/images/kui/IMG_20190731_225526.jpg?quality=70' name: ‘2019.07.15’ url: ‘https://cdn.statically.io/img/blog.joway.io/images/kui/mmexport1563203324319.jpg?quality=70' name: ‘2019.07.11’ url: ‘https://cdn.statically.io/img/blog.joway.io/images/kui/IMG_20190711_003011-edited.jpg?quality=70' name: ‘2019.07.05’ url: ‘https://cdn.statically.io/img/blog.joway.io/images/kui/IMG_20190705_202300.jpg?quality=70'

Presentations

0001-01-01 08:00:00

Design a modern local memory cache, 2019.11 How to write a Database - I/O Stack, 2019.08 How to write a Database - Index, 2019.07 Golang : Make Probleming Happy Again, 2017.12 Django 现代开发 - 入门与实践, 2017.08