7AA7 Blog

Zones · Curated

Latest posts, this week's hits and top-rated picks — everything worth reading in the 7AA7 community, in one place.

1updated in 24h 21published this week 5Active authors
Riven·2026-10-15
灰度发布与蓝绿部署:流量切分的工程实现

发布本身不是难点,难点是新版本有问题时能在多大范围快速止损。滚动发布逐个替换实例,回滚要数十分钟,这段时间全部用户都在受影响面。蓝绿和灰度解决的都是这件事:把”回滚”换成”切流”,把止损时间从重建实例的量级压缩到改一次路由配置的量级。 蓝绿与灰度的差异 蓝绿部署维护两套完整环境。当前流量全走绿环境,新版本部署到蓝环境完成自检验证,通过后一次性从绿切到蓝,原绿环境保留一段时间作为回滚目标——回退就是再切回去。 优点直接:切换瞬间完成,回滚也瞬间,不存在”部分实例是新版本”的中间状态。缺点同样直接:要一倍资源冗余,含数据库在内的整套链路要么共享要么同步,成本实打实。 灰度发布(金丝雀)不是一刀切,而是让新版本先承接一小部分流量,观察指标后逐步放量到 100%。资源占用接近常态,能做到渐进验证,代价是放量周期长,且存在新旧版本并行的窗口,这段时间内两套代码都在处理真实请求。 维度 蓝绿 灰度 资源占用 双倍 接近常态 切换速度 一次性全切 分阶段放量 回滚 切回另一套环境 把权重调回 0 适用 版本差异大、强一致要求 常规迭代、需要观察用户行为 选择依据是变化的风险和资源的余量。基础设施层的重大变更(数据库版本、中间件升级)倾向于蓝绿;日常业务迭代倾向于灰度。实际中也常见组合:先用灰度在小范围验证,确认无误后再一次性切全量,此时的”切全量”在效果上等同于蓝绿。 流量切分的依据 放量的第一步是把哪些请求路由到新版本。常见的四种依据各有适用场景: 按权重。最简单,随机把 5% 请求打到新版本。适合验证性能与稳定性,不适合有状态体验——同一用户两次请求落到不同版本,表现可能不一致。 按用户 ID 哈希。对用户标识取哈希后按数值区间分配,同一用户稳定命中同一版本。代价是新版本必须兼容旧版本的数据。 # 对用户标识做哈希,稳定命中同一版本 split_clients "${cookie_uid}${remote_addr}" $canary { 5% "canary"; * "stable"; } 实际参数化时通常用整数区间而不是字符串前缀,方便在不改文法的情况下调整比例: hash(user_id) % 100 < 5 -> 灰度组(可调 5、10、20、50、100) 按…

2568 150 0
灰度发布与蓝绿部署:流量切分的工程实现
码同客·2026-09-22
容器网络入门:CNI、Service 与 Ingress 的流量路径

一台机器上跑多个容器,用 veth pair 加网桥就能互通。跨了机器,问题就变成另一个:Pod 的 IP 是 10.244.1.5,另一台宿主机上的 Pod 要访问它,怎么知道把包发到哪里。容器网络的设计,基本都围绕这个”怎么知道”展开。 要解决的问题:跨主机 Pod 互通 Kubernetes 对网络的要求可概括成三条:Pod 之间不经 NAT 直接互通;节点与 Pod 互通;Pod 自己看到的地址与其他 Pod 看到的一致。三条合起来的效果是 Pod 可以像独立主机一样被看待,应用不必感知 NAT。 单主机内实现这三条不难:每创建一个 Pod,创建一对 veth,一端放进 Pod 的 network namespace 命名成 eth0,另一端留在主机上挂到网桥(cni0 一类),配上地址和默认路由,同一网桥上的 Pod 就在二层互通了。 跨主机才是难点。底层网络只知道宿主机的地址,不知道宿主机上还挂着一段 Pod 网段。让”某个 Pod 网段在哪台机器上”这件事被底层获知,就是 CNI 插件的工作。配套的还有 IPAM:谁来分配地址、如何保证不冲突、节点扩缩容时网段如何伸缩。 CNI 插件:两类实现与它们的代价 CNI 本身是一个接口约定:容器创建时调用插件做 ADD,删除时做 DEL,插件负责往…

2093 118 0
容器网络入门:CNI、Service 与 Ingress 的流量路径
Bit 序章·2026-09-29
从零实现一个最小可用 RAG 系统

