• Redis简单延时队列


    Redis实现简单延队列, 利用zset有序的数据结构, score设置为延时的时间戳. 

    实现思路:

    1、使用命令 [zrangebyscore keyName socreMin socreMax] 会返回已score排序由小到大的一个list

    2、list非空则使用[zrem keyName value]  删除第一个元素, 删除成功即代表消费成功, 可以解决多线程并发消费的问题.

    使用jedis实现代码:

      1 package com.nancy.utils;
      2 
      3 import com.alibaba.fastjson.JSON;
      4 import com.alibaba.fastjson.TypeReference;
      5 import redis.clients.jedis.Jedis;
      6 
      7 import java.lang.reflect.Type;
      8 import java.util.*;
      9 
     10 /**
     11  * redis实现延时队列,但是对于要求行极高的环境不建议使用,主要原因:
     12  * 1、没有可靠的消息持久机制,消息容易丢失
     13  * 2、ack应答机制确实,没有传统MQ机制的可靠性
     14  *
     15  * @author  zhou.guangfeng on 2019/3/9 下午4:31
     16  */
     17 public class RedisDelayQueue<T> {
     18 
     19     static class TaskItem<T>{
     20         public String id ;
     21         public T msg ;
     22     }
     23 
     24     private Jedis jedis ;
     25     private String queueKey ;
     26     private Type taskType = new TypeReference<TaskItem<T>>(){}.getType();
     27 
     28     RedisDelayQueue(Jedis jedis, String queueKey){
     29         this.jedis = jedis ;
     30         this.queueKey = queueKey ;
     31     }
     32 
     33     public void delay(T msg){
     34         TaskItem<T> item = new TaskItem<>() ;
     35 
     36         item.id = UUID.randomUUID().toString() ;
     37         item.msg = msg ;
     38 
     39         String content = JSON.toJSONString(item) ;
     40         try {
     41             Thread.sleep(10L);
     42         } catch (InterruptedException e) {
     43             e.printStackTrace();
     44         }
     45         jedis.zadd(queueKey, System.currentTimeMillis() + 5000, content) ;
     46     }
     47 
     48     public void loop(){
     49         while (!Thread.interrupted()){
     50             Boolean flag = consumer() ;
     51             try {
     52                 if(!flag) {
     53                     Thread.sleep(500L);
     54                 }
     55             }catch (InterruptedException ex){
     56                 break;
     57             }
     58 
     59         }
     60     }
     61 
     62     /**
     63      *
     64      * 队列消费,利用zrem操作,删除成功即也消费。 并发环境可能出现zrem删除失败情况,从而导致无效的请求。
     65      * @param
     66      * @return
     67      */
     68     private Boolean consumer(){
     69          // 按照分数即时间, 有序集成员按 score 值递增(从小到大)次序排列。
     70          Set<String> values = jedis.zrangeByScore(queueKey, 0, System.currentTimeMillis()) ;
     71          if (values == null || values.isEmpty()){
     72             return false ;
     73          }
     74 
     75          String content = values.iterator().next() ;
     76          if(jedis.zrem(queueKey, content) <= 0){
     77              return false ;
     78          }
     79 
     80         TaskItem<T> item = JSON.parseObject(content, taskType) ;
     81         handleMsg(item.msg) ;
     82         return true ;
     83     }
     84 
     85     public void handleMsg(T msg){
     86         System.out.println(msg);
     87     }
     88 
     89     public static void main(String[] args) {
     90         RedisDelayQueue<String> queue = new RedisDelayQueue<>(AbstractDistributedLock.getJedisPool().getResource(), "delay-queue-demo") ;
     91 
     92         System.out.println("delay queue start, time = " + new Date());
     93         Thread producer = new Thread(){
     94             @Override
     95             public void run() {
     96                 for (int i = 0; i < 10; i++) {
     97                     queue.delay("codehole:" + i);
     98                 }
     99             }
    100         };
    101 
    102         Thread consumer = new Thread(){
    103             @Override
    104             public void run() {
    105                 queue.loop();
    106             }
    107         };
    108 
    109         producer.start();
    110         consumer.start();
    111 
    112         try {
    113             producer.join();
    114             Thread.sleep(6000L);
    115 
    116             consumer.interrupt();
    117             consumer.join();
    118         }catch (InterruptedException ex){
    119 
    120         }finally {
    121             System.out.println("delay queue start, end = " + new Date());
    122         }
    123 
    124     }
    125 
    126 }
     1 public abstract class AbstractDistributedLock implements RedisLock {
     2 
     3     private static JedisPool jedisPool;
     4 
     5     protected static final String LOCK_SUCCESS = "OK";
     6     protected static final Long RELEASE_SUCCESS = 1L;
     7     protected static final String SET_IF_NOT_EXIST = "NX";
     8     protected static final String SET_WITH_EXPIRE_TIME = "EX";
     9     protected static final long DEFAULT_EXPIRE_TIME = 1000 * 10 ;
    10     protected static final long DEFAULT_DELAY_TIME = 2 ;
    11 
    12     static {
    13         JedisPoolConfig config = new JedisPoolConfig();
    14         // 设置最大连接数
    15         config.setMaxTotal(500);
    16         // 设置最大空闲数
    17         config.setMaxIdle(50);
    18         // 设置最大等待时间
    19         config.setMaxWaitMillis(1000 * 100);
    20         // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的
    21         config.setTestOnBorrow(true);
    22         jedisPool = new JedisPool(config, "127.0.0.1", 6379, 3000);
    23     }
    24 
    25     public static JedisPool getJedisPool() {
    26         return jedisPool;
    27     }
    28 
    29     @Override
    30     public String getLockKey(String lockKey){
    31         return  "lock:" + lockKey;
    32     }
    33 
    34 }
    AbstractDistributedLock

    运行结果:

    delay queue start, time = Mon Mar 11 10:21:25 CST 2019
    codehole:0
    codehole:1
    codehole:2
    codehole:3
    codehole:4
    codehole:5
    codehole:6
    codehole:7
    codehole:8
    codehole:9
    delay queue start, end = Mon Mar 11 10:21:31 CST 2019

    以上的代码 jedis.zrangeByScore 和  jedis.zrem 为非原子操作.  如果jedis.zrem一旦失败, 会进入休眠, 造成资源浪费. 因此改造为使用lua脚本执行jedis.zrangeByScore 和  jedis.zrem 保证原子性.

     1    /**
     2      * 队列消费 使用lua脚本, 保证zrangebyscore 和 zrem操作原子性。
     3      *
     4      * @param
     5      * @return
     6      */
     7     private Boolean consumerWithLua(){
     8         String script = " local resultDelayMsg = {}; " +
     9                         " local arr = redis.call('zrangebyscore', KEYS[1], '0', ARGV[1]) ; " +
    10                         " if next(arr) == nil then return resultDelayMsg  end ;" +
    11                         " if redis.call('zrem', KEYS[1], arr[1]) > 0 then table.insert(resultDelayMsg, arr[1]) return resultDelayMsg end ; " +
    12                         " return resultDelayMsg ; ";
    13         Object result = jedis.eval(script, Collections.singletonList(queueKey), Collections.singletonList("" + System.currentTimeMillis()));
    14         List<String> msg = null ;
    15         if (result == null || (msg = (List<String>) result).isEmpty()) {
    16             return false ;
    17         }
    18 
    19         TaskItem<T> item = JSON.parseObject(msg.get(0), taskType) ;
    20         handleMsg(item.msg) ;
    21         return true ;
    22     }

     redis实现延时队列,但是对于要求行极高的环境不建议使用,主要原因:

    1、没有可靠的消息持久机制,消息容易丢失. 需要自己实现

    2、ack应答机制缺失,没有传统MQ机制的可靠性

    因此, 如果对数据一致性有严格的要求, 还是建议使用传统MQ.

  • 相关阅读:
    语句覆盖、判断覆盖、条件覆盖、条件判定组合覆盖、多条件覆盖、修正条件覆盖
    Python日志
    Python基础
    curl-awk-grep
    bash使用 变量定义与使用、预定义变量、数组变量、变量计算、掐头去尾与内容替换、数字比较大小、字符串比较、判断文件夹是否存在、逻辑控制if/for/while/
    V模型 W模型 H模型 X模型 前置测试模型
    算法:回文、素数
    JAVA并发思维导图
    工作常见的git命令
    dubbo同步/异步调用的方式
  • 原文地址:https://www.cnblogs.com/xiaoxing/p/10509072.html
Copyright © 2020-2023  润新知