很多技术概念都是对现实的映射、类比、模拟。
背景
我对很多技术的理解都是基于现实中的某个概念、实体、过程,因此想把这些记录下来。
人类思考、记忆
CPU-大脑
想想人脑的思考过程,大脑相当于计算机的CPU。
内存buffer-大脑空间
而求解一个问题时我们需要「想一想」,这个过程中产生的临时变量存放的地方则是我们的大脑空间。
计算结果-脑突触
当一个问题有结果时,我们的大脑中产生对应的一个新的突触。
磁盘-永久突触
随着类似逻辑运算次数叠加,我们的结果进行了缓存,形成了大脑中永久长成的突触,下次提取结果时无需重新计算,比如「1+1=2」是大部分上过小学的人都有的突触例子之一。
耗时较长的任务执行(如定时长任务)-暗时间记忆
业务形态为:耗时较长的任务,比如一些业务补偿、延迟执行的定时任务,我们一般放在业务低峰期执行。
而一些白天记忆的东西,计算量、记忆量可能较大,适合我们晚上复习或者睡觉时让大脑自动帮我们跑比较久去复习(利用好暗时间)。
人类行动
提前读-预判了你的预判
通俗来说,cache
是编程中一种「提前读」的通用实现。
而打羽毛球的时候,牛逼的运动员可以根据自己将要打的球以及对手动作、习惯做出预判,提前做好接球的准备。整个过程反应极快,将预判动作训练为自己的身体反应(这是一种缓存),所以速度快。
延迟写-脏衣篓里攒了三天的衣服
通俗来说,buffer
是编程中一种「延迟写」的通用实现。
使用内存buffer
来缓冲后面开销较大的任务的冲击,比如写磁盘开销比内存操作更大(重IO
)。
而在洗衣服的场景里,我家里的脏衣篓一般会攒三天的衣服,三四天再跑去洗一次。利用脏衣篓(buffer
)缓冲了洗衣任务,而洗衣服对我来说相对开销较大(懒),开销大体现在一方面有心理负担,一方面合租时候去洗的次数多容易与室友冲突。
同时一批任务量也得到了归集内聚,也是一种批量的思路。
某个任务背景下的铺垫-数据预热
此类比也可以描述为:前戏等同于资源初始化。
在真正的业务处理前,我们的系统很有可能需要提前准备一些资源,一般是在PostConstruct
或者进程启动阶段执行,就算是进程内部,比如JVM
启动也分了好几个阶段:编译、加载、链接、初始化。
如果将初始化、预热的动作延后到真正的任务处理时,那在实现层面就会很冗余、耦合,效率也会降低。比如开会不提前发背景、安排会议室,等到临时才安排,大家就会感受很被动。
所以该预热的一定要提前安排。
主从高可用-公司家两台电脑
19年的时候我买了第二台mbp放家里。在这些情况下比只有一台电脑可用性要高:
- 公司的电脑坏了,19年的时候真实发生过;
- 突然通知要居家办公;
- 下了班需要
oncall
;
既然是主从,一定存在数据同步、一致性的问题。这块我主要依赖:
- 云文档; 可以保证数据变更的实时性,飞书文档帮我保证数据读取的一致性;
- iCloud; 无法保证实时性的一致,不过由于物理上公司与家有一定距离,并且回到家开启电脑联网也有一定间隔,基本在我需要的时候数据已经从云端下载到了本地目录;
- 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)
函数,很多时候所谓的逻辑其实是各种业务策略。
从接收到入参的时候,我们的接口做两件事:
- 校验,看入参是否有效。
- 处理真正的业务逻辑。
fail-fast
-快速跳过不需要的信息
在select()
阶段一,RD首先要做的是:无效数据直接返回,不进入后续逻辑。
这样节省资源,也无需在后续代码中处理(clean code
)。
如同我在支撑000217中所说:人生,也是关乎各种选择。
而快速失败是非常实用的选择策略:
- 人生是一场无限游戏,快速尝试快速
MVP
长远来看可以帮到我们很多; - 选择投资标的时,有时候需要根据「不能接受的点」进行选择,即
fail-fast
,通过这项校验的标的才有后续深入了解的必要;
二分法算不算一种fail-fast
?
时间换空间-通勤住得远
很多时候时间换空间是因为我们空间资源不足。
假设我们有一个对实时性要求不高的业务,比如后台导出一份千万数据的报表,一般服务端配置的内存有限,一次查询数据会有IO
、程序内存等空间瓶颈。
这种情况下,我们需要选择节省空间(提高空间利用效率)的方案。一般,我会分批处理,比如一批一万条数据,内存可控,并且业务有可控预期(只需要执行一千次)。这就是典型的时间换空间的策略。
人在穷的时候,往往需要使用「时间换空间」策略。比如通勤,选择住比较远的地方,通勤时间长,但是住的大、房租低。居住空间与房租是这种场景下的空间。
反之则是「空间换时间」策略,比如住得近通勤时间短,但是居住空间小。
时间换空间-食堂没座位
食堂打完饭,但是没有座位,怎么办?只能等,或者拿着托盘转悠一会儿。
组织管理安排
-
分组
本质上是为了内聚资源,提高资源利用率,并且起到一定的资源隔离作用。
组织分组对应到技术可能对应这些概念:
- 池化
pooling
- 分区
partition
- 池化
线程池-人员分组
一般在一个公司内我们会有部门、组,把一个人放在特定的业务方向、组织节点上,而人力外包则一般是把员工当做流动劳动力,哪里需要分派到哪里。
本质上人员分组是一种「线程池」的映射(vice versa)。一组线程池我们起了十个线程,这十个线程就负责这块业务。同时项目需求紧张的时候可以增派外包,这是我们上面提过的maximumPoolSize-corePoolSize
。
不同的组则对应我们不同的线程池,组与组之间隔离,而不同的线程池也是为了资源隔离。
而人力外包的模式下,单个线程职责不够专注,生命周期内比较难发挥大的价值。所以做人要做重要线程池中的线程,不要做临时new
出来的外包单线程。
交通管理、设计
限流-地铁限流、预约服务
地铁在高峰期都会执行限流政策,比如:
- 广州八号线的万胜围作为枢纽中转站,一般在周五下班时间会使用护栏限制走道宽度;
- 核酸检测、个税退税都需要预约(可以指定单日只放一万个号);
限制宽度、服务号码预约中心思想是一致的:给服务端(地铁班次、核检网点)一个稳定、有限范围的预期。
限流调度-信号量(红绿灯)、令牌桶、时间窗口
信号量(红绿灯)、令牌桶、时间窗口这些设计本质上是一致的:针对同一资源进行同步化访问控制。
在十字路口,通行权就是同步访问权限。而服务端流量执行业务逻辑,则是对应的同步访问权。
红绿灯+一定路宽在时间、通过量上做了限制,一个通行时间窗口 * 路口单位时间通行量 = 程序中Semaphore
的permits
量。
排队
AQS
队列-无红绿灯式环岛
AQS
队列与显式加锁相比,减少了锁开销。本质上与无红绿灯式环岛减少交通冲突逻辑一致。
AQS
队列通过排队固定内部的通行速率,这一点在环岛上现实体现为进入环岛的车辆车速较低。
非公平排队策略-插队
以清河地铁站往小米科技园步行的一段路为例,最开始有一个铁门只开了单次通行一个人的空隙。所以大家出了地铁默认都会排队。(不排队大家谁也别想过去!)
但是总是有人插队的,这一点是我在广州、北京生活多年的观察结论。
插队就是非公平锁获取的逻辑。对应ReentrantLock.Sync
默认为NonFair
。
对于排队的人(某个节点)来说,插队可以提高自己穿行的效率(提高了吞吐throughput
),但是对于整体队列来说肯定是有人受损的(被插队的节点往后都比原先慢了一个通过时间)。
插队提高了单节点的吞吐,但是降低了满载队列通过的公平性(没有什么岁月静好,有的是后面的人给你负重前行了)。
死锁-十字路口你不让我我不让你
以望京地区大山子某个十字路口为例,这个路口有点奇葩,红绿灯同时支持对面车直行+本侧车左转。
碰到严重的时候,这里基本会堵死,因为左转与对侧直行车辆互不相让(owner
权限无法正常流转)。
死锁发生后,一般需要系统层面的介入,比如交管同志来介入协调(与数据库死锁后运维同学介入同理)。
人类学习
术 VS 道
具体的技术实现-术
比如技术栈、框架、库、写法,是术。
对应练武功时候的招式。招式需要练习、强化。
原理、思路、方法论-道
比如理论原理:
- 操作系统
- 网络
- 数据结构
思路:
- 系统设计思路
- 优化思路
方法论:
- 设计模式
- 管理模式
- 最佳实践
是道。
对应练武功时候的心法。心法需要先学、后悟。是从低阶晋升高阶的底层支撑。