fix(fr007-cache): 直查命中失效快照+去冗余排序+重载失败保旧快照防重试风暴
- EodPriceQueryService.TryGetPrice: FR007 快照 miss 直查库命中后 Invalidate, 下次访问整表重载回 O(1),否则该键 TTL 窗口内每次读取都退化为点查 - 移除点查多余的 OrderByDescending(主键等值查询取首行即可) - Fr007FixingCache.EnsureFresh: 重载失败(DB抖动)保留旧快照不抛并重置TTL时钟 ——历史定盘不可变旧值依旧正确,同时防重试风暴;版本失效路径不受TTL掩盖 - 补两测试: 重载失败保快照窗口内不重试 / 首次装载失败miss不抛窗口内不重试
This commit is contained in:
@@ -101,5 +101,37 @@ namespace YLErp.Modules.DataProviderModule
|
|||||||
|
|
||||||
Assert.AreEqual(1, _loadCount, "稳态窗口内多次读取零重载(零查询)");
|
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窗口内不重试");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ namespace YLErp.Modules.DataProviderModule
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
using var db = DbContextFactory.GetYLDbContext();
|
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)
|
if (data != null)
|
||||||
{
|
{
|
||||||
@@ -164,6 +164,11 @@ namespace YLErp.Modules.DataProviderModule
|
|||||||
// 利息腿计算直接作为 floatRate 参与 principal*(fixedRate+floatRate)/annualDays,无需再 ÷100。
|
// 利息腿计算直接作为 floatRate 参与 principal*(fixedRate+floatRate)/annualDays,无需再 ÷100。
|
||||||
price = data.ReferencePrice ?? 0;
|
price = data.ReferencePrice ?? 0;
|
||||||
|
|
||||||
|
// 快照漏掉的行(当日新发布/补录历史)直查命中——失效快照,下次访问整表重载后回到 O(1),
|
||||||
|
// 否则该键在 TTL 窗口内每次读取都退化为点查。
|
||||||
|
if (underlyingCode == Fr007FixingCache.UnderlyingCode)
|
||||||
|
Fr007FixingCache.Invalidate();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,11 +80,24 @@ namespace YLErp.Modules.DataProviderModule
|
|||||||
lock (_reloadLock)
|
lock (_reloadLock)
|
||||||
{
|
{
|
||||||
if (IsFresh()) return;
|
if (IsFresh()) return;
|
||||||
var snap = LoadSnapshot();
|
try
|
||||||
Volatile.Write(ref _snapshot, snap);
|
{
|
||||||
Volatile.Write(ref _loaded, true);
|
var snap = LoadSnapshot();
|
||||||
Volatile.Write(ref _loadedAt, NowTicks());
|
Volatile.Write(ref _snapshot, snap);
|
||||||
Volatile.Write(ref _snapVersion, Volatile.Read(ref _writeVersion));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user