481 lines
15 KiB
Markdown
481 lines
15 KiB
Markdown
|
|
# 去重策略规范 (Deduplication Specification)
|
|||
|
|
|
|||
|
|
## 1. 去重概述
|
|||
|
|
|
|||
|
|
本系统实现**双层去重**策略,分别在内存缓冲和数据库写入两个层面对心跳数据进行去重:
|
|||
|
|
|
|||
|
|
1. **Layer 1 - 5秒缓冲去重**: 内存中维护 5 秒时间窗口,同一键只保留最新记录
|
|||
|
|
2. **Layer 2 - 30秒写入冷却**: 每个键写入 DB 后,30 秒内不再写入,减轻数据库压力
|
|||
|
|
|
|||
|
|
## 2. 去重键设计
|
|||
|
|
|
|||
|
|
### 2.1 键的组成
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const key = `${hotel_id}:${room_id}`;
|
|||
|
|
|
|||
|
|
// 示例:
|
|||
|
|
// hotel_id = "2045", room_id = "6010"
|
|||
|
|
// → key = "2045:6010"
|
|||
|
|
|
|||
|
|
// hotel_id = "1309", room_id = "大会议室"
|
|||
|
|
// → key = "1309:大会议室"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 为什么选择 (hotel_id, room_id) 作为去重键
|
|||
|
|
|
|||
|
|
**业务含义**: 一个酒店内的一个房间在同一时刻只能有一个设备状态
|
|||
|
|
|
|||
|
|
**设计决策**:
|
|||
|
|
- **不包含 device_id**: 同一房间的多个设备(如多个传感器)应被视为同一状态
|
|||
|
|
- **不包含 ts_ms**: 时间戳用于排序,不用于去重
|
|||
|
|
- **不包含 current_time**: 冗余时间戳,已有 ts_ms
|
|||
|
|
|
|||
|
|
**SQL 对应**: 数据库表 `room_status_moment_g5` 的主键 = `(hotel_id, room_id)`
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE room_status_moment_g5 (
|
|||
|
|
hotel_id SMALLINT,
|
|||
|
|
room_id TEXT,
|
|||
|
|
device_id VARCHAR(255),
|
|||
|
|
ts_ms BIGINT,
|
|||
|
|
status SMALLINT,
|
|||
|
|
PRIMARY KEY (hotel_id, room_id)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 3. 第一层:5秒缓冲去重
|
|||
|
|
|
|||
|
|
### 3.1 工作原理
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class HeartbeatBuffer {
|
|||
|
|
constructor(maxBufferSize = 5000, windowMs = 5000) {
|
|||
|
|
this.buffer = new Map(); // key → latest record
|
|||
|
|
this.maxBufferSize = maxBufferSize;
|
|||
|
|
this.windowMs = windowMs; // 5000ms
|
|||
|
|
this.flushTimer = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
add(record) {
|
|||
|
|
const key = this._getKey(record);
|
|||
|
|
|
|||
|
|
if (this.buffer.has(key)) {
|
|||
|
|
// 更新逻辑:只保留 ts_ms 最新的
|
|||
|
|
const existing = this.buffer.get(key);
|
|||
|
|
if (record.ts_ms > existing.ts_ms) {
|
|||
|
|
// 新记录更新 → 覆盖旧的
|
|||
|
|
this.buffer.set(key, record);
|
|||
|
|
}
|
|||
|
|
// 否则丢弃更旧的记录,保持缓冲中的最新版本
|
|||
|
|
} else {
|
|||
|
|
// 新键 → 直接添加
|
|||
|
|
this.buffer.set(key, record);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果缓冲满 → 立即刷新(不等待 5s)
|
|||
|
|
if (this.buffer.size >= this.maxBufferSize) {
|
|||
|
|
this._flush();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_getKey(record) {
|
|||
|
|
return `${record.hotel_id}:${record.room_id}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_flush() {
|
|||
|
|
// 触发数据库写入...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 时间窗口示意
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
时间轴 (单位: 毫秒)
|
|||
|
|
|
|||
|
|
T=0ms ┌─ add({hotel_id:"2045", room_id:"6010", ts_ms:1000})
|
|||
|
|
│ buffer = {"2045:6010" → {ts_ms:1000}}
|
|||
|
|
│
|
|||
|
|
T=100ms ├─ add({hotel_id:"2045", room_id:"6010", ts_ms:1050})
|
|||
|
|
│ ts_ms 1050 > 1000 → 更新
|
|||
|
|
│ buffer = {"2045:6010" → {ts_ms:1050}}
|
|||
|
|
│
|
|||
|
|
T=200ms ├─ add({hotel_id:"2045", room_id:"6010", ts_ms:1030})
|
|||
|
|
│ ts_ms 1030 < 1050 → 丢弃,保持 1050
|
|||
|
|
│ buffer = {"2045:6010" → {ts_ms:1050}}
|
|||
|
|
│
|
|||
|
|
T=500ms ├─ add({hotel_id:"2045", room_id:"6010", ts_ms:1200})
|
|||
|
|
│ ts_ms 1200 > 1050 → 更新
|
|||
|
|
│ buffer = {"2045:6010" → {ts_ms:1200}}
|
|||
|
|
│
|
|||
|
|
T=5000ms └─ [Scheduled Flush]
|
|||
|
|
Write {hotel_id:"2045", room_id:"6010", ts_ms:1200}
|
|||
|
|
→ database
|
|||
|
|
|
|||
|
|
结果: 5 秒内 4 条重复消息,实际只写入 1 条(最新的)
|
|||
|
|
去重率: 75% (4-1)/4
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 去重效果分析
|
|||
|
|
|
|||
|
|
**输入场景**: 同一房间心跳设备在 5 秒内发送多条消息
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 真实数据示例
|
|||
|
|
const messagesIn5Seconds = [
|
|||
|
|
{hotel_id:"2045", room_id:"6010", device_id:"DEV1", ts_ms:1000},
|
|||
|
|
{hotel_id:"2045", room_id:"6010", device_id:"DEV1", ts_ms:1010}, // 重复
|
|||
|
|
{hotel_id:"2045", room_id:"6010", device_id:"DEV2", ts_ms:1005}, // 同房不同设备
|
|||
|
|
{hotel_id:"2045", room_id:"6010", device_id:"DEV1", ts_ms:1015}, // 重复(最新)
|
|||
|
|
{hotel_id:"2045", room_id:"6010", device_id:"DEV1", ts_ms:1008}, // 重复(旧)
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 缓冲处理
|
|||
|
|
const buffer = new HeartbeatBuffer(5000, 5000);
|
|||
|
|
for (const msg of messagesIn5Seconds) {
|
|||
|
|
const parsed = parseHeartbeat(JSON.stringify(msg));
|
|||
|
|
buffer.add(parsed);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 缓冲内容(5秒后刷新)
|
|||
|
|
// {"2045:6010" → {ts_ms: 1015}}
|
|||
|
|
|
|||
|
|
// 结果:5 条输入 → 1 条输出
|
|||
|
|
// device_id 合并(同房)+ ts_ms 排序(保留最新) = 高效去重
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.4 缓冲满时的行为
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 配置
|
|||
|
|
const buffer = new HeartbeatBuffer(maxBufferSize = 5000, windowMs = 5000);
|
|||
|
|
|
|||
|
|
// 如果在短时间内收到超过 5000 条不同键的消息
|
|||
|
|
for (let i = 0; i < 6000; i++) {
|
|||
|
|
buffer.add({
|
|||
|
|
hotel_id: String(Math.floor(i / 1000)), // 0-5
|
|||
|
|
room_id: String(i % 1000), // 0-999
|
|||
|
|
device_id: "DEV1",
|
|||
|
|
ts_ms: Date.now()
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 当 buffer.size >= 5000 时,主动触发 flush(不等待 5s)
|
|||
|
|
// 这是防止内存溢出的安全机制
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 4. 第二层:30秒写入冷却期
|
|||
|
|
|
|||
|
|
### 4.1 冷却期的核心逻辑
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class HeartbeatBuffer {
|
|||
|
|
constructor(cooldownMs = 30000) {
|
|||
|
|
this.lastWrittenAt = new Map(); // key → timestamp
|
|||
|
|
this.cooldownMs = cooldownMs; // 30000ms
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async _flush() {
|
|||
|
|
const nowTs = this.now();
|
|||
|
|
const writableEntries = [];
|
|||
|
|
let minCooldownDelayMs = null;
|
|||
|
|
|
|||
|
|
// 遍历缓冲中的所有键
|
|||
|
|
for (const [key, row] of this.buffer.entries()) {
|
|||
|
|
|
|||
|
|
// 检查冷却期
|
|||
|
|
const cooldownDelayMs = this._getCooldownDelayMs(key, nowTs);
|
|||
|
|
|
|||
|
|
if (cooldownDelayMs > 0) {
|
|||
|
|
// 仍在冷却期 → 跳过,保留在缓冲中等待
|
|||
|
|
minCooldownDelayMs = minCooldownDelayMs == null
|
|||
|
|
? cooldownDelayMs
|
|||
|
|
: Math.min(minCooldownDelayMs, cooldownDelayMs);
|
|||
|
|
continue; // 不写入
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 冷却期已过 → 标记为可写
|
|||
|
|
writableEntries.push([key, row]);
|
|||
|
|
this.buffer.delete(key); // 从缓冲移除
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 执行数据库写入
|
|||
|
|
if (writableEntries.length > 0) {
|
|||
|
|
try {
|
|||
|
|
const rows = writableEntries.map(([, row]) => row);
|
|||
|
|
await this.dbManager.upsertBatch(rows);
|
|||
|
|
|
|||
|
|
// 标记写入时间(启动新冷却期)
|
|||
|
|
const writtenAt = this.now();
|
|||
|
|
for (const [key] of writableEntries) {
|
|||
|
|
this.lastWrittenAt.set(key, writtenAt);
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
// 写入失败 → 重新添加到缓冲
|
|||
|
|
for (const [key, row] of writableEntries) {
|
|||
|
|
this.buffer.set(key, row);
|
|||
|
|
}
|
|||
|
|
throw err;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 安排下次刷新
|
|||
|
|
const nextFlushDelayMs = minCooldownDelayMs ?? this.windowMs;
|
|||
|
|
this.flushTimer = setTimeout(() => this._flush(), nextFlushDelayMs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_getCooldownDelayMs(key, nowTs) {
|
|||
|
|
const lastWritten = this.lastWrittenAt.get(key);
|
|||
|
|
|
|||
|
|
if (lastWritten == null) {
|
|||
|
|
// 从未写入 → 立即可写
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算冷却期剩余时间
|
|||
|
|
const cooldownExpiry = lastWritten + this.cooldownMs;
|
|||
|
|
const delayMs = cooldownExpiry - nowTs;
|
|||
|
|
|
|||
|
|
return Math.max(0, delayMs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
now() {
|
|||
|
|
return Date.now();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 冷却期时间线示例
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
时间点 事件
|
|||
|
|
─────────────────────────────────────────
|
|||
|
|
|
|||
|
|
T=0s write("2045:6010") to DB
|
|||
|
|
lastWrittenAt["2045:6010"] = 0
|
|||
|
|
↓ 冷却期开始
|
|||
|
|
|
|||
|
|
T=1s buffer 中有 "2045:6010" 的新数据
|
|||
|
|
但 cooldownLeft = 30000 - 1000 = 29000ms > 0
|
|||
|
|
✗ 跳过写入,保留在缓冲中
|
|||
|
|
|
|||
|
|
T=15s buffer 仍有 "2045:6010"
|
|||
|
|
cooldownLeft = 30000 - 15000 = 15000ms > 0
|
|||
|
|
✗ 跳过写入
|
|||
|
|
|
|||
|
|
T=29s buffer 收到 "2045:6010" 的最新更新
|
|||
|
|
cooldownLeft = 30000 - 29000 = 1000ms > 0
|
|||
|
|
✗ 跳过写入,但缓冲中的值已是最新的
|
|||
|
|
|
|||
|
|
T=30s flush() 检查 "2045:6010"
|
|||
|
|
cooldownLeft = 30000 - 30000 = 0 ≤ 0
|
|||
|
|
✓ 可以写入!
|
|||
|
|
write("2045:6010") to DB with latest value
|
|||
|
|
lastWrittenAt["2045:6010"] = 30000
|
|||
|
|
↓ 新冷却期开始
|
|||
|
|
|
|||
|
|
T=31s buffer 有新的 "2045:6010"
|
|||
|
|
cooldownLeft = 60000 - 31000 = 29000ms > 0
|
|||
|
|
✗ 跳过写入
|
|||
|
|
|
|||
|
|
...循环...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 冷却期的优势
|
|||
|
|
|
|||
|
|
| 优势 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| 减轻 DB 压力 | 同一键 30s 只写一次,而不是每 5s 写一次 |
|
|||
|
|
| 保持数据新鲜 | 虽然 30s 内不写 DB,但缓冲中保留最新值 |
|
|||
|
|
| 防止频繁更新 | 避免 UPDATE 语句的过度执行 |
|
|||
|
|
| 简化版本控制 | 每 30s 保证一次更新,易于追踪数据变化 |
|
|||
|
|
|
|||
|
|
### 4.4 与缓冲窗口的关系
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────┐
|
|||
|
|
│ 5秒缓冲窗口 (Layer 1) │
|
|||
|
|
├────────────────────────────────────────┬──────────────┤
|
|||
|
|
│ buffer = { │ @T=5s flush: │
|
|||
|
|
│ "2045:6010" → {ts_ms: 1200}, │ write if no │
|
|||
|
|
│ "1309:8809" → {ts_ms: 2300}, │ cooldown │
|
|||
|
|
│ ... │ │
|
|||
|
|
│ } │ │
|
|||
|
|
└────────────────────────────────────────┴──────────────┘
|
|||
|
|
|
|||
|
|
↓ 满足 2 个条件之一:
|
|||
|
|
- 缓冲满(≥5000 条)
|
|||
|
|
- 5秒时间过期
|
|||
|
|
|
|||
|
|
┌──────────────────────────────────────────────────────────┐
|
|||
|
|
│ 30秒冷却期检查 (Layer 2) │
|
|||
|
|
├─────────────────────────────────────────────────────────┤
|
|||
|
|
│ for each key in buffer: │
|
|||
|
|
│ if (now - lastWrittenAt[key]) < 30000: │
|
|||
|
|
│ → skip (keep in buffer) │
|
|||
|
|
│ else: │
|
|||
|
|
│ → write to DB, update lastWrittenAt[key] │
|
|||
|
|
└─────────────────────────────────────────────────────────┘
|
|||
|
|
|
|||
|
|
↓ DB 的最终状态
|
|||
|
|
|
|||
|
|
┌──────────────────────────────────────────────────────────┐
|
|||
|
|
│ PostgreSQL (room_status_moment_g5) │
|
|||
|
|
├─────────────────────────────────────────────────────────┤
|
|||
|
|
│ (hotel_id:2045, room_id:6010) → {ts_ms: 1200, ...} │
|
|||
|
|
│ (hotel_id:1309, room_id:8809) → {ts_ms: 2300, ...} │
|
|||
|
|
│ ... │
|
|||
|
|
└─────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 5. 去重命中率估算
|
|||
|
|
|
|||
|
|
### 5.1 典型场景分析
|
|||
|
|
|
|||
|
|
**假设**:
|
|||
|
|
- 消费速率:30,000 msg/s
|
|||
|
|
- 酒店数:100
|
|||
|
|
- 房间数/酒店:1000
|
|||
|
|
- 总不同键:100 × 1000 = 100,000 个
|
|||
|
|
- 每键消息频率:30,000 / 100,000 = 0.3 msg/s = 1 msg/3.3s
|
|||
|
|
|
|||
|
|
**5秒缓冲去重率**:
|
|||
|
|
```
|
|||
|
|
同一键在 5s 内的消息数:0.3 × 5 = 1.5(平均)
|
|||
|
|
→ 缓冲虽有去重,但每键大多只有 1-2 条,去重率较低 ~20-30%
|
|||
|
|
|
|||
|
|
结论:缓冲主要用于吸收毛刺(短时间内的重复),不是主要去重机制
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**30秒冷却期去重率**:
|
|||
|
|
```
|
|||
|
|
不考虑冷却期:每键 5s 写一次 → 30s 内写 6 次
|
|||
|
|
使用冷却期:每键 30s 内只写 1 次 → 去重率 = (6-1)/6 = 83.3%
|
|||
|
|
|
|||
|
|
结论:30秒冷却期是关键,减轻 DB 压力 83%
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 极端场景
|
|||
|
|
|
|||
|
|
**场景 A:单键频繁更新**
|
|||
|
|
```
|
|||
|
|
同一房间的设备每 100ms 发送一次心跳
|
|||
|
|
|
|||
|
|
缓冲处理:
|
|||
|
|
T=0ms: add({...ts_ms:1000})
|
|||
|
|
T=100ms: add({...ts_ms:1100}) → 缓冲中更新到 1100
|
|||
|
|
T=200ms: add({...ts_ms:1200}) → 缓冲中更新到 1200
|
|||
|
|
...
|
|||
|
|
T=5000ms: flush() → 写入 {ts_ms:5000}
|
|||
|
|
T=10000ms: flush() → 冷却期仍有 20s 剩余 → 跳过
|
|||
|
|
...
|
|||
|
|
T=35000ms: flush() → 冷却期过 → 写入最新值
|
|||
|
|
|
|||
|
|
结果:50 条消息(T=0-5000ms 内)→ 1 条写入(T=5000ms)
|
|||
|
|
→ 再加 1 条写入(T=35000ms 冷却期过)
|
|||
|
|
总计 50 msg → 2 DB writes,去重率 96%
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**场景 B:多键均匀分布**
|
|||
|
|
```
|
|||
|
|
100 个不同的键,每键每 30s 写一次
|
|||
|
|
|
|||
|
|
缓冲 + 冷却期协同:
|
|||
|
|
Layer 1 (5s): 100 键中有去重 → 实际缓冲可能只有 80 条(去重 20%)
|
|||
|
|
Layer 2 (30s): 无冷却期情况下 30s 写 6 次,现在只写 1 次 → 减少 83%
|
|||
|
|
|
|||
|
|
整体效果:DB 写入量减少到原来的 1/6(约 16.7%)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 6. 错误场景与恢复
|
|||
|
|
|
|||
|
|
### 6.1 缓冲满的处理
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
add(record) {
|
|||
|
|
const key = this._getKey(record);
|
|||
|
|
if (this.buffer.has(key)) {
|
|||
|
|
const existing = this.buffer.get(key);
|
|||
|
|
if (record.ts_ms > existing.ts_ms) {
|
|||
|
|
this.buffer.set(key, record);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
this.buffer.set(key, record);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 防止内存溢出:缓冲满 → 立即刷新
|
|||
|
|
if (this.buffer.size >= this.maxBufferSize) {
|
|||
|
|
this._flush(); // 进入 Layer 2 检查和写入
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// maxBufferSize 默认 5000,可配置
|
|||
|
|
// HEARTBEAT_BUFFER_SIZE_MAX=5000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 写入失败的恢复
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
async _flush() {
|
|||
|
|
// ... 选出 writableEntries ...
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const rows = writableEntries.map(([, row]) => row);
|
|||
|
|
await this.dbManager.upsertBatch(rows);
|
|||
|
|
|
|||
|
|
// 成功 → 更新 lastWrittenAt
|
|||
|
|
const writtenAt = this.now();
|
|||
|
|
for (const [key] of writableEntries) {
|
|||
|
|
this.lastWrittenAt.set(key, writtenAt);
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
// 失败 → 重新添加到缓冲,稍后重试
|
|||
|
|
for (const [key, row] of writableEntries) {
|
|||
|
|
this.buffer.set(key, row);
|
|||
|
|
}
|
|||
|
|
logger.error(`Batch upsert failed: ${err.message}`);
|
|||
|
|
throw err; // 可选:传播错误或继续
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 7. 性能特征
|
|||
|
|
|
|||
|
|
### 7.1 内存占用
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
缓冲区最大容量:5000 条记录
|
|||
|
|
每条记录大小:≈200 bytes (包括 ts_ms, hotel_id, room_id, device_id)
|
|||
|
|
最大缓冲内存:5000 × 200 = 1 MB
|
|||
|
|
|
|||
|
|
lastWrittenAt 追踪:
|
|||
|
|
最多 100K 个键(100 酒店 × 1000 房间)
|
|||
|
|
每个 Map 条目:≈50 bytes (key + timestamp)
|
|||
|
|
总计:100K × 50 = 5 MB
|
|||
|
|
|
|||
|
|
整体估计:≈6-10 MB(可接受)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 CPU 开销
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
add() 操作:O(1) Map 查找 + 比较
|
|||
|
|
_getCooldownDelayMs():O(1) 查找 + 算术
|
|||
|
|
flush() 循环:O(缓冲大小) ≈ O(5000)
|
|||
|
|
|
|||
|
|
典型负载:30K msg/s
|
|||
|
|
= 30K add() 调用/s
|
|||
|
|
= 30K × O(1) = 常数时间,CPU 占用低
|
|||
|
|
|
|||
|
|
flush() 每 5s 或缓冲满时执行一次 ≈ 6-10 次/s
|
|||
|
|
= 6 × O(5000) = 30K 操作/s ≈ 与消费速率相当
|
|||
|
|
|
|||
|
|
总 CPU:中等(不是瓶颈)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**上次修订**: 2026-03-11
|
|||
|
|
**维护者**: BLS OldRCU Heartbeat Team
|