package nsqd

import (
    "bytes"
    "container/heap"
    "errors"
    "math"
    "strings"
    "sync"
    "sync/atomic"
    "time"

    "github.com/nsqio/nsq/internal/pqueue"
    "github.com/nsqio/nsq/internal/quantile"
)

type Consumer interface {
    UnPause()
    Pause()
    Close() error
    TimedOutMessage()
    Stats() ClientStats
    Empty()
}

// Channel represents the concrete type for a NSQ channel (and also
// implements the Queue interface)
//
// There can be multiple channels per topic, each with there own unique set
// of subscribers (clients).
//
// Channels maintain all client and message metadata, orchestrating in-flight
// messages, timeouts, requeuing, etc.
type Channel struct {
    // 64bit atomic vars need to be first for proper alignment on 32bit platforms
    requeueCount uint64
    messageCount uint64
    timeoutCount uint64

    sync.RWMutex

    topicName string
    name      string
    ctx       *context

    backend BackendQueue

    memoryMsgChan chan *Message
    exitFlag      int32
    exitMutex     sync.RWMutex

    // state tracking
    clients        map[int64]Consumer
    paused         int32
    ephemeral      bool
    deleteCallback func(*Channel)
    deleter        sync.Once

    // Stats tracking
    e2eProcessingLatencyStream *quantile.Quantile

    // TODO: these can be DRYd up
    deferredMessages map[MessageID]*pqueue.Item
    deferredPQ       pqueue.PriorityQueue
    deferredMutex    sync.Mutex
    inFlightMessages map[MessageID]*Message
    inFlightPQ       inFlightPqueue
    inFlightMutex    sync.Mutex
}

// NewChannel creates a new instance of the Channel type and returns a pointer
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),
        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)
    }

    c.ctx.nsqd.Notify(c)

    return c
}

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()
}

// Exiting returns a boolean indicating if this channel is closed/exiting
func (c *Channel) Exiting() bool {
    return atomic.LoadInt32(&c.exitFlag) == 1
}

// Delete empties the channel and closes
func (c *Channel) Delete() error {
    return c.exit(true)
}

// Close cleanly closes the Channel
func (c *Channel) Close() error {
    return c.exit(false)
}

func (c *Channel) exit(deleted bool) error {
    c.exitMutex.Lock()
    defer c.exitMutex.Unlock()

    if !atomic.CompareAndSwapInt32(&c.exitFlag, 0, 1) {
        return errors.New("exiting")
    }

    if deleted {
        c.ctx.nsqd.logf("CHANNEL(%s): deleting", c.name)

        // since we are explicitly deleting a channel (not just at system exit time)
        // de-register this from the lookupd
        c.ctx.nsqd.Notify(c)
    } else {
        c.ctx.nsqd.logf("CHANNEL(%s): closing", c.name)
    }

    // this forceably closes client connections
    c.RLock()
    for _, client := range c.clients {
        client.Close()
    }
    c.RUnlock()

    if deleted {
        // empty the queue (deletes the backend files, too)
        c.Empty()
        return c.backend.Delete()
    }

    // write anything leftover to disk
    c.flush()
    return c.backend.Close()
}

func (c *Channel) Empty() error {
    c.Lock()
    defer c.Unlock()

    c.initPQ()
    for _, client := range c.clients {
        client.Empty()
    }

    for {
        select {
        case <-c.memoryMsgChan:
        default:
            goto finish
        }
    }

finish:
    return c.backend.Empty()
}

// flush persists all the messages in internal memory buffers to the backend
// it does not drain inflight/deferred because it is only called in Close()
func (c *Channel) flush() error {
    var msgBuf bytes.Buffer

    if len(c.memoryMsgChan) > 0 || len(c.inFlightMessages) > 0 || len(c.deferredMessages) > 0 {
        c.ctx.nsqd.logf("CHANNEL(%s): flushing %d memory %d in-flight %d deferred messages to backend",
            c.name, len(c.memoryMsgChan), len(c.inFlightMessages), len(c.deferredMessages))
    }

    for {
        select {
        case msg := <-c.memoryMsgChan:
            err := writeMessageToBackend(&msgBuf, msg, c.backend)
            if err != nil {
                c.ctx.nsqd.logf("ERROR: failed to write message to backend - %s", err)
            }
        default:
            goto finish
        }
    }

finish:
    for _, msg := range c.inFlightMessages {
        err := writeMessageToBackend(&msgBuf, msg, c.backend)
        if err != nil {
            c.ctx.nsqd.logf("ERROR: failed to write message to backend - %s", err)
        }
    }

    for _, item := range c.deferredMessages {
        msg := item.Value.(*Message)
        err := writeMessageToBackend(&msgBuf, msg, c.backend)
        if err != nil {
            c.ctx.nsqd.logf("ERROR: failed to write message to backend - %s", err)
        }
    }

    return nil
}