完整链路 RAG 不是”接个向量库”这么一件事,它是一条有八个环节的流水线,任何一环掉链子都会体现在最终答案上: 文档解析 → 切块 → embedding → 入库 → 检索 → 重排 → 拼装 → 生成 解析环节最容易被低估。PDF 转文本会丢掉表格结构、把双栏排版读成左右交错、把页眉页脚混进正文。扫描件还得过 OCR。解析质量差,后面所有优化都是白做——检索到的原文本身就是乱的。 切块决定检索的最小粒度。embedding 决定语义空间的几何。入库环节要带上元数据(来源、时间、权限标签),因为过滤要在检索时做。检索环节产出候选。重排把候选里真正相关的提到前面。拼装决定模型看到什么。生成决定答案怎么用这些材料。 切块参数怎么定 三个参数:块大小、重叠、切分依据。 块大小是召回率和精度的拉扯。块太小,语义不完整,一段话被切断后单独编码出来的向量可能指向完全不同的方向,检索时召回的是碎片,模型拼不出完整答案。块太大,一个块里混入多个主题,向量是多个语义的平均,跟任何一个具体问题的相似度都不高,召回时被更”纯粹”的其他块挤掉。 经验起点是 300 到 800 个 token,具体值取决于文档类型:FAQ 类一条一块最合适;技术手册按小节切;法律合同按条款切。没有通用最优值,得用评测集扫一遍。 重叠的作用是防止语义被切断。相邻块之间保留 10% 到 20% 的重复内容,让跨边界的语句至少在一个块里完整出现。重叠的代价是索引膨胀和检索结果冗余——同一个句子可能出现在两三个块里,全部塞进上下文会浪费窗口。检索后做一次去重能缓解。 按语义切比按长度切好:优先在段落、标题、句号处断开,实在太长才强制切。实现上先按标题分层,层内再按段落合并,直到接近目标长度。这份额外的代码量换来的召回提升通常很明显。 def chunk(text, encode, decode, size=512, overlap=64): """按 token 切块,带重叠。encode/decode 为占位接口。""" ids = encode(text)…

1975 117 1
从零实现一个最小可用 RAG 系统
HexCreek·2026-09-09
结对编程与远程协作的工程习惯

结对编程最常见的失败形态是两个人坐在一起,一个写代码,另一个看手机。名义上在结对,实际上是一个人写代码加一个观众,成本翻倍,收益接近零。结对要有效,需要角色分工是真实的,而不是两个人轮流打字。 驾驶与导航 两个角色有明确的分界: 驾驶者(driver):操作键盘鼠标,负责把想法落实成代码,处理语法、命名、编辑器操作这些细节。 导航者(navigator):不碰键盘,负责观察全局——当前写法是否符合目标、下一步该做什么、有没有偏离方向、有没有遗漏的边界情况。 分工的价值在于认知负载的分离。驾驶者处在”如何实现”的层面,容易陷入细节;导航者处在”要达成什么”的层面,能持续对照目标。一个人同时做这两件事时,通常会在细节里待很久才发现方向偏了。 导航者要克制”抢键盘”的冲动。看到驾驶者写出一个自己不认可的写法,正确做法是先记下来,在当前这一步完成后讨论,而不是马上接手改掉。频繁打断会让驾驶者失去连续性,结对的效率反而低于单人。 导航者的职责是预判 导航者如果只是”看着屏幕等对方写完”,价值很低。真正的职责是提前一步: 驾驶者开始写一个循环,导航者应该已经在想边界:空输入、只有一个元素、超大输入。 驾驶者在改一个函数,导航者应该在想调用方:有几处调用、哪些测试会受影响、错误信息是否还准确。 驾驶者卡住了,导航者应该能提出两个可能的方向,而不是一起沉默。 预判需要导航者掌握足够上下文。如果导航者对这块代码完全陌生,结对就退化成”边看边学”,这时应该先花十分钟让驾驶者讲清楚背景,或者直接换成讲解模式。 具体做法:导航者手边放一张便签(或一份临时文档),记录”待讨论”和”待处理”两类事项——不打断当前思路,也不让发现的问题被遗忘。这张清单在轮换时交接,是轮换能无缝进行的关键。 轮换节奏 轮换的目的是让两个人都保持参与,同时避免某一个人长期占据驾驶位。经验上 25 到 40 分钟一轮比较合适:太短会打断心流,太长会让导航者失去注意力。 轮换时的交接要说三件事:当前做到哪一步、接下来打算做什么、待处理清单里有什么。这个过程通常需要两三分钟,是必要的成本。 轮换还有一层作用:它强迫知识双向流动。如果一直是同一个人驾驶,另一个人只会在旁边看,学到的东西有限。轮换之后,两个人都要亲手实现,理解深度完全不同。 有些情况不适合机械轮换:驾驶者正在一个复杂实现的中间状态,强行交接会让两个人都失去上下文。这时可以约定”完成这一步再换”。 什么时候值得结对 场景 结对的收益 备注 复杂业务逻辑(状态机、计费规则) 高:边界情况被双重检查 缺陷最集中的地方 疑难 bug 排查 高:假设被即时挑战 一人容易陷入单一假设 知识传递(新人接手陌生模块) 高:上下文直接转移 比文档更快,但要配合文档 关键设计决策的落地 中高:方案在实现中即时验证 设计文档先行效果更好 生产环境操作 高:双人复核防误操作 已有变更流程的可沿用 机械性任务(批量改字段名、写样板代码) 低 单人做更快,事后评审即可 探索性尝试(不知道该怎么做) 视情况 各自先摸索反而更高效 判断标准可以简化成一句:这个任务的错误代价高不高,以及思路是否需要被即时挑战。两个答案都是”是”,就值得结对。 远程结对的额外成本…

