nsq问题解答

消费者如何通过lookupd找到话题

nsqd在发起时,必须绑定到一个lookupd:

$ nsqd --lookupd-tcp-address=127.0.0.1:4160
1

也就说,消费者必须提前知道,他要的话题,被投递到了哪个nsqd上,可以通过对应的lookupd获取到该消息

3 nsq怎么解决重复投递的问题

很遗憾,nsq不能解决重复投递问题,需要客户端自己去处理。可以参考数据库唯一键,redis SETNX。不过前提是,消息本身具备一个唯一性的id

nsq怎么解决执行顺序

很遗憾,nsq同样不保证消息顺序。那么消费者如果有顺序需求,怎么办呢? 解决方案: 标记消息权重,使用redis排行榜机制(zadd-添加,zrange-列表,zcard-长度,复杂度logN)

  • 在消息中,增加标记头,头部包含以下信息:

{
    "total_number": 10,
    "number":1
}
1234
  • 消费者在消费时

// 自带去重,不怕重复消费
conn.Do("ZADD", "话题名", number, "消息")

// 插入后检查长度
length ,_ := redis.Int64(conn.Do("ZCARD", "话题名"))

if length == total_number {
    messagss, _ := redis.Bytes(conn.Do("ZRANGE", "话题名",0, -1))
    // 按照messages顺序消费完。
    for i,v:=range message{
        handle(v)        
    }
}
// 最后,设置定期销毁该定序队列
// 该步不可忽视,因为可能出现单个消息重复投递,如果在消费完10个数据后,重复数据到达消费者这里,
// 那么该话题就会永久保留一个多余的消息,不会失效。
conn.Do("Expire", "话题名", 60 *60 * 24)

nsqd挂了,消息会丢失吗?

默认配置下,会丢失,但是nsqd是可以配置成持久化的。

nsqd --lookupd-tcp-address=127.0.0.1:4160 --mem-queue-size 0
1

mem-queue-size设置成0后,则都会持久化,重启不会造成丢失。

工作方式总结

  • 生产者 生产者需要指定将消息写入哪个实例nsqd 当一个nsqd实例宕机时,生产者可以选择将消息写到其他的实例 生产者也可以将同一条消息写入两个nsqd(HA)

  • nsqd 消息不会在nsqd之间传递,生产者把消息写到哪个nsqd就只能在该nsqd消费 不同的nsqd可以接收同一个生产者的相同的消息,参考生产者的说明 nsqd会将自己的服务信息广播给集群内的nsqlookupd

  • 服务发现nsqlookupd nsqlookupd与集群内的所有nsqd建立连接,检测实例的状态,并接收实例广播过来的服务注册 nsqlookupd接收消费者客户端的服务发现请求,将对应的实例返回给消费者(这里可以是多个实例) 一个集群可以有多个nsqlookupd

  • 消费者comsumer 消费者可以与nsqd直连,但为了防止单点故障,不应该有这种固定的关系 官方推荐走服务发现nsqlookupd,参考生产者的说明,当一个nsqd宕机时,生产者可以将消息写入其他的nsqd,或者为了HA,生产者可以双写

支撑系统高可用的方案:

- 当一个nsqd宕机时,这台机上尚未消费且尚未落盘的消息会丢失;集群本身不提供副本和分片
- 鉴于上一条,对于幂等且不可丢失的消息,生产者可以选择双写,一条消息同时写两个实例
- 当正在写的nsqd宕机时,生产者可以选择写入其他的nsqd。前提是消费者通过nsqlookupd完成服务发现,及时感知集群的变化;或者消费者可以同时连上集群内所有的Nsqd实例
- 生产者可以做一定的负载均衡,将消息分散生产到不同的nsqd中

总结

NSQ基本核心就是简单性,是一个简单的队列,这意味着它很容易进行故障推理和很容易发现bug。消费者可以自行处理故障事件而不会影响系统剩下的其余部分。

事实上,简单性是我们决定使用NSQ的首要因素,这方便与我们的许多其他软件一起维护,通过引入队列使我们得到了堪称完美的表现,通过队列甚至让我们增加了几个数量级的吞吐量。越来越多的consumer需要一套严格可靠性和顺序性保障,这已经超过了NSQ提供的简单功能。

对于相对比较敏感的消息,无法容忍某个nsqd宕机,或者磁盘无法使用的情况,简单性和可靠性似乎不能完全满足。就需要其他更适合业务的mq

参考文章

Last updated