如何用 redis 实现批量 pop?
最近在业务上遇到需要批量pop的场景,起初以为是个很简单的方案,然而在实现过程中走了很多弯路,和大家分享一下,避免踩坑
1.简单的循环 pop
这是最简单的方案,伪代码如下所示:
var values []string
for(i:=0;i<num;i++){
values=append(values,redis.rpop())
}
这也是我们最初的方案,后来随着业务量上来后,大量的网络开销导致耗时严重,业务逐渐不能接受
总结
优点
- 最简单的方案
缺点
- 串行,耗时严重
2.易实现的并发 pop
通过并发 pop,进而缩短网络耗时,伪代码如下所示:
var values []string
locker:=new(sync.mutux)
wg:=new(sync.WaitGroup)
for(i:=0;i<n;i++){
wg.Add(1)
go func(){
locker.Lock()
values=append(values,redis.rpop())
locker.Unlock()
wg.Done()
}
}
wg.Wait()
最终耗时≈网络中最慢的一次请求
总结
优点
- 最容易想到的优化方案
- 性能提升显著
缺点
- 需要处理并发
- 有大量的网络开销,server端处理网络连接的压力较大
3.lrange+ltrim+txpipeline
开始有同事提到用 lrange 来批量获取队列中的消息,但后来发现 lrange 只能查看,而不能同时移除消息,这就令我们很头疼了
上网上查了下,说是接着用 ltrim,同时用 txpipelline 来保证原子性,如下所示:
p:=redis.txpipeline()
// 查询 0~n-1 之间的元素
p.lrange(key, 0, n - 1)
// 保留 n~最后(-1)之间的元素,换言之删除 0~n-1 之间的元素
p.ltrim(key, n, -1)
values:=p.execute()
一眼看上去确实很完美,一个打包命令实现了批量pop,避免了大量网络开销,但是真的是这样吗?
txpipeline的原子性
pipeline
首先,pipeline本身非原子性,其只是将一批命令一口气发到server,server依次处理这些命令,中间可能穿插执行其他 client 传来的命令,所以只用 pipeline的话是无法保证pop正确运行的,存在pop同一个元素的风险,或者多删元素等并发风险
transaction
其次,我们来了解下redis的事务,其主要由multi 和exec构成,用来封装一个原子执行单位,其实本质上,不算是真正的事务:
- 不能回滚
- 部分命令出错后,仍然能继续执行
该事务能显性检查一些语法错误,从而避免执行事务;但对于一些单条语法上无法辨别的错误是无法check的,例如对set进行pop,这就导致了上述 2 的发送
txpipeline
所以 txpipeline 就是在 pipeline前后加入了事务操作,从而保证了这批命令的原子性。从这个角度上看上述实现是没有问题的,但由于事务开销比较大,会阻塞其他命令,导致server响应较慢
先进先出
在使用lrange批量取元素时,首先要明白元素在队列中是如何排列的,这里分为rpush 和 lpush 两种不同的排列方式:
我们可以看到在rpush中,index 与 我们推送元素的顺序是一致,这时候通过lrange取元素的时候,就直接从头取就可以了:
p.lrange(key, 0, n - 1)
取出来的顺序(a,b,c)也是我们推送的顺序,保证了先进先出
但是在lpush中,index 与 我们推送元素是相反的,这时候通过lrange取元素的时候,需要从尾部取:
p.lrange(key, -n, -1)
首先index的写法与rpush不一致,其次拿出来顺序会是(c,b,a),为保证先进先出,我们需要逆序消费
总结
优点
- 一次网络请求,网络开销小
缺点
- 需要事务操作,server存在阻塞风险
- 根据rpush与lpush不同,批量处理方式不同,细节较多,需要考虑清楚
4.pipeline+pop
在了解完 pipeline 后,我们很自然的想到了这种方案:
p:=redis.txpipeline()
for(i:=0;i<n;i++){
p.rpop()
}
values:=p.execute()
该方案本质上就是将一堆pop一口气打包到了server端,让server依次处理,避免了网络开销和事务问题
实质上该方案与方案3,有一个性能的平衡点,即该方案的大量pop带来的性能损耗 == 方案3的事务操作
总结
优点
- 易实现
缺点
- 需要考虑pop的数量,无限制的增长会带来大量的性能损耗(相对于方案3,因为每次pop都是查+写)
参考
转载自:https://juejin.cn/post/7031571425622392840