2417 115 2
结对编程与远程协作的工程习惯
Vector 陈·2026-09-29
密钥与配置管理:Vault 与 Sealed Secrets

配置与密钥都能改变应用行为,但泄露后果不同。端口、功能开关、重试次数通常可公开评审,适合进入普通配置仓库;数据库密码、签名私钥、访问令牌和加密材料不可公开,需要限制读取、轮换和审计。判断标准不是文件名,而是内容泄露后是否会赋予权限、暴露数据或破坏身份可信度。 密钥不能以明文进入 Git 把密钥从当前分支删除,并不等于它已经消失。Git 对象、历史提交、分支、标签、代码评审缓存、CI 工作区和开发者本地克隆都可能保留副本。重写历史还会影响协作,也无法证明所有副本已被清除。因此一旦发现明文入库,应立即吊销或轮换该凭据,再评估历史清理;只提交一次“删除密码”的修复并不安全。 忽略文件只能防止未来误提交,无法代替扫描与权限控制。提交前钩子可拦截明显模式,服务端扫描负责统一门禁,但两者都可能误报或漏报。更稳妥的做法是让仓库只出现密钥引用或密文,并使开发、CI 和运行环境通过各自身份获取所需值。 Kubernetes Secret 中的 data 默认只是 Base64 编码,不是保密加密。能读取对象、备份或底层存储的人仍可能获得明文。集群需要启用静态加密、严格 RBAC 和审计,同时避免把 Secret 直接导出到工单与聊天记录。 加密即代码与 Sealed Secrets “加密即代码”允许把密文清单提交仓库,通过常规评审和 GitOps 流程发布。以 Sealed Secrets 模式为例,提交者使用集群控制器的公钥加密秘密,仓库保存 SealedSecret;集群内控制器持有私钥,解密后创建普通 Secret。仓库和 CI 无需接触解密私钥。 apiVersion: example.io/v1 kind: SealedSecret metadata: name: payment-credential namespace: checkout spec: encryptedData: username: Ag... password: Ag... 密文通常可绑定命名空间与名称,防止被复制到另一个位置解密。修改对象名、目标命名空间或作用域时需要重新封装。密文不是永远安全:攻击者若同时获得控制器私钥和仓库历史,仍可解开旧值。私钥应备份、限制访问并建立轮换;恢复集群前要确认解密能力,否则仓库里的密文无法生成运行 Secret。 这种模式适合相对静态、由 Git 驱动交付的秘密。它不天然提供短期凭据、按请求授权或自动撤销,轮换仍需生成新密文并发布。频繁变化的数据库账号或云访问凭据更适合动态密钥系统。…

2319 114 2
密钥与配置管理:Vault 与 Sealed Secrets
苏念禾·2026-10-14
MQTT 协议详解:QoS、遗嘱与断线重连

