Redis系列1:深刻理解高性能Redis的本质

Redis系列2:数据持久化提高可用性

Redis系列3:高可用之主从架构

Redis系列4:高可用之Sentinel(哨兵模式)

Redis系列5:深入分析Cluster 集群模式

追求性能极致:Redis6.0的多线程模型

追求性能极致:客户端缓存带来的革命

Redis系列8:Bitmap实现亿万级数据计算

Redis系列9:Geo 类型赋能亿级地图位置计算

Redis系列10:HyperLogLog实现海量数据基数统计

Redis系列11:内存淘汰策略

Redis系列12:Redis 的事务机制

Redis系列13:分布式锁实现

1 介绍

在分布式系统中,很重要的一个能力就是消息中间件。我们通过消息队列实现 功能解耦、消息有序性、消息路由、异步处理、流量削峰 等能力。

目前主流的Mq主要有 RabbitMQ 、RocketMQ、kafka,可以参考这篇《MQ系列2:消息中间件技术选型》。

那除了这些主流MQ之外,咱们的这一节要说的Redis也具备实现消息队列的能力。

我们来看看消息队列主要要实现哪些能力,原理是什么,以及如何在 Redission 中应用。

2 关于消息队列

2.1 什么是消息队列

消息中间件是指在分布式系统中完成消息的发送和接收的基础软件。

消息中间件也可以称消息队列(Message Queue / MQ),用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息队列模型,可以在分布式环境下扩展进程的通信。

简而言之,互联网场景中经常使用消息中间件进行消息路由、订阅发布、异步处理等操作,来缓解系统的压力。

  • Broker: 消息服务器,作为Server提供消息核心服务,一般会包含多个Q。
  • Producer: 消息生产者,业务的发起方,负责生产消息传输给broker,
  • Consumer: 消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理

2.2 它解决了我们哪些问题

1、解耦: 比如说系统A会交给系统B去处理一些事情,但是A不想直接跟B有关联,避免耦合太强,就可以通过在A,B中间加入消息队列,A将要任务的事情交给消息队列 ,B订阅消息队列来执行任务。

这种场景很常见,比如A是订单系统,B是库存系统,可以通过消息队列把削减库存的工作交予B系统去处理。如果A系统同时想让B、C、D...多个系统处理问题的时候,这种优势就更加明显了。

2、有序性: 先进先出原理,先来先处理,比如一个系统处理某件事需要很长一段时间,但是在处理这件事情时候,有其他人也发出了请求,可以把请求放在消息队里,一个一个来处理。

对数据的顺序性和一致性有强需求的业务,比如同一张银行卡同时被多个入口使用,需要保证入账出账的顺序性,避免出现数据不一致。

3、消息路由: 按照不同的规则,将队列中消息发送到不同的其他队列中

通过消息队列将不同染色的请求发送到不同的服务去操作。这样达成了流量按照业务拆分的目的。

4、异步处理: 处理一项任务的时候,有3个步骤A、B、C,需要先完成A操作, 然后做B、C 操作。任务执行成功与否强依赖A的结果,但不依赖B、C 的结果。

如果我们使用串行的执行方式,那处理任务的周期就会变长,系统的整体吞吐能力也会降低(在同一个系统中做异步其实也是比较大的开销),所以使用消息队列是比较好的办法。

登录操作就是典型的场景:A:执行登录并得到结果、B:记录登录日志、C:将用户信息和Token写入缓存。 执行完A就可以从登录页跳到首页了,B、C让服务慢慢去消化,不阻塞当前操作。

5、削峰: 将峰值期间的操作削减,比如A同学的整个操作流程包含12个步骤,后续的11个步骤是不需要强关注结果的数据,可以放在消息队列中。

详细可参考笔者这篇《MQ系列1:消息中间件执行原理》。

2.3 消息队列满足的业务特性

2.3.1 消息有序性

正如上面提到的有序性一样,他能够保证消息按照生产的顺序进行处理和消费,避免消息被无序处理的情况发生。

