github.com/nsqio/nsq@v1.3.0/nsqd/guid.go (about)

     1  package nsqd
     2  
     3  // the core algorithm here was borrowed from:
     4  // Blake Mizerany's `noeqd` https://github.com/bmizerany/noeqd
     5  // and indirectly:
     6  // Twitter's `snowflake` https://github.com/twitter/snowflake
     7  
     8  // only minor cleanup and changes to introduce a type, combine the concept
     9  // of workerID + datacenterId into a single identifier, and modify the
    10  // behavior when sequences rollover for our specific implementation needs
    11  
    12  import (
    13  	"encoding/hex"
    14  	"errors"
    15  	"sync"
    16  	"time"
    17  )
    18  
    19  const (
    20  	nodeIDBits     = uint64(10)
    21  	sequenceBits   = uint64(12)
    22  	nodeIDShift    = sequenceBits
    23  	timestampShift = sequenceBits + nodeIDBits
    24  	sequenceMask   = int64(-1) ^ (int64(-1) << sequenceBits)
    25  
    26  	// ( 2012-10-28 16:23:42 UTC ).UnixNano() >> 20
    27  	twepoch = int64(1288834974288)
    28  )
    29  
    30  var ErrTimeBackwards = errors.New("time has gone backwards")
    31  var ErrSequenceExpired = errors.New("sequence expired")
    32  var ErrIDBackwards = errors.New("ID went backward")
    33  
    34  type guid int64
    35  
    36  type guidFactory struct {
    37  	sync.Mutex
    38  
    39  	nodeID        int64
    40  	sequence      int64
    41  	lastTimestamp int64
    42  	lastID        guid
    43  }
    44  
    45  func NewGUIDFactory(nodeID int64) *guidFactory {
    46  	return &guidFactory{
    47  		nodeID: nodeID,
    48  	}
    49  }
    50  
    51  func (f *guidFactory) NewGUID() (guid, error) {
    52  	f.Lock()
    53  
    54  	// divide by 1048576, giving pseudo-milliseconds
    55  	ts := time.Now().UnixNano() >> 20
    56  
    57  	if ts < f.lastTimestamp {
    58  		f.Unlock()
    59  		return 0, ErrTimeBackwards
    60  	}
    61  
    62  	if f.lastTimestamp == ts {
    63  		f.sequence = (f.sequence + 1) & sequenceMask
    64  		if f.sequence == 0 {
    65  			f.Unlock()
    66  			return 0, ErrSequenceExpired
    67  		}
    68  	} else {
    69  		f.sequence = 0
    70  	}
    71  
    72  	f.lastTimestamp = ts
    73  
    74  	id := guid(((ts - twepoch) << timestampShift) |
    75  		(f.nodeID << nodeIDShift) |
    76  		f.sequence)
    77  
    78  	if id <= f.lastID {
    79  		f.Unlock()
    80  		return 0, ErrIDBackwards
    81  	}
    82  
    83  	f.lastID = id
    84  
    85  	f.Unlock()
    86  
    87  	return id, nil
    88  }
    89  
    90  func (g guid) Hex() MessageID {
    91  	var h MessageID
    92  	var b [8]byte
    93  
    94  	b[0] = byte(g >> 56)
    95  	b[1] = byte(g >> 48)
    96  	b[2] = byte(g >> 40)
    97  	b[3] = byte(g >> 32)
    98  	b[4] = byte(g >> 24)
    99  	b[5] = byte(g >> 16)
   100  	b[6] = byte(g >> 8)
   101  	b[7] = byte(g)
   102  
   103  	hex.Encode(h[:], b[:])
   104  	return h
   105  }