>데이터 베이스 >Redis >Redis가 지연 대기열을 구현하는 방법

Redis가 지연 대기열을 구현하는 방법

WBOY
WBOY앞으로
2023-05-26 20:39:281594검색

    종속성 구성

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.12.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.homeey</groupId>
        <artifactId>redis-delay-queue</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>redis-delay-queue</name>
        <description>redis-delay-queue</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson-spring-boot-starter</artifactId>
                <version>3.19.3</version>
            </dependency>
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson-spring-data-23</artifactId>
                <version>3.19.3</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

    사용 구성 [가장 쉬움]

    두 번째: 별도의 redisson 구성 파일 사용

    • 세 번째: spring.redis.redisson을 사용하여 구성 키 아래 구성

    • 자세한 통합을 보려면 springboot 통합 redisson 구성

      spring:
        redis:
          database: 0
          host: localhost
          port: 6379
          timeout: 10000
          lettuce:
            pool:
              max-active: 8
              max-wait: -1
              min-idle: 0
              max-idle: 8
    • 을 참조하세요. 데모 코드
    • package com.homeey.redisdelayqueue.delay;
      
      import lombok.RequiredArgsConstructor;
      import lombok.extern.slf4j.Slf4j;
      import org.redisson.api.RBlockingQueue;
      import org.redisson.api.RDelayedQueue;
      import org.redisson.api.RedissonClient;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.stereotype.Component;
      
      import javax.annotation.PostConstruct;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.TimeUnit;
      
      /**
       * 明天的你会因今天到的努力而幸运
       *
       * @author jt4mrg@qq.com
       * 23:11 2023-02-19 2023
       **/
      @Slf4j
      @Component
      @RequiredArgsConstructor
      public class RedissonDelayQueue {
      
          private final RDelayedQueue<String> delayedQueue;
          private final RBlockingQueue<String> blockingQueue;
      
      
          @PostConstruct
          public void init() {
              ExecutorService executorService = Executors.newFixedThreadPool(1);
              executorService.submit(() -> {
                  while (true) {
                      try {
                          String task = blockingQueue.take();
                          log.info("rev delay task:{}", task);
                      } catch (Exception e) {
                          log.error("occur error", e);
                      }
                  }
              });
          }
      
          public void offerTask(String task, long seconds) {
              log.info("add delay task:{},delay time:{}s", task, seconds);
              delayedQueue.offer(task, seconds, TimeUnit.SECONDS);
          }
      
      
          @Configuration
          static class RedissonDelayQueueConfigure {
      
              @Bean
              public RBlockingQueue<String> blockingQueue(RedissonClient redissonClient) {
                  return redissonClient.getBlockingQueue("TOKEN-RENEWAL");
              }
      
              @Bean
              public RDelayedQueue<String> delayedQueue(RBlockingQueue<String> blockingQueue,
                                                        RedissonClient redissonClient) {
                  return redissonClient.getDelayedQueue(blockingQueue);
              }
          }
      }

      실행 효과

    원리 분석

    RedissonDelayedQueue 구현에서 네 가지 역할이 있음을 알 수 있습니다

    Redis가 지연 대기열을 구현하는 방법

    Redis가 지연 대기열을 구현하는 방법

    redisson_delay_queue_timeout:xxx, 정렬된 세트 데이터 유형, 모든 지연된 작업은 지연된 작업의 만료 타임스탬프(작업 제출 시 타임스탬프 + 지연 시간)에 따라 저장 및 정렬되므로 목록 앞의 첫 번째 요소는 전체 지연된 대기열에서 실행되는 가장 빠른 작업입니다. important

    RedissonDelayedQueue实现中我们看到有四个角色

    Redis가 지연 대기열을 구현하는 방법

    • redisson_delay_queue_timeout:xxx,sorted set数据类型,存放所有延迟任务,按照延迟任务的到期时间戳(提交任务时的时间戳 + 延迟时间)来排序的,所以列表的最前面的第一个元素就是整个延迟队列中最早要被执行的任务,这个概念很重要

    • redisson_delay_queue:xxx,list数据类型,暂时没发现什么用,只是在提交任务时会写入这里面,队列转移时又会删除里面的元素

    • xxx:list数据类型,被称为目标队列,这个里面存放的任务都是已经到了延迟时间的,可以被消费者获取的任务,所以上面demo中的RBlockingQueue的take方法是从这个目标队列中获取到任务的

    • redisson_delay_queue_channel:xxx,是一个channel,用来通知客户端开启一个延迟任务

    队列创建

    RedissonDelayedQueue延迟队列创建时,指定了队列转移服务,以及实现延迟队列的四个重要校色的key。核心代码是指定队列转移任务

     QueueTransferTask task = new QueueTransferTask(commandExecutor.getConnectionManager()) {
                
                @Override
                protected RFuture<Long> pushTaskAsync() {
                    return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
                            "local expiredValues = redis.call(&#39;zrangebyscore&#39;, KEYS[2], 0, ARGV[1], &#39;limit&#39;, 0, ARGV[2]); "//拿到zset中过期的值列表
                          + "if #expiredValues > 0 then " //如果有
                              + "for i, v in ipairs(expiredValues) do "
                                  + "local randomId, value = struct.unpack(&#39;dLc0&#39;, v);"//解构消息,在提交任务时打包的消息
                                  + "redis.call(&#39;rpush&#39;, KEYS[1], value);" //放入无前缀的list 队头
                                  + "redis.call(&#39;lrem&#39;, KEYS[3], 1, v);"//移除带前缀list 队尾元素
                              + "end; "
                              + "redis.call(&#39;zrem&#39;, KEYS[2], unpack(expiredValues));" //移除zset中本次读取的过期元素
                          + "end; "
                            // get startTime from scheduler queue head task
                          + "local v = redis.call(&#39;zrange&#39;, KEYS[2], 0, 0, &#39;WITHSCORES&#39;); "//取zset最小分值的元素
                          + "if v[1] ~= nil then "
                             + "return v[2]; " //返回分值,即过期时间
                          + "end "
                          + "return nil;",
                          Arrays.asList(getRawName(), timeoutSetName, queueName),
                          System.currentTimeMillis(), 100);
                }
                
                @Override
                protected RTopic getTopic() {
                    return RedissonTopic.createRaw(LongCodec.INSTANCE, commandExecutor, channelName);
                }
            };

    生产者

    Redis가 지연 대기열을 구현하는 방법

    核心代码RedissonDelayedQueue#offerAsync

    redisson_delay_queue:xxx, list 데이터 유형, 현재는 아무 쓸모도 발견하지 못했습니다. 작업 제출 시 여기에만 작성되며, 안에 있는 요소는 다음 작업이 완료되면 삭제됩니다. queue is transfer

    xxx: 목록 데이터 유형, 대상 대기열이라고 합니다. 여기에 저장된 작업은 지연 시간에 도달한 모든 작업이며 소비자가 얻을 수 있으므로 RBlockingQueue 위의 데모 take 메소드는 이 대상 큐🎜🎜🎜🎜redisson_delay_queue_channel:xxx에서 작업을 가져옵니다. 이 채널은 지연된 작업을 시작하도록 클라이언트에 알리는 데 사용되는 채널입니다🎜🎜🎜🎜큐 생성🎜🎜 RedissonDelayedQueue지연 대기열이 생성되면 대기열 전송 서비스와 지연 대기열에 대한 4가지 중요한 색상 보정 키가 지정됩니다. 핵심 코드는 대기열 전송 작업🎜
     return commandExecutor.evalWriteNoRetryAsync(getRawName(), codec, RedisCommands.EVAL_VOID,
                    "local value = struct.pack(&#39;dLc0&#39;, tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);" //打包消息体:消息id,消息长度,消息值
                  + "redis.call(&#39;zadd&#39;, KEYS[2], ARGV[1], value);"//zset中加入消息及其超时分值
                  + "redis.call(&#39;rpush&#39;, KEYS[3], value);" //向带前缀的list中添加消息
                  // if new object added to queue head when publish its startTime 
                  // to all scheduler workers 
                  + "local v = redis.call(&#39;zrange&#39;, KEYS[2], 0, 0); "//取出zset中第一个元素
                  + "if v[1] == value then " //如果最快过期的元素就是这次发送的消息
                     + "redis.call(&#39;publish&#39;, KEYS[4], ARGV[1]); " //channel中发布一下超时时间
                  + "end;",
                  Arrays.asList(getRawName(), timeoutSetName, queueName, channelName),
                  timeout, randomId, encode(e));
    🎜producer🎜🎜Redis 방법🎜🎜핵심 코드RedissonDelayedQueue#offerAsync🎜rrreee🎜Consumer🎜🎜Consumer는 가장 간단합니다. 접두사 없이 목록에서 직접 BLPOP을 읽습니다🎜

    위 내용은 Redis가 지연 대기열을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제