Score 设计

最简单的 Score 设计

最直观的方案是直接使用时间戳作为 score:

这个方案看起来没问题,但实际使用时会遇到一些坑。


Score 设计的挑战

挑战 1:时间精度

挑战 2:任务去重

挑战 3:并发竞争


Score 设计方案

方案 1:毫秒级时间戳

优点:

  • ✅ 精度提高到毫秒级
  • ✅ 同一秒内的任务不会冲突

缺点:

  • ❌ 同一毫秒内的任务仍可能冲突
  • ❌ 如果并发很高,仍可能有问题

方案 2:时间戳 + 随机数

示例:

原始时间戳:1710505800000 (毫秒)

score 设计:
├─ 时间戳部分(高 46 位):1710505800000 << 6
└─ 随机数部分(低 6 位):0 ~ 63

示例 score:
1710505800000 << 6 + 0 = 109543571200000
1710505800000 << 6 + 1 = 109543571200001
...
1710505800000 << 6 + 63 = 109543571200063

优点:

  • ✅ 同一毫秒内的任务不会冲突(最多 64 个)
  • ✅ 保持时间顺序

缺点:

  • ❌ 如果并发 > 64,仍可能冲突
  • ❌ 需要合理的随机位数选择

方案 3:时间戳 + 序列号(推荐)

示例:

score 设计:
├─ 时间戳部分(高 42 位)
└─ 序列号部分(低 10 位)

同一秒内的任务:
1710505800000 << 10 + 0 = 1752904332800000
1710505800000 << 10 + 1 = 1752904332800001
...
1710505800000 << 10 + 1023 = 1752904332801023

可以支持同一毫秒内 1024 个任务

优点:

  • ✅ 完全避免冲突(序列号唯一)
  • ✅ 保持时间顺序
  • ✅ 支持高并发

缺点:

  • ❌ 序列号会一直增长,需要定期重置

方案 4:时间戳 + UUID(适用于分布式)

优点:

  • ✅ 分布式环境下也能保证唯一性
  • ✅ 不需要序列号管理

缺点:

  • ❌ 随机性可能导致顺序不完全按时间

Score 设计对比

方案冲突概率顺序保证分布式友好复杂度
秒级时间戳🔴 高✅ 完全✅ 友好⭐ 简单
毫秒级时间戳🟡 中✅ 完全✅ 友好⭐ 简单
时间戳 + 随机数🟢 低✅ 完全✅ 友好⭐⭐ 中等
时间戳 + 序列号🟢 无✅ 完全⚠️ 需要协调⭐⭐⭐ 复杂
时间戳 + UUID🟢 无⚠️ 近似✅ 友好⭐⭐ 中等

最佳实践

推荐方案:时间戳 + 序列号


想一想

思考 1

如何处理 score 溢出的问题?

参考答案

问题分析:

解决方案:

方案 1:使用相对时间

优点:

  • ✅ 避免绝对时间戳溢出
  • ✅ 可以支持任意长的延时

缺点:

  • ❌ 系统重启后需要重新计算 base_time
  • ❌ 需要持久化 base_time

方案 2:周期性重置序列号

优点:

  • ✅ 序列号不会无限增长
  • ✅ 延长了可使用时间

缺点:

  • ⚠️ 需要管理周期号
  • ⚠️ 实现复杂度增加

方案 3:使用浮点数

优点:

  • ✅ 简单直接
  • ✅ 不需要管理序列号

缺点:

  • ❌ Redis 使用 double 存储,精度有限
  • ❌ 可能有浮点数精度问题

最佳实践:

  1. 短期使用:直接使用毫秒级时间戳
  2. 长期使用:使用相对时间或周期性重置
  3. 避免溢出:定期检查 score 是否接近上限
  4. 监控告警:score 接近上限时发出告警