2.3.2 消息去重

同样的,生产和消费的消息需要保证幂等性原理。避免出现重复执行的情况,

而消息队列的去重机制,也需要确保避免消息被重复消费的问题。

2.3.3 消息的可靠性传输

消息队列的数据可以实现重试、持久化存储、死信队列记录等,以避免消息无法成功传递所产生的不一致现象。

当消息服务器或者消费者恢复健康的时候,可以继续读取消息进行处理,防止消息遗漏。

3 使用Redis的List实现消息队列

稍微学过数据结构都知道。我们经常说Queue(队列),他的存储和使用规则是【先进先出】,栈的存储和使用规则是【先进后出】。

所以List本质上是一个线性的有序结构,也就是Queue的存储关系,它能够保证消费的有序性,按照顺序进行处理。

3.1 入列操作 LPUSH

即进行消息生产,入列操作语法:

 LPUSH key element[element...]

如果key存在,Producer 通过 LPUSH 将消息插入该队列的头部;如果 key 不存在,则是先创建一个空队列,然后在进行数据插入。

下面举个例子,往队列中插入几个消息,然后得到的返回值是插入消息的个数。

> LPUSH msg_queue msg1 msg2 msg3
(integer) 3

这边往 key 为 msg_queue 的队列中插入了三个消息 msg1、msg2、msg3。

3.2 出列操作 RPOP

即进行消息消费,消费的顺序是先进先出(先生产先消费),出列使用的语法如下:

> RPOP msg_queue
"msg1"
> RPOP msg_queue
"msg2"
> RPOP msg_queue
"msg3"
> RPOP msg_queue
(nil)

都消费完成之后,就是nil了。

3.3 消费及时性问题

不同于常规的MQ,具备订阅模式,消费者可以感知到有新的消息生产出来了,再进行消费。

List的问题在于,生产者向队列插入数据的时候,List 并不会主动通知消费者,所以消费者做不到及时消费。

为了保证消费的及时,可能需要做一个心跳包(1秒执行一次),不断地执行 RPOP 指令,当探测到有新消息就会取出消息进行消费,没有消息的时候就返回nil。

但是这种也存在明显的短板,就是不断的调用 RPOP 指令,占用 I/O 资源和CPU资源。

比较好的解决办法就是在队列为空队列的时候,暂停读取,等有消息入列的时候,恢复取数和消费的工作,这样也避免了无效的资源浪费。

Redis 提供了 BLPOP、BRPOP ,无数据的时候自动阻塞读取的命令,有新消息进入的时候,恢复消息取数,如下:

# BRPOP  key  timeout
BRPOP msg_queue 0

命令最后一个参数 timeout 是超时时间,单位是秒,如果 timeout 大于0,则到达指定的秒数即使没有弹出成功也会返回,如果 timeout 的值为0,则会一直阻塞等待其他连接向列表中插入元素, timeout 参数不允许为负数。

3.4 消息的重复消费问题

目前 List 没有纯幂等的鉴别能力,但是可以通过以下两种方法来实现:

  • List为每一条消息生成一个 Glocal ID,重复的Glocal ID 不进行重复消费。
  • Producer在生产消息的时候在消息中创建一个Glocal ID,当消费的时候把Glocal ID Record一下,后续的消费先判断再消费,避免重复消费同一个消息。

    这样就保证了对于同一条消息,消费者始终只处理一次,结果始终保持一致。

3.5 消息的可靠性传输问题

可靠性传输我们在MQ篇章用了一整节来介绍持久化存储、消息ACK 、二次记录保障。这边我们也来看看Redis List中的可靠性传输的保障。

Redis中缺少了一个消息确认(ACK)的机制,如果消费数据的时候运行崩溃了,没有确认机制,很可能这条消息就被错过了,无法保证数据的一致性。

解决方案:Redis 提供了 RPOPLPUSH 指令,当List读取消息的时候,会同步的把该消息复制到另外一个List以作备份。

