MQ 是什么?

MQ(Message Queue)消息队列,是基础数据结构中“先进先出”的一种数据结构。

指把要传输的数据(消息)放在队列中,用队列机制来实现消息传递——生产者产生消息并把消息放入队列,然后由消费者去处理。

消费者可以到指定队列拉取消息,或者订阅相应的队列,由MQ服务端给其推送消息。

MQ 的作用?

消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。

解耦:一个业务需要多个模块共同实现,或者一条消息有多个系统需要对应处理,只需要主业务完成以后,发送一条MQ,其余模块消费MQ消息,即可实现业务,降低模块之间的耦合。

异步:主业务执行结束后从属业务通过MQ,异步执行,减低业务的响应时间,提高用户体验。

削峰:高并发情况下,业务异步处理,提供高峰期业务处理能力,避免系统瘫痪。

ps: 以上内容摘选自百科。

实现 mq 的准备工作

maven 引入

<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>

模块划分

The message queue in java. 作为 mq 的从零开始的学习项目,目前已开源。

项目的模块如下:

模块 说明
mq-common 公共代码
mq-broker 注册中心
mq-producer 消息生产者
mq-consumer 消息消费者

消息消费者

接口定义

package com.github.houbb.mq.consumer.api;

/**
* @author binbin.hou
* @since 1.0.0
*/
public interface IMqConsumer { /**
* 订阅
* @param topicName topic 名称
* @param tagRegex 标签正则
*/
void subscribe(String topicName, String tagRegex); /**
* 注册监听器
* @param listener 监听器
*/
void registerListener(final IMqConsumerListener listener); }

IMqConsumerListener 作为消息监听类的接口,定义如下:

public interface IMqConsumerListener {

    /**
* 消费
* @param mqMessage 消息体
* @param context 上下文
* @return 结果
*/
ConsumerStatus consumer(final MqMessage mqMessage,
final IMqConsumerListenerContext context); }

ConsumerStatus 代表消息消费的几种状态。

消息体

启动消息体 MqMessage 定义如下:

package com.github.houbb.mq.common.dto;

import java.util.Arrays;
import java.util.List; /**
* @author binbin.hou
* @since 1.0.0
*/
public class MqMessage { /**
* 标题名称
*/
private String topic; /**
* 标签
*/
private List<String> tags; /**
* 内容
*/
private byte[] payload; /**
* 业务标识
*/
private String bizKey; /**
* 负载分片标识
*/
private String shardingKey; // getter&setter&toString }

push 消费者策略实现

消费者启动的实现如下:

/**
* 推送消费策略
*
* @author binbin.hou
* @since 1.0.0
*/
public class MqConsumerPush extends Thread implements IMqConsumer { // 省略... @Override
public void run() {
// 启动服务端
log.info("MQ 消费者开始启动服务端 groupName: {}, port: {}, brokerAddress: {}",
groupName, port, brokerAddress); EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup(); try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(workerGroup, bossGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new MqConsumerHandler());
}
})
// 这个参数影响的是还没有被accept 取出的连接
.option(ChannelOption.SO_BACKLOG, 128)
// 这个参数只是过一段时间内客户端没有响应,服务端会发送一个 ack 包,以判断客户端是否还活着。
.childOption(ChannelOption.SO_KEEPALIVE, true); // 绑定端口,开始接收进来的链接
ChannelFuture channelFuture = serverBootstrap.bind(port).syncUninterruptibly();
log.info("MQ 消费者启动完成,监听【" + port + "】端口"); channelFuture.channel().closeFuture().syncUninterruptibly();
log.info("MQ 消费者关闭完成");
} catch (Exception e) {
log.error("MQ 消费者启动异常", e);
throw new MqException(ConsumerRespCode.RPC_INIT_FAILED);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
} } // 省略... }

ps: 初期我们把 consumer 作为服务端,后续引入 broker 则只有 broker 是服务端。

MqConsumerHandler 处理类

这个类是一个空的实现。

public class MqConsumerHandler extends SimpleChannelInboundHandler {

    @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object object) throws Exception {
//nothing
} }

测试代码

MqConsumerPush mqConsumerPush = new MqConsumerPush();
mqConsumerPush.start();

启动日志:

[DEBUG] [2022-04-21 19:16:41.343] [main] [c.g.h.l.i.c.LogFactory.setImplementation] - Logging initialized using 'class com.github.houbb.log.integration.adaptors.stdout.StdOutExImpl' adapter.
[INFO] [2022-04-21 19:16:41.356] [Thread-0] [c.g.h.m.c.c.MqConsumerPush.run] - MQ 消费者开始启动服务端 groupName: C_DEFAULT_GROUP_NAME, port: 9527, brokerAddress:
[INFO] [2022-04-21 19:16:43.196] [Thread-0] [c.g.h.m.c.c.MqConsumerPush.run] - MQ 消费者启动完成,监听【9527】端口

