这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
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 存储,精度有限
- ❌ 可能有浮点数精度问题
最佳实践:
- ✅ 短期使用:直接使用毫秒级时间戳
- ✅ 长期使用:使用相对时间或周期性重置
- ✅ 避免溢出:定期检查 score 是否接近上限
- ✅ 监控告警:score 接近上限时发出告警