在 Redis 中原子地分配最少使用的代理
使用有序集合和一段简短的 Lua 脚本,在多个 worker 之间选择并递增某个代理的分配计数。
当多个 worker 通过 Redis 共享一个代理池时,有序集合可以实现一条简单策略:选择已记录分配次数最少的合格代理。把代理标识存为成员,把它的分配次数存为分值。
对于服务已获允许的公共请求的代理池,免费代理列表可以提供端点候选,供按该池的资格规则评估。用商定的初始计数初始化合格成员,然后让选择与递增成为一个服务端操作:
local chosen = redis.call('ZRANGE', KEYS[1], 0, 0)[1]
if not chosen then
return nil
end
redis.call('ZINCRBY', KEYS[1], 1, chosen)
return chosen通过 Redis 客户端的 EVAL 接口,把有序集合的键作为脚本唯一的键参数传入。空池会返回 null,应用必须在不发出请求的情况下处理它。
为什么要合并这两条命令?
分开的读取与递增会让两个 worker 在任何一方更新之前看到同一个最小值。Redis 以原子方式执行 Lua 脚本,因此另一个 worker 无法在这段脚本的读取与递增之间插入选择。脚本要保持简短,因为它的执行会阻塞其他 Redis 工作。
ZRANGE 先返回最低分值;分值相同时按成员顺序决定,而不是随机。ZINCRBY 会把被选中成员的分值加一,供下一次分配使用。
定义分值的含义
它统计的是分配次数,也包括网络请求后来失败的分配。它不衡量延迟、成功请求或正在进行的工作。仍然可能反复选中唯一的合格成员;脚本并不会独占保留它。
用代理池的健康与策略规则维护资格。如果你移除某个成员、之后又恢复它,要决定是否保留它的历史计数。让恢复后的代理从零开始,可能在它追赶期间把分配集中到它身上。
最小并发策略需要另一套生命周期,包括完成与放弃任务的处置。不要把这个累计计数器当成那套生命周期。