Go消息中间件Nsq系列(六)------Message结构

上一篇: Go消息中间件Nsq系列(五)------apps/nsqd源码阅读

通过此次message消息结构程序源码阅读, 可以学习到

  1. GUID 的生成算法了解
  2. ByteOrder 字节序的使用
  3. 数据包封装的参考实现
  4. nsq中message的应用, 从client读取序列化,在channel中传递

1. Message 消息结构体定义

type Message struct {
    ID        MessageID     // 消息唯一id 16字节数组
    Body      []byte        // 消息主体内容
    Timestamp int64     // 构建消息时间
    Attempts  uint16        // 消息处理次数 

    // for in-flight handling  处理消息的时候使用以下字段
    deliveryTS time.Time     // 分发时间 time.Now()
    clientID   int64         // 用户端id
    pri        int64         // 优先级 time.Now().Add(timeout).UnixNano()
    index      int           // 索引
    deferred   time.Duration // 消息的延时
}

2 消息 数据包 定义协议

// decodeMessage deserializes data (as []byte) and creates a new Message
// message format:
// [x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x][x]...
// |       (int64)        ||    ||      (hex string encoded in ASCII)           || (binary)
// |       8-byte         ||    ||                 16-byte                      || N-byte
// ------------------------------------------------------------------------------------------...
//   nanosecond timestamp    ^^                   message ID                       message body
//                        (uint16)
//                         2-byte
//                        attempts
title nanosecond timestamp attempts message ID message body
描述 时间戳ns 处理次数 消息全局唯一id 消息体
所占字节 8-byte 2-byte 16-byte N-byte

3 Message 数据包存储方式

采用大字节序(Byte Order)进行数据包的处理. 即大端模式:(从高字节到低字节)
按照从低地址到高地址的顺序存放数据的高位字节到低位字节

// 277 对应二进制 100010101
b := []byte{0,0,1,21}  // 使用大端 从高到低
b1 := []byte{21,1,0,0} // 使用小端 从低到高

// A ByteOrder specifies how to convert byte sequences into
// 16-, 32-, or 64-bit unsigned integers.
type ByteOrder interface {
    Uint16([]byte) uint16  // 获取16/32/64位
    Uint32([]byte) uint32  
    Uint64([]byte) uint64 
    PutUint16([]byte, uint16) // 10进制转为大/小字节序byte
    PutUint32([]byte, uint32)
    PutUint64([]byte, uint64)
    String() string  // 打印LittleEndian/BigEndian
}
// 大字节序实现
var LittleEndian littleEndian
// 小字节序实现
var BigEndian bigEndian

4. MessageID的构造

messageID的获取是通过NewGUIDFactory(node-id).NewGUID()生成的全局唯一ID
messageID是64位的, 也就是8字节
生成算法参考Blake Mizerany's noeqd 间接参考 Twitter's snowflake GUID生成算法


const (
    // 10位表示节点id, 最大2^{10} 部署1024个节点
    nodeIDBits     = uint64(10)
    // 12位,序列号,用来记录同毫秒内产生的不同id。
    // 表示同一机器同一时间截(毫秒)内产生的不超过4096个序列号
    sequenceBits   = uint64(12)

    // 节点左移12位
    nodeIDShift    = sequenceBits
    // 时间戳左移22位
    timestampShift = sequenceBits + nodeIDBits

    // sequenceMask 通过位与运算保证计算的结果范围4096  防止溢出
    sequenceMask   = int64(-1) ^ (int64(-1) << sequenceBits)

    // 自定义纪元
    // ( 2012-10-28 16:23:42 UTC ).UnixNano() >> 20
    twepoch = int64(1288834974288)
)

var ErrTimeBackwards = errors.New("time has gone backwards")
var ErrSequenceExpired = errors.New("sequence expired")
var ErrIDBackwards = errors.New("ID went backward")

type guid int64

// 生成算法参考Blake Mizerany's `noeqd` 间接参考 Twitter's `snowflake` GUID生成算法
//  构成的概念workerID(特定工作线程) + datacenterId(家庭计算机系统的设备) 作为特定标识符
type guidFactory struct {
    sync.Mutex

    nodeID        int64
    sequence      int64
    lastTimestamp int64
    lastID        guid
}

func NewGUIDFactory(nodeID int64) *guidFactory {
    return &guidFactory{
        nodeID: nodeID,
    }
}

func (f *guidFactory) NewGUID() (guid, error) {
    // 枷锁
    f.Lock()

    // divide by 1048576, giving pseudo-milliseconds
    // 右移20位即除以2^20, 获取一个伪时间戳  毫秒值
    ts := time.Now().UnixNano() >> 20

    // 小于最后一个时间戳, 则时间发生错误, 倒退了
    if ts < f.lastTimestamp {
        f.Unlock()
        return 0, ErrTimeBackwards
    }
    // 同一毫秒, 如果时间戳相等, 避免重复  最大4096个
    if f.lastTimestamp == ts {
        // 序号加1与mask 进行位与运算 防止溢出
        f.sequence = (f.sequence + 1) & sequenceMask
        // 过期了
        if f.sequence == 0 {
            f.Unlock()
            return 0, ErrSequenceExpired
        }
    } else {
        f.sequence = 0
    }

    f.lastTimestamp = ts

    // (ts - twepoch)算出偏移量
    // ((int64(id) >>timestampShift))+twepoch 反计算出时间戳ns 在 << 20 /1e6 获取ms
    id := guid(((ts - twepoch) << timestampShift) |
        (f.nodeID << nodeIDShift) |
        f.sequence)

    // 防止id出现倒退 即小于最后一次生成id
    if id <= f.lastID {
        f.Unlock()
        return 0, ErrIDBackwards
    }

    f.lastID = id

    f.Unlock()

    return id, nil
}
// 通过hex.Encode 转为16进制 messageID
func (g guid) Hex() MessageID {
    // ... 省略 g 位移动作
    hex.Encode(h[:], b[:])
    return h
}

// guidFactory NewGUIDFactory() 需要传入的节点id, 也就是
nodeIDBits = uint64(10)

// node-id 节点id不能超过1024 
if opts.ID < 0 || opts.ID >= 1024 {
        return nil, errors.New("--node-id must be [0,1024)")
    }

5. Message 编解码

// message 编码写入buffer
func (m *Message) WriteTo(w io.Writer) (int64, error) {
    var buf [10]byte
    var total int64 // 统计发送数据长度

    //获取 大字节序 8字节时间戳
    binary.BigEndian.PutUint64(buf[:8], uint64(m.Timestamp))
    //获取 大字节序 2字节 消息处理次数
    binary.BigEndian.PutUint16(buf[8:10], uint16(m.Attempts))

    // 把大字节序的拼接数据写出
    n, err := w.Write(buf[:])
    total += int64(n)
    if err != nil {
        return total, err
    }
    // 继续写入消息id
    n, err = w.Write(m.ID[:])
    total += int64(n)
    if err != nil {
        return total, err
    }
    // 写入 消息主体内容
    n, err = w.Write(m.Body)
    total += int64(n)
    if err != nil {
        return total, err
    }

    return total, nil
}


// message 解码
func decodeMessage(b []byte) (*Message, error) {
    var msg Message

    if len(b) < minValidMsgLength {
        return nil, fmt.Errorf("invalid message buffer size (%d)", len(b))
    }
    // 获取时间戳
    msg.Timestamp = int64(binary.BigEndian.Uint64(b[:8]))
    // 获取消息处理次数
    msg.Attempts = binary.BigEndian.Uint16(b[8:10])
    // 获取16位msg guid
    copy(msg.ID[:], b[10:10+MsgIDLength])
    // 获取消息主体
    msg.Body = b[10+MsgIDLength:]

    return &msg, nil
}
// 写到buffer 然后写入channel 在序列化到disk
func writeMessageToBackend(buf *bytes.Buffer, msg *Message, bq BackendQueue) error {
    buf.Reset()
    _, err := msg.WriteTo(buf)
    if err != nil {
        return err
    }
    return bq.Put(buf.Bytes())
}

自此 message分析完毕.

使用实例:

func (t *Topic) GenerateID() MessageID {
retry:
    id, err := t.idFactory.NewGUID()
    if err != nil {
        time.Sleep(time.Millisecond)
        goto retry
    }
    return id.Hex()
}

你可能感兴趣的:(Go消息中间件Nsq系列(六)------Message结构)