IT技术博客大学习 共学习 共进步

系统架构

共 731 篇文章

IT 2026-05-10 18:35:06 / 累计浏览 107

嵌入主线程消息循环的任务调度器

这篇文章聚焦于将基于多线程调度器的项目 `soluna`,移植到 WebAssembly 等非 Windows 平台时遇到的核心难题。作者从项目维护的实际需求出发,解释了为何在可能退化为单线程的环境中,模拟出 `ltask` 调度器的能力成为关键挑战。 文中介绍的核心实现思路颇具巧思:将原本独立的任务调度器巧妙地“嵌入”到主线程自身的消息循环里。这意味着主线程需要同时承担两份工作——既要处理自己的消息队列,又要扮演调度器的角色,为其他逻辑“线程”(通常是协程)分配执行机会。作者没有停留在理论层面,而是深入探讨了如何在单一线程上,通过协作式调度和精心设计的队列,来模拟多线程环境下的任务切换与调度行为。 这种设计确保了项目的多线程调度架构得以保留,其核心价值不因平台变化而丧失。文章清晰地展示了一个现实场景下的工程权衡与技术适配过程,对于需要处理类似跨平台移植问题,或是对单线程高并发模型感兴趣的开发者而言,提供了非常具体的实现参考。

IT 2021-06-13 22:41:22 / 累计浏览 2,759

分布式系统升级所遇到的问题

分布式系统升级的一大痛点是无法像单机软件那样“原子化”完成。升级期间新旧版本长期共存,如果新版本引入了数据格式变更,旧版本无法识别新格式,就会导致服务报错。特别是“向前兼容”(旧版本兼容新数据)几乎无法实现。 作者提出了一个经典的“中间版本”策略来化解这个困局。核心思路是设计一个特殊的中间过渡版本:它既能识别未来的新数据格式,又不会产生新格式数据。升级分两阶段进行:先将所有节点升级至中间版本,此时没有新格式数据产生,因此无兼容问题;等全部就绪后,再将中间版本升级至最终版本,新旧格式共存也能被新版本识别。 这样,通过引入一个精心设计的“桥梁”版本,就将复杂的向后兼容问题,转化为两个相对简单的、可逐步滚动执行的阶段,从而在完全不停服的前提下,实现了版本的平滑过渡。这个方案为解决分布式系统中的类似版本演进问题提供了一个清晰的范式。

IT 2021-06-13 22:34:06 / 累计浏览 2,519

可靠通信的三条基本定理

这篇讲的是可靠通信背后必须遵循的三条基本定理,以及如何用它们指导系统设计。作者认为,广义的可靠通信必须满足不丢包、不重复、完整性这三个要求,而对应的解决方案分别是确认与重传、排队(串行化)、单点标记或自校验。文章澄清了一个常见误区:去重问题的根源在于操作需要排队,即使采用全局位图方案,对位图的操作本身也需排队,而像CAS这样的实现,其内部本质也是排队。 这些定理并非空谈,文章通过实例展示了其强大的解释力。例如,TCP的可靠性正是因为它遵循了这三条定理:序号机制实现了排队,超时重传机制兜底处理丢包,checksum则保证了数据完整性。再比如,在线商店的库存超卖问题,本质上是一个需要排队的去重问题;而订单与支付两大独立系统之间的数据一致,则依赖于定理一(确认与重传)来保证不丢包。 作者的核心观点是,实际问题千变万化,但可靠的系统设计必须回归到这三条基础定理上。它们提供了最根本的讨论框架和判断标准:符合定理的设计方案才是可靠的,否则就存在隐患。理解这些基础模型,是构建正确、健壮系统的理论前提。

IT 2021-05-27 08:10:24 / 累计浏览 1,834

初识前端智能化

从推荐算法到前端委员会,一位深耕技术多年的实践者,在2018年提出了“前端智能化”方向。这篇讲的是作者对这一概念的系统性思考,旨在为困惑的同行厘清概念、指明路径。 文章的核心观点很明确:前端智能并非要求前端工程师成为算法专家,而是要用工程化思维,在前端技术生态内高效地落地和整合成熟的AI能力。它旨在降低AI的应用成本,让最懂用户和交互的前端开发者,能真正驱动业务智能化升级。 作者首先厘清了概念,指出前端智能关注的是“问题定义-模型选择-工程集成-业务验证”的闭环。随后,他将前端智能化比作Node.js之后技术土壤上长出的“新物种”——它不仅拓展了前端的应用边界,更从根本上变革了“用户-端-服务”的技术链路:模型将直接在端侧参与理解用户与场景。 文章也直面了当前的挑战:移动端极度复杂的时空场景、人脸/手势等新型交互带来的技术栈不兼容,以及追求极致个性化与研发成本之间的矛盾。这些分析指明了前端技术下一步升级必须解决的核心问题,为从业者描绘了一幅清晰的演进路线图。

IT 2021-05-27 07:52:49 / 累计浏览 2,673

裁剪和空间管理

这篇讲的是游戏引擎里渲染优化的一个关键模块——Culling与空间管理。作者从一个基本问题出发:当场景里可渲染对象很多,但相机实际能看到的只有一小部分时,如何避免让GPU处理所有对象,从而节省CPU到GPU的带宽?最朴素的做法是对每个对象做视锥体检测,复杂度是O(n)。但当对象数量n极大时,我们需要更好的方法。 核心思路是利用空间结构来加速。把对象按空间位置组织成树状结构,这样在检测时可以快速剔除一整个分组,将复杂度降到O(log n)。文章梳理了这条技术路径的演进:从简单的等距网格,到处理不均匀分布的四叉树/八叉树,再到为解决对象跨边界问题而提出的BSP。作者特别推崇K-D Tree和BVH方案,它们通过在每次二分时智能选择分割线位置(比如让两侧对象数量均衡),能形成完全二叉树,既紧凑又高效。 作者强调,Culling本质上是一个可选的“加速缓存”,而非引擎核心容器,因此其数据结构应保持独立和简洁。例如,他建议用一块连续内存存储K-D Tree的切割信息,实现简单且支持持久化。最后,文章还探讨了实现细节,比如如何高效地对对象进行二分,以及是否需要动态调整树结构,给出了倾向于静态构建、按需重建的设计观点。

IT 2021-05-26 22:57:06 / 累计浏览 1,774

选择开源项目的几点原则

这篇讲的是资深工程师在面对琳琅满目的开源项目时,如何做出不后悔的选择。作者从自己曾受邀为校招生做技术分享的背景出发,分享了沉淀下来的三点实用原则。 核心观点非常明确:选项目,本质上是选“人”。具体来说,一要看项目是否活跃,有持续演进的历史,拒绝“已死”的项目;二要看项目主导者是否善于沟通,这是项目能否健康演进的关键;三要看项目是否专注,解决单一问题的“小而美”项目更便于集成与取舍。 作者特别强调,我们不必苛求代码完美,因为选择使用一个开源项目,就意味着选择了与维护者同行。真正重要的是找到那些勤奋、开明且专注的“合作伙伴”。文中还顺带吐槽了国内某些只发代码快照、缺乏持续维护的“伪开源”现象,让这个选择原则显得更加切中时弊。

IT 2021-05-26 22:53:10 / 累计浏览 1,955

内存的惰性初始化

这篇文章从一个 MMO 服务器压力测试的优化场景切入,探讨了当使用 A* 算法在一个巨大的三维网格(10MB 内存)中寻路时,如何解决初始化开销过大的矛盾。 实现者为避免每次调用都 memset 清零,采用在格点中记录版本号的技巧,实现了“用到时再判断”的惰性逻辑,但这依然需要全局保留这块内存。作者从更高层面指出,这本质上是一个用平坦内存空间模拟稀疏矩阵的权衡问题。 为此,他设计了一套惰性初始化的内存结构:以 64 字节(cacheline 大小)为单位划分内存,仅用一个二级标记树(总开销约 20KB)来记录哪些段落已被初始化。访问时检查标记,按需清零。这样,绝大多数未被访问的内存区域永远不会被初始化,将时间开销降至接近于零,同时空间代价极小。 文章结尾更提出了一个巧妙的延伸思路:对于这种障碍物静态且局部的寻路,与其在运行时寻路,不如用巨大的预计算空间将路径全部存储下来,实现 O(1) 查询。这为解决此类特定问题提供了不同的架构视角。

