上周四凌晨,我们的实时风控系统突然告警——用户行为事件的时间戳出现乱序,导致风控规则误判。
这个系统每秒处理约2万条事件,采用Redis Pipeline批量写入以降低网络开销。
问题来了:
代码里明明按时间顺序将事件推入Pipeline,但Redis里却出现了时间戳倒序的数据。 你是不是也认为Pipeline只是"批量发送命令"?
我也曾这么想,直到这次踩坑。
先看当时的错误代码(Python示例,其他语言同理):
|
1 2 3 4 |
pipe = redis.pipeline() for event in sorted_events: # 假设已按时间戳升序排序 pipe.zadd("user_events", {event.id: event.timestamp}) # 写入有序集合 pipe.execute() |
理论上,sorted_events是按时间升序排列的,写入Redis后ZRANGE user_events 0 -1应该得到有序结果。
但实际观测到部分数据乱序,例如:
|
1 |
实际存储: [事件A(ts=100), 事件C(ts=300), 事件B(ts=200)] |
尽管客户端通过单个TCP连接顺序发送命令,但TCP/IP协议栈可能存在:
即使命令按序到达Redis:
与单条命令不同,Pipeline将多个命令作为一个"批处理单元"提交。
如果其中某个命令执行较慢(例如遇到大Key),可能导致后续命令先被执行完成。
正确写法需要满足两个条件:
|
1 2 3 4 5 6 7 8 9 10 11 |
with redis.pipeline() as pipe: while True: try: pipe.watch("user_events") # 监视Key pipe.multi() # 开启事务 for event in sorted_events: pipe.zadd("user_events", {event.id: event.timestamp}) pipe.execute() # 原子化执行 break except WatchError: continue # 冲突则重试 |
实测对比(10万条数据,单位:ms):
| 方案 | 耗时 | 乱序率 |
|---|---|---|
| 纯Pipeline | 1200 | 0.7% |
| WATCH+MULTI | 1800 | 0% |
| 单条命令 | 9500 | 0% |
关键区别:
顺序敏感场景慎用纯Pipeline
监控Pipeline的乱序率
|
1 2 3 |
# 通过Redis慢查询日志观察命令执行顺序 slowlog-log-slower-than 10000 slowlog-max-len 1000 |
区分"网络批量化"和"原子性"
经过这次教训,我的决策树变成:
允许最终一致:用纯Pipeline(性能最优)
如果
需要强顺序:WATCH+MULTI+Pipeline(性能折衷)
如果
需要强原子性:Lua脚本(功能最稳)