NSQ,基于 Go 语言实现的分布式消息队列

初识 NSQ?

本次我们来聊一聊 NSQ,它是一个分布式消息传递平台,不仅支持大规模运行,并且还是实时的。使用 NSQ,每天可以处理数十亿条消息。所以 NSQ 和 Kafka、RabbitMQ 类似,也是消息队列的一种,但 NSQ 是使用 Go 语言编写的。对于 Go 语言编写的项目,最大的特点就是部署简单,很多都只是一个二进制文件,直接启动就完事了,不需要其它的依赖项。

那么 NSQ 都具有哪些特点呢?

  • 分布式:去中心化、无单点故障;
  • 可伸缩:没有中心化的 broker,可以随意添加节点进行水平扩展;
  • 高性能:消息推送的延迟低,并且主要基于内存(超过高水位标记的消息会保存在磁盘);
  • 功能丰富:不仅支持 pub-bub、load-balance 等多种消息传递方式,而且能够同时处理面向流(高吞吐)和面向作业(低吞吐)的工作负载;
  • 容错性:提供了消息重试和回退等特性以增强容错能力,可以确保消息在处理过程中的可靠性;
  • 运维友好:配置和部署非常简单,并自带 admin UI;
  • 集成方便:通信使用 TCP 协议,数据格式简单,大部分语言都有相应的 SDK;

然后 NSQ 的组成部分,主要包括:

  • nsqd:一个守护进程,负责处理来自客户端的请求,所以 nsqd 就类似于 Kafka broker,是真正用来处理消息的;
    • 监听端口为:4150(TCP)、4151(HTTP)、4152(HTTPS,可选)
  • nsqlookupd:同样是一个守护进程,提供了对 nsqd 节点进行服务发现的功能;
    • nsqd 向 nsqlookupd 广播 topic 和 channel 信息
    • 客户端通过查询 nsqlookupd 寻找指定的 topic 对应的 nsqd 节点
    • 监听端口:4160(TCP)和 4161(HTTP)
  • nsqadmin:一个 web 界面,用来进行集群管理、数据可视化等功能;
  • utilities:NSQ 也提供了一些工具供我们使用;
    • nsq_stat:负责拉取指定 topic/channel 的所有消费者,展示统计数据
    • nsq_tail:负责消费指定 topic/channel 的数据,并输出到控制台
    • nsq_to_file:负责消费指定 topic/channel 的数据,并写到文件中,有选择的滚动和/或压缩文件
    • nsq_to_http:负责消费指定 topic/channel 的数据,发送到指定的 HTTP 端点
    • nsq_to_nsq:负责消费者指定 topic/channel 的数据,通过 TCP 重发布消息到目的 nsqd
    • to_nsq:通过标准输入流将数据发送到目的 nsqd

这几个组件不难理解,但我们提到了 topic 和 channel,其中 channel 一会说,但 topic 可能会让你想到 Kafka 的 topic。当生产者向 nsqd 发送消息时,也需要指定一个 topic,你可以把 topic 理解为一个特定类型的消息流。但除了 topic,在 NSQ 中还有一个概念叫 channel,channel 可以理解为 topic 下的一个子类别。当消息被发布到一个 topic 后,它会被复制到这个 topic 下的所有 channel 中,每个 channel 都会保持自己的消息队列,这样多个消费者可以独立地从各自的 channel 消息消息,互不影响。

比如订单 topic 下面有三个 channel,订单服务作为生产者将订单信息发给 nsqd 的 订单 topic 即可,nsqd 会自动将消息发送给该 topic 下的所有 channel。所以一个 topic 下的所有 channel 的数据都是一样的,然后每个服务消费一个 channel,从而实现所谓的广播功能。比如积分服务、库存服务、优惠券服务都需要订单信息,那么它们只需要订阅相应的 channel 即可。

然后需要再补充一点:nsqd 在收到消息后会直接保存在内存中,并根据需要将消息落盘存储。nsqd 节点在启动时会创建两个数据目录,一个用于存放元数据,另一个用于存放消息数据。当 nsqd 收到消息后,会尽可能的将消息保留在内存中,这样可以快速地将消息投递给消费者。然而,当内存的使用量达到一定阈值,或者进程退出时,nsqd 会将消息写入到磁盘上。这种设计旨在确保即使在面临内存压力或者进程重启的情况下,消息也不会丢失。

而尽管 NSQ 提供了消息落盘的功能,但它的设计宗旨并不是一个持久化的消息队列系统。NSQ 更关注于实时消息处理,因此在设计上偏重于消息的实时处理性能,而对消息的持久性和一致性提供的保障较弱。因此,如果你的系统需要高度的消息持久化保证,那么你可能需要考虑其他的消息队列解决方案,比如 Apache Kafka 或者 RabbitMQ。

NSQ 安装

