From 7a5089555dd73f0d28a6f65ad06cda0316fbfae9 Mon Sep 17 00:00:00 2001 From: hjhan Date: Tue, 18 Aug 2026 09:33:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(fr007-cache):=20=E7=9B=B4=E6=9F=A5=E5=91=BD?= =?UTF-8?q?=E4=B8=AD=E5=A4=B1=E6=95=88=E5=BF=AB=E7=85=A7+=E5=8E=BB?= =?UTF-8?q?=E5=86=97=E4=BD=99=E6=8E=92=E5=BA=8F+=E9=87=8D=E8=BD=BD?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E4=BF=9D=E6=97=A7=E5=BF=AB=E7=85=A7=E9=98=B2?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E9=A3=8E=E6=9A=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EodPriceQueryService.TryGetPrice: FR007 快照 miss 直查库命中后 Invalidate, 下次访问整表重载回 O(1),否则该键 TTL 窗口内每次读取都退化为点查 - 移除点查多余的 OrderByDescending(主键等值查询取首行即可) - Fr007FixingCache.EnsureFresh: 重载失败(DB抖动)保留旧快照不抛并重置TTL时钟 ——历史定盘不可变旧值依旧正确,同时防重试风暴;版本失效路径不受TTL掩盖 - 补两测试: 重载失败保快照窗口内不重试 / 首次装载失败miss不抛窗口内不重试 --- .../Fr007FixingCacheTest.cs | 32 +++++++++++++++++++ .../EodPriceQueryService.cs | 7 +++- .../DataProviderModule/Fr007FixingCache.cs | 23 ++++++++++--- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/UnitTestProject/Modules/DataProviderModule/Fr007FixingCacheTest.cs b/UnitTestProject/Modules/DataProviderModule/Fr007FixingCacheTest.cs index 02eeb6ed..f4784bb2 100644 --- a/UnitTestProject/Modules/DataProviderModule/Fr007FixingCacheTest.cs +++ b/UnitTestProject/Modules/DataProviderModule/Fr007FixingCacheTest.cs @@ -101,5 +101,37 @@ namespace YLErp.Modules.DataProviderModule Assert.AreEqual(1, _loadCount, "稳态窗口内多次读取零重载(零查询)"); } + + [TestMethod] + public void TTL过期重载失败_保留旧快照不抛_且窗口内不重试() + { + Fr007FixingCache.NowTicks = () => 1_000_000L; + Assert.IsTrue(Fr007FixingCache.TryGet(D1, out _), "先成功装载一次"); + Assert.AreEqual(1, _loadCount); + + Fr007FixingCache.LoadSnapshot = () => { _loadCount++; throw new InvalidOperationException("db down"); }; + Fr007FixingCache.NowTicks = () => 1_000_000L + TimeSpan.FromMinutes(1).Ticks + 1; // TTL 过期 → 触发重载 → 失败 + + // 不抛:历史定盘不可变,命中继续走旧快照(异常会直接 fail 本用例) + Assert.IsTrue(Fr007FixingCache.TryGet(D1, out var p), "重载失败应保留旧快照继续命中"); + Assert.AreEqual(0.0142, p, 1e-12, "旧快照值不变"); + Assert.AreEqual(2, _loadCount, "失败的重载尝试恰好一次"); + + Fr007FixingCache.TryGet(D2, out _); // TTL 时钟已被重置:窗口内不再重试(防重试风暴) + Assert.AreEqual(2, _loadCount, "失败后TTL窗口内不得反复重试全表SELECT"); + } + + [TestMethod] + public void 首次装载失败_miss不抛_窗口内不重试() + { + Fr007FixingCache.LoadSnapshot = () => { _loadCount++; throw new InvalidOperationException("db down"); }; + Fr007FixingCache.NowTicks = () => 1_000_000L; + + Assert.IsFalse(Fr007FixingCache.TryGet(D1, out _), "装载失败=空快照miss(调用方直查库兜底,新数据该响仍响)"); + Assert.AreEqual(1, _loadCount); + + Fr007FixingCache.TryGet(D1, out _); + Assert.AreEqual(1, _loadCount, "失败后TTL窗口内不重试"); + } } } diff --git a/YLErpDAL/Modules/DataProviderModule/EodPriceQueryService.cs b/YLErpDAL/Modules/DataProviderModule/EodPriceQueryService.cs index c8a73b43..23e1dd26 100644 --- a/YLErpDAL/Modules/DataProviderModule/EodPriceQueryService.cs +++ b/YLErpDAL/Modules/DataProviderModule/EodPriceQueryService.cs @@ -156,7 +156,7 @@ namespace YLErp.Modules.DataProviderModule return true; using var db = DbContextFactory.GetYLDbContext(); - var data = db.eod_commodity_future_price.Where(x => x.ValueDate == valueDate && x.UnderlyingCode == underlyingCode).OrderByDescending(o => o.ValueDate).FirstOrDefault(); + var data = db.eod_commodity_future_price.Where(x => x.ValueDate == valueDate && x.UnderlyingCode == underlyingCode).FirstOrDefault(); if (data != null) { @@ -164,6 +164,11 @@ namespace YLErp.Modules.DataProviderModule // 利息腿计算直接作为 floatRate 参与 principal*(fixedRate+floatRate)/annualDays,无需再 ÷100。 price = data.ReferencePrice ?? 0; + // 快照漏掉的行(当日新发布/补录历史)直查命中——失效快照,下次访问整表重载后回到 O(1), + // 否则该键在 TTL 窗口内每次读取都退化为点查。 + if (underlyingCode == Fr007FixingCache.UnderlyingCode) + Fr007FixingCache.Invalidate(); + return true; } diff --git a/YLErpDAL/Modules/DataProviderModule/Fr007FixingCache.cs b/YLErpDAL/Modules/DataProviderModule/Fr007FixingCache.cs index 16d6ef20..8fa0d85f 100644 --- a/YLErpDAL/Modules/DataProviderModule/Fr007FixingCache.cs +++ b/YLErpDAL/Modules/DataProviderModule/Fr007FixingCache.cs @@ -80,11 +80,24 @@ namespace YLErp.Modules.DataProviderModule lock (_reloadLock) { if (IsFresh()) return; - var snap = LoadSnapshot(); - Volatile.Write(ref _snapshot, snap); - Volatile.Write(ref _loaded, true); - Volatile.Write(ref _loadedAt, NowTicks()); - Volatile.Write(ref _snapVersion, Volatile.Read(ref _writeVersion)); + try + { + var snap = LoadSnapshot(); + Volatile.Write(ref _snapshot, snap); + Volatile.Write(ref _loaded, true); + Volatile.Write(ref _loadedAt, NowTicks()); + Volatile.Write(ref _snapVersion, Volatile.Read(ref _writeVersion)); + } + catch (Exception ex) + { + // 重载失败(如DB抖动):保留旧快照并重置TTL时钟——历史定盘不可变,旧值依旧正确; + // 同时防止每次读取都重试全表SELECT(重试风暴)。不抛:命中继续走旧快照, + // miss照旧回退直查库,新数据取不到仍会在直查处响亮报错。 + // 注:版本失效(Invalidate)后的重载失败不会被TTL掩盖——版本不相等会持续重试直到成功。 + Volatile.Write(ref _loaded, true); + Volatile.Write(ref _loadedAt, NowTicks()); + LogFactory.GetLogger("Fr007FixingCache").Error("FR007定盘缓存重载失败,沿用旧快照", ex); + } } }