IT 2021-05-24 22:46:03 / 累计浏览 2,541

用 Pomerium 来实现基于身份的访问控制

这篇讲的是如何将 Google 提出的零信任安全架构 BeyondCorp 中的一个关键环节——基于用户身份的访问控制——通过开源项目 Pomerium 进行落地实施。作者从 BeyondCorp “内网不再等于安全”的核心理念出发,选择了 Pomerium 作为实现鉴权反向代理的方案。 文章没有停留在理论介绍,而是详细记录了在 FreeBSD 系统上的完整实践过程。从安装(甚至为系统制作了 port)、配置必要参数(如证书、随机密钥),到处理非特权用户使用 443 端口的系统限制,都给出了具体说明。核心部分聚焦于如何将 Pomerium 与 OAuth 2.0 认证流程、Google 或 Azure 等身份提供商集成,并根据系统规模选择 allowed_users、allowed_domains 或 allowed_groups 策略。 作者还特别指出了两个实践中的“坑”:一是身份提供商(如 G Suite)对服务账户权限的特殊要求,二是需防止后端服务器在重定向时陷入循环。整篇文章像是一份扎实的部署笔记,不仅分享了工具的使用,更传递了将一个安全理念转化为实际配置时需要注意的细节和经验。对于想尝试零信任方案或寻找身份感知代理工具的读者,这提供了可操作的参考路径。

IT 2020-02-05 15:13:31 / 累计浏览 2,038

浅谈 Web 应用的内存优化

这篇讲的是在复杂Web应用和Node.js服务端环境中,为何以及如何进行JavaScript内存优化。 作者从Web应用复杂度提升、需长时间运行的背景出发,点明了自动内存管理机制下常被忽视的内存问题。文章不仅梳理了深拷贝、闭包等基础概念,更将重点放在了开发实践中的具体注意事项上。 摘要需要体现的核心是那六条实用建议:避免无意创建全局变量(可借助严格模式)、用完大型数据后及时解除引用、减少循环中的频繁对象创建(可借鉴享元模式)、区分内存与缓存并设定过期机制、警惕复杂递归导致的栈溢出,以及对IndexedDB等本地存储进行定期清理,避免浏览器因数据膨胀而崩溃。 文章特色在于它并非泛泛而谈理论,而是紧密结合前端编码场景,通过具体代码示例揭示了常见的内存消耗陷阱。这些基于实际案例的优化要点,能帮助开发者在编码时形成更好的内存意识,从而构建更稳定、流畅的Web应用。

IT 2020-02-03 12:38:46 / 累计浏览 1,975

让 lua 运行时动态切换操作系统线程

这篇讲的是开发者在构建跨平台游戏引擎时,如何巧妙解决一个操作系统级的线程调度矛盾。作者从 iOS 的一个严苛限制出发:系统要求窗口消息循环必须运行在主线程,否则程序可能被杀;而引擎为了隔离耗时的业务逻辑,又必须把窗口管理模块与用户主逻辑分到不同线程。 矛盾在于,用户的业务代码期望运行在 Lua 解释器启动时的主虚拟机(VM)中,窗口模块期望在独立线程,同时窗口模块还必须占据操作系统意义上的“主线程”。作者最初认为这无解,除非像 Skynet 那样深度定制 Lua 运行时,让 VM 能自由迁移。 真正的转机来自一个巧妙的 API 设计:`thread.fork`。它通常让 func1 在当前 VM,func2 在新建 VM 和线程上并行。但作者反其道而行,让 func1(用户主逻辑)在**新线程**上运行,而让 func2(窗口模块的新 VM)继续留在**当前线程**(即操作系统主线程)上执行。由于两者都通过 `pcall` 被限制在各自作用域内,用户代码完全感知不到自身线程已切换,而窗口模块则恰好满足了系统对主线程的要求。 这个方案的巧妙之处在于,没有去硬撼操作系统的规则,而是通过“偷梁换柱”——交换两个执行流所在线程的位置,让看似不可调和的约束在架构层得到了圆满解决。

IT 2020-02-02 11:20:18 / 累计浏览 2,699

保障IDC安全:分布式HIDS集群架构设计

