题目重点:面向 Hiring Manager 轮的日志收集题,拆解错误事件规范化、滑窗计数、容错和告警展示。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 仅 severity=ERROR 的日志计数,服务身份由可信元数据而非日志正文提取;窗口为 (now−300s,now],同数按 serviceId 排序。
- 训练代码接收已过滤且按时间有序的 (timestamp,serviceId) 错误事件;生产链路需处理重复、迟到和服务重命名。
解题路线
- 入口先做鉴权、结构化解析、错误级别过滤和事件 ID 去重,再写持久日志;收集层与查询层隔离,避免查询慢导致摄入阻塞。
- 按 serviceId 分区聚合窗口内计数,并为过期贡献保留事件或时间桶。Top K 展示可周期性重建,实时性 SLA 决定桶宽与刷新间隔。
- 核心方法复用精确滑窗:队列记录窗口事件,计数 map 增减,查询排序。生产大规模状态需按分区持久化、checkpoint 与重放去重。
- 监控摄入延迟、丢弃率、解析失败、状态积压、排行新鲜度与分区热点;查询故障时展示最后成功快照和时间戳,不伪装为实时结果。
手动走一遍
服务 A 在 5 分钟内有三条错误、B 有两条,A 排首。若 A 的两条在当前 now−300s 边界上,按开区间定义它们已过期,A 仅剩一条,B 应排首。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""Exact event-time rolling counts for monotonic input; training reference."""
from collections import deque
class RollingTopK:
def __init__(self, window_seconds: int):
if window_seconds <= 0:
raise ValueError("window_seconds must be positive")
self.window_seconds = window_seconds
self.events: deque[tuple[int, str]] = deque()
self.counts: dict[str, int] = {}
self.now: int | None = None
def _expire(self, now: int) -> None:
cutoff = now - self.window_seconds
while self.events and self.events[0][0] <= cutoff:
_, key = self.events.popleft()
remaining = self.counts[key] - 1
if remaining:
self.counts[key] = remaining
else:
del self.counts[key]
def add(self, timestamp: int, key: str) -> None:
if self.now is not None and timestamp < self.now:
raise ValueError("events must arrive in timestamp order")
self.now = timestamp
self._expire(timestamp)
self.events.append((timestamp, key))
self.counts[key] = self.counts.get(key, 0) + 1
def top_k(self, now: int, k: int) -> list[tuple[str, int]]:
if k < 0:
raise ValueError("k must be nonnegative")
if self.now is not None and now < self.now:
raise ValueError("queries must not move time backwards")
self.now = now
self._expire(now)
return sorted(self.counts.items(), key=lambda pair: (-pair[1], pair[0]))[:k]
复杂度
单机窗口内 E 条错误、S 个活跃服务:摄入均摊 O(1),Top K 排序 O(S log S),空间 O(E+S)。服务数远小于用户数时全量重建往往可接受,但须用目标查询频率实测。
易错点与追问
容易失分的边界
- 把所有日志条数当错误条数,未定义 ERROR 过滤和多级日志。
- 消费者重放后不按事件 ID 去重,恢复越多次错误数越大。
- 只返回排名,不标注统计时间、延迟与部分分区缺失。
继续深挖
- 某服务错误暴涨导致单分区热点,如何拆分采集与聚合?
- 允许 30 秒刷新时,与严格实时计数相比可节省哪些状态和计算?
