天天看点

用redis实现队列

用redis实现队列

什么是Redis?简述它的优缺点?

Redis的全称是:Remote Dictionary.Server,本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能。比方说用他的List来做FIFO双向链表,实现一个轻量级的高性能消息队列服务,用他的Set可以做高性能的tag系统等等。另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

Redis与memcached相比有哪些优势?

1.memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 2.redis的速度比memcached快很多redis的速度比memcached快很多 3.redis可以持久化其数据redis可以持久化其数据

Redis支持哪几种数据类型?

String、List、Set、Sorted Set、hashes

Redis List应用场景

Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。

Lists 就是链表,相信略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。

Lists的另一个应用就是消息队列,

可以利用Lists的PUSH操作,将任务存在Lists中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作Lists中某一段的api,你可以直接查询,删除Lists中某一段的元素。

实现方式

Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。1)入队

用redis实现队列

2)出队(非阻塞模式) lpop弹出列表首元素(即最后入队的元素)

用redis实现队列

Rpop弹出列表尾元素 (即入队的最开始的一个元素)

注意:如果要当作队列功能,应该是用这个出队

用redis实现队列

这里的出队都是非阻塞模式,就是你用pop出队的时候,如果队列是空的话,你得到的是一个NULL的值

3)出队(阻塞模式) 假如现在queue队列为空 我们用brpop命令

BRPOP 是一个阻塞的列表弹出原语。它是 RPOP 的阻塞版本,因为这个命令会在给定list无法弹出任何元素的时候阻塞连接。该命令会按照给出的 key 顺序查看 list,并在找到的第一个非空 list 的尾部弹出一个元素。

用redis的list当作队列可能存在的问题

1)redis崩溃的时候队列功能失效

2)如果入队端一直在塞数据,而出队端没有消费数据,或者是入队的频率大而多,出队端的消费频率慢会导致内存暴涨

3)Redis的队列也可以像rabbitmq那样 即可以做消息的持久化,也可以不做消息的持久化。

当做持久话的时候,需要启动redis的dump数据的功能.暂时不建议开启持久化。

Redis其实只适合作为缓存,而不是数据库或是存储。它的持久化方式适用于救救急啥的,不太适合当作一个普通功能来用。应为dump时候,会影响性能,数据量小的时候还看不出来,当数据量达到百万级别,内存10g左右的时候,非常影响性能。

4)假如有多个消费者同时监听一个队列,其中一个出队了一个元素,另一个则获取不到该元素

5)Redis的队列应用场景是一对多或者一对一的关系,即有多个入队端,但是只有一个消费端(出队)

消息队列任务调度

用redis实现队列

代码模拟

生产者模拟程序

package scheduleTest;  
  
import java.util.Random;  
import java.util.UUID;  
  
import redis.clients.jedis.Jedis;  
  
/** 
 * 模拟一个生产者 
 * <p>Title: TaskProducer</p> 
 * <p>Description: </p> 
 * <p>Company: </p> 
 * @date 
 * @vesion 1.0 
*/  
public class TaskProducer implements Runnable{  
    Jedis jedis = new Jedis("120.55.195.177",6379);  
      
    public void run() {  
        Random random = new Random();  
        while(true){  
            try{  
                Thread.sleep(random.nextInt(600) + 600);  
                // 模拟生成一个任务  
                UUID taskid = UUID.randomUUID();  
                //将任务插入任务队列:task-queue  
                jedis.lpush("task-queue", taskid.toString());  
                System.out.println("插入了一个新的任务:" + taskid);  
  
            }catch(Exception e){  
                e.printStackTrace();  
            }  
        }  
          
    }  
  
}      

消费者模拟程序

package scheduleTest;  
  
import java.util.Random;  
  
import redis.clients.jedis.Jedis;  
  
/** 
 * 模拟消费者 
 * <p>Title: TaskConsumer</p> 
 * <p>Description: </p> 
 * <p>Company: </p> 
 * @date
 * @vesion 1.0 
*/  
public class TaskConsumer implements Runnable {  
    Jedis jedis = new Jedis("120.55.195.177",6379);  
  
    public void run() {  
        Random random = new Random();  
          
        while(true){  
              
            //从任务队列"task-queue"中获取一个任务,并将该任务放入暂存队列"tmp-queue"  
            String taskid = jedis.rpoplpush("task-queue", "tmp-queue");  
              
              
            // 处理任务----纯属业务逻辑,模拟一下:睡觉  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
              
              
            //模拟成功和失败的偶然现象  
            if(random.nextInt(13) % 7 == 0){// 模拟失败的情况,概率为2/13  
                //将本次处理失败的任务从暂存队列"tmp-queue"中,弹回任务队列"task-queue"  
                jedis.rpoplpush("tmp-queue", "task-queue");  
                System.out.println(taskid + "处理失败,被弹回任务队列");  
              
            } else {// 模拟成功的情况  
                  
                // 将本次任务从暂存队列"tmp-queue"中清除  
                jedis.rpop("tmp-queue");  
                System.out.println(taskid+"处理成功,被清除");  
                  
            }     
        }  
                          
    }  
      
}      

调度主程序

/** 
 *  
 */  
package scheduleTest;  
  
/** 
 * <p>Title: TaskShedulerSystem</p> 
 * <p>Description: </p> 
 * <p>Company: </p> 
 * @date
 * @vesion 1.0 
*/  
public class TaskShedulerSystem {  
    public static void main(String[] args) throws Exception {  
          
        // 启动一个生产者线程,模拟任务的产生  
        new Thread(new TaskProducer()).start();  
          
        Thread.sleep(15000);  
          
        //启动一个线程者线程,模拟任务的处理  
        new Thread(new TaskConsumer()).start();  
          
        //主线程休眠  
        Thread.sleep(Long.MAX_VALUE);  
    }  
}      

运行结果

用redis实现队列

资料整理

整理了一份java面试资料,包括:

  • ActiveMQ消息中间件面试专题
  • BAT80道面试题
  • BAT面试的Mysql面试55题
  • Dubbo面试专题
  • JVM面试专题
  • Kafka面试专题
  • Linux面试专题
  • memcached面试专题
  • MongoDB面试专题
  • MyBatis面试专题
  • mysql面试专题
  • Mysql性能优化面试专题
  • Netty面试专题
  • ……
用redis实现队列

网盘地址:

【地址获取:回复58】​

继续阅读