消息生产者

接口定义

最基本的消息发送接口。

package com.github.houbb.mq.producer.api;

import com.github.houbb.mq.common.dto.MqMessage;
import com.github.houbb.mq.producer.dto.SendResult; /**
* @author binbin.hou
* @since 1.0.0
*/
public interface IMqProducer { /**
* 同步发送消息
* @param mqMessage 消息类型
* @return 结果
*/
SendResult send(final MqMessage mqMessage); /**
* 单向发送消息
* @param mqMessage 消息类型
* @return 结果
*/
SendResult sendOneWay(final MqMessage mqMessage); }

生产者实现

MqProducer 启动的实现如下,基于 netty。

package com.github.houbb.mq.producer.core;

/**
* 默认 mq 生产者
* @author binbin.hou
* @since 1.0.0
*/
public class MqProducer extends Thread implements IMqProducer { //省略... @Override
public void run() {
// 启动服务端
log.info("MQ 生产者开始启动客户端 GROUP: {}, PORT: {}, brokerAddress: {}",
groupName, port, brokerAddress); EventLoopGroup workerGroup = new NioEventLoopGroup(); try {
Bootstrap bootstrap = new Bootstrap();
ChannelFuture channelFuture = bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<Channel>(){
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
.addLast(new LoggingHandler(LogLevel.INFO))
.addLast(new MqProducerHandler());
}
})
.connect("localhost", port)
.syncUninterruptibly(); log.info("MQ 生产者启动客户端完成,监听端口:" + port);
channelFuture.channel().closeFuture().syncUninterruptibly();
log.info("MQ 生产者开始客户端已关闭");
} catch (Exception e) {
log.error("MQ 生产者启动遇到异常", e);
throw new MqException(ProducerRespCode.RPC_INIT_FAILED);
} finally {
workerGroup.shutdownGracefully();
}
} //省略...
}

MqProducerHandler 处理类

默认的空实现,什么都不做。

package com.github.houbb.mq.producer.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; /**
* @author binbin.hou
* @since 1.0.0
*/
public class MqProducerHandler extends SimpleChannelInboundHandler { @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object object) throws Exception {
//do nothing now
} }

启动代码

MqProducer mqProducer = new MqProducer();
mqProducer.start();

启动日志:

[DEBUG] [2022-04-21 19:17:11.960] [main] [c.g.h.l.i.c.LogFactory.setImplementation] - Logging initialized using 'class com.github.houbb.log.integration.adaptors.stdout.StdOutExImpl' adapter.
[INFO] [2022-04-21 19:17:11.974] [Thread-0] [c.g.h.m.p.c.MqProducer.run] - MQ 生产者开始启动客户端 GROUP: P_DEFAULT_GROUP_NAME, PORT: 9527, brokerAddress:
四月 21, 2022 7:17:13 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x5cb48145] REGISTERED
四月 21, 2022 7:17:13 下午 io.netty.handler.logging.LoggingHandler connect
信息: [id: 0x5cb48145] CONNECT: localhost/127.0.0.1:9527
四月 21, 2022 7:17:13 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x5cb48145, L:/127.0.0.1:57740 - R:localhost/127.0.0.1:9527] ACTIVE
[INFO] [2022-04-21 19:17:13.833] [Thread-0] [c.g.h.m.p.c.MqProducer.run] - MQ 生产者启动客户端完成,监听端口:9527

小结

基于 netty 最基本的服务端启动、客户端启动到这里就结束了。

千里之行,始于足下。

我们下一节将和大家一起学习,如何实现客户端与服务端之间的交互。

希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。

我是老马,期待与你的下次重逢。

开源地址

The message queue in java.(java 简易版本 mq 实现): https://github.com/houbb/mq

拓展阅读

rpc-从零开始实现 rpc: https://github.com/houbb/rpc

【mq】从零开始实现 mq-01-生产者、消费者启动

