nsq源码阅读笔记之nsqd(四)——Channel

本文详细介绍了nsqd中的Channel,作为消费者订阅Topic的抽象,Channel负责消息的分发和处理。Channel在创建时初始化相关数据结构,并在消息进入时通过消息循环进行处理。Channel维护inFlight和deferred两个队列,用于管理和重试消息投递。在消息投递成功或失败后,Channel有相应的处理机制,包括消息超时和延迟投递。此外,还探讨了Channel如何处理消费者的反馈以及消息重试和超时重置策略。
摘要由CSDN通过智能技术生成

本博客与RayXXZhang的博客保持同步更新,转载请注明来自RayXXZhang的博客-nsq源码阅读笔记之nsqd(四)——Channel

Channel相关的代码主要位于nsqd/channel.go, nsqd/nsqd.go中。

Channel与Topic的关系

Channel是消费者订阅特定Topic的一种抽象。对于发往Topic的消息,nsqd向该Topic下的所有Channel投递消息,而同一个Channel只投递一次,Channel下如果存在多个消费者,则随机选择一个消费者做投递。这种投递方式可以被用作消费者负载均衡。

Channel从属于特定Topic,可以认为是Topic的下一级。在同一个Topic之下可以有零个或多个Channel。
和Topic一样,Channel同样有永久和临时之分,永久的Channel只能通过显式删除销毁,临时的Channel在最后一个消费者断开连接的时候被销毁。

与服务于生产者的Topic不同,Channel直接面向消费者。

生产者 -> 消息 -> Topic -> Channel -> 消费者1
                       -> Channel2 -> 消费者2
                       -> Channel3 -> 消费者3

在代码上Channel和Topic有许多相似之处,对于和Topic相同或者相似的部分,以下不再赘述,可以参考Topic相关博文。

Channel的创建

func NewChannel(topicName string, channelName string, ctx *context,
    deleteCallback func(*Channel)) *Channel {

    c := &Channel{
        topicName:      topicName,
        name:           channelName,
        memoryMsgChan:  make(chan *Message, ctx.nsqd.getOpts().MemQueueSize),
        clientMsgChan:  make(chan *Message),
        exitChan:       make(chan int),
        clients:        make(map[int64]Consumer),
        deleteCallback: deleteCallback,
        ctx:            ctx,
    }
    if len(ctx.nsqd.getOpts().E2EProcessingLatencyPercentiles) > 0 {
        c.e2eProcessingLatencyStream = quantile.New(
            ctx.nsqd.getOpts().E2EProcessingLatencyWindowTime,
            ctx.nsqd.getOpts().E2EProcessingLatencyPercentiles,
        )
    }

    c.initPQ()

    if strings.HasSuffix(channelName, "#ephemeral") {
        c.ephemeral = true
        c.backend = newDummyBackendQueue()
    } else {
        // backend names, for uniqueness, automatically include the topic...
        backendName := getBackendName(topicName, channelName)
        c.backend = newDiskQueue(backendName,
            ctx.nsqd.getOpts().DataPath,
            ctx.nsqd.getOpts().MaxBytesPerFile,
            int32(minValidMsgLength),
            int32(ctx.nsqd.getOpts().MaxMsgSize)+minValidMsgLength,
            ctx.nsqd.getOpts().SyncEvery,
            ctx.nsqd.getOpts().SyncTimeout,
            ctx.nsqd.getOpts().Logger)
    }

    go c.messagePump()

    c.ctx.nsqd.Notify(c)

    return c
}

Channel和Topic在创建的时候都会初始化结构,初始化backend,创建消息循环,不同的是Channel在创建时多了给e2eProcessingLatencyStream赋值的以及initPQ部分。

其中e2eProcessingLatencyStream主要用于统计消息投递的延迟等,将在以后的博文中叙述。

func (c *Channel) initPQ() {
    pqSize := int(math.Max(1, float64(c.ctx.nsqd.getOpts().MemQueueSize)/10))

    c.inFlightMessages = make(map[MessageID]*Message)
    c.deferredMessages = make(map[MessageID]*pqueue.Item)

    c.inFlightMutex.Lock()
    c.inFlightPQ = newInFlightPqueue(pqSize)
    c.inFlightMutex.Unlock()

    c.deferredMutex.Lock()
    c.deferredPQ = pqueue.New(pqSize)
    c.deferredMutex.Unlock()
}

initPQ函数创建了两个字典inFlightMessagesdeferredMessages和两个队列inFlightPQdeferredPQ。在nsq中inFlight指的是正在投递但还没确认投递成功的消息,defferred指的是投递失败,等待重新投递的消息。initPQ创建的字典和队列主要用于索引和存放这两类消息。其中两个字典使用消息ID作索引。

inFlightPQ使用newInFlightPqueue初始化,InFlightPqueue位于nsqd\in_flight_pqueue.gonsqd\in_flight_pqueue.go是nsq实现的一个优先级队列,提供了常用的队列操作,值得学习。

deferredPQ使用pqueue.New初始化,pqueue位于nsqd\pqueue.go,也是一个优先级队列。

待投递消息进入Channel

在分析Topic时提到,消息进入Topic的消息循环后会被投递到该Topic下所有的Channel,由Channel的PutMessage函数进行处理。

// PutMessage writes a Message to the queue
func (c *Channel) PutMessage(m *Message) error {
    c.RLock()
    defer c.RUnlock()
    if atomic.LoadInt32(&c.exitFlag) == 1 {
        return errors.New("exiting")
    }
    err := c.put(m)
    if err != nil {
        return err
    }
    atomic.AddUint64(&c.messageCount, 1)
    
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值