很多技术概念都是对现实的映射、类比、模拟。

背景

我对很多技术的理解都是基于现实中的某个概念、实体、过程,因此想把这些记录下来。

人类思考、记忆

CPU-大脑

想想人脑的思考过程,大脑相当于计算机的CPU。

内存buffer-大脑空间

而求解一个问题时我们需要「想一想」,这个过程中产生的临时变量存放的地方则是我们的大脑空间。

计算结果-脑突触

当一个问题有结果时,我们的大脑中产生对应的一个新的突触。

磁盘-永久突触

随着类似逻辑运算次数叠加,我们的结果进行了缓存,形成了大脑中永久长成的突触,下次提取结果时无需重新计算,比如「1+1=2」是大部分上过小学的人都有的突触例子之一。

耗时较长的任务执行(如定时长任务)-暗时间记忆

业务形态为:耗时较长的任务,比如一些业务补偿、延迟执行的定时任务,我们一般放在业务低峰期执行。

而一些白天记忆的东西,计算量、记忆量可能较大,适合我们晚上复习或者睡觉时让大脑自动帮我们跑比较久去复习(利用好暗时间)。

人类行动

提前读-预判了你的预判

通俗来说,cache是编程中一种「提前读」的通用实现。

而打羽毛球的时候,牛逼的运动员可以根据自己将要打的球以及对手动作、习惯做出预判,提前做好接球的准备。整个过程反应极快,将预判动作训练为自己的身体反应(这是一种缓存),所以速度快。

延迟写-脏衣篓里攒了三天的衣服

通俗来说,buffer是编程中一种「延迟写」的通用实现。

使用内存buffer来缓冲后面开销较大的任务的冲击,比如写磁盘开销比内存操作更大(重IO)。

而在洗衣服的场景里,我家里的脏衣篓一般会攒三天的衣服,三四天再跑去洗一次。利用脏衣篓(buffer)缓冲了洗衣任务,而洗衣服对我来说相对开销较大(懒),开销大体现在一方面有心理负担,一方面合租时候去洗的次数多容易与室友冲突。

同时一批任务量也得到了归集内聚,也是一种批量的思路。

某个任务背景下的铺垫-数据预热

此类比也可以描述为:前戏等同于资源初始化。

在真正的业务处理前,我们的系统很有可能需要提前准备一些资源,一般是在PostConstruct或者进程启动阶段执行,就算是进程内部,比如JVM启动也分了好几个阶段:编译、加载、链接、初始化。

如果将初始化、预热的动作延后到真正的任务处理时,那在实现层面就会很冗余、耦合,效率也会降低。比如开会不提前发背景、安排会议室,等到临时才安排,大家就会感受很被动。

所以该预热的一定要提前安排。

主从高可用-公司家两台电脑

19年的时候我买了第二台mbp放家里。在这些情况下比只有一台电脑可用性要高:

  1. 公司的电脑坏了,19年的时候真实发生过;
  2. 突然通知要居家办公;
  3. 下了班需要oncall

既然是主从,一定存在数据同步、一致性的问题。这块我主要依赖:

  1. 云文档; 可以保证数据变更的实时性,飞书文档帮我保证数据读取的一致性;
  2. iCloud; 无法保证实时性的一致,不过由于物理上公司与家有一定距离,并且回到家开启电脑联网也有一定间隔,基本在我需要的时候数据已经从云端下载到了本地目录;
  3. Git仓库; 需要手动提交数据,是典型的分布式数据读取模型;

其中前两项同时保证了Mac与iPhone端的数据一致。

衣物收纳

衣服对于我们来说,相当于信息之于计算机程序。

排序-收纳

收纳:规整、分类衣物。

分盒分类:Hash分桶,按照分类查找时复杂度O(1)

夏季将冬季衣物收进箱子:链表式。此时重点在于将暂时不用的衣物依次放进大箱子,关注收拾的速度,舍弃查找的速度(需要的时候通常全量拿出)。

吃饭