面对百万级服务器规模的IDC环境,如何设计一套既可靠又高效的主机入侵检测系统(HIDS)集群?这篇文章从美团安全部的实际需求出发,剖析了在如此大规模下HIDS Agent管理面临的核心挑战——包括如何实现低损耗部署、集群的快速精准控制、配置一致性保障,以及Agent与服务器间通信的安全性。 作者详细阐述了架构选型的思考过程。在分布式系统的CAP定理框架下,为保障控制指令的最终一致性(即下发关停时,Agent必须执行),团队果断选择了CP架构。通过对比etcd、ZooKeeper与Consul,最终选定etcd作为核心组件,利用其Watch机制实现实时配置下发、Lease租约感知主机下线、以及细粒度的TLS加密与RBAC权限控制。 文章不止于理论,更深入到实战层面,分享了基于etcd的Key前缀设计策略,以及为应对DNS故障而采用的IP与域名混合部署的集群管理实践。对于从事大规模运维或安全架构设计的工程师而言,文中关于“如何在有限资源下管理百万级终端”的具体思路与踩坑经验,具有很强的参考价值。

IT 2020-02-01 19:45:48 / 累计浏览 2,095

ECS 中的概念缺失

这篇讲的是作者团队在实际项目中对 ECS(实体-组件-系统)架构的一次重要反思与演进。他们指出,传统 ECS 过分强调 Component 和 System 这两个底层概念,导致在搭建复杂业务(如现代渲染管线)时,开发者需要手动组合数十个组件和系统,流程繁琐且极易出错。 为此,他们引入了两个更高层的抽象概念来解决问题。第一是 **Policy**,它将实现某个特定功能(如“可渲染物件”)所需的所有 Component 及其初始化流程封装起来。开发者创建 Entity 时只需描述其具备哪些 Policy,而无需关心底层数据构成。第二是 **Pipeline**,它用一棵树状结构来定义 System 的执行顺序,开发者可以将 System 注册到流水线的特定节点上。 通过这套设计,他们实现了将框架的复杂性封装起来,让业务层开发者只需关注 Policy 列表、Pipeline 配置和初始数据,无需直接处理 ECS 的底层细节。这既保持了 ECS 解耦与数据驱动的核心优势,又大幅降低了实际使用的门槛,确保框架能真正服务于业务开发效率。

IT 2020-02-01 16:44:33 / 累计浏览 2,341

资源文件的转换问题

这篇讲的是作者在自研游戏引擎中遇到的一个资源转换缓存失效问题,以及由此展开的架构优化。 他们引擎的资源转换采用惰性策略:在虚拟文件系统中,根据`.lk`描述文件和平台信息按需生成最终资源。但最近发现,对于 shader 这类依赖其他 include 文件的“代码型”资源,仅靠源文件和`.lk`文件的 hash 作为缓存 key 是不够的——修改依赖文件后,系统并未感知变化,错误地返回了旧缓存。 根因在于初始设计过于简化,未考虑编译的完整依赖链。放弃惰性构建的方案很快被否决,团队最终提出一个更巧妙的方案:当请求构建时,系统会在后台无条件重新编译,并将此次编译的**完整参数和依赖关系**(包括所有依赖文件的路径及当前 hash)写入一个新的构建脚本文件(如 `a.sc.lk.ios`)。这个文件本身唯一确定了一个编译结果,其 hash 就成了新的、精确的缓存 key。 这个机制既保留了惰性转换的优点,又实现了准确缓存。相比 Unity 的 cache server,它的优势很明显:缓存键是包含依赖的完整过程,因此可以跨项目复用(同一张贴图不会因路径改变而需重新编译)。此外,文件服务器还能利用空闲时间预编译其他平台版本,这是纯键值存储的 cache server 做不到的。这个设计有点类似 Git 大文件存储,用一个轻量引用指向背后的编译服务。

IT 2020-02-01 15:20:17 / 累计浏览 2,478

ECS 中的消息发布订阅机制

