NSQ简介

NSQ 是实时的分布式消息处理平台,其设计的目的是用来大规模地处理每天数以十亿计级别的消息。NSQ 具有分布式和去中心化拓扑结构,该结构具有无单点故障、故障容错、高可用性以及能够保证消息的可靠传递的特征,是一个成熟的、已在大规模生成环境下应用的产品。

NSQ 由 3 个守护进程组成: 
nsqd 是接收、保存和传送消息到客户端的守护进程。 
nsqlookupd 是管理的拓扑信息,维护着所有nsqd的状态,并提供了最终一致发现服务的守护进程。 
nsqadmin 是一个 Web UI 来实时监控集群和执行各种管理任务。 

这篇文章介绍主要介绍nsqd的实现。

Topic与Channel

Topic与Channel是NSQ中重要的两个概念。 
生产者将消息写到Topic中,一个Topic下可以有多个Channel,每个Channel都是Topic的完整副本。 
消费者从Channel处订阅消息,如果有多个消费者订阅同一个Channel,Channel中的消息将被传递到一个随机的消费者。

要理解Topic Channel中各种chan的作用,关键是要理解golang中如何在并发环境下如何操作一个结构体(多个goroutine同时操作topic),与C/C++多线程操作同一个结构体时加锁(mutex,rwmutex)不同,go语言中一般是为这个结构体(topic,channel)开启一个主goroutine(messagePump函数),所有对该结构体的改变的操作都应是该主goroutine完成的,也就不存在并发的问题了,其它goroutine如果想要改变这个结构体则应该向结构体提供的chan中发送消息(msgchan)或者通知(exitchan,updatechan),主goroutine会一直监听所有的chan,当有消息或者通知到来时做相应的处理。

数据的持久化

了解数据的持久化之前,我们先来看两个问题? 
1. 往Topic中写入消息就是将消息发送到Topic.memoryMsgChan中,但是memoryMsgChan是一个固定内存大小的内存队列,如果队列满了怎么办呢?会阻塞吗? 
2. 如果消息都存放在memoryMsgChan这个内存队列中,程序退出了消息就全部丢失了吗?

NSQ是如何解决的呢,nsq在创建Topic、Channel的时候都会创建一个DiskQueue,DiskQueue负责向磁盘文件中写入消息、从磁盘文件中读取消息,是NSQ实现数据持久化的最重要结构。 
以Topic为例,如果向Topic.memoryMsgChan写入消息但是memoryMsgChan已满时,nsq会将消息写到topic.DiskQueue中,DiskQueue会负责将消息内存同步到磁盘上。 
如果从Topic.memoryMsgChan中读取消息时,但是memoryMsgChan并没有消息时,就从topic.DiskQueue中取出同步到磁盘文件中的消息。

我们看到topic.backend(diskQueue)负责将消息写到磁盘并从磁盘中读取消息,diskQueue提供了两个chan供外部使用:readChan与writeChan。 
我们来看下diskQueue实现中的几个要点。

  1. diskQueue在创建时会开启一个goroutine,从磁盘文件中读取消息写到readChan中,外部goroutine可以从readChan中获取消息;随时监听writeChan,当有消息时从wirtechan中取出消息,写到本地磁盘文件。
  2. diskQueue既要提供文件的读服务又要提供文件的写服务,所以要记录下文件的读位置(readIndex),写位置(writeIndex)。每次从文件中读取消息时使用file.Seek(readindex)定位到文件读位置然后读取消息信息,每次往文件中写入消息时都要file.Seek(writeIndex)定位到写位置再将消息写入。
  3. readIndex,writeIndex很重要,程序退出时要将这些信息(meta data)写到另外的磁盘文件(元信息文件)中,程序启动时首先读取元信息文件,在根据元信息文件中的readIndex writeIndex操作存储信息的文件。
  4. 由于操作系统层也有缓存,调用file.Write()写入的信息,也可能只是存在缓存中并没有同步到磁盘,需要显示调用file.sync()才可以强制要求操作系统把缓存同步到磁盘。可以通过指定创建diskQueue时传入的syncEvery,syncTimeout来控制调用file.sync()的频率。syncTimeout是指每隔syncTimeout秒调用一次file.sync(),syncEvery是指每当写入syncEvery个消息后调用一次file.sync()。这两个参数都可以在启动nsqd程序时通过命令行指定。

网络架构

nsq是一个可靠的、高性能的服务端网络程序,通过阅读nsqd的源码来学习如何搭建一个可靠的网络服务端程序。

客户端已成功的与服务器建立链接了,每一个客户端建立连接后,nsqd都会创建一个Client接口体,该结构体内保存一些client的状态信息。 
每一个Client都会有两个goroutine,一个goroutine负责读取客户端主动发送的各种命令,解析命令,处理命令并将处理结果回复给客户端。 
另一个goutine负责定时发送心跳信息给客户端,如果客户端订阅某个channel的话则将channel中的将消息通过网络发送给客户端。

如果服务端不需要主动推送大量消息给客户端,一个连接只需要开一个goroutine处理请求并发送回复就可以了,这是最简单的方式。开启两个goroutine操作同一个conn的话就需要注意加锁了。

