上一篇: Go消息中间件Nsq系列(五)------apps/nsqd源码阅读
通过此次message消息结构程序源码阅读, 可以学习到
- GUID 的生成算法了解
- ByteOrder 字节序的使用
- 数据包封装的参考实现
- 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()
}