在支付系统架构中,我们经常需要将交易请求分配到不同的支付渠道。这个分配过程需要考虑多个因素:渠道稳定性、手续费成本、到账时效等。而加权随机算法,就是一种在满足业务规则前提下实现智能分配的核心技术。
传统做法是使用简单的轮询或完全随机分配,但这会带来两个严重问题:
举个例子:假设我们有三个支付渠道,权重分别为A(50%)、B(30%)、C(20%)。使用普通随机算法时,可能在某个5分钟时段内出现A渠道只获得30%流量的情况,这显然不符合业务预期。
我们选择Redis的List结构作为底层存储,主要基于以下考虑:
具体数据结构设计如下:
code复制支付渠道队列:
key: payment:channel:pool
value: [A,A,A,A,A,B,B,B,C,C]
(A出现5次对应50%权重,B3次30%,C2次20%)
bash复制# 清空旧队列
DEL payment:channel:pool
# 按权重初始化队列
LPUSH payment:channel:pool A A A A A B B B C C
bash复制# 原子性获取并旋转队列
RPOPLPUSH payment:channel:pool payment:channel:pool
关键技巧:使用RPOPLPUSH命令实现原子性的"取出+放回"操作,保证在高并发下也不会出现重复分配问题。
当需要修改渠道权重时,需要重建整个队列:
python复制def rebuild_pool(weights):
redis.delete("payment:channel:pool")
pool = []
for channel, weight in weights.items():
pool += [channel] * int(weight * 10) # 假设总权重为10
random.shuffle(pool) # 打乱顺序
redis.lpush("payment:channel:pool", *pool)
单纯的加权随机在短时间窗口内可能出现偏差。我们的方案通过以下机制保证均匀性:
实测数据显示,在100万次请求测试中:
python复制for i in range(10):
pool = generate_pool(weights)
redis.lpush(f"payment:channel:pool:{i}", *pool)
本地缓存:客户端缓存最近使用的5个渠道,减少Redis访问
Lua脚本:使用Redis脚本保证原子性
lua复制local channel = redis.call('RPOP', KEYS[1])
redis.call('LPUSH', KEYS[1], channel)
return channel
bash复制# 监控脚本示例
length=$(redis-cli LLEN payment:channel:pool)
if [ $length -lt 5 ]; then
send_alert "Channel pool is running low!"
fi
LRANGE命令抽样检查队列内容| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Redis List | 实现简单,性能好 | 权重调整需要重建队列 | 中小规模稳定权重场景 |
| Redis ZSET | 权重调整灵活 | 实现复杂,性能稍差 | 频繁调整权重的场景 |
| 算法实现 | 不依赖外部存储 | 集群环境下难以保持一致 | 单机应用 |
我在实际项目中发现,对于支付渠道分配这种权重相对稳定的场景,Redis List方案在实现难度和运行效率上取得了很好的平衡。特别是在处理突发流量时,其性能表现明显优于其他方案。