整个操作过程是具备原子性的,避免读取消息了,但是同步备份不成功。

如果出现处理消息出现故障的情况,在故障回复之后,可以从备份的List中复制消息继续消费。操作如下:

# 生产消息 msg1 msg2
> LPUSH list_queue msg1 msg2
(integer) 2
# 消费消息并同步到备份
> RPOPLPUSH list_queue list_queue_bak
"msg1"
# 当发生故障的时候去消费备份的数据,可以消费到
> RPOP list_queue_bak
"msg1"

如果消费成功则把 list_queue_bak 消息删除即可,如果发生故障,则可以继续从 list_queue_bak 再次读取消息处理。

4 使用 Redission 实现队列能力

这边以Java SpringBoot为例子进行说明,可以参考官方文档

4.1 添加maven依赖 和 配置基本连接

# maven信息
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.8</version>
</dependency>
# 基本配置
spring:
application:
name: redission_test
redis:
host: x.x.x.x
port: 6379
ssl: false
password: xxxx.xxxx

4.2 Java程序实现

@Slf4j
@Service
public class RedisQueueService { @Autowired
private RedissonClient redissonClient; private static final String REDIS_QUEUE = "listQueue"; /**
* 消息生产
*
* @param msg
*/
public void msgProduce(String msg) {
RBlockingDeque<String> blockDeque = redissonClient.getBlockingDeque(REDIS_QUEUE);
try {
blockDeque.putFirst(msg); // 消息写入队列头部
} catch (InterruptedException e) {
log.error(e.printStackTrace());
}
} /**
* 消息消费:阻塞
*/
public void msgConsume() {
RBlockingDeque<String> blockDeque = redissonClient.getBlockingDeque(REDIS_QUEUE);
Boolen isCheck = true;
while (isCheck) {
try {
String msg = blockDeque.takeLast(); // 从队列中取出消息
} catch (InterruptedException e) {
log.error(e.printStackTrace());
}
}
}

5 总结

  • Redis中使用List 数据结构实现消息队列,满足FIFO的处理机制,使用 RPOP 进行消息读取。
  • 使用 BRPOP 指令处理消费及时性问题
  • 使用 BRPOPLPUSH 命令进行消息数据备份,解决消息可靠性传输问题。
  • 相对于专业的MQ,如kafka和RocketMQ,处理能力会差很多。所以在在消息量不大的场景中使用,可以作为一个比较不错的消息队列解决方案。但是过于复杂的场景容易造成消息堆积。

Redis系列14:使用List实现消息队列的更多相关文章

  1. Redis系列(五):消息队列

    消息队列已经成为现在互联网服务端的标配组件,现在比较常用的消息中间件有RabbitMQ.Kafka.RocketMQ.ActiveMQ.说出来你可能不信,Redis作为一个缓存中间件,居然也提供了消息 ...

  2. Redis系列二之事务及消息通知

    一.事务 Redis中的事务是一组命令的集合.一个事务中的命令要么都执行,要么都不执行. 1.事务简介 事务的原理是先将一个事务的命令发送给Redis,然后再让Redis依次执行这些命令.下面看一个示 ...

  3. 手把手教你用redis实现一个简单的mq消息队列(java)

    众所周知,消息队列是应用系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构.目前使用较多的消息队列有 ActiveMQ,RabbitMQ,Zero ...

  4. redis k-v数据库、高速缓存、消息队列代理

    Redis 简介   Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 与其他 key - value 缓存产品有以下三个特点: Redis支持数据的 ...

  5. Redis中的Stream数据类型作为消息队列的尝试

    Redis的List数据类型作为消息队列,已经比较合适了,但存在一些不足,比如只能独立消费,订阅发布又无法支持数据的持久化,相对前两者,Redis Stream作为消息队列的使用更为有优势.   相信 ...

  6. redis系列-14点的灵异事件