我们来看下NSQ中几个比较重要的命令:

  • NOP 心跳回复,没有实际意义
  • PUB 发布一个消息到 话题(topic) 
    PUB <topic_name>\n [ 四字节消息的大小 ][ 消息的内容 ]
  • SUB 订阅话题(topic) /通道(channel) 
    SUB <topic_name> <channel_name>\n
  • RDY 更新 RDY 状态 (表示客户端已经准备好接收N 消息) 
    RDY <count>\n
  • FIN 完成一个消息 (表示成功处理) 
    FIN <message_id>\n

生产者产生消息的过程比较简单,就是一个PUB命令,先读取四字节的消息大小,然后根据消息大小读取消息内容,然后将内容写到topic.MessageChan中。 
我们重点来看下消费者是如何从nsq中读取消息的。 
1. 消费者首先需要发送SUB命令,告诉nsqd它想订阅哪个Channel,然后nsqd将该Client与Channel建立对应关系。 
2. 消费者发送RDY命令,告诉服务端它以准备好接受count个消息,服务端则向消费者发送count个消息,如果消费者想继续接受消息就需要不断发送RDY命令告诉服务端自己准备好接受消息(类似TCP协议中滑动窗口的概念,消费者并不是按照顺序一个个的消费消息,NSQD最多可以同时count个消息给消费者,每推送给消费者一个消息count数目减一,当消费者处理完消息回复FIN指令时count+1)。


NSQ源码剖析之nsqd的更多相关文章

  1. NSQ源码剖析——主要结构方法和消息生产消费过程

    目录 1 概述 2 主要结构体及方法 2.1 NSQD 2.2 tcpServer 2.3 protocolV2 2.4 clientV2 2.5 Topic 2.6 channel 3 启动过程 4 ...

  2. (转)go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin

    转自:http://www.baiyuxiong.com/?p=886 ---------------------------------------------------------------- ...

  3. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  4. Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

    声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...

  5. Apache Spark源码剖析

    Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著   ISBN 978-7-121-25420- ...

  6. 基于mybatis-generator-core 1.3.5项目的修订版以及源码剖析

    项目简单说明 mybatis-generator,是根据数据库表.字段反向生成实体类等代码文件.我在国庆时候,没事剖析了mybatis-generator-core源码,写了相当详细的中文注释,可以去 ...

  7. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  8. SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现

    SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...

  9. 自己实现多线程的socket,socketserver源码剖析

    1,IO多路复用 三种多路复用的机制:select.poll.epoll 用的多的两个:select和epoll 简单的说就是:1,select和poll所有平台都支持,epoll只有linux支持2 ...

随机推荐

  1. python标准库之MultiProcessing库的研究 (1)

    MultiProcessing模块是一个优秀的类似多线程MultiThreading模块处理并发的包之前接触过一点这个库,但是并没有深入研究,这次闲着无聊就研究了一下,算是解惑吧.今天先研究下appl ...

  2. java 中 printf()语句的理解

    对print和println的理解很简单,今天突然接触到printf(),有点懵,整理了下也帮自己理一理 printf是格式化输出的形式 下在举个例子: package other; public c ...

  3. Linux笔记2

    touch 创建文件. echo  输出   >> 将输出写入到文件中   echo sss >> a.txt cat   查看文件内容 帮助命令   man  命令 man ...

  4. 开启flume的远程调试功能

    各种组件,比如tomcat.storm.flume,我们都可以通过JMX方式开启远程调试,主要可以用来跟踪源码,了解程序内部的运行机制,其次,也有利于你修改源码. 首先,本质上是要修改flume本身启 ...

  5. Linux下使用Kickstart自动化安装平台架构

    PXE工作于Client/Server的网络模式.在启动过程中,终端要求服务器分配IP地址,再用TFTP协议下载一个自动启动软件包到内存中执行. 要使用kickstart安装平台,包括完整的架构为:K ...

  6. Java 锁机制 synchronized

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/75126630 本文出自[赵彦军的博客] 1.前言 在多线程并发编程中Synchro ...

  7. NewLife.Net——管道处理器解决粘包

    Tcp网络编程,必须要解决的一个问题就是粘包,尽管解决办法有很多,这里讲一个比较简单的方法. 老规矩,先上代码:https://github.com/nnhy/NewLife.Net.Tests 一. ...

  8. Pycharm快捷键记录

    这里只记录自己用过的,记录而已 会慢慢添加进来,没有考虑分类和顺序,后期足够多了会整理 参考文章: 1. pycharm的一些快捷键 2. pycharm快捷键及一些常用设置 Ctrl+C  直接复制 ...

  9. IDEA安装教程

    1.下载安装程序A,链接:https://pan.baidu.com/s/1IAsGDbApfyNsHuS7_m0rdw 密码:fthp 2.下载一个配置程序B,下载安装之后,暂时不用管,之后会用到. ...

  10. VS2010+OpenCV3.4.1+zbar 64位

    1. OpenCV3.4.1和zbar文件夹放到指定的路径下,我把它们放在了"D:\二维码\环境"中. zbar:链接:https://pan.baidu.com/s/11eCDV ...