점수 값 팝을 기반으로 한 Redis-zset 원자적 연산

점수 값 팝을 기반으로 한 Redis-zset 원자적 연산

배경:

최근에 요청을 받았는데, 논리적 순서는 zRangeByScore를 통해 zset의 요소를 확인하고 일부 비즈니스 작업을 수행한 다음 zrem을 통해 해당 요소를 제거하는 것입니다. 이 두 명령의 작동은 어렵지 않지만 비즈니스 설계 측면에서 이 zset은 전 세계 모든 사용자가 공유합니다. 동시성의 경우 예를 들면 다음과 같습니다.

zRangeByScore zset 0 1001. 스레드 -> 비즈니스 운영 -> zrem delete 명령에 따라 zset에서 일괄 데이터 쿼리를 실행합니다.

2. 스레드 b는 zRangeByScore zset 50 160명령 -> 비즈니스 운영 -> zrem delete에 따라 zset에서 일괄 데이터를 쿼리합니다.

스레드 a의 비즈니스 작업이 완료되지 않고 zrem 작업이 실행되지 않고 스레드 b가 쿼리하는 경우 두 스레드는 중복 데이터에 액세스하여 중복 작업을 생성합니다. 비즈니스 작업이 멱등적이면 괜찮고 그렇지 않으면 괜찮습니다. 버그.

일반적인 상황에서는 이 전체 작업의 외부 레이어에 잠금을 추가하는 것을 고려할 것입니다. 이 접근 방식에는 두 가지 단점이 있습니다.

  1. 너무 오랫동안 잠금이 유지됨
  2. 잠금 세분성은 상대적으로 큽니다. 위에서 언급했듯이 전제는 이 zset에 의해 전역적으로 공유되는 데이터가 사용자에게 정확하지 않으며 모든 스레드가 여기에서 실행될 때 직렬 작업이 된다는 것입니다. 이는 매우 비효율적입니다.

따라서 또 다른 구현 계획은 다음과 같습니다. 점수 값 팝을 기반으로 한 zset 원자 연산, 최적화된 논리는 다음과 같습니다. 1. 스레드 a는 쿼리에
따라 zset에서 데이터 일괄 삭제 -> 비즈니스 작업 수행zpopByScore zset 0 100

2. 스레드 a는 쿼리 에 따라 zset에서 zpopByScore zset 50 150데이터 일괄 삭제 -> 비즈니스 작업 수행

zpopByScore의 작업이 원자적 이면 (다음에서 이를 구현하는 방법에 대해 설명) 잠금 세분성이 크게 줄어들고 효율성이 향상될 수 있음을 알 수 있습니다.

주목해야 할 또 다른 점은 실제 비즈니스 운영 중에 예외가 발생할 수 있으며 롤백해야 하므로 다음과 같이 논리를 최적화할 수 있다는 것입니다.

zpopByScore zset 0 1001. 스레드 a 는 쿼리에 따라 zset에서 일괄 데이터 삭제 -> 비즈니스 작업 수행 -> 비정상 시 롤백(팝된 데이터를 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 명령을 구현하는 방법을 소개합니다. 배경 소개에 많은 공간을 소비하며 주로 어떤 종류의 비즈니스 시나리오를 사용할지 설명합니다. 모든 사람들이 직면하게 될 문제에 대해 생각해 보기를 바랍니다. 이 솔루션이 비슷한 문제에 실제로 적용 가능합니다. 결국 기술은 비즈니스 서비스를 위한 것이므로 최상의 솔루션은 없고 가장 적합한 솔루션만 있을 뿐입니다.

Supongo que te gusta

Origin blog.csdn.net/Arhhhhhhh/article/details/132367939
Recomendado
Clasificación