SpringBoot集成NSQ消息队列
今天,和一位朋友的谈话让我有很多感触! 朋友说,自己刚开始有点心高气傲,现在回想起来,有一些后悔! 我思虑良久,想起来2022年4月,大专大二的自己,在这口罩事件严重的时候,就要被学校赶出来了,就要面临实习!那时候的自己,没有想过校招,也因为家庭困难,很怕因为不恰当的决策隔离产生昂贵非要,就再天津就近找了一个乱七八糟的活!现在去向,如果自己走校招,那么,自己现在可能会有更好的前途! 可是,仔细回想,在那种情况下,能就业就已经很不错,大厂都在裁员,经济形式一片萎靡,何处高就! 所以,仔细去思考,每一个时间点的每一个决策,都是正确的,因为那个时候的我们,从经验和环境各方面因素而言,我们都只能去走这一步棋,没有对错!即使再次在那个时间点,那个环境,我们再做一次选择,依然如是! 所以,和自己和解吧,和过去和解,面朝大海,春暖花开!
文章概述
nsq.io/deployment/... NSQ下载地址
nsq.io/overview/de... NSQ官方文档
nsqio.cn/design.html NSQ中文文档
NSQ(NSQ Messaging System)是一种实时分布式消息传递平台,由Bitly公司开发。它旨在解决大规模系统中的消息传递和处理问题,特别适用于处理大量数据的微服务架构。
-
分布式架构:NSQ采用分布式架构,允许通过多个节点进行消息的生产和消费。这使得系统具有高可用性和容错性。
-
去中心化设计:NSQ没有单点故障,消息传递不依赖于中央服务器,而是通过消息队列将消息传递到消费者。
-
实时消息传递:NSQ被设计用于实时消息传递,支持高吞吐量和低延迟的消息传递,适用于需要即时响应的应用场景。
-
水平扩展:NSQ的节点可以水平扩展,可以根据需求动态地添加或删除节点,以适应不断增长的消息流量。
-
消息顺序保证:NSQ保证消息在同一个主题(topic)和通道(channel)中的顺序传递,但不保证不同通道之间的消息顺序。
-
容错性:NSQ具有消息重试和失败处理机制,能够处理消息传递过程中的故障,确保消息不会丢失或重复传递。
-
监控和管理:NSQ提供了监控和管理工具,可以实时查看消息队列的状态、吞吐量和性能指标,以便进行调优和故障排查。
-
支持多种客户端库:NSQ支持多种编程语言的客户端库,包括Go、Python、Java等,使得开发者可以方便地集成NSQ到他们的应用程序中。
NSQ、RabbitMQ、ActiveMQ和Kafka是消息传递系统,每个系统都有自己的特点和优势,这里,我们再对比一下几大流行消息队列做一个大致分析
-
NSQ vs RabbitMQ:
- 架构差异:NSQ是去中心化的,没有单点故障,而RabbitMQ是集中式的,依赖于中央服务器。这使得NSQ更适合于分布式和高可用性需求。
- 消息传递保证:RabbitMQ支持丰富的消息传递模式,如点对点、发布/订阅等,而NSQ专注于实时消息传递,不支持像RabbitMQ那样丰富的消息传递模式。
- 性能差异:NSQ在某些情况下可能具有更好的性能,特别是在高吞吐量和低延迟的场景下,但RabbitMQ具有更多的功能和灵活性。
-
NSQ vs ActiveMQ:
- 架构差异:NSQ是去中心化的,而ActiveMQ是集中式的,这意味着NSQ更适合于构建分布式系统和微服务架构。
- 消息处理保证:ActiveMQ支持事务和持久化等高级消息处理特性,而NSQ主要关注于实时消息传递,不提供类似的高级特性。
- 语言支持:ActiveMQ支持更多的编程语言和协议,如Java、C++、.NET等,而NSQ主要使用Go语言进行开发,并提供了一些其他语言的客户端库。
-
NSQ vs Kafka:
- 数据处理模型:Kafka是一个分布式事件流平台,支持持久性和高吞吐量的事件流处理。相比之下,NSQ更专注于实时消息传递,适用于即时响应的场景。
- 消息保证:Kafka提供了严格的消息顺序保证和持久化存储,适合于需要确保消息顺序和可靠性的场景。而NSQ则更注重于高性能和低延迟的消息传递,并不提供严格的消息顺序保证。
- 存储和消费者管理:Kafka通过分区和复制来实现高可用性和水平扩展,同时支持消费者组和消息回溯等特性。相比之下,NSQ使用多播(multicast)和重新排队(requeue)来实现消息传递和消费者管理。
总的来说,SQ适合于构建实时、高吞吐量的分布式系统,特别是微服务架构;RabbitMQ和ActiveMQ提供了丰富的消息处理特性,适用于更复杂的消息传递场景;而Kafka则专注于事件流处理和大数据处理,适合于构建实时数据流应用。选择合适的系统取决于具体的需求和场景。
NSQ安装
-
在一个 shell 中,开始
nsqlookupd
:ruby$ nsqlookupd
-
再开启一个 shell ,运行
nsqd
:ini$ nsqd --lookupd-tcp-address=127.0.0.1:4160
-
再开启第三个 shell ,运行
nsqadmin
:ini$ nsqadmin --lookupd-http-address=127.0.0.1:4161
-
发布一条初始消息 (并且在集群中创建一个 topic):
ruby$ curl -d 'hello world 1' 'http://127.0.0.1:4151/pub?topic=test'
-
最后,在第五个 shell 中,运行
nsq_to_file
:css$ nsq_to_file --topic=test --output-dir=/tmp --lookupd-http-address=127.0.0.1:4161
-
推送更多的数据到
nsqd
:ruby$ curl -d 'hello world 2' 'http://127.0.0.1:4151/pub?topic=test' $ curl -d 'hello world 3' 'http://127.0.0.1:4151/pub?topic=test'
-
为了验证事情是否按预期进行,请在打开的网络浏览器
http://127.0.0.1:4171/
中查看nsqadmin
用户界面并查看统计信息。另外,检查 (test.*.log
) 写入的日志文件内容从/tmp
的目录.
这里重要的是 nsq_to_file
(客户端)没有明确告知 test
主题的产生地,它从 nsqlookupd
获取信息,即使在消息推送之后才开始连接 nsqd,消息也并没有消失。
注:如果win系统中,curl命令无法执行,可参考如下命令
shInvoke-WebRequest -Uri "http://127.0.0.1:4151/pub?topic=test" -Method POST -Body "hello world 1"
注意:
这里,此处涉及到我们再yml中的相关配置,尤其是端口号,此处切不可弄错,如此部署,yml中对应的端口如下:
xmlnsq: host: 127.0.0.1 port: produce: 4150 lookup: 4161
系统集成
首先,pom引入如下依赖
xml
<!-- https://mvnrepository.com/artifact/com.github.brainlag/nsq-client -->
<dependency>
<groupId>com.github.brainlag</groupId>
<artifactId>nsq-client</artifactId>
<version>1.0.0.RC4</version>
</dependency>
然后我们创建一个Java类 NsqConsume
java
package com.nsqclient.config;
import com.github.brainlag.nsq.NSQConsumer;
import com.github.brainlag.nsq.lookup.DefaultNSQLookup;
import com.github.brainlag.nsq.lookup.NSQLookup;
import com.nsqclient.service.TestTopicHanlder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class NsqConsume implements ApplicationRunner {
@Value("${nsq.host}")
private String nsqHost;
@Value("${nsq.port.lookup}")
private Integer nsqLookupPort;
@Value("${nsq.topic}")
private String topic;
@Value("${nsq.channel}")
private String channel;
private final TestTopicHanlder topicHanlder;
public NsqConsume(TestTopicHanlder topicHanlder) {
this.topicHanlder = topicHanlder;
}
@Override
public void run(ApplicationArguments args) {
NSQLookup lookup = new DefaultNSQLookup();
lookup.addLookupAddress(nsqHost, nsqLookupPort);
log.info("=========================NSQ消息通信地址:【{}】=========================", nsqHost);
log.info("=========================NSQ消息监听端口:【{}】=========================", nsqLookupPort);
NSQConsumer acsConsumer = new NSQConsumer(lookup, topic, channel, topicHanlder);
acsConsumer.start();
}
}
它实现了ApplicationRunner
接口。该类用于配置和启动NSQ消息队列的消费者。
在代码中,使用了Lombok库提供的@Slf4j
注解来自动生成日志对象log
。通过@Value
注解从配置文件中获取了NSQ相关的配置信息,包括主机地址、查找端口、主题和通道等。
在run
方法中,创建了一个DefaultNSQLookup
对象,并使用addLookupAddress
方法将NSQ主机地址和查找端口添加到查找器中。然后,打印出NSQ消息通信地址和监听端口的信息。
接下来,创建了一个NSQConsumer
对象,传入查找器、主题、通道和处理程序(TestTopicHanlder
)作为参数。最后,调用start
方法启动消费者。
总结起来,这段代码的作用是配置和启动一个NSQ消息队列的消费者,用于接收和处理特定主题的消息。 那么,我们就得配置一个生产者
java
package com.nsqclient.config;
import com.github.brainlag.nsq.NSQProducer;
import com.github.brainlag.nsq.exceptions.NSQException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeoutException;
@Slf4j
@Component(value = "NsqProduce")
public class NsqProduce implements ApplicationRunner {
@Value("${nsq.host}")
private String nsqHost;
@Value("${nsq.port.produce}")
private Integer port;
@Value("${nsq.port.lookup}")
private Integer nsqLookupPort;
@Value("${nsq.topic}")
private String topic;
@Value("${nsq.channel}")
private String channel;
private NSQProducer producer;
private static boolean is_start = false;
private static final byte[] LOCK = new byte[0];
public void run(ApplicationArguments args) {
producer = new NSQProducer();
producer.addAddress(nsqHost, port).start();
is_start = true;
}
public NSQProducer getProducer() {
if (!is_start) {
log.info("========================NSQProduce no start====================");
}
if (producer == null) {
synchronized (LOCK) {
if (producer == null) {
producer = new NSQProducer();
producer.addAddress(nsqHost, port).start();
}
}
}
return producer;
}
public void sendMsgToTestTopic(String msg) {
try {
this.getProducer().produce(topic, msg.getBytes());
} catch (NSQException | TimeoutException e) {
log.error(e.getMessage());
}
}
}
这段代码是用于配置 NSQ 生产者(Producer)。在分布式消息系统中,生产者负责向消息队列发送消息,而消费者负责从队列中接收并处理消息。根据代码中的功能和命名,可以看出这段代码是用于配置 NSQ 的生产者,并提供了发送消息的功能。
-
NSQProducer 初始化与配置: 该类使用了第三方库 com.github.brainlag.nsq 中的 NSQProducer 类,用于与 NSQ(一个实时分布式消息平台)交互。在 run 方法中,通过读取配置文件中的 nsqHost 和 port 属性,初始化 NSQProducer,并连接到指定的 NSQ 服务器上。
-
消息发送功能: 通过 sendMsgToTestTopic 方法,可以向指定的 NSQ topic 发送消息。消息以字节数组的形式传递,并在发送过程中处理可能抛出的 NSQException 或 TimeoutException。
-
单例模式实现: 为了确保 NSQProducer 的唯一性,使用了双重检查锁(double-checked locking)的单例模式实现。这种方式在多线程环境下保证了对象的唯一性,避免了多次创建 NSQProducer 的开销。
-
日志记录: 使用了 SLF4J 日志框架,通过 @Slf4j 注解实现日志记录,便于在代码中输出日志信息,以便于调试和监控。
总体来说,这段代码实现了与 NSQ 交互的功能,并且在 Spring Boot 应用程序启动时初始化 NSQProducer。 然后,我就得再继续配置一个TestTopicHanlder
用来处理消息
java
package com.nsqclient.service;
import com.github.brainlag.nsq.NSQMessage;
import com.github.brainlag.nsq.callbacks.NSQMessageCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class TestTopicHanlder implements NSQMessageCallback {
@Override
public void message(NSQMessage nsqMessage) {
log.info("=========================接收到消息===========================");
log.info(new String(nsqMessage.getMessage()));
}
}
这段代码是一个用于处理 NSQ 消息的服务类。它实现了 com.github.brainlag.nsq.callbacks.NSQMessageCallback 接口,用于定义处理接收到的 NSQ 消息的逻辑。
主要功能包括:
-
消息处理方法: 在 message 方法中,对接收到的 NSQ 消息进行处理。在这个示例中,它简单地打印了接收到的消息内容到日志中,使用了 SLF4J 日志框架。
-
日志记录: 使用了 @Slf4j 注解,以便在代码中使用日志记录功能,方便查看程序运行时的状态信息。
总体来说,这段代码是一个用于处理接收到的 NSQ 消息的处理器类。 然后,肯定需要有人发消息,我们再写一个发送消息的服务
java
package com.nsqclient.dao;
import lombok.Data;
@Data
public class NsqMsg {
//消息ID
private String msgId;
//消息内容
private String msgText;
//消息标识
private String msgFlag;
}
java
package com.nsqclient.service;
import com.nsqclient.config.NsqProduce;
import com.nsqclient.dao.NsqMsg;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
public class TestTopicSender {
@Autowired
private NsqProduce nsqProduce;
public void sendMsgToTopic(){
NsqMsg nsqMsg=new NsqMsg();
nsqMsg.setMsgId(UUID.randomUUID().toString());
nsqMsg.setMsgText("测试");
nsqMsg.setMsgFlag("1");
nsqProduce.sendMsgToTestTopic(nsqMsg.toString());
}
}
我们再写一个控制层去调用
java
package com.nsqclient.controller;
import com.alibaba.fastjson.JSONObject;
import com.nsqclient.config.NsqProduce;
import com.nsqclient.service.TestTopicSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/nsq")
public class NSQClientController {
@Autowired
private TestTopicSender topicSender;
@GetMapping("/sendMsg")
public JSONObject sendMsgToTopic() {
topicSender.sendMsgToTopic();
JSONObject result = new JSONObject();
result.put("status", 200);
result.put("msg", "发送成功");
return result;
}
}
测试效果