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

但是如果你不想为你的系统引入一个重量级(相对 redis 来说)的 mq,但是想要享受解耦、异步消息等特性,通过本文你就 get 到了,通过 redis 实现一个简单版的 mq。

为什么是 redis

  • redis 通常作为缓存服务引入,因此大部分系统都会有 redis
  • redis 本身的资源消耗是极小的,符合我们的轻量要求
  • redis 速度很快,几乎不会出现速度瓶颈
  • redis 有持久化方案,调整配置项可以在数据安全和速度间进行取舍(参考这篇)[https://segmentfault.com/a/1190000002906345]

如何实现

利用 redis 的队列结构来实现消息队列。redis 单个队列最多支持 2*32-1 条数据,对于大部分应用是完全够用的。

简单来说就是:

  • 每个 topic 对应一条队列
  • 从队列一段写入数据,从另一端读取数据
  • 消费失败,重新将消息放入队列

注意:代码仅供个人尝鲜使用,请勿用于真实生产环境

代码仅可在 springboot 环境中使用

首先定义注解和接口类

注解代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface MqConsumer {
/**
* 队列主题
*/
String topic() default "default_es_topic";
}

被该注解修饰的类,将会接收 topic 下的消息。

接口代码如下:

public interface RedisConsumer {

    /**
* 功能描述: 消费方法,消费者类必须继承此方法
*
* @param message 数据载体
* @author 123
* @date 2020/3/28 22:41
*/
void deal(String message);
}

本接口用于定于接受消息的处理方法。

扫描注解修饰类

本部分为核心代码,首先需要获取代码中被注解修饰的类,然后建立一个循环从 redis 队列中取数据,最后调用类对象的 deal 方法消费消息,如果 deal 方法抛出错误,认为消费失败,重新将该数据放入队列中。

  1. 扫描部分代码如下:
/**
* MqConfiguration.java
*/
@Override
public void run(ApplicationArguments args) {
Map<String, Object> map = context.getBeansWithAnnotation(MqConsumer.class);
map.values().forEach(item -> {
if (!(item instanceof RedisConsumer)) {
log.warn("注意检测到被@EsConsumer注解的类{}未实现RedisConsumer接口", item.getClass().getCanonicalName());
return;
}
MqConsumer[] annotations = item.getClass().getAnnotationsByType(MqConsumer.class);
MqConsumer annotation = annotations[0];
String topic = annotation.topic();
if (topicMap.containsKey(topic)) {
log.error("多个消费者{},消费同一个消息:{},已忽略", item.getClass().getCanonicalName(), topic);
} else {
topicMap.put(topic, (RedisConsumer) item);
} });
log.info("redis订阅信息汇总完毕!!!!!!");
//由一个线程始终循环获取es队列数据
threadPoolExecutor.execute(loop());
}

run 方法在 spring 扫描完毕后调用,通过实现ApplicationRunner接口实现,通过 spring 的方法来获取所有被MqConsumer接口注解的类(否则需要自己写类加载器)。数据汇总完毕后使用一个线程来进行无线循环从 redis 队列中取数据。

  1. 执行线程部分代码如下:
private Runnable loop() {
return () -> {
while (true) {
AtomicInteger count = new AtomicInteger(0);
topicMap.forEach((k, v) -> {
try {
String message = mqUtil.getRedisTemplate().opsForList().rightPop(k);
if (message == null) {
count.getAndIncrement();
} else {
pushTask(v, message, k);
}
} catch (RedisConnectionFailureException connException) {
log.error("redis无法连接,10s后重试", connException);
sleep(10);
} catch (Exception e) {
log.error("redis消息队列异常", e);
}
});
if (count.get() == topicMap.keySet().size()) {
//当所有的队列都为空时休眠1s
sleep(1);
}
}
};
}
private void pushTask(RedisConsumer item, String value, String key) {
threadPoolExecutor.execute(() -> {
try {
item.deal(value);
} catch (Exception e) {
log.error("执行消费任务出错", e);
//非广播消息进行数据回补
mqUtil.getRedisTemplate().opsForList().rightPush(key, value);
}
});
}

loop 方法无限循环根据 topic 从 redis 中取数据,如果取到数据,调用 pushTask 方法执行,如果执行报错将会进行数据回补。

完整代码见本文结尾

测试

运行项目后调用,MainController中的接口即可测试。

完整代码:github

本文原创发布于:手把手教你用 redis 实现一个简单的 mq 消息队列

手把手教你用redis实现一个简单的mq消息队列(java)的更多相关文章

  1. 手把手教你用vue-cli构建一个简单的路由应用

    上一章说道:十分钟上手-搭建vue开发环境(新手教程)https://www.jianshu.com/p/0c6678671635 开发环境搭建好之后,那么开始新添加一些页面,构建最基本的vue项目, ...

  2. 手把手教你从零写一个简单的 VUE

    本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...

  3. 手把手教你从零写一个简单的 VUE--模板篇

    教程目录1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 Hello,我又回来了,上一次的文章教会了大家如何书写一个简单 VUE,里面实现了VUE 的数据驱动视图 ...

  4. 基于Redis实现一个安全可靠的消息队列

    http://doc.redisfans.com/list/rpoplpush.html

  5. 180626-Spring之借助Redis设计一个简单访问计数器

    文章链接:https://liuyueyi.github.io/hexblog/2018/06/26/180626-Spring之借助Redis设计一个简单访问计数器/ Spring之借助Redis设 ...

  6. 手把手教你使用FineUI开发一个b/s结构的取送货管理信息系统系列博文索引

    近阶段接到一些b/s类型的软件项目,但是团队成员之前大部分没有这方面的开发经验,于是自己选择了一套目前网上比较容易上手的开发框架(FineUI),计划录制一套视频讲座,来讲解如何利用FineUI快速开 ...

  7. Python使用Redis实现一个简单作业调度系统

    Python使用Redis实现一个简单作业调度系统 概述 Redis作为内存数据库的一个典型代表,已经在非常多应用场景中被使用,这里仅就Redis的pub/sub功能来说说如何通过此功能来实现一个简单 ...

  8. 分布式锁没那么难,手把手教你实现 Redis 分布锁!|保姆级教程

    书接上文 上篇文章「MySQL 可重复读,差点就让我背上了一个 P0 事故!」发布之后,收到很多小伙伴们的留言,从中又学习到很多,总结一下. 上篇文章可能举得例子有点不恰当,导致有些小伙伴没看懂为什么 ...

  9. 手把手教你使用 js 实现一个 Canvas 编辑器

    手把手教你使用 js 实现一个 Canvas 编辑器 拖拽 缩放,等比缩放 导出 image 模版 撤销,重做 OOP,封装,继承,多态 发布库 CI/CD (gitlab/github) ... h ...

随机推荐

  1. How to delete the eclipse plugin.

    click  Help-->installation Details 2. choose the plugin that you want to delete.Then click uninst ...

  2. MySQL 数据库的基本使用

    MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,而MySQL AB 公司被 Oracle 公司收购,故 MySQL 现在属于 Oracle 公司.MySQL 是一种关联数据 ...

  3. SWPU邮件登录界面的仿写(第二次作业)

    (一).检查并下载网页元素 在需仿写的页面按F12,点击element,寻找需要的图片元素. (二). 分析网页的布局 查看网页源代码. (三).开始仿写 由于我们的目标是仿写网页,所以可以直接复制网 ...

  4. 3、JSP中的Cookie 用于存储 web 页面的用户信息。

    cookie 在平时生活中的运用 存储用户在网页上的登陆信息,包括账号和密码. 有的网站,登陆的时候,会出现一个选项,问你是否要一周内或者一个月内保持登陆状态.如果你选了,那么一周之内,都不需要再输入 ...

  5. Poj1753 翻转棋子

    这个题就是用枚举举遍所有情况,然后一个一个深搜看看是不是符合条件,符合条件直接退出,不符合则继续, 由于表格只有16个所以可以得知最多的步数只能是16,所以可以根据步数从0到16依次枚举, 第一个符合 ...

  6. 化学元素周期表的英文全称 Periodic Table of the Elements

    化学元素周期表的英文全称 Periodic Table of the Elements   缩写 PTE 拉丁文 英文 1 H 氢 Hydrogenium Hydrogen 2 He 氦 Helium ...

  7. 读Pyqt4教程,带你入门Pyqt4 _005

    对话框窗体或对话框是现代GUI应用不可或缺的一部分.dialog定义为两个或多个人之间的交谈.在计算机程序中dialog是一个窗体,用来和程序“交谈”.对话框用来输入数据.修改数据.改变程序设置等等. ...

  8. 树莓派4B获取IP地址的几种简易方法

    首先声明一下,使用的是Paspbian系统,其实其他系统和本文说的获取IP地址关系也不大. 1.当你有路由器,有PC客户端的情况,你把你的树莓派用网线将其连接起来.你可以借助这个软件,advanced ...

  9. Spring boot Sample 005之spring-boot-profile

    一.环境 1.1.Idea 2020.1 1.2.JDK 1.8 二.目的 通过yaml文件配置spring boot 属性文件 三.步骤 3.1.点击File -> New Project - ...

  10. Chisel3 - model - UserModule commands

    https://mp.weixin.qq.com/s/0ECca6XyFyEri0B4ckOZ4A   介绍UserModule类中,如何管理构建硬件模型所需的命令.   ​​   1. _comma ...