    概述 项目组每天14点都会遭遇惊魂时刻.一条条告警短信把工程师从午后小憩中拉回现实.之后问题又神秘消失.是PM喊你上工了?还是服务器给你开玩笑?下面请看工程师如何一步一步揪出真凶,解决问题. 如果不想 ...

  7. Redis 竟然能用 List 实现消息队列

    分布式系统中必备的一个中间件就是消息队列,通过消息队列我们能对服务间进行异步解耦.流量消峰.实现最终一致性. 目前市面上已经有 RabbitMQ.RochetMQ.ActiveMQ.Kafka等,有人 ...

  8. redis 系列14 有序集合对象

    一. 有序集合概述 Redis 有序集合对象和集合对象一样也是string类型元素的集合,且不允许重复的成员.不同的是每个元素都会关联一个double类型的分数.redis正是通过分数来为集合中的成员 ...

  9. 使用redis的发布订阅模式实现消息队列

    配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://w ...

  10. Redis用LPUSH和RPOP实现消息队列

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using ServiceS ...

随机推荐

  1. Apollo 分布式配置中心理论到实践

    携程开源的配置管理中心(统一管理各种应用配置的基础服务组件),能够集中化管理应用的不同环境,不同集群的配置,配置修改后能够实时推送到应用端,适合微服务配置管理场景.Apollo包括服务端和客户端. 在 ...

  2. [PKM] 个人知识管理

    1 个人知识管理的需求 1.1 背景 随着信息大爆炸,碎片化的知识越来越多,原来中小学阶段在学校中习得的.传统的.基于纸质笔记的知识管理方式已不能满足当前的诉求. 传统的基于纸质笔记的知识管理方式 工 ...

  3. stable diffusion打造自己专属的LORA模型

    通过Lora小模型可以控制很多特定场景的内容生成. 但是那些模型是别人训练好的,你肯定很好奇,我也想训练一个自己的专属模型(也叫炼丹-_-). 甚至可以训练一个专属家庭版的模型(family mode ...

  4. Redis 数据类型 Zset

    Redis 数据类型 Zset(有序集合) Redis 常用命令,思维导图 >>> zset是Redis提供的一个非常特别的数据结构,常用作排行榜等功能,以用户id为value,关注 ...

  5. 如何通过Java应用程序在PPT中创建SmartArt图形

    SmartArt其实就是一个文字的可视化工具,用户可在PowerPoint,Word,Excel中使用该特性创建各种图形图表.SmartArt 图形是信息和观点的视觉表示形式.可以通过从多种不同布局中 ...

  6. 带你用三种不同的工具体验AI作诗

    摘要:本实验基于华为云API Arts和API Explorer,向用户介绍诗歌生成API,指导用户使用华为云工具,体验AI作诗的过程. 本文分享自华为云社区<AI语言能力体验:通过三种不同的工 ...

  7. linux下live555编译和调试

    linux下live555编译和调试 live555 支持 h.264 初步告捷,可以播放,尽管不是很稳定,或者说暂时只能播放 1 帧(主要是我现在还不了解 帧的概念),同时还有 Mal SDP 的传 ...

  8. 从原理聊JVM(三):详解现代垃圾回收器Shenandoah和ZGC

    作者:京东科技 康志兴 Shenandoah Shenandoah一词来自于印第安语,十九世纪四十年代有一首著名的航海歌曲在水手中广为流传,讲述一位年轻富商爱上印第安酋长Shenandoah的女儿的故 ...

  9. VUE的路由懒加载及组件懒加载

    一,为什么要使用路由懒加载 为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题 二,懒加载简单来说就是延迟加载或按需加载,即在需要的时候的时候进行加载 三,常用的懒加载方式有两种:即使用v ...

  10. WPF 引用字体文件资源

    外部字体文件 1.后台代码引用字体 将一个名为"ChineseCharacterSpecialFont.ttf"的ttf文件,放在桌面路径,后台引用方式如下: 1 var ttfF ...