最近在项目中遇到一个场景需要使用分布式的优先级队列,第一反应就是通过redis
的sortedset
数据结构来实现。但是阅读其API发现其没有类似List
的LPOP
与RPOP
指令,但是可以根据其提供的ZRANG
、ZREVRANGE
、ZREM
组合命令来实现POP
的操作。
POP
的动作可以拆解成两步:
- 获取队首元素
- 删除队首元素
为保证执行的原子性,我们可以通过定义LUA
脚本来执行组合指令:
local result = redis.call('ZRANGE', KEYS[1], 0, 0)
local element = result[1]
if element then
redis.call('ZREM', KEYS[1], element)
return element
else
return nil
end"
将上面脚本中
ZRANG
替换为ZREVRANGE
就可以实现从对尾POP
了
相关Java代码的实现,使用了spring-data-redis
的RedisTemplate
:
private static final String ZSET_LPOP_SCRIPT = "local result = redis.call('ZRANGE', KEYS[1], 0, 0)\n" +
"local element = result[1]\n" +
"if element then\n" +
" redis.call('ZREM', KEYS[1], element)\n" +
" return element\n" +
"else\n" +
" return nil\n" +
"end";
/**
* 有序集合按得分的升序提取元素,提取完成后并该删除
* @param key zset的key名字
* @param resultClass 返回值类型
* @return 提取的元素,集合为空时返回null
*/
public <T> T zsetLPop(String key, Class<T> resultClass){
return redisTemplate.execute(new DefaultRedisScript<>(ZSET_LPOP_SCRIPT, resultClass), Collections.singletonList(key));
}
测试代码:
@Test
public void testZsetLpop() {
String zsetKey = "test_sorted";
Set<ZSetOperations.TypedTuple<String>> initValues = new HashSet<>();
initValues.add(new DefaultTypedTuple<>("zhangsan", 1d));
initValues.add(new DefaultTypedTuple<>("lisi", 2d));
initValues.add(new DefaultTypedTuple<>("zhaoliu", 4d));
initValues.add(new DefaultTypedTuple<>("wangwu", 3d));
Long count = stringRedisTemplate.boundZSetOps(zsetKey).add(initValues);
System.out.println("成功添加" + count + "条初始化数据");
String element;
do {
element = redisUtil.zsetLPop(zsetKey, String.class);
if (Objects.nonNull(element)) {
System.out.println("Get element: " + element);
}
}
while (element != null);
}
// 控制台输出
// 成功添加4条初始化数据
// Get element: zhangsan
// Get element: lisi
// Get element: wangwu
// Get element: zhaoliu