这篇讲的是作者在用Lua实现ECS框架时,如何解决“周期性状态迭代”与“响应式事件处理”之间的矛盾,并最终引入一套完备的消息发布订阅机制。 作者从实践中发现,纯粹的游戏循环难以高效处理复杂外部输入。因此,他最初刻意在ECS中回避事件系统,将内部事件都转化为状态变化。但这并不完全符合游戏混合型业务的本质。 于是,他决定为框架增加消息发布订阅模块。核心实现非常灵活:每条消息都是一个Lua table构成的键值对,例如 `{ type = "mouse", action = "move", x = 100 }`。系统通过 `world:pub` 发布,任何System则通过一个模式(pattern)来订阅感兴趣的消息,比如订阅所有 `type="new"` 的消息,或者只订阅特定实体的状态变化。 巧妙之处在于其性能优化思路。作者没有选择简单的遍历匹配,而是在订阅时建立索引缓存。发布时,先根据消息中的各个条件快速排除不相关的订阅者,大幅减少了比较次数。这种用空间换时间的策略,让消息分发效率更高。文章也探讨了面对复杂条件可能导致的缓存膨胀问题,为后续优化留下了空间。

IT 2020-02-01 15:07:16 / 累计浏览 1,937

一文读懂分布式系统CAP定理

这篇讲的是分布式系统中著名的CAP定理,作者从自己初遇概念时的困惑出发,试图用更直白的方式把这个“看不见的手”讲明白。文章核心指出,在分布式环境中,一致性(C)、可用性(A)和分区容错性(P)三者不可兼得,必须做出权衡。 作者通过具体的MySQL集群案例,对比了三种典型选择:追求CA时,如基于主键的分库分表,能保证强一致和高可用,但无法应对网络分区;选择CP时,如严格的主从复制模式,数据一致性得到保障,但分区会导致写操作不可用;而倾向AP时,如允许异步复制的集群,则优先保证服务不中断,但可能读到过时数据。 文章最后自然引出了BASE思想,这是许多高并发系统在CAP约束下的实际选择——优先保障基本可用,接受临时数据不一致,最终达到一致。作者用面试经历和自学过程串联全文,让理论落地到实际架构的考量中。

IT 2019-08-10 22:26:45 / 累计浏览 2,531

协程并发模型及使用感受

这篇讲的是协程并发模型在真实项目中的“两面性”。作者以一个Python项目为例,分享了使用gevent协程后的编程体验:它让并发模型变得简洁,一个协程对应一个任务,抛弃了传统的线程池。但文章重点剖析了在CPU资源受限的单核环境下,协程暴露的一系列生产陷阱。 文中指出的陷阱非常具体:协程中的间接死循环会导致其他协程被饿死;引入了未被“green化”的阻塞库(如MySQL-python)会阻塞整个事件循环,导致调度延迟;在单核CPU被压榨到80%-90%时,无法设定优先级的协程库会使高时延敏感的API协程与耗时任务协程争夺资源,影响服务质量。此外,程序员在自动切换环境下容易忽略协程挂起(如长时间sleep)对整个协程池吞吐量的影响。 作者最终的实践是,为了规避CPU瓶颈,项目还是演进到了多进程结构。文章总结道,协程在低CPU系统中能带来编程简便性,但当系统负载上去后,其资源管理和调试的复杂度可能会抵消甚至超过多线程模型。对于考虑引入协程的开发者而言,这篇经验分享提前点明了从理论便利走向工程现实时需要应对的挑战。

IT 2019-06-28 13:38:28 / 累计浏览 2,583

10分钟看懂!基于Zookeeper的分布式锁

这篇讲的是如何用Zookeeper实现一个可靠的分布式锁。 作者从分布式系统协调的核心需求——分布式锁出发,直接对比了常见的数据库、Redis与Zookeeper三种方案,重点聚焦在Zookeeper的实现上。文章首先通俗地解释了Zookeeper是什么:一个提供配置管理、分布式协同等底层服务的中心化框架,其核心是一个类似文件系统的、保存在内存中的有序树状结构。 实现分布式锁的核心思路巧妙地利用了Zookeeper的几个关键特性:**有序节点**来排队,**临时节点**来防止客户端宕机导致的死锁,以及**事件监听**来高效地通知锁的释放。基本的算法是:客户端在指定根路径下创建临时有序子节点,序号最小的获得锁;否则就监听前一个节点的删除事件,从而实现公平的等待队列。 文章还深入讨论了两个关键优化。一是如何避免“羊群效应”,即每个客户端只监听自己前一个节点,而不是所有节点变更,这大大提升了性能。二是分析了Curator这个开源库如何将这些复杂逻辑封装成简单的 `acquire()` 和 `release()` API,让开发者能轻松使用。 总的来说,这篇文章没有停留在理论,而是深入到了算法细节与源码实现,把Zookeeper利用临时有序节点解决分布式锁的精髓讲得清晰透彻。

