redis使用场景(list)
前言
在开始redis list使用场景之前,让我们回顾一下redis 的list数据结构。除了特殊场景下(内容较小使用压缩链表),都是采用linkedList,那么linkedList的数据结构固然对我们的使用场景起着决定性因素。无序链表决定我们不能随机取出队列中的某个元素,只能从头或者尾取数据。也就是我们的队列也只能保证这种规则,请看官方给出的redis-list相关命令。
// 入队操作
#将1 从队列右边放入1
rpush mykey 1
#将1 从队列右边放入1 如果队列不存在 不做任何操作
rpushx mykey 1
#将1 从队列左边放入1
lpush mykey 1
#将1 从队列左边放入1 如果队列不存在 不做任何操作
lpushx mykey 1
//出队操作
#从队列左边取出一条数据
lpop mykey
#从队列右边取出一条数据
rpop mykey
#从队列左边取出一条数据 并把数据从右边放进去
lpoprpush mykey
#从队列右边取出一条数据 并把数据从左边放进去
rpoplpush mykey
//骚操作
# b block 阻塞的意思 所以 顾名思义 阻塞拿数据 如果队列为空 将进行阻塞(可以指定时间)
blpop mykey 0
brpop mykey 0
正文
我们已经了解了redis操作list的基本命令,已经可以通过客户端愉快地操作redis的队列了。但是有没有小伙伴在疑惑,目前已经有那么成熟的队列可以使用了(rabbitMq、rocketMq、kafka)等,为什么还要用redis的队列呢?redis队列有什么牛逼的呢?答案是没有!任何的技术没有好与不好只有适不适合。不同的应用场景会采取不同的技术方案,这也是为什么我们要学习多种技术(为了混口饭吃呀!!!)。我这边给出了一张表,以rabbitMQ为例,列出了二者的对比。
RabbitMQ | redis队列 | |
可靠性 | 可靠 | 不可靠(自行实现) |
高可用 | 支持 | 支持 |
持久化 | 可选择 | 整个redis实力都会被持久化 |
消费者负载均衡 | 支持 | 不支持(自行实现) |
队列监控 | 有控制台 | 无 |
流量控制 | 可控 | 不可控 |
性能 | 较低 | 较高 |
总体 | 牛逼 | 一般 |
为什么要使用redis队列?
大家看了上边的对比图,就会发现rabbitMQ好强大呀,用什么redis队列,啥玩意都要自己搞。那为什么不是所有的场景都用rabbitMQ呢? 第一:维护难。rabbitMQ是一个中间件,从单机到集群都需要搭建以及运维。 第二:成本大。rabbitMQ属于CS服务,那么服务端会占用不少的资源。说白了,如果你足够有钱,足够膨胀那就所有场景都RabbitMQ(如果真有这样的,请让我和你做朋友),^_^
实现一个redis队列
想法:redis队列的入队就比较简单,往里边放就行了。而客户端取数据,就需要启动一个定时任务,定时去取redis队列中的数据。 基于这个简单的想法,我们上代码。
//生产者
public class RedisQueueProducer{
@Autowired
private RedisTemplate redisTemplate;
/**
* 入队
*/
public void add( Object o) {
redisTemplate.opsForList().leftPush("mykey", e);
}
}
//消费者
pubic class Consumer{
@Autowired
private RedisTemplate redisTemplate;
/**
* 出队
*/
public Object pop() {
return redisTemplate.opsForList().rightPop("mykey");
//消费
public void consume(){
//循环消费队列
while(true){
log.info(pop());
}
}
}
细心的同学已经发现了上边代码的问题。当队列为空的了,消费者还在不断的去pop队列,浪费了CPU资源并且对redis性能有所影响。那么我们如何解决呢?
if(pop() == null){
//如果发现队列为空 进行休眠
Thread.sleep(100);
}
这样万事大吉了吗? 当你读到这句话,就已经明白战斗还没有结束。休眠难道不对吗?感觉貌似没啥问题呀! 是的,这样确实没啥性能问题,但是休眠的时间如何界定?太长——数据不实时 太短——性能影响太大。 怎么办呢? 还记得上边redis命令中的一个骚操作吗?何为骚操作,就是在你觉得无解的时候,嗅到了出路。
//brpop/blpop 这两个命令上边已经说了 阻塞取数据,
//使用此命令会阻塞队列,每次弹出一个消息,如果没有消息会阻塞,
//把阻塞时间设置为0,就是没有消息就一直阻塞,只要有消息为止。
//这样不仅解决了性能问题,也解决了消费实时问题,代码如下
public Object pop(){
//一直阻塞
return redisTemplate.opsForList().rightPop(key, 0, TimeUnit.SECONDS);
}
//消费
public void consume(){
//循环消费队列
while(true){
log.info(pop());
}
}
如何实现ACK
为什么要ack?解决worker拿到消息然后挂了,导致该消息从队列移除,但是没有实现自己的价值。怎么解决呢?
-
维护一个队列,一个表
-
当出队后, 分配一个work线程去处理消息(给消息增加当前时间戳以及当前线程名称),然后写入表
-
入表后,work线程消费消息,然后移除表中对应数据
-
启动定时任务扫表,如果消息中的时间戳超时,检查是否work线程是否存在,如果存在取消任务,回滚事务,把消息重新入队
-
如果消费者业务处理失败,主动回滚,把消息重新入队
结语
消息队列作为我们目前最火热的解耦、削峰方案。不管是redis队列、rabbitMQ、rocketMQ还是kafka,任何的技术都有自己的优缺点,所以没有最好的技术,只有最好的使用场景。
关注不迷路