每个人都需要吃饭,而在食堂、饭店吃饭时,我发现对我理解IO、线程池帮助很大。

IO

  • 数据准备;
  • 数据复制;

阻塞、非阻塞-食堂排队时堵不堵?

例如食堂高峰期吃饭时,餐线排队的人很多,但是由于大家打饭、结账时基本是缓慢执行的,所以此时排队并不堵,我们的餐线(IO过程)其实是不堵塞的。

但如果有人结账或者打汤(小米食堂一般汤在最后面)时停留了比较久,比如结账卡找不到了,或者打汤洒了,那么此时餐线也就阻塞了。

对应我们的IO阶段,也就是数据准备、复制阶段任一阶段是否发生阻塞。

线程池-托盘回收传送带

固定长度的托盘回收传送带:线程池队列,带有界capacity。现实中不太可能产生OOM,因为物理资源明显是有限的。

  • 餐盘:线程池任务。
  • 向传送带放一个餐盘:submit一个线程池任务到池。
  • corePoolSize:传送带背后固定的处理人数。
  • maximumPoolSize-corePoolSize:传送带背后流动的处理人数。或者更形象点,一个团队中的外包人数(残酷但是现实:项目用人需求变少时就裁掉外包)。

同步、异步-食堂或者高级饭店

在食堂,一般经过排队之后,我们的饭需要自己端着盘子取、送。这就是同步的,因为食物需要自己取到餐桌上。

而在高级点的饭店(非美食广场),我们点完餐之后(告诉服务端我们要什么数据,你去准备吧!),之后就可以开始玩手机,食物准备好后,服务员会帮我们把食物端到餐桌上(服务端给我们主动发送准备好的数据、拷贝)。这就是异步的,因为当前线程(点菜、吃菜的我)不需要自己执行数据的准备、复制。

IO多路复用-饭店迎宾员

还是在高级饭店的场景下,饭店会配备迎宾员、服务员、厨师,工作专业度强度依次提高。

假设一个200平的饭店,一般情况下一名迎宾员就够了。因为他的任务足够轻快,使用单线程足矣完成。这就是IO多路复用,多路对应n桌客人,复用对应单个迎宾员。

IO线程池计算型线程池分离-岗位分离

迎宾员、服务员、厨师使用不同的人员规模管理、工资,类似于不同业务线程池分离。

迎宾员任务只配备一个线程,而服务员需要配备三个线程,而厨师需要配备五个线程。

人类策略

服务端的大部分接口,其实就是一个func select(request) (response)函数,很多时候所谓的逻辑其实是各种业务策略。

从接收到入参的时候,我们的接口做两件事:

  1. 校验,看入参是否有效。
  2. 处理真正的业务逻辑。

fail-fast-快速跳过不需要的信息

select()阶段一,RD首先要做的是:无效数据直接返回,不进入后续逻辑。 这样节省资源,也无需在后续代码中处理(clean code)。

如同我在支撑000217中所说:人生,也是关乎各种选择。

而快速失败是非常实用的选择策略:

  1. 人生是一场无限游戏,快速尝试快速MVP长远来看可以帮到我们很多;
  2. 选择投资标的时,有时候需要根据「不能接受的点」进行选择,即fail-fast,通过这项校验的标的才有后续深入了解的必要;

二分法算不算一种fail-fast

时间换空间-通勤住得远

很多时候时间换空间是因为我们空间资源不足。

假设我们有一个对实时性要求不高的业务,比如后台导出一份千万数据的报表,一般服务端配置的内存有限,一次查询数据会有IO、程序内存等空间瓶颈。

这种情况下,我们需要选择节省空间(提高空间利用效率)的方案。一般,我会分批处理,比如一批一万条数据,内存可控,并且业务有可控预期(只需要执行一千次)。这就是典型的时间换空间的策略。

人在穷的时候,往往需要使用「时间换空间」策略。比如通勤,选择住比较远的地方,通勤时间长,但是住的大、房租低。居住空间与房租是这种场景下的空间。

反之则是「空间换时间」策略,比如住得近通勤时间短,但是居住空间小。