IT 2018-12-26 23:38:42 / 累计浏览 2,292

交易系统如何确保账簿100%准确

这篇讲的是如何从设计层面根本性解决交易系统的账簿对账难题。作者指出,对账处理不好会带来巨大的人力成本和线上修改风险,因此提出一个核心设计原则:时刻保持整个系统的资产负债表为零。 文章以一个比特币交易系统为例,展示了只存储用户余额(资产)的账户表为何难以对账。关键一步是引入一个虚拟的“负债”(DEBT)账户来平衡整个资产负债。这样,无论用户间如何交易、资产如何转移(包括手续费进入FEE账户),所有账户的余额按币种求和,结果理论上都应精确为零。 基于此,对账逻辑变得极其简单:在每笔交易后执行一句SQL查询,检查各币种余额总和是否为零。文章还解释了用户入金和出金的本质是资产与负债账户之间的转移。这套设计不仅让系统能自动化、近乎实时地验证账簿准确性,也极大简化了财务核算,体现了用清晰架构提升系统可靠性的思路。

IT 2018-07-05 13:31:24 / 累计浏览 2,416

断点单步跟踪是一种低效的调试方法

作者从自己二十年的开发经历出发,对“断点单步跟踪”这一经典调试方法提出了一个颇具挑战性的观点:它本质上牺牲了效率来换取低门槛,是一种低效的方法。 文章详细阐述了这一观点的由来。作者从早期深度依赖图形化调试器,到转向跨平台开发后因工具不便而开始反思,逐渐转向以代码审查(Code Review)和日志输出为核心的调试范式。他认为,调试器容易让人陷入“眼前状态”的机械追踪,忽视了对程序所有可能执行路径的并行思考。相比之下,经过训练的大脑在阅读代码时,能更高效地分析所有分支并做剪枝,对程序的理解是全局且可回溯的。 文章进一步论证了这一方法的优势:它能倒逼开发者写出复杂度更低的代码,并能与日志输出形成完美配合。日志不仅提供了调试器所需的路径与状态信息,还具备更好的回溯能力和对并发系统的适应性。作者并未完全否定调试器,认为在分析崩溃现场等场景下它依然有用,但日常的 Bug 定位,理应建立在更深刻的代码理解之上。 这篇文章在开发者群体中引发了广泛共鸣,它不仅仅是在对比工具,更是在倡导一种通过提升自身心智模型来驾驭复杂度的工程哲学。

IT 2018-07-05 13:27:32 / 累计浏览 2,817

评审的艺术——谈谈现实中的代码评审

这篇讲的是代码评审的实践智慧——那些很难从课本学到、却在日常团队协作中至关重要的经验。作者从自身出发,认为代码创造本身就带有个性,评审时过度追求一致既不现实也容易扼杀活力。他坦言评审风格因人而异,有人温和、有人直接,而自己更倾向于对事不对事地坚持原则。 文章将评审问题分为三类:需求业务层面、代码结构层面和风格格式层面。前两者可能成为合并代码的阻碍,而后者通常不是关键。作者分享了一套沟通技巧:比如为风格问题标注“picky”以降低火药味;多用“也许”“可能”等虚拟语气缓和建议;或是用提问代替断言(如“为什么这里用3?”)。他还强调,评审应“先抓主干”,遇到满是问题的代码时,先聚焦核心设计缺陷而非细枝末节。 一个有趣的观点是,评审标准不必完全统一。对新项目代码和新人提交的代码要求应更严格,前者影响后续质量基调,后者关乎职业习惯养成。最后作者指出,若对业务或技术背景不熟,勉强评审反而有害,此时不如坦诚沟通、确保质量。整篇文章没有空谈流程,而是像一位经验丰富的工程师在分享如何在真实团队中让评审变得高效而有人情味。