func (c *Channel) Depth() int64 {
    return int64(len(c.memoryMsgChan)) + c.backend.Depth()
}

func (c *Channel) Pause() error {
    return c.doPause(true)
}

func (c *Channel) UnPause() error {
    return c.doPause(false)
}

func (c *Channel) doPause(pause bool) error {
    if pause {
        atomic.StoreInt32(&c.paused, 1)
    } else {
        atomic.StoreInt32(&c.paused, 0)
    }

    c.RLock()
    for _, client := range c.clients {
        if pause {
            client.Pause()
        } else {
            client.UnPause()
        }
    }
    c.RUnlock()
    return nil
}

func (c *Channel) IsPaused() bool {
    return atomic.LoadInt32(&c.paused) == 1
}

// 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)
    return nil
}

func (c *Channel) put(m *Message) error {
    select {
    case c.memoryMsgChan <- m:
    default:
        b := bufferPoolGet()
        err := writeMessageToBackend(b, m, c.backend)
        bufferPoolPut(b)
        c.ctx.nsqd.SetHealth(err)
        if err != nil {
            c.ctx.nsqd.logf("CHANNEL(%s) ERROR: failed to write message to backend - %s",
                c.name, err)
            return err
        }
    }
    return nil
}

// TouchMessage resets the timeout for an in-flight message
func (c *Channel) TouchMessage(clientID int64, id MessageID, clientMsgTimeout time.Duration) error {
    msg, err := c.popInFlightMessage(clientID, id)
    if err != nil {
        return err
    }
    c.removeFromInFlightPQ(msg)

    newTimeout := time.Now().Add(clientMsgTimeout)
    if newTimeout.Sub(msg.deliveryTS) >=
        c.ctx.nsqd.getOpts().MaxMsgTimeout {
        // we would have gone over, set to the max
        newTimeout = msg.deliveryTS.Add(c.ctx.nsqd.getOpts().MaxMsgTimeout)
    }

    msg.pri = newTimeout.UnixNano()
    err = c.pushInFlightMessage(msg)
    if err != nil {
        return err
    }
    c.addToInFlightPQ(msg)
    return nil
}

// FinishMessage successfully discards an in-flight message
func (c *Channel) FinishMessage(clientID int64, id MessageID) error {
    msg, err := c.popInFlightMessage(clientID, id)
    if err != nil {
        return err
    }
    c.removeFromInFlightPQ(msg)
    if c.e2eProcessingLatencyStream != nil {
        c.e2eProcessingLatencyStream.Insert(msg.Timestamp)
    }
    return nil
}

// RequeueMessage requeues a message based on `time.Duration`, ie:
//
// `timeoutMs` == 0 - requeue a message immediately
// `timeoutMs`  > 0 - asynchronously wait for the specified timeout
//     and requeue a message (aka "deferred requeue")
//
func (c *Channel) RequeueMessage(clientID int64, id MessageID, timeout time.Duration) error {
    // remove from inflight first
    msg, err := c.popInFlightMessage(clientID, id)
    if err != nil {
        return err
    }
    c.removeFromInFlightPQ(msg)

    if timeout == 0 {
        c.exitMutex.RLock()
        err := c.doRequeue(msg)
        c.exitMutex.RUnlock()
        return err
    }

    // deferred requeue
    return c.StartDeferredTimeout(msg, timeout)
}

// AddClient adds a client to the Channel's client list
func (c *Channel) AddClient(clientID int64, client Consumer) {
    c.Lock()
    defer c.Unlock()

    _, ok := c.clients[clientID]
    if ok {
        return
    }
    c.clients[clientID] = client
}

// RemoveClient removes a client from the Channel's client list
func (c *Channel) RemoveClient(clientID int64) {
    c.Lock()
    defer c.Unlock()

    _, ok := c.clients[clientID]
    if !ok {
        return
    }
    delete(c.clients, clientID)

    if len(c.clients) == 0 && c.ephemeral == true {
        go c.deleter.Do(func() { c.deleteCallback(c) })
    }
}

func (c *Channel) StartInFlightTimeout(msg *Message, clientID int64, timeout time.Duration) error {
    now := time.Now()
    msg.clientID = clientID
    msg.deliveryTS = now
    msg.pri = now.Add(timeout).UnixNano()
    err := c.pushInFlightMessage(msg)
    if err != nil {
        return err
    }
    c.addToInFlightPQ(msg)
    return nil
}

func (c *Channel) StartDeferredTimeout(msg *Message, timeout time.Duration) error {
    absTs := time.Now().Add(timeout).UnixNano()
    item := &pqueue.Item{Value: msg, Priority: absTs}
    err := c.pushDeferredMessage(item)
    if err != nil {
        return err
    }
    c.addToDeferredPQ(item)
    return nil
}