单从概念上来看,NSQ 本身是比较简单的,然后我们来看看它的安装方式和具体用法。首先是安装,我们去 NSQ 的官网去下载相应的安装包,然后扔到服务器上。

# 如果服务器的网速够快,那么也可以直接使用 wget 下载
# 注意压缩包的名字:nsq-1.2.1 表示 NSQ 的版本是 1.2.1
# 而 go1.16.6 则表示 NSQ 的源码是由 1.16.6 版本的 Go 编译器进行编译的
wget https://s3.amazonaws.com/bitly-downloads/nsq/nsq-1.2.1.linux-amd64.go1.16.6.tar.gz
# 解压安装包到 /opt 目录
tar -zxvf nsq-1.2.1.linux-amd64.go1.16.6.tar.gz -C /opt

好了,安装完毕,因为 NSQ 的组件都是使用 Go 语言编写的,均以一个二进制文件的形式存在。

安装之后主目录中只有一个 bin 目录,bin 目录里面则是几个二进制文件,非常干净,不需要任何依赖。

启动 nsqd

直接执行 nsqd 二进制文件即可启动 nsqd,但在启动时支持很多参数,我们来看一下。

  • --config <file>

    指定要加载的配置文件。

  • --tcp-address <address>

    nsqd 监听的 TCP 连接的地址和端口,默认为 0.0.0.0:4150。

  • --http-address <address>

    nsqd 监听的 HTTP 连接的地址和端口,默认为 0.0.0.0:4151。

  • --https-address <address>

    nsqd 监听的 HTTPS 连接的地址和端口,默认为 0.0.0.0:4152。

  • --broadcast-address <address>

    nsqd 实例在 nsqlookupd 中注册的地址(只需要指定 IP 即可,默认是主机名),在一个分布式的 NSQ 系统中,这个参数可以帮助多个 nsqd 实例之间找到彼此并进行连接。至于端口则通过 --broadcast-http-port 和 --broadcast-tcp-port 指定,默认和 nsqd 监听的端口保持一致。

  • --lookupd-tcp-address <address>

    nsqlookupd 的地址和端口,可以指定多个。

  • --data-path <path>

    指定用于数据持久化后的存储路径。

  • --mem-queue-size <number>

    每个 topic/channel 在内存中的最大消息数,默认为 10000。

  • --max-rdy-count <number>

    客户端可以提高其就绪数(RDY)的最大值,就绪数(RDY)是 NSQ 中的一种流控机制,它限制了消费者在一次操作中可以消费的消息数量。当消费者向 nsqd 发送 RDY 命令时,它告诉 nsqd 自己已经准备好处理一定数量的消息。例如消费者发送了 "RDY 100",那么 nsqd 将会向该消费者发送最多 100 条消息,然后等待消费者处理这些消息并发送新的 RDY 命令。

    默认为 2500,表示消费者在任何时候都不能向 nsqd 声明它已经准备好处理超过 2500 条的消息。这个参数的设置需要根据消费者的处理能力和系统的实际需求来决定,如果设置得过低,可能会限制消费者的处理能力,导致消息处理速度不够快。如果设置得过高,那么当消费者无法快速处理大量消息时,可能会导致内存压力增大,影响系统的稳定性。

  • --max-req-timeout <duration>

    用于设置 nsqd 中客户端可以请求重新排队(requeue)消息的最大时间,举个例子:如果消费者接收到一个消息,但由于某种原因(比如正在处理其它任务,或者产生了一些错误等)无法立即处理该消息。那么消费者可以发送一个 REQ 命令,请求 nsqd 把这个消息重新放入队列,并指定一个延迟时间,这个延迟时间就是消费者告诉 nsqd 要多久后再次尝试发送这个消息。

    而该参数就是负责设置这个延迟时间的最大值,例如设置 --max-req-timeout 10m,那么消费者就不能请求超过 10 分钟的延迟消息。注意这里的 m 表示分钟,还有 s 表示秒、h 表示小时。该参数默认为 1h,也就是 1 小时。

  • --max-msg-size <number>

    客户端可以发送的最大消息的大小,超过这个大小的消息会被 nsqd 拒绝,消息的大小包括消息的内容和元数据。默认为 1048576,即 1M。

  • --max-body-size <number>

    客户端可以发送的最大消息体的大小,因为客户端可以一次性发送多条消息,这些消息会被打包在一起成为一个消息体。所以 --max-msg-size 是限制单个消息的大小,而 --max-body-size 则是限制一次性传输的所有消息的总大小。默认值为 5242880,约为 5M。

  • --max-msg-timeout <duration>

    消费者从 nsqd 获取消息后,允许在没有确认消息之前保留该消息的最大时间,要注意它和 --max-req-timeout 的区别。当消费者从 nsqd 接收到消息后,需要在一定时间内处理完消息并向 nsqd 发送一个 FIN 命令,告知 nsqd 这条消息已经被成功处理,这个时间就是消息超时时间(Message Timeout)。如果消费者在这个时间内没有确认消息,nsqd 会认为这条消息没有被成功处理,会尝试将其重新投递给消费者。

    所以 --max-msg-timeout 是消费者在处理一条消息时能够使用的最大时间,而 --max-req-timeout 是消费者请求稍后再处理一条消息的最大时间。该参数默认为 15m,表示消费者在处理每条消息时必须在 15 分钟内处理完毕并给 nsqd 返回响应,否则 nsqd 就会认为消费者处理失败。

