1111 (12)

📅 发布时间:2026/7/8 1:12:19 👁️ 浏览次数:
1111 (12)
1111 (12)最近遇到一个业务上的问题在网上看到一个对应场景下的解决方案我感觉这个场景还挺有通用性的分享一下。以后遇到类似问题或者当它以面试场景题出现的时候你可以拿去就用。事情是这样的。程序里面有一条“线路”这个“线路”是购买的外部服务使用起来是要收费的。为了更好的理解这个“收费的线路”你可以假设为这是一个付费的 AI 接口。然后你可以把“线路”简单的理解为一个 FIFO 的公共队列对应着多个生产者。也就是同时有很多人即消费者在使用这个“AI 接口”提问。画个示意图是这样的理想情况下我们期望大家和和气气轮流用你一个我一个节奏均匀得像心跳。但是实际使用过程中可能会出现一个卷王 A 生产者突然快速的生产了大批量的数据导致 B、C 生产者产生的少量的数据排在队列的最后面等到天荒地老整个队列呈现出的短时间内只为 A 生产者服务的效果。即 A 生产者“长时间霸占”了整个队列。很明显这样对其他生产者不友好。我在网上查询了一下这个现象还有一个专门的名词叫做吵闹邻居问题Noisy Neighbor Problem。主要是指在多租户环境中单个用户过度占用资源导致其他用户服务质量下降的现象。常见的方案针对这个问题常见的方案一般有两个。第一个是把队列即“线路”分来就像这样各玩儿各的互不干扰。没有邻居也就不存在“吵闹邻居”的问题。这样可以解决问题但是会带来一个新的问题。前面说了这个“线路”是有购买成本的。如果为每一个消费者都提供一个单独的队列即上面说的“线路”那成本就太高了。那你可能会反驳一句不需要为每个人提供单独的队列只为高频使用的人员提供就行了嘛。是的这样也没有毛病。但是实际情况是高频使用的人 也只是在某个小段时间内高频使用随后就是长期的闲置浪费购买成本。而且在实际情况中还会出现一个情况是某个低频使用的用户突然在某一段时间出现业务高峰。那这种情况为了不影响其他用户还得紧急给业务高峰的用户搞个专门的队列。运营成本太高。所以这个方案适用于长期稳定都是高频用户的情况。第二个方案是限制生产者的生产速度。这个方案在解决问题的同时也带来了新问题。第一个问题是我需要实现一个限流功能提升了基础组件的复杂度。第二个问题是由于下游有限流机制那上游必然就要有重试机制增加了整体系统的复杂度。这两个常见的方案一个烧钱一个烧脑。我了解了之后发现和我的场景都不太匹配不能直接使用。Amazon SQS在我向大模型求助的时候它给了我这样一个关键词智能调度算法正如Amazon SQS所使用的公平队列Fair Queueing 机制在软件层面确保资源被公平地分配给所有用户防止任何一个用户垄断资源。于是我在网上找到了 Amazon 官方网站中这个文章https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-fair-queues.html从文章中的描述看它有一个识别谁是“吵闹邻居”的机制当识别到 A 是一个“吵闹邻居”之后Amazon SQS 会把其他租户B、C 和 D的消息放在最前面。这里的“租户”你可以认为就是我们前面提到的生产者。这种优先级有助于保持安静租户 B、C 和 D 的低停留时间而租户 A 的消息停留时间会延长直到队列积压被消耗而不会影响其他租户看起来确实能解决我的问题。于是追问了一下大模型关于它的问题想要进一步了解一下底层原理从大模型的回答来看它核心逻辑是有一个“动态权重调整机制”。“动态权重调整机制”的目的我个人理解是为了给每个生产者一个合适的权重从而决定这次生产的任务是应该放在队列的前面还是后面。大概是这个意思初步了解之后感觉它底层实现还有点复杂我把握不住。有一种杀鸡用牛刀的感觉所以我不打算使用它。但是也不算白忙活至少知道了 Amazon SQS 这个东西的存在。换个思路于是我在网上继续搜索找到了这篇文章它描述的思路完美解决了我的问题https://densumesh.dev/blog/fair-queue/而且它的思路很简单简单到让我觉得如果让我深入的思考一下也许我也能想到这个方案。它的核心思路用文章中的这张图就能说清楚给每个生产者分配一个存放 messages 的队列同时给每个生产者分配一个 client id。然后你注意看这里把 client id 放在一个 Round Robin Queue。这是个什么玩意其实就是一个简单的轮询策略。先从队首取出 client id。然后由选择出的 client id 找到对应的 work 去从对应的队列中取出消息来消费。最后视情况而定是否需要把这个 client id 放在队尾。由于轮询机制所以会确保各个生产者的消息是交替执行。作者使用 Rust 语言实现了上面的逻辑并取名叫做Broccoli。翻译过来是一个我不喜欢吃的蔬菜西兰花。这是对应的仓库链接https://github.com/densumesh/broccoli这个“西兰花”的核心架构非常简单主要有两个主要组件每个客户端的专用队列和单个轮转调度器。对于基础组件来说设计越简单就越可靠。作者在文章中也介绍了核心逻辑的伪代码并不复杂只有几行代码我带你盘一盘。核心逻辑分为两坨。第一坨逻辑是产生新消息对应插入操作。首先将某个生产者新产生的消息存储在一个专门的对应生产者的队列中。然后检查这个生产者对应的 client id 是否已经在轮询队列中。如果在那就完事了。如果不在那就把这个 client id 加在轮询队列的末尾。只要放到轮询队列里面去了就只需要等着被调度就行了。第二坨逻辑是消费消息。首先从轮询队列中获取队首的 client id。然后从这个 client id 的专属队列中获取一条消息进行处理。处理完毕后检查这个 client id 的专属队列是否还有消息。如果专属队列空了这个 client id 就不需要放回到轮询队列了。如果专属队列还有消息那把这个 client id 放回到轮询队列的队尾就完事了。看起来逻辑确实非常简单、清晰。这个方法的优点在于它完全能自我平衡。“吵闹的邻居”会留在轮询队列中“空闲的邻居”会自动退出并且无论他们排队的工作量有多少每个人都能公平地获得处理时间。看到这里有的小伙伴可能会产生一个疑问前面不是说了如果为每一个消费者都提供一个单独的队列即付费“线路”成本太高了吗那为什么这里就可以一人一个呢我把上面的图再多画一点出来你就明白了这里的“一人一个”是真的就一个队列而已。经过这套逻辑之后各个生产者的消息会呈现出交叉串行的形态再穿过真正的“付费线路”。“付费线路”只有一条并没有产生额外的费用。代码思路有了代码不是手到擒来的事儿吗我这里也不给你粘代码了直接给你上个代码截图但是我可以告诉你怎么去获取对应的代码实现。去问大模型就行了。我把流程示意图和伪代码描述直接扔给 DeepSeek让它给我一份 Java 代码就行了如果你让我按照上面的思路去敲代码我觉得我至少得写 30 分钟才能把初版写好而且这都算是快的。现在大模型会在一分钟内给你安排的明明白白你只需要最终检查一下代码逻辑就行了。哎我已经忘记上次纯古法手工敲代码是什么时候的事儿了。眼熟吗另外你有没有发现这个“Broccoli”方案有点眼熟这不就是操作系统的进程调度器玩了几十年的经典套路吗早期的操作系统或某些简单场景下CPU 调度使用先来先服务FCFS策略。先到的进程先获得 CPU执行完了才轮到下一个。如果一个“计算密集型”的进程比如 A 用户拿到 CPU它可能执行很长时间比如一个耗时循环导致后面所有“交互密集型”的进程比如 B、C 用户的轻量任务都被阻塞系统响应速度急剧下降。这不就是“吵闹邻居”问题的翻版吗A 进程就是那个吵闹邻居。为了解决 FCFS 的公平性问题操作系统引入了时间片轮转调度算法。这几乎和“Broccoli”的方案一模一样。核心思想是为每个进程分配一个固定的时间片。进程在 CPU 上运行一个时间片后就会被强制剥夺 CPU 使用权并排到就绪队列的末尾让下一个进程运行。对应“西兰花”来说CPU 时间片 -你的调度器每次从用户专属队列里取出一个任务进行处理。就绪队列 - 你的 Round Robin Queue轮询队列里面放的是有任务待处理的用户 ID。剥夺 CPU 并排到队尾 - 处理完一个用户的一个任务后如果他还有任务就把他的用户 ID 重新放回轮询队列的队尾。现在看这张图是不是感觉它就是一个微型的操作系统调度器而且操作系统还有更高级的玩法——多级反馈队列。这可以给“西兰花”未来的优化提供方向多队列设置多个不同优先级的队列。新来的进程先进入最高优先级队列。时间片不同高优先级队列的时间片短保证响应快低优先级队列的时间片长提高吞吐量。反馈机制如果一个进程在一个时间片内用完了还没结束说明它可能是“长任务”就把它降级到低优先级队列。如果一个进程在时间片用完前主动放弃 CPU比如进行I/O操作说明它可能是交互式的“短任务”就让它留在高优先级队列。另外操作系统调度磁盘 I/O 请求时也会遇到一模一样的问题。如果完全按照 FIFO某个进程的大量顺序读写请求会霸占磁头导致其他进程的随机读写请求饥饿。解决方案之一就是电梯算法SCAN 或其变体其核心也是将单个 FIFO 队列拆解重新排序请求以在公平性和效率之间取得平衡。这和我们前面的思路可以说是同宗同源。所以恭喜你无意中在应用层瞥见了一个微内核的操作系统调度器最后在上个价值。计算机科学中很多看似复杂的底层原理其核心思想都具有极强的普适性。真正优秀的设计模式会反复出现在从硬件到应用、从底层到高层的各个层面。下次再有人问你如何解决资源争抢问题你不仅可以甩出“西兰花”方案还可以淡定地补充一句这其实就是操作系统级的时间片轮转调度算法在分布式系统中的应用。我们不过是在业务层用最低的成本复刻了操作系统几十年来验证过的公平性智慧。