// doRequeue performs the low level operations to requeue a message
//
// Callers of this method need to ensure that a simultaneous exit will not occur
func (c *Channel) doRequeue(m *Message) error {
    err := c.put(m)
    if err != nil {
        return err
    }
    atomic.AddUint64(&c.requeueCount, 1)
    return nil
}

// pushInFlightMessage atomically adds a message to the in-flight dictionary
func (c *Channel) pushInFlightMessage(msg *Message) error {
    c.inFlightMutex.Lock()
    _, ok := c.inFlightMessages[msg.ID]
    if ok {
        c.inFlightMutex.Unlock()
        return errors.New("ID already in flight")
    }
    c.inFlightMessages[msg.ID] = msg
    c.inFlightMutex.Unlock()
    return nil
}

// popInFlightMessage atomically removes a message from the in-flight dictionary
func (c *Channel) popInFlightMessage(clientID int64, id MessageID) (*Message, error) {
    c.inFlightMutex.Lock()
    msg, ok := c.inFlightMessages[id]
    if !ok {
        c.inFlightMutex.Unlock()
        return nil, errors.New("ID not in flight")
    }
    if msg.clientID != clientID {
        c.inFlightMutex.Unlock()
        return nil, errors.New("client does not own message")
    }
    delete(c.inFlightMessages, id)
    c.inFlightMutex.Unlock()
    return msg, nil
}

func (c *Channel) addToInFlightPQ(msg *Message) {
    c.inFlightMutex.Lock()
    c.inFlightPQ.Push(msg)
    c.inFlightMutex.Unlock()
}

func (c *Channel) removeFromInFlightPQ(msg *Message) {
    c.inFlightMutex.Lock()
    if msg.index == -1 {
        // this item has already been popped off the pqueue
        c.inFlightMutex.Unlock()
        return
    }
    c.inFlightPQ.Remove(msg.index)
    c.inFlightMutex.Unlock()
}

func (c *Channel) pushDeferredMessage(item *pqueue.Item) error {
    c.deferredMutex.Lock()
    // TODO: these map lookups are costly
    id := item.Value.(*Message).ID
    _, ok := c.deferredMessages[id]
    if ok {
        c.deferredMutex.Unlock()
        return errors.New("ID already deferred")
    }
    c.deferredMessages[id] = item
    c.deferredMutex.Unlock()
    return nil
}

func (c *Channel) popDeferredMessage(id MessageID) (*pqueue.Item, error) {
    c.deferredMutex.Lock()
    // TODO: these map lookups are costly
    item, ok := c.deferredMessages[id]
    if !ok {
        c.deferredMutex.Unlock()
        return nil, errors.New("ID not deferred")
    }
    delete(c.deferredMessages, id)
    c.deferredMutex.Unlock()
    return item, nil
}

func (c *Channel) addToDeferredPQ(item *pqueue.Item) {
    c.deferredMutex.Lock()
    heap.Push(&c.deferredPQ, item)
    c.deferredMutex.Unlock()
}

func (c *Channel) processDeferredQueue(t int64) bool {
    c.exitMutex.RLock()
    defer c.exitMutex.RUnlock()

    if c.Exiting() {
        return false
    }

    dirty := false
    for {
        c.deferredMutex.Lock()
        item, _ := c.deferredPQ.PeekAndShift(t)
        c.deferredMutex.Unlock()

        if item == nil {
            goto exit
        }
        dirty = true

        msg := item.Value.(*Message)
        _, err := c.popDeferredMessage(msg.ID)
        if err != nil {
            goto exit
        }
        c.doRequeue(msg)
    }

exit:
    return dirty
}

func (c *Channel) processInFlightQueue(t int64) bool {
    c.exitMutex.RLock()
    defer c.exitMutex.RUnlock()

    if c.Exiting() {
        return false
    }

    dirty := false
    for {
        c.inFlightMutex.Lock()
        msg, _ := c.inFlightPQ.PeekAndShift(t)
        c.inFlightMutex.Unlock()

        if msg == nil {
            goto exit
        }
        dirty = true

        _, err := c.popInFlightMessage(msg.clientID, msg.ID)
        if err != nil {
            goto exit
        }
        atomic.AddUint64(&c.timeoutCount, 1)
        c.RLock()
        client, ok := c.clients[msg.clientID]
        c.RUnlock()
        if ok {
            client.TimedOutMessage()
        }
        c.doRequeue(msg)
    }

exit:
    return dirty
}