MQTT 跑在 TCP 之上,报文头最小只有 2 字节,这套设计是为窄带宽、高延迟、不稳定的链路准备的。理解它的关键点不在于”能连上发消息”,而在于想清楚消息在哪个环节可能丢、丢了之后由谁负责重发。 发布订阅模型与 broker 的位置 设备不直接互相寻址。发布者把消息发到 broker,带上一个主题字符串;订阅者提前告诉 broker 自己关心哪些主题;broker 负责匹配和转发。这个解耦带来三个结果:发布者不需要知道订阅者是谁、有几个;双方不需要同时在线;拓扑变化时不用改任何一端的代码。 代价是 broker 成为单点。broker 挂掉,所有设备都失联,所以生产环境需要考虑 broker 的高可用与集群,而设备侧要能承受 broker 不可用的时间段——这正是断线重连逻辑存在的理由。 三种 QoS 的语义差异 QoS 不是一个全局开关,而是两段独立的协商:发布者到 broker 一段,broker 到订阅者一段。客户端 PUBLISH 时指定第一段,订阅时 SUBSCRIBE 指定第二段。最终订阅者收到的消息 QoS 取两者的较小值。 QoS 0(最多一次):发出去就不管了。没有确认,没有重传。适合周期性上报且允许丢一帧的传感器数据,比如每秒一次的温度采样——丢一帧下一秒就补上了。 QoS 1(至少一次):PUBLISH 之后等 PUBACK。没收到就在超时后重发,重发时 DUP 标志置位。发送方必须把报文存在本地队列里直到收到 PUBACK。这个”至少一次”意味着会重复,接收方必须做幂等处理:在消息里带一个单调递增的序号或唯一 ID,重复的直接丢弃。常见的做法是业务层维护一个最近 N 条 ID 的集合。 QoS 2(恰好一次):两阶段握手。PUBLISH…

2564 110 3
MQTT 协议详解:QoS、遗嘱与断线重连
AsyncLab·2026-10-04
分布式事务:2PC、TCC、Saga 与本地消息表

