深入解析Sentinel滑动窗口LeapArray源码与参数调优实践
如果不是因为线上一次离奇的限流误伤我可能到现在都不会去翻 Sentinel 底层的 LeapArray 源码。那次事故的表现很典型规则明明设置了每秒 100 QPS流量平稳时一点问题没有但只要一出现突发小高峰就会有大量本不该被拦截的请求被拦截而且现象只在某些时间点出现用固定窗口的思路怎么解释都解释不通。后来把源码一行行捋完才发现问题的答案根本不在于限流算法本身而在于最底下那个负责统计的滑动窗口数组——LeapArray。这篇文章我会从使用者的角度把 LeapArray 的类结构、核心方法、数据存取逻辑完整拆一遍同时结合我自己改参数跑压力测试的实测结果聊聊 sampleCount 和 intervalInMs 到底应该怎么配。如果你是做 Sentinel 二次开发或者想在自己的框架里写一个类似的滑动窗口计数器这篇文章应该能帮你省不少时间。1. 拆源码之前先定位LeapArray 在限流链路里是什么角色1.1 一次请求进来后Sentinel 做了什么先不急着看代码我们得搞清楚 LeapArray 在 Sentinel 整个限流链路里到底站在什么位置。Sentinel 的核心是一个责任链每个请求进来之后会依次经过一堆 Slot比如 FlowSlot 负责流量控制DegradeSlot 负责熔断降级ParamFlowSlot 负责热点参数限流。这些 Slot 看起来各管一摊但它们判断“放行还是拒绝”的时候靠的全是同一个东西——当前时间窗口内的统计指标。这些指标从哪里来从每个资源对应的节点来节点的实现是 StatisticNode。StatisticNode 内部维护了两个最重要的滚动计数器一个是秒级统计一个是分钟级统计代码大概是这样的private transient volatile Metric rollingCounterInSecond new ArrayMetric(2, 1000); private transient volatile Metric rollingCounterInMinute new ArrayMetric(60, 60000);ArrayMetric的内部又包了一个LeapArray。看到这里你就明白了所谓的 QPS、RT、异常比例、慢调用比例这些数字最终都落在 LeapArray 身上。所以当你发现限流不准、统计值跳变诡异的时候与其盯着规则配置怀疑人生不如先怀疑统计源头。1.2 LeapArray 在类层次里的位置LeapArray 本身是一个抽象基类名字拆开看很有意思Leap是“跳跃、滑动”Array是数组合在一起就是一个“会向前滑动跳跃的环形数组”。它下面有两个比较核心的子类BucketLeapArray用于常规的秒级/分钟级指标统计统计桶是 MetricBucket。HotLeapArray用于热点参数统计底层思路一致但行为上略有差异。数组里每个元素不是裸的统计数据而是一个包装类WindowWrapT。T 就是真正的统计桶比如 MetricBucket。WindowWrap 里装了三样东西窗口长度、窗口起始时间、统计数据本身。我用一个生活化的类比帮你建立直觉LeapArray 就像一条环形传送带传送带被等分成若干个仓位每个仓位放一个 WindowWrap 包装盒包装盒里才是真正的统计报表。传送带不停转某个时间点你会站在某个仓位面前如果你面前这个盒子已经过了时效你不会把盒子扔掉换新的而是直接把这个盒子里的旧报表清空重新写上当前时间对应的新批次数据。1.3 高并发下的读写特点限流统计是一个典型的“读多写多”场景。每个请求进来要先写一笔计数每个请求判断要不要放行又要读一次统计结果。在这种场景下给整个数组加一把锁肯定不行锁竞争会让限流组件本身变成系统的瓶颈。所以 Sentinel 选择了AtomicReferenceArrayWindowWrapT这个数据结构。它的好处是底层是一个普通对象数组但支持对数组里某个元素做原子性的 compare-and-set 操作。也就是说并发更新某个槽位的时候不需要锁整个数组只需要 CAS 这一个槽位即可。后面你会看到LeapArray 的核心方法就是靠这个 CAS 特性来保证线程安全的。2. 先理解它为什么存在固定窗口的临界突刺2.1 固定窗口实现初看起来没问题要真正看懂 LeapArray 的巧妙之处得先看它的对立面固定窗口统计。假设我们要做一分钟维度限流阈值为 6000 次。最简单粗暴的做法是维护一个计数器 count再记录一个窗口起始时间 startTime。请求进来时long now System.currentTimeMillis(); if (now - startTime 60 * 1000) { startTime now; count 0; } count; if (count 6000) { // 拒绝 }逻辑很清晰很多初级限流方案就是这么写的。它的问题在哪里在窗口边界。2.2 边界上的“两倍流量黑洞”我们用一个具体的例子来说明。窗口 A 是第 0 秒到第 60 秒窗口 B 是第 60 秒到第 120 秒。阈值是每分钟 6000 次换算一下大概是每秒 100 次。如果前 59 秒里几乎没什么流量只来了 1000 个请求那么窗口 A 还剩下 5000 次的配额。在最后 1 秒突然涌入 5000 个请求窗口 A 会全部放行。紧接着第 60 秒新的窗口 B 开始了计数清零又可以放行 6000 个。那么在第 59 秒到第 61 秒这 2 秒内系统实际可能承受了近 12000 次请求远超阈值。这种问题有个专门的名字叫“临界突刺”它并不是偶发情况只要流量存在毛刺在固定窗口下就一定会出现。你可能觉得每秒 100 次限流不算什么但如果是抢购、秒杀、接口抖动这种场景这 2 秒的高峰就足以把下游打挂。2.3 滑动窗口的朴素解决思路滑动窗口的思路是不要用一个 60 秒的大窗口而是把 1 分钟切成 60 个小窗口每个小窗口 1 秒。统计时不再看单一窗口的累计值而是把最近 60 个小窗口的值全部加起来作为当前这一分钟的流量。这样在窗口边界发生问题时旧的 1 秒小窗口会一个一个地滑出去而不是整个窗口清零重建。流量最多只会在 1 秒粒度上抖动不会出现 2 秒内翻倍的情况。LeapArray 做的事本质就是把“最近 intervalInMs 毫秒”这个滑动窗口拆成 sampleCount 个小桶然后用一个环形数组高效地管理它们。3. 类骨架逐字段拆解LeapArray 的定义、构造与 WindowWrap3.1 三个时间参数先看 LeapArray 的核心字段和构造方法public abstract class LeapArrayT { protected int windowLengthInMs; // 每个小窗口的时间跨度 protected int sampleCount; // 小窗口个数也就是数组长度 protected int intervalInMs; // 整个滑动窗口的总跨度 protected final AtomicReferenceArrayWindowWrapT array; public LeapArray(int sampleCount, int intervalInMs) { this.windowLengthInMs intervalInMs / sampleCount; this.intervalInMs intervalInMs; this.sampleCount sampleCount; this.array new AtomicReferenceArray(sampleCount); } }注意三者的关系windowLengthInMs intervalInMs / sampleCount。也就是说总窗口跨度被均分成了 sampleCount 份每一份是一个独立的统计桶。这个除法要求能够整除否则最后一个窗口会同其他窗口长度不一致导致统计偏差后面我会专门提到这个坑。array.length()就是 sampleCount。数组下标是固定的但数组元素WindowWrap里记录的 windowStart 是随着时间不断变化的所以它才能“复用”。3.2 WindowWrap窗口的包装盒WindowWrap 的代码非常简洁public class WindowWrapT { private final long windowLengthInMs; // 窗口长度 private long windowStart; // 窗口起始时间戳 private T value; // 统计桶 public WindowWrap(long windowLengthInMs, long windowStart, T value) { this.windowLengthInMs windowLengthInMs; this.windowStart windowStart; this.value value; } public long windowStart() { return windowStart; } public T value() { return value; } public void setValue(T value) { this.value value; } public void resetTo(long startTime) { this.windowStart startTime; } }这里有一个关键点为什么 windowLengthInMs 用final而 windowStart 是可变的因为一个数组槽位对应的窗口长度是永远不变的变的是“这个槽位当前代表哪个时间段”。随着时间推进同一个数组槽位会被反复重置给新的时间段这就是滑动窗口的核心机制。真正会变的是 windowStart它在每次窗口过期时被重置到新的时间点。3.3 两个抽象方法新桶与重置LeapArray 定义了两个抽象方法留给子类去实现public abstract T newEmptyBucket(long timeMillis); protected abstract WindowWrapT resetWindowStart(WindowWrapT windowWrap, long startTime);newEmptyBucket创建一个全新的统计桶。对于 BucketLeapArray 来说就是 new 一个 MetricBucket。resetWindowStart当数组槽位上的旧窗口已经过期时把这个 WindowWrap 重置到新的时间点。BucketLeapArray 的实现大概是这样的public class BucketLeapArray extends LeapArrayMetricBucket { public BucketLeapArray(int sampleCount, int intervalInMs) { super(sampleCount, intervalInMs); } Override public MetricBucket newEmptyBucket(long timeMillis) { return new MetricBucket(); } Override protected WindowWrapMetricBucket resetWindowStart(WindowWrapMetricBucket w, long startTime) { w.resetTo(startTime); w.value().reset(); return w; } }注意这里的设计取舍窗口过期时它没有扔掉旧的 WindowWrap也没有创建新的 WindowWrap而是直接复用对象只改 windowStart并且把旧统计桶里的数据清空。这样带来的最大好处是大幅减少对象创建频率。限流场景下每秒可能有成千上万个请求在读写统计桶如果每次都 new 对象GC 压力会非常难看。我见过一些自研限流组件在高并发下频繁 Full GC查到最后就是这种小对象的分配问题。4. currentWindow() 核心方法拆解时间戳到桶的下标映射与桶复用4.1 两个辅助计算时间戳如何变成数组下标要拿到“当前时间对应的窗口”需要先做两个计算private int calculateTimeIdx(long timeMillis) { long timeId timeMillis / windowLengthInMs; return (int) (timeId % array.length()); } protected long calculateWindowStart(long timeMillis) { return timeMillis - timeMillis % windowLengthInMs; }这里面的逻辑非常精妙。timeMillis / windowLengthInMs可以得到“从某个基准时刻到现在一共经历了多少个完整的小窗口”这个数字记为 timeId。然后timeId % array.length()得到一个固定范围内的数组下标。因为 timeId 会随着时间递增所以取模的结果会在 0 到 sampleCount-1 之间循环这就是“环形数组”的由来。calculateWindowStart的作用是把任意一个时间戳“对齐”到它所属窗口的起始时间。比如 windowLengthInMs 是 500ms当前时间戳是 1630000000123ms那么窗口起始时间就是 1630000000000ms也就是把时间戳向下取整到 500 的整倍数。4.2 currentWindow() 的完整逻辑核心方法来了这段代码是整个 LeapArray 的心脏public WindowWrapT currentWindow(long timeMillis) { if (timeMillis 0) { return null; } int idx calculateTimeIdx(timeMillis); long windowStart calculateWindowStart(timeMillis); while (true) { WindowWrapT old array.get(idx); if (old null) { // 槽位还是空的创建新窗口并 CAS 放入 WindowWrapT window new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); if (array.compareAndSet(idx, null, window)) { return window; } else { Thread.yield(); } } else if (windowStart old.windowStart()) { // 当前时间正好落在 old 这个窗口内直接复用 return old; } else if (windowStart old.windowStart()) { // 槽位上是一个已经过期的窗口重置它 if (resetWindowStart(old, windowStart)) { return old; } else { Thread.yield(); } } else { // 理论上是时钟回拨或者异常情况创建一个临时窗口返回 return new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); } } }这段代码虽然短但每一个分支都有它的存在理由。4.3 四种分支逐一分析第一种情况old null。这个槽位从来没有被写入过说明当前时间点前面的窗口还没人碰过这个位置。此时创建一个新的 WindowWrap然后用compareAndSet(idx, null, window)尝试放入。CAS 成功说明没有竞争直接返回CAS 失败说明有另一个线程抢先创建了那么当前线程就让出 CPU重新进入循环再读一次。第二种情况windowStart old.windowStart()。这是最常见的情况说明当前时间戳和这个槽位里记录的时间段正好匹配。比如 10:00:00.100 和 10:00:00.400 都属于同一个 500ms 窗口所以直接返回 old 即可所有并发请求都会拿到同一个统计桶往里面累加数据。第三种情况windowStart old.windowStart()。说明槽位里存的是一个“过去”的窗口它已经不在当前滑动窗口范围内了。此时需要把它重置到新的时间点然后返回 old。这里之所以能复用对象就是因为它避开了频繁创建新对象的开销。但并发下可能有两个线程同时发现这个窗口过期都去执行重置于是 CAS 或某种同步机制会保证只有一个线程重置成功失败者自旋重试。第四种情况windowStart old.windowStart()。槽位里的窗口起始时间比当前计算的窗口起始时间还要晚这只能说明发生了时钟回拨或者某种极端异常。此时反正拿不到正确的窗口索性直接 new 一个临时 WindowWrap 返回保证当前请求能有一个桶可以用同时不污染数组里的已有数据。4.4 为什么非要 while(true) 自旋一开始我觉得这个自旋有点“浪费 CPU”后来在压测环境下才真正体会它的价值。在高并发下多个线程可能同时发现同一个槽位过期同时尝试重置。如果重置操作本身是 CAS 实现的那么只有一个线程能成功其他线程必须重新读取数组。一旦成功线程返回后其他线程再读数组就会发现 windowStart 已经更新进入第二种情况直接复用窗口整个过程就收敛了。如果没有这个自旋失败线程直接返回 null 或者创建一个新窗口就会出现某个请求拿到的统计桶不是当前的正式桶数据写到临时对象里丢了限流统计就会出现莫名其妙的误差。所以这个 while(true) 不是性能浪费而是正确性的兜底。5. 统计侧源码getPreviousWindow、values() 和上层聚合5.1 getPreviousWindow上一个窗口怎么找currentWindow 是拿“当前窗口”但有些场景需要参考“上一个窗口”的数据比如慢调用比例、异常比例等需要对比当前窗口和上一个窗口的差值来判断趋势。public WindowWrapT getPreviousWindow(long timeMillis) { if (timeMillis 0) { return null; } int idx calculateTimeIdx(timeMillis - windowLengthInMs); long windowStart calculateWindowStart(timeMillis - windowLengthInMs); while (true) { WindowWrapT previousWindow array.get(idx); if (previousWindow null) { return null; } if (windowStart previousWindow.windowStart()) { return previousWindow; } if (windowStart previousWindow.windowStart()) { return null; } return null; } }它的逻辑很直观先把时间往前拨一个窗口长度再计算对应的数组下标和窗口起始时间然后去数组里找。如果找到的窗口起始时间和预期的 windowStart 一致说明确实存在一个完整的上一窗口如果那个槽位还是空的或者已经是更老的窗口就返回 null。有一个容易忽略的细节这个方法访问数组时没有写操作所以在并发下读到不一致的数据是可能的。但因为统计本身是近似的上一窗口少统计一点或多统计一点在工程上是可以接受的Sentinel 就是这么权衡的。5.2 values()只把“活着”的窗口捞出来上层聚合统计时最常用的方法是 values()它用来获取当前时刻所有“还没有结束”的窗口public ListT values(long timeMillis) { if (timeMillis 0) { return new ArrayList(); } int size array.length(); ListT result new ArrayList(size); for (int i 0; i size; i) { WindowWrapT windowWrap array.get(i); if (windowWrap null) { continue; } if (timeMillis windowWrap.windowStart() windowLengthInMs) { result.add(windowWrap.value()); } } return result; }关键在于这个判断timeMillis windowWrap.windowStart() windowLengthInMs。它检查的是这个窗口的结束时间是否在当前时间之后。如果窗口的结束时间已经小于等于当前时间说明这个窗口已经彻底滑出统计区间了虽然它还物理存在于数组里但不能再参与当前统计。这就是滑动窗口“滑动”的另一面不是靠后台线程去清理旧窗口而是在读取时用时间戳自然过滤。懒加载、懒清理这个思路在 Sentinel 里用得很彻底。5.3 上层怎么聚合出 QPS以秒级统计为例StatisticNode 里的 QPS 计算过程大致是把 values() 返回的所有 MetricBucket 里的 pass 计数加起来。我用简化代码表示public long passQps() { long pass 0; ListMetricBucket buckets rollingCounterInSecond.values(); for (MetricBucket bucket : buckets) { pass bucket.pass(); } return pass; }这里的聚合必须用“遍历所有小窗口累加”而不是只取当前窗口的值再乘一个倍数。因为最近 1 秒这个滑动窗口里可能包含部分上个周期的窗口和部分当前窗口只有全部取出来相加才能得到真正的“最近 1 秒”统计值。这里也顺带解释了为什么限流的 QPS 数值总是有轻微波动因为它是多个小窗口的瞬时累加窗口之间天然存在切换的瞬间。理解了这个你就不会因为看到 QPS 显示 98、99、101 而觉得系统出 bug 了。6. sampleCount 和 intervalInMs 的选型以及我踩过的坑6.1 默认参数对照Sentinel 里默认的统计配置是这样的使用场景sampleCountintervalInMs单个窗口长度秒级统计21000500ms分钟级统计60600001000ms秒级统计用 2 个 500ms 的小窗口分钟级统计用 60 个 1s 的小窗口。这个设计是在“精度”和“内存/CPU开销”之间取的平衡。你如果自己配置需要清楚调整任何一个参数带来的连锁反应。6.2 sampleCount 大小如何取舍我实测过一组数据同一个限流规则阈值 100 QPS突发流量用脚本每 200ms 打进一批请求对比 sampleCount 为 2、10、60 时的通过情况。sampleCount单窗口长度实际表现2500ms窗口边界处有明显抖动突发时偶尔多放行十几二十个请求10100ms平滑很多限流曲线更贴合阈值60约16.7ms统计很精细但每个桶的计数周期太短内存和遍历开销上升明显简单来说sampleCount 太小滑动窗口的“滑动粒度”太粗临界突刺虽然比固定窗口好很多但还是会有毛刺sampleCount 太大每个小窗口的时间太短很容易出现某个窗口只有一两个请求甚至没有请求统计的离散性反而变大同时遍历和内存成本也上去了。如果问我怎么选我的建议是默认配置能满足绝大多数场景不用动。如果你确实对精度有要求比如希望限流反应更快可以调大 sampleCount但不要超过 60。超过之后收益基本下降成本反而明显上升。6.3 踩坑被重置的旧窗口返回瞬间统计为 0这是我实际遇到的一个问题。某个接口用 Sentinel 做慢调用比例熔断阈值设的是“RT 超过 200ms 的比例达到 30% 就熔断”。上线之后熔断偶尔会误触发查监控发现触发瞬间的慢调用比例高达 40% 以上但事后看请求的 p99 明明在 150ms 左右。后来定位到原因慢调用比例的统计依赖秒级窗口而秒级窗口只有 2 个 500ms 的桶。当某个桶刚好被重置到新的时间窗口时这个桶里的统计数据是空的0而另一个桶里保留的是上一个窗口的累积数据。此时如果恰好有慢请求打进来新的桶里瞬间累积了较高的慢调用比例两桶相加之后比例数值就被短暂放大触发了熔断。这种问题不算 LeapArray 的 bug而是“统计窗口很粗 桶复用机制”带来的瞬时失真。解决思路是在判断比例类指标时对刚重置的窗口做一定的忽略或预热或者把 sampleCount 调大一些让单个桶的影响变小。6.4 排查统计不准的三个方向如果你也遇到限流统计和真实流量对不上我建议按这个顺序排查第一检查 intervalInMs 是否能被 sampleCount 整除。如果不能整除最后一个窗口的长度会和前面的不一致导致 values() 聚合出来的时间跨度不是预期值。这个属于配置错误最隐蔽。第二检查系统时钟是否发生过跃变。LeapArray 的所有计算都依赖时间戳时钟往前跳一次可能导致 windowStart 计算出来的值反而小于 old.windowStart()触发第四种分支产生临时桶时钟往后退一次则可能出现窗口长期不切换统计值叠加。第三检查你是不是在多个线程里手动修改了 WindowWrap 的 windowStart 或 value。LeapArray 的内部机制保证了一个槽位在并发下只有一个所有者但你如果绕过 currentWindow() 直接操作数组就会破坏这个约束。我自己调试时干过这种事最后统计乱得没法看折腾了一下午才找到原因。6.5 给自己项目写类似组件的一点启发读完 LeapArray我最想强调的是“复用”这两个字。现在很多做技术的人一遇到性能问题就上缓存、加锁、换数据库但 Sentinel 给我们展示了一个非常经典的思路在高频读写场景里用环形数组 时间戳取模 CAS 复用对象可以做到无锁、无频繁对象创建同时保持统计精度。如果你的项目也需要一个轻量滑动窗口计数器比如接口 QPS 统计、登录限流、验证码发送频率控制完全可以参考这个设计。用 Java 的话可以直接抄 AtomicReferenceArray 的写法用 Go 的话可以用 atomic 包里的 CompareAndSwapPointer 做类似的事情。数据结构本身是跨语言通用的。最后再分享一个我自己养成的习惯拿到一个开源限流框架先别忙着配规则先花半小时把它最底层的统计结构读一遍。因为所有规则、算法、配置最终都是建立在统计数据之上的。数据结构的边界在哪里整个系统的行为边界就在哪里。LeapArray 虽然代码不长但它把“环形数组、时间窗口、CAS、对象复用”这四件事揉在一起读透之后以后再看任何限流、熔断、监控组件都会有一种豁然开朗的感觉。

相关新闻

微服务架构故障下的技术取舍:从限流降级到根因修复的决策之路

微服务架构故障下的技术取舍:从限流降级到根因修复的决策之路

凌晨两点十七分,监控大屏上的红色告警已经持续跳动了快四十分钟。我坐在工位上,面前的笔记本上画着两条完全不同的方案路径。旁边放着一杯已经彻底凉透的咖啡,那是晚上十点续的第二杯。整个办公区只剩我这一片还亮着灯,运维同事在…

2026/10/14 21:48:52 阅读更多 →
inSSIDer 2.0 无线勘察实战:从 802.11 管理帧到信道规划与干扰排查

inSSIDer 2.0 无线勘察实战:从 802.11 管理帧到信道规划与干扰排查

简介:inSSIDer2.0是一款面向Windows平台的免费Wi-Fi分析工具,适合家庭用户、网络运维人员及无线网络爱好者排查信号弱、网速慢、信道拥堵等问题。它可实时监测周边无线网络的信号强度,分析同频段信道占用情况并推荐更空闲的频道,同…

2026/10/14 21:48:52 阅读更多 →
音乐创作中的默契合作与技术创新

音乐创作中的默契合作与技术创新

1. 音乐合作背后的故事解析《赴一场前世的约定》这首作品最打动人的地方在于它延续了徐子崴与罗姣之间独特的音乐默契。作为长期合作的"歌坛知音",两人的配合已经达到了心有灵犀的程度。徐子崴擅长用音乐讲故事,而罗姣的嗓音则像一把打开情感之…

2026/10/14 21:47:51 阅读更多 →

最新新闻

AI芯片浪潮下,晶圆级测试为何成为关键路径?

AI芯片浪潮下,晶圆级测试为何成为关键路径?

1. 一个被AI芯片浪潮重新“翻红”的工序1.1 从“例行公事”到“生死关键”前几天跟一个做AI芯片设计的团队复盘项目,对方跑来跟我抱怨:“流片回来之前,没人把晶圆级测试当回事;回来之后,所有人都在催测试结果。”这话我…

2026/10/14 23:18:19 阅读更多 →
Spring Boot @ConditionalOnProperty 详解:用配置属性控制 Bean 创建

Spring Boot @ConditionalOnProperty 详解:用配置属性控制 Bean 创建

后端开发里有个高频需求:同一个模块,测试环境想用一个轻量实现,生产环境要换正式实现;或者新功能的代码提前合并进了主干,但上线节奏没到,必须靠一个开关把它“摁住”。Spring Boot 的ConditionalOnPropert…

2026/10/14 23:18:19 阅读更多 →
线性表六种实现原理与实战选型指南

线性表六种实现原理与实战选型指南

1. 为什么“线性表”总像一层毛玻璃?——从模糊认知到结构直觉的转变刚学数据结构那会儿,我对着课本上“线性表是零个或多个数据元素的有限序列”这句话看了三遍,点头说“懂了”,转头写代码就卡在“到底该用数组还是指针”“reall…

2026/10/14 23:18:19 阅读更多 →
GMM高斯混合模型MATLAB实现:从手写EM到fitgmdist实战指南

GMM高斯混合模型MATLAB实现:从手写EM到fitgmdist实战指南

简介:这份MATLAB编写的GMM代码包聚焦高斯混合模型在图像背景减除中的应用,适合计算机视觉学习者、图像处理研究者及需要快速实现前景检测的开发者。压缩包共7个文件,以5个m脚本、1个eps图和1个mat数据文件为主体,整体约40KB&#…

2026/10/14 23:18:19 阅读更多 →
nanoMuse:轻量化世界模型,游戏生成AI的消费级实践

nanoMuse:轻量化世界模型,游戏生成AI的消费级实践

最近几天,各个技术群和朋友圈里,nanoMuse 这个名字出现的频率明显高了起来。作为一个常年关注生成式 AI 落地的从业者,我也第一时间把这套开源项目拉下来跑了一遍,整体感受可以用“意料之中,也有点惊喜”来形容。nanoM…

2026/10/14 23:18:19 阅读更多 →
华为BFD双向转发检测实战:毫秒级故障检测与调优排错指南

华为BFD双向转发检测实战:毫秒级故障检测与调优排错指南

1. 从一个“链路假死”的经典故障说起如果你做过企业级网络运维,大概率遇到过这种让人后背发凉的情况:核心交换机和上行路由器之间的物理链路指示灯一切正常,端口状态是 UP,光模块收发光功率也在正常范围内,但业务流量…

2026/10/14 23:17:13 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →