GUID
    
    
      Posted on 
      
    
  
  
    在点外卖或者网上购物的时候,每个订单中都会有一串独一无二的数字,但是处理订单的服务器肯定不止一台,如何保证多台服务器,也就是说一个计算机集群中,在相互独立的情况下(不沟通自己生成的订单号的信息)不生成相同的订单号码呢,下面我们就来介绍一种全球唯一标识符( GUID )的生成算法。
这里我们的 GUID 共64比特。sequence 部分占12比特,nodeID 占10比特,偏移10比特,时间戳 timestamp 占剩余的42比特,偏移22比特。
| 12
 3
 
 | +-------------------------------------------+----------+------------+|                timestamp                  |nodeIDBits|sequenceBits|
 +-------------------------------------------+----------+------------+
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | const (nodeIDBits = uint64(10)
 sequenceBits = uint64(12)
 nodeIDShift = sequenceBits
 timestampShift = sequenceBits + nodeIDBits
 sequenceMask   = int64(-1) ^ (int64(-1) << sequenceBits)
 
 twepoch = int64(1288834974288)
 )
 
 | 
| 12
 3
 4
 5
 
 | var (ErrIDBackwards = errors.New("ID went backward")
 ErrTimeBackwards = errors.New("time has gone backwards")
 ErrSequenceExpired = errors.New("sequence expired")
 )
 
 | 
下面我们定义一个生成 guid 的工厂, guid 由 timestamp  ,nodeID 和 sequence 这三个部分构成,每个工厂有自己的 nodeID。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | type guid int64
 type guidFactory struct {
 sync.Metex
 nodeID        int64
 sequence      int64
 lastTimestamp int64
 lastID        guid
 }
 
 func NewGUIDFactory(nodeID int64) *guidFactory {
 return &guidFactory{
 nodeID: nodeID,
 }
 }
 
 | 
在利用工厂模式生成 GUID 时,首先计算当前时间戳,并且除以1048576(2的20次方),这样是为了去除时间的假随机部分,如果新生成的timestamp 小于 lastTimestamp ,则返回 ErrTimeBackwards 错误,对于相同的时间戳,我们的 sequence 采取递增策略,如果 sequence 达到最大值后归0,则返回 ErrSequenceExpired 错误,如果时间戳不相同,则令 sequence 重新从0开始,最后使三部分拼接组成一个新的 guid ,如果生成的 guid 小于 lastID ,则返回 ErrIDBackwards 错误,如果正常则返回结果。整个计算的过程需要由锁来保护。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 
 | func (g *guidFactory) Create() (guid, error) {f.Lock()
 
 
 ts := time.Now().UnixNano() >> 20
 
 if ts < f.lastTimestamp {
 f.Unlock()
 return 0, ErrTimeBackwards
 }
 
 if f.lastTimestamp == ts {
 f.sequence = (f.sequence + 1) & sequenceMask
 if f.sequence == 0 {
 f.Unlock()
 return 0, ErrSequenceExpired
 }
 } else {
 f.sequence = 0
 }
 
 f.lastTimestamp = ts
 
 id := guid(((ts - twepoch) << timestampShift) |
 (f.nodeID << nodeIDShift) |
 f.sequence)
 
 if id <= f.lastID {
 f.Unlock()
 return 0, ErrIDBackwards
 }
 
 f.lastID = id
 
 f.Unlock()
 
 return id, nil
 }
 
 | 
为了方便结果的表示,我们会将10进制转换为16进制,利用标准库中的 hex 就可以轻松实现了。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | func (g guid) Hex() MessageID {var h MessageID
 var b [8]byte
 
 b[0] = byte(g >> 56)
 b[1] = byte(g >> 48)
 b[2] = byte(g >> 40)
 b[3] = byte(g >> 32)
 b[4] = byte(g >> 24)
 b[5] = byte(g >> 16)
 b[6] = byte(g >> 8)
 b[7] = byte(g)
 
 hex.Encode(h[:], b[:])
 return h
 }
 
 |