下单要扣库存、创建订单、冻结优惠券,三个动作跨了三个库。单机事务保证的同生共死在这里失效。所有分布式事务方案处理的其实是同一个困境:参与者没有共享的内存和锁表,谁也无法替谁做承诺,只能靠协议协商。差别在于承诺的强度,以及维持这个强度要付多少代价。 本地事务与分布式事务的差别 本地事务依赖数据库内部的锁、回滚段和原子落盘。事务边界内的中间状态对其他事务不可见,崩溃时由数据库自己用 undo 日志回退。 分布式事务没有这种统一的控制者。跨服务时每个参与者有自己的连接、自己的锁、自己的提交时刻。由此带来三个新难点: 一是部分失败。库存扣成功、订单创建失败不是异常而是常态——网络分区时你甚至不知道对方是否执行成功。 二是可见性。本地事务未提交的中间态对外不可见,而分布式事务每个子事务提交后立即可见,中间状态会被读到。 三是时间不确定。没有全局时钟,超时只能靠本地计时,等待方无法区分对方是慢还是死了。 这三条决定了所有方案都绕不开幂等:同一操作执行多次,效果必须与执行一次相同。 2PC 的承诺与代价 两阶段提交引入一个协调者。第一阶段协调者询问所有参与者能否提交,参与者写 redo/undo 后回复 yes/no 并锁住资源;第二阶段协调者根据回复下发 commit 或 rollback。 它的吸引力在于语义清晰:应用层几乎不用改代码,靠数据库或事务中间件就能接近强一致。代价同样清晰: 阻塞。参与者回复 yes 后进入不确定状态,锁必须一直持有到收到决定。协调者此时宕机,参与者只能等待。实践中靠协调者高可用加超时兜底,超时后尝试询问其他参与者或接任的协调者;但存在一个所有参与者都回复 yes 却无人知道最终决定的窗口,无法消除。 资源占用。持锁时间从”事务执行时间”延长到”最慢参与者执行时间 + 两轮网络往返”。热点数据在这种方案下很快成为瓶颈。 不支持长时间事务。几分钟以上的业务事务不可能长时间占着数据库锁。 适用场景因此很窄:参与者少(同机房两三个库)、事务短(毫秒到百毫秒级)、对锁竞争不敏感。跨服务、跨机房、涉及外部调用的场景不适合。 TCC 的业务侵入 TCC 把提交拆成三段:Try 冻结资源,Confirm 确认扣除,Cancel 释放冻结。以账户扣款为例: public interface AccountTccService { @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel") boolean…

2459 106 3
分布式事务:2PC、TCC、Saga 与本地消息表
码同客·2026-10-10
单元测试怎么写才有价值:测试边界与可测性设计

一套 90% 覆盖率的测试套件,完全可能是零价值的:改错一个判断条件,测试照样全绿;重构一次内部实现,三十个用例同时变红,其中没有一个对应真实缺陷。覆盖率衡量的是”代码被执行过”,它不衡量”错误能被发现”。 价值来自改动时的信心 给测试估值的一个近似公式是: 测试的价值 = 改动时提供的信心 − 维护这些测试的成本 分子取决于”如果我改坏了,会不会红”,分母取决于”我改对了,会不会也红”。多数团队只盯分子,忘了分母,于是测试越写越多、越写越脆,最后变成每次重构都要先修两小时测试的负担。 有一个可以自己做的检验:故意把某行判断取反、把 < 改成 <=,跑一遍测试看有没有失败。没失败,说明这行代码没有被真正断言,覆盖率里那 100% 是虚的。这种做法规模化之后就是变异测试,不必天天跑,在关键模块上跑一次能很快照出断言的空洞。 四种脆弱测试 脆弱测试的共同点是:代码行为没变,测试却红了。频繁出现会摧毁对套件的信任,最后没人愿意看红色。 依赖实现细节。断言内部调用次数或调用顺序,而不是最终可观测结果。 # 脆弱:绑定了实现步骤 def test_place_order(): repo = Mock() svc = OrderService(repo) svc.place_order(cart) repo.save.assert_called_once() # 内部改成分批保存就红 repo.update_cache.assert_called_once() # 加了缓存就红 # 更稳:断言可观测状态 def test_place_order_persists_and_is_queryable(): repo = FakeOrderRepo() # 内存实现,不是 Mock svc = OrderService(repo) svc.place_order(cart) assert…

1794 105 1
单元测试怎么写才有价值:测试边界与可测性设计
Elowen·2026-09-26
大表 DDL 的在线变更方案

给一张几百 GB 的表加个字段,开发以为是一行 SQL 的事,DBA 知道这可能是几小时的磁盘 IO 和一次业务抖动。搞清楚 DDL 背后的执行路径,才能判断一次变更到底需要多少资源、会不会阻塞业务。 DDL 为什么会锁表 原因有两类。 第一类:元数据锁(MDL)。任何访问表的语句(包括 SELECT)都会持有该表的 MDL 读锁,DDL 需要 MDL 写锁。两者互斥。这意味着: 一个长时间运行的查询会阻塞后面的 DDL。 一个等待中的 DDL 会阻塞它之后所有要访问这张表的查询——即使 DDL 本身执行很快,排在它后面的请求全部卡住,连接迅速堆积。 这是”加个字段导致整个服务不可用”最常见的成因,与 DDL 本身的耗时无关。 -- 排查 MDL 等待的源头 SELECT object_type, object_name, lock_type, lock_duration, lock_status, owner_thread_id FROM performance_schema.metadata_locks WHERE object_name = 'orders'; SELECT * FROM sys.schema_table_lock_waitsG --…

2043 104 0
大表 DDL 的在线变更方案
NullRef·2026-09-17
语义化版本与发布纪律

版本号是一种契约:它告诉使用方”升级这一步,我需要付出多少注意力”。1.4.2 → 1.4.3 意味着可以直接升,1.4.2 → 2.0.0 意味着要读变更说明、可能要改代码。契约一旦失真,版本号就退化成一个递增的序号,使用方只能选择永远不升或者每次都全量回归。 三段各自的契约 MAJOR.MINOR.PATCH: MAJOR:包含不兼容的变更。使用方必须修改代码或配置才能升级。 MINOR:以向后兼容的方式增加功能。使用方不需要改动,可以直接升。 PATCH:以向后兼容的方式修复问题。使用方应该尽快升。 关键词是”兼容”而非”大小”。删掉一个函数可能只改一行,但它是 MAJOR;新增一个模块可能写了两千行,它是 MINOR。按改动量定版本的做法,是对这个契约最常见的误读。 另一个常被忽略的点:废弃(deprecation)不算破坏性变更。标记 @Deprecated 并保证在若干个 MINOR 版本内继续可用,属于 MINOR;真正移除它才是 MAJOR。 什么算破坏性变更 最容易漏判的不是删接口,而是”接口没变,行为变了”。 变更 是否破坏性 说明 删除或重命名公开函数/字段 是 编译或运行直接失败 函数参数顺序或个数变化 是 即使类型兼容 返回值类型变化 是 包括 null 变成抛异常 默认值变化(超时从 30s 到 10s) 是 使用方行为被悄悄改变 错误码 / 异常类型变化 是 依赖错误类型的分支逻辑会失效 边界行为变化(越界从抛错到静默截断) 是 语义变了…

1782 102 2
语义化版本与发布纪律
许既白·2026-09-27
IoT 固件 OTA:差分升级与回滚

OTA 的风险不在下载过程,而在”写进去之后能不能跑起来”和”跑不起来怎么办”。所有 OTA 设计都围绕这两点:让失败可检测,让失败可回退。 分区方案的取舍 单分区 + 备份区:运行区只有一个,升级时把新固件下载到备份区,校验后由 bootloader 拷贝到运行区。地址固定、代码无需位置无关处理;但拷贝期间断电会留下半个固件。 A/B 双分区:flash 里放两个同样大小的应用分区,设备总是从其中一个启动。升级时把新固件写入当前未使用的那个分区,写完校验通过,只改一个标记位告诉 bootloader 下次从另一个分区启动。 A/B 的关键优势是运行区永远完整。写入发生在非活动分区,任何时刻断电下次仍跑旧固件,最多这次升级失败。代价是 flash 占用几乎翻倍——4 MB flash 扣掉 bootloader、分区表、NVS 后,每边可能只剩 1 MB 出头。 单分区 + 下载缓冲:介于两者之间,用外部 flash 做缓冲,校验后写入运行区,风险窗口仍在但可以做得很小。 差分减小占用:传差分包只需较小的暂存区,A/B 的空间压力会小很多。 选择时主要看 flash 够不够、能否接受变砖风险。能上 A/B 就上,不够再考虑其他方案。 bootloader 的跳转逻辑 bootloader 是上电后第一段执行的代码,不参与业务逻辑,职责明确: 读取分区表,找到两个应用分区 读取非易失存储里的启动标记,判断该从哪个分区启动 校验目标分区的完整性(CRC 或签名) 校验通过则跳转,失败则尝试另一个分区 两边都失败,进入恢复模式(比如等待串口下载) 跳转的关键操作是设置主栈指针和复位向量: typedef struct…

2075 100 4
IoT 固件 OTA:差分升级与回滚
Liora·2026-10-08
学习新技术的高效路径:30 天可验证计划

学新技术最常见的两种走法都容易半途而废:一种是从第一章开始逐页读,读到第三章还没写过一行代码;另一种是直接上手抄 demo,跑通了就以为会了,等真要用时发现处处是洞。 差别在于有没有一个可验收的产出节奏。下面这套路径按 30 天设计,每周一个能拿出来的东西。 先找它的中心思想 任何一项技术都有一个可以被一句话概括的核心主张。找到它之前,细节是无处安放的。 例: RDBMS 的中心思想 —— 用关系代数 + 事务隔离,换取数据一致性与查询表达力 Git 的中心思想 —— 内容寻址的不可变对象图 + 指针(分支即指针) Kafka 的中心思想 —— 追加写的分布式日志,消费进度由消费者自己持有 React 的中心思想 —— UI 是状态的函数,更新是重新计算而非就地修改 React 的中心思想(另一说)—— 把变更收敛到单向数据流里 找中心思想的方法不是读教程,而是看三样东西:官方首页第一段怎么描述自己、它解决的原问题是哪个、它明确说了自己不解决什么。第三样最省时间——边界往往比能力更能揭示设计取向。 有了中心思想之后,再遇到具体特性就可以做归类:这条是中心思想的直接推论,那条是为了补偿中心思想带来的代价。比如”Kafka 不提供单条消息的删除”,是追加日志这个设计的直接推论;”消费者需要自己维护 offset”,是它换取吞吐时让渡出去的责任。这样记,细节是挂在树上的,不是散在地上的。 让它先跑起来,再问为什么 顺序上,跑通优先于理解。原因不是”实践出真知”这种说法,而是很实际的:一个能运行的最小系统给你提供了可实验的对象,之后你对每一个”为什么”都可以改一下参数看结果,而不是停在猜测里。 最小可跑通的标准是:一个输入、一条主路径、一个可见输出。不要在其中加入任何”反正以后也要用”的东西——配置中心、日志框架、错误处理、性能优化,这些会在第一阶段把时间吃掉。 # 例:学一个新的序列化框架,第一天的目标就是这个 # 1. 起一个空项目,只加一个依赖 # 2. 定义一个结构,序列化再反序列化,打印字节长度 # 3. 改一个字段名,再跑一次,看兼容性行为 跑通之后立刻做一件事:故意打破它。改字段名、传空值、塞一个超大的输入、并发跑两遍。这些失败案例比成功路径更能建立准确的心智模型——你会知道它的边界在哪,而不是只知道它能工作。 信息来源分层…

2243 95 0
学习新技术的高效路径:30 天可验证计划