Redis-zset基于score值pop弹出的原子性操作

Redis-zset基于score值pop弹出的原子性操作

背景:

近期接到一个需求,逻辑顺序是将一个zset中的元素通过将zRangeByScore查出来,做某些业务操作,再通过zrem移除。本身这两个命令操作并不难,但业务设计上来说这这个zset是全局所有用户共享,在并发的情况下,比如:

1、线程a从zset中根据zRangeByScore zset 0 100命令查询一批数据->业务操作->zrem删除

2、线程b从zset中根据zRangeByScore zset 50 160命令查询一批数据->业务操作->zrem删除

如果在线程a业务操作还没有完成,没有执行zrem操作时,线程b来查询,两个线程就会访问到重复数据,产生重复操作,如果业务操作上做了幂等还好,否则就是bug。

一般情况下我们会考虑在这一整个操作外层加锁,这种做法有两个缺点:

  1. 锁持有时间过长
  2. 锁粒度较大,上面说过前提是这个zset全局共享的数据,没有精确到用户,所有线程执行到这里都会变成串型化操作,效率很低

所以这里介绍另一种实现方案:zset基于score值pop弹出的原子性操作,优化后的逻辑如下:
1、线程a从zset中根据zpopByScore zset 0 100查询并删除一批数据->做业务操作

2、线程a从zset中根据zpopByScore zset 50 150查询并删除一批数据->做业务操作

可以看到只要这里只要保证zpopByScore的操作是原子性(下面介绍如何实现)的就能大幅降低锁粒度,提高效率。

另外注意一点,再实际业务操作过程中可能遇到异常,需要回滚,所以我们可把逻辑优化成:

1、线程a从zset中根据zpopByScore zset 0 100查询并删除一批数据->做业务操作->异常时回滚(将pop出来的数据重新添加到zset中)

zPopByScore实现:

原生的redis命令zset有实现zpopmin、zpopmax等,有兴趣的可以参考https://redis.io/commands/?name=zpo

这里通过lua脚本实现自定义操作:

Java版本-引入依赖:

<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.2.4.RELEASE</version>
</dependency>

代码实现:

public static final RedisScript<List<Object>> ZPOP_BY_SCORE_SCRIPT;
/**
	KEYS[1]:需要查询的zset-key
	ARGV[1]:score下限
	ARGV[2]:score上限
*/
	static {
    
    
		 String string= "local result = redis.call('zrangebyscore', KEYS[1], ARGV[1], ARGV[2]) " +
				"if result[1] " +
				"then " +
				"    redis.call('zrem', KEYS[1], unpack(result)) " +
				"    return result " +
				"else " +
				"    return {} " +
				"end ";
		ZPOP_BY_SCORE_SCRIPT = new DefaultRedisScript(string, List.class);
	}

/**
	简单调用示例	
*/
	public static List<Object> zPopByScore(String key, double min, double max) {
    
    
		List<String> keys = Lists.newArrayList(key);
		List<Object> args = Lists.newArrayList(min, max);
		return redisTemplate.execute(RedisUtil.ZPOP_BY_SCORE_SCRIPT, keys, args.toArray());
	}

总结:

本文主要介绍的是redis如何通过lua脚本方式实现popByScore命令,其中花了比较大的篇幅做背景介绍,主要是为了讲清楚什么样的业务场景下会用到它,希望大家同时也思考一下你遇到类似问题时这个方案是否真的适用。毕竟技术是为了业务服务,没有最好的方案,只有最合适的。

猜你喜欢

转载自blog.csdn.net/Arhhhhhhh/article/details/132367939