时间换空间-食堂没座位

食堂打完饭,但是没有座位,怎么办?只能等,或者拿着托盘转悠一会儿。

组织管理安排

  • 分组

    本质上是为了内聚资源,提高资源利用率,并且起到一定的资源隔离作用。

    组织分组对应到技术可能对应这些概念:

    • 池化pooling
    • 分区partition

线程池-人员分组

一般在一个公司内我们会有部门、组,把一个人放在特定的业务方向、组织节点上,而人力外包则一般是把员工当做流动劳动力,哪里需要分派到哪里。

本质上人员分组是一种「线程池」的映射(vice versa)。一组线程池我们起了十个线程,这十个线程就负责这块业务。同时项目需求紧张的时候可以增派外包,这是我们上面提过的maximumPoolSize-corePoolSize

不同的组则对应我们不同的线程池,组与组之间隔离,而不同的线程池也是为了资源隔离。

而人力外包的模式下,单个线程职责不够专注,生命周期内比较难发挥大的价值。所以做人要做重要线程池中的线程,不要做临时new出来的外包单线程。

交通管理、设计

限流-地铁限流、预约服务

地铁在高峰期都会执行限流政策,比如:

  • 广州八号线的万胜围作为枢纽中转站,一般在周五下班时间会使用护栏限制走道宽度;
  • 核酸检测、个税退税都需要预约(可以指定单日只放一万个号);

限制宽度、服务号码预约中心思想是一致的:给服务端(地铁班次、核检网点)一个稳定、有限范围的预期。

限流调度-信号量(红绿灯)、令牌桶、时间窗口

信号量(红绿灯)、令牌桶、时间窗口这些设计本质上是一致的:针对同一资源进行同步化访问控制。

在十字路口,通行权就是同步访问权限。而服务端流量执行业务逻辑,则是对应的同步访问权。

红绿灯+一定路宽在时间、通过量上做了限制,一个通行时间窗口 * 路口单位时间通行量 = 程序中Semaphorepermits量。

排队

AQS队列-无红绿灯式环岛

AQS队列与显式加锁相比,减少了锁开销。本质上与无红绿灯式环岛减少交通冲突逻辑一致。

AQS队列通过排队固定内部的通行速率,这一点在环岛上现实体现为进入环岛的车辆车速较低。

非公平排队策略-插队

以清河地铁站往小米科技园步行的一段路为例,最开始有一个铁门只开了单次通行一个人的空隙。所以大家出了地铁默认都会排队。(不排队大家谁也别想过去!

但是总是有人插队的,这一点是我在广州、北京生活多年的观察结论。

插队就是非公平锁获取的逻辑。对应ReentrantLock.Sync默认为NonFair

对于排队的人(某个节点)来说,插队可以提高自己穿行的效率(提高了吞吐throughput),但是对于整体队列来说肯定是有人受损的(被插队的节点往后都比原先慢了一个通过时间)。

插队提高了单节点的吞吐,但是降低了满载队列通过的公平性(没有什么岁月静好,有的是后面的人给你负重前行了)。

死锁-十字路口你不让我我不让你

以望京地区大山子某个十字路口为例,这个路口有点奇葩,红绿灯同时支持对面车直行+本侧车左转。

碰到严重的时候,这里基本会堵死,因为左转与对侧直行车辆互不相让(owner权限无法正常流转)。

死锁发生后,一般需要系统层面的介入,比如交管同志来介入协调(与数据库死锁后运维同学介入同理)。

人类学习

术 VS 道

具体的技术实现-术

比如技术栈、框架、库、写法,是术。

对应练武功时候的招式。招式需要练习、强化。

原理、思路、方法论-道

比如理论原理:

  • 操作系统
  • 网络
  • 数据结构

思路:

  • 系统设计思路
  • 优化思路

方法论:

  • 设计模式
  • 管理模式
  • 最佳实践

是道。

对应练武功时候的心法。心法需要先学、后悟。是从低阶晋升高阶的底层支撑。