【mq】从零开始实现 mq-01-生产者、消费者启动的更多相关文章

  1. 利用生产者消费者模型和MQ模型写一个自己的日志系统-并发设计里一定会用到的手段

    一:前言 写这个程序主要是用来理解生产者消费者模型,以及通过这个Demo来理解Redis的单线程取原子任务是怎么实现的和巩固一下并发相关的知识:这个虽然是个Demo,但是只要稍加改下Appender部 ...

  2. 实战Spring4+ActiveMQ整合实现消息队列(生产者+消费者)

    引言: 最近公司做了一个以信息安全为主的项目,其中有一个业务需求就是,项目定时监控操作用户的行为,对于一些违规操作严重的行为,以发送邮件(ForMail)的形式进行邮件告警,可能是多人,也可能是一个人 ...

  3. centos7单机安装kafka,进行生产者消费者测试

    [转载请注明]: 原文出处:https://www.cnblogs.com/jstarseven/p/11364852.html   作者:jstarseven    码字挺辛苦的.....  一.k ...

  4. RocketMQ之消费者启动与消费流程

    vivo 互联网服务器团队 - Li Kui 一.简介 1.1 RocketMQ 简介 RocketMQ是由阿里巴巴开源的分布式消息中间件,支持顺序消息.定时消息.自定义过滤器.负载均衡.pull/p ...

  5. java多线程系列15 设计模式 生产者 - 消费者模式

    生产者-消费者 生产者消费者模式是一个非常经典的多线程模式,比如我们用到的Mq就是其中一种具体实现 在该模式中 通常会有2类线程,消费者线程和生产者线程 生产者提交用户请求 消费者负责处理生产者提交的 ...

  6. python2.0_s12_day9之day8遗留知识(queue队列&生产者消费者模型)

    4.线程 1.语法 2.join 3.线程锁之Lock\Rlock\信号量 4.将线程变为守护进程 5.Event事件 * 6.queue队列 * 7.生产者消费者模型 4.6 queue队列 que ...

  7. java——利用生产者消费者模式思想实现简易版handler机制

    参考教程:http://www.sohu.com/a/237792762_659256 首先说一下这里面涉及到的线程: 1.mainLooper: 这个线程可以理解为消费者线程,里面运行了一个死循环, ...

  8. 多道技术 进程 线程 协程 GIL锁 同步异步 高并发的解决方案 生产者消费者模型

    本文基本内容 多道技术 进程 线程 协程 并发 多线程 多进程 线程池 进程池 GIL锁 互斥锁 网络IO 同步 异步等 实现高并发的几种方式 协程:单线程实现并发 一 多道技术 产生背景 所有程序串 ...

  9. java多线程系类:基础篇:10生产者消费者的问题

    概要 本章,会对"生产/消费者问题"进行讨论.涉及到的内容包括:1. 生产/消费者模型2. 生产/消费者实现 转载请注明出处:http://www.cnblogs.com/skyw ...

随机推荐

  1. 『忘了再学』Shell基础 — 3、echo命令的介绍与使用

    目录 1.echo命令的作用 2.echo命令的基本用法 3.echo命令的-e选项用法 4.echo命令一些特殊用法 (1)输出字符带有字体颜色 (2)输出字符带有背景颜色 在讲Shell脚本之前, ...

  2. C++ TCHAR* 与char* 互转

    C++ TCHAR* 与char* 互转 在MSDN中有这么一段: Note: The ANSI code pages can be different on different computers, ...

  3. Schema和数据类型优化?

    整数TinyInt,SmallInt,MediumInt,Int,BigInt 使用的存储8,16,24,32,64位存储空间.使用Unsigned表示不允许负数,可以使正数的上线提高一倍.实数 Fl ...

  4. IDEA terminal无法从vim的编辑模式转换为命令模式

    Git 修改最后一次的commit历史记录:https://www.baidu.com/link?url=2WF8yFd0iBuVmXLWfutmSoXa12K9D143e_B0A3PTYYHEP9r ...

  5. DWR是什么?有什么作用?

    DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架,可以帮助开发人员开发包含AJAX技术的网站. 它可以允许在浏览器里的代码使用运行 ...

  6. 什么是Spring的MVC框架?

    Spring 配备构建Web 应用的全功能MVC框架.Spring可以很便捷地和其他MVC框架集成,如Struts,Spring 的MVC框架用控制反转把业务对象和控制逻辑清晰地隔离.它也允许以声明的 ...

  7. memcached 最大能存储多大的单个 item?

    1MB.如果你的数据大于 1MB,可以考虑在客户端压缩或拆分到多个 key 中. 为什么单个 item 的大小被限制在 1M byte 之内? 简单的回答:因为内存分配器的算法就是这样的. 详细的回答 ...

  8. 体验javascript之美6:如果你觉得什么都会了或者不知道js学什么了看这里-面向对象编程

    概述 当大家已经把js的语言基础理解了,然后能够写出一些简单的例子了,这个时候基本上达到了一年工作经验的水平,而自己能够独立的写一些小功能,完成一些小效果,或者临摹修改一些比较复杂的插件的时候差不多就 ...

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

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

  10. 7分钟理解JS的节流、防抖及使用场景

    前言 据说阿里有一道面试题就是谈谈函数节流和函数防抖.糟了,这可触碰到我的知识盲区了,好像听也没听过这2个东西,痛定思痛,赶紧学习学习.here we go! 概念和例子 函数防抖(debounce) ...