以上就是一些比较常用的参数,至于其它参数可以查看文档,下面来启动 nsqd。

启动成功,默认是前台启动,如果你想后台启动,那么使用 nohup。

然后从输出来看,启动过程会将 topic/channel 的元数据持久化到 nsqd.dat 文件中,由于在启动时指定了数据存储路径,所以该文件会位于 ../data 中。然后监听的 TCP 端口为 4150,HTTP 端口为 4151,都是默认选项,当然也可以手动指定。

由于前台启动不太方便,这里改成后台启动。

启动 nsqadmin

NSQ 自带了 webUI 负责管理集群(当前是单机部署),也就是 nsqadmin,我们来看看它的一些启动参数。

  • --config <file>

    指定要加载的配置文件。

  • --template-dir <dir>

    指定模板文件的目录,nsqadmin 使用 HTML 模板文件来生成 Web 用户界面,这个参数可以设置这些模板文件的目录。

  • --http-address <address>

    nsqadmin 监听的 HTTP 连接的地址和端口,默认为 0.0.0.0:4171。

  • --https-address <address>

    nsqadmin 监听的 HTTPS 连接的地址和端口。

  • --nsqd-http-address <address>

    nsqadmin 负责管理 nsqd 组成的集群,那么它肯定要知道都有哪些 nsqd 节点,所以该参数负责指定 nsqadmin 可以通过 HTTP 协议连接的 nsqd 服务的地址和端口。这个参数可以指定多次,以连接多个 nsqd 服务。

  • --lookupd-http-address <address>

    设置 nsqadmin 可以通过 HTTP 协议连接的 nsqlookupd 服务的地址和端口。这个参数可以多次使用,以连接多个 nsqlookupd 服务。

  • --graphite-url <url>

    设置连接 Graphite 服务的 URL,Graphite 是一个监控工具,用于收集和展示 nsqd 的运行数据。

  • --proxy-graphite

    启用这个选项后,nsqadmin 会代理所有发送到 Graphite 的请求。

  • --notification-http-endpoint <url>

    当 nsqd 的状态发生变化时,nsqadmin 会发送 HTTP POST 请求到这个 URL,以通知用户。

以上就是一些比较常用的参数,至于其它参数可以查看文档,下面来启动 nsqadmin。

启动的时候,要指定 nsqd 服务监听的 ip 和端口,连接方式是 HTTP,所以端口是 4151。而 nsqadmin 监听的端口是 4171,所以我们访问 IP:4171 即可查看相应的 webUI。

目前还没有 topic 信息,因为目前还没有创建 topic,然后点击 Nodes 可以看到 nsqd 的信息。

目前 nsqadmin 就搭建好了,我们改成后台启动。

搭建 NSQ 集群

如果要搭建集群,那么需要额外启动 nsqlookupd,nsqlookupd 可以部署在任意个节点上,一般推荐至少三节点,启动参数如下。

  • --config <file>

    指定要加载的配置文件。

  • --http-address <address>

    nsqlookupd 监听的 HTTP 连接的地址和端口,默认为 0.0.0.0:4161。

  • --tcp-address <address>

    nsqlookupd 监听的 TCP 连接的地址和端口,默认为 0.0.0.0:4160。

# 在相应的节点上启动 nsqlookupd,监听的 TCP 端口为 4160、HTTP 端口为 4161
./nsqlookupd

启动 nsqlookupd 之后,再启动 nsqd。

# 在相应的节点上启动 nsqd,指定 nsqlookupd 的地址
# 可以通过 TCP 的方式连接,也可以通过 HTTP 的方式,这里选择 TCP
./nsqd --lookupd-tcp-address=xxx:4160 --lookupd-tcp-address=yyy:4160 ...

启动 nsqadmin。

# nsqadmin 只支持通过 HTTP 的方式连接 nsqlookupd,连接端口是 4161
./nsqadmin --lookupd-http-address=xxx:4161 --lookupd-http-address=yyy:4161 ...

nsqadmin 在连接到 nsqlookupd 之后,会自动找到已经连接到 nsqlookupd 上面的 nsqd 服务。

集群的搭建方式也是比较简单的,不过这里我们就不搭建集群了,直接使用上面已经搭建好的单机版即可。

未完待续。。。

posted @ 2023-08-04 16:05  古明地盆  阅读(305)  评论(0编辑  收藏  举报