channel.go的更多相关文章

  1. Golang, 以17个简短代码片段,切底弄懂 channel 基础

    (原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程.管道等高并发编程知识 ...

  2. TODO:Go语言goroutine和channel使用

    TODO:Go语言goroutine和channel使用 goroutine是Go语言中的轻量级线程实现,由Go语言运行时(runtime)管理.使用的时候在函数前面加"go"这个 ...

  3. GO语言之channel

    前言: 初识go语言不到半年,我是一次偶然的机会认识了golang这门语言,看到他简洁的语法风格和强大的语言特性,瞬间有了学习他的兴趣.我是很看好go这样的语言的,一方面因为他有谷歌主推,另一方面他确 ...

  4. Critical: Update Your Windows Secure Channel (cve-2014-6321,MS14-066)

    前言:风雨欲来山满楼,下半年开始各种凶猛的漏洞层出不穷,天下已经不太平,互联网已经进入一个新的台阶 0x01 cve-2014-6321 11月的补丁月,微软请windows的用户吃了顿大餐,发布了1 ...

  5. JAVA NIO Channel

    Basic:   多数通道都是链接到开发的文件描述符的.Channel类提供维持平台独立性的抽象过程.   通道是一种途径,访问和操作操作系统,缓冲区是数据操作点: Channel类继承结构图: 通过 ...

  6. channel Golang

    Golang, 以17个简短代码片段,切底弄懂 channel 基础 (原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的 ...

  7. go channel

    channel 是go语言中不同goroutine之间通信一种方法 //定义一个channel c := make(chan bool) //向channel中写入 c <- true //读取 ...

  8. [bigdata] flume file channel CPU消耗比 memory channel高的原因

    https://www.quora.com/Why-does-flume-take-more-resource-CPU-when-file-channel-is-used-compared-to-wh ...

  9. 图解Netty之Pipeline、channel、Context之间的数据流向。

    声明:本文为原创博文,禁止转载.       以下所绘制图形均基于Netty4.0.28版本. 一.connect(outbound类型事件)  当用户调用channel的connect时,会发起一个 ...

  10. go:channel(未完)

    注:1)以下的所有讨论建立在包含整形元素的通道类型之上,即 chan int 2)对于“<-”我的理解是,它可能是一个操作符(接收操作符),也  可能是类型的一部分(如“chan<- in ...

随机推荐

  1. Java Socket:Java-NIO-Selector

    Selector 的出现,大大改善了多个 Java Socket的效率.在没有NIO的时候,轮询多个socket是通过read阻塞来完成,即使是非阻塞模式,我们在轮询socket是否就绪的时候依然需要 ...

  2. ELF 文件 动态连接 - 延迟绑定(PLT)

    PLT 全称:Procedure Linkage Table ,直译:过程连接表 由于在动态连接中,程序的模块之间包含了大量的函数引用,所以在程序开始执行前,动态链接会耗费较多的时间用于模块之间函数引 ...

  3. css左侧固定宽度右侧自适应

    左侧固定宽,右侧自适应屏幕宽: 左右两列,等高布局: 左右两列要求有最小高度,例如:200px;(当内容超出200时,会自动以等高的方式增高) 要求不用JS或CSS行为实现: 仔细分析试题要求,要达到 ...

  4. CKEditor5 + ckfinder3(php)

    CKEditor5资源下载,这里我们选择ckeditor5-build-classic下载: https://ckeditor.com/ckeditor-5-builds/download/ ckfi ...

  5. Servlet 学习总结

    Servlet资料整理[很全很强大] 分类: J2EE2009-10-23 00:51 671人阅读 评论(0) 收藏 举报 servletsessionstring服务器initialization ...

  6. Day4_装饰器

    装饰器: #模板def auth(func): def wrapper(*args,**kwargs): res=func(*args,**kwargs) return res return wrap ...

  7. 关于Python的那些话

    1.第一个选择:版本2还是3,我选择2,保守谨慎,3的成熟周期会很长2.三种基本的文本操作:     2.1.解析数据并将数据反序列化到程序的数据结构中     2.2.将数据以某种方式转化为另一种相 ...

  8. C++的拷贝构造函数、operator=运算符重载,深拷贝和浅拷贝、explicit关键字

    原文地址:https://blog.csdn.net/shine_journey/article/details/53081523 1.在C++编码过程中,类的创建十分频繁. 简单的功能,当然不用考虑 ...

  9. ZeroMQ 教程 002 : 高级技巧

    本文主要译自 zguide - chapter two. 但并不是照本翻译. 上一章我们简单的介绍了一个ZMQ, 并给出了三个套路的例子: 请求-回应, 订阅-发布, 流水线(分治). 这一章, 我们 ...

  10. cocos2d-x 欢乐捕鱼游戏总结

    这几天一直都在做一个捕鱼游戏Demo,大概花掉了我快一个礼拜的时间.游戏主体是使用的cocos2d-x高级开发教程里面提供的小部分框架基本功能.然后自己加入所有的UI元素和玩法.变成了一个体验不错的捕 ...