工作中有用到Redis滤重队列。
原来的方法如下:
方法一
- 为了保证操作原子性,使用Redis执行Lua脚本。
- 在脚本中的逻辑是,如果队列不超过某个数值,进行一次lrem操作(队列使用list结构),然后将新元素入列。
优点:
简单,直观。
缺陷:
- lrem的时间复杂度为O(N),N为队列中的元素个数;所以,性能一般。
- 因为防止队列内容过多,防止发生N级别的删除操作,限制了一个滤重的阀值,如果超过这个阀值就不能使用滤重功能。
方法二
为了解决以上痛点,新玩法为:
- 为了保证操作原子性,使用Redis执行Lua脚本。
- 同样使用Lua脚本,排重分为两步,使用了Redis自带的二进制数组进行维护是否存在重复的状态:
- 在入队之前,先从二进制数组中查询下这个key是否存在,即
getbit key offset
。如果存在说明队列中存在一个这个offset的值,就不需要进行入队操作,直接中断执行就好。 - 在出队的时候,将出队的元素在二进制数组中设置为不存在,即,setbit key offset 0。
优点:
- 因为是bitmap算法,在查询是否存在执行的offset的时候,时间复杂度是O(1),并且与队列中元素个数无关。
- 优雅,如果算是优点的话,哈哈。
缺点:
- 最重要的一点是redis bitmap的offset必须是
int
,比如,long范围的offset是不存在的,这是一个很重要
的点,一定要注意(都是血泪史)。 - 因为入队和出队都进行了bitmap的数据维护,所以需要确保在编码的时候一定谨慎,足够健壮。
总结
从上面的分析来看,感觉方法二完胜方法一。其实不尽然,只能说各有不同的场景。
方法一比较通用,不论入队的内容是什么,都可能滤重,方法二依赖与Bitmap算法,意味key只能是数值型的元素。
在实际应用中,以上两种滤重方式一般是可以联合使用的。如果key是数值类型,没有超出int的取值范围,那么就直接使用方法二,如果超出了int的取值范围的数值就使用方法一。
扩展
还有一种滤重的算法叫:布隆过滤器,感兴趣的同学可以了解下:Bloom filter。如果不需要删除,不在乎误判率的话那应该是很合适的一个算法,空间和时间都很高效。
另外如果有人遇到过其他的一些坑或者有更好的建议,欢迎指点。