github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/id/x_snowflake_id.go (about)

     1  package id
     2  
     3  import (
     4  	"strconv"
     5  	"sync/atomic"
     6  	"time"
     7  
     8  	"github.com/benz9527/xboot/lib/infra"
     9  )
    10  
    11  // SnowFlakeID Clock Rollback Issue.
    12  // References:
    13  // https://github.com/apache/incubator-seata/blob/2.x/common/src/main/java/org/apache/seata/common/util/IdWorker.java
    14  // https://seata.apache.org/zh-cn/blog/seata-snowflake-explain/
    15  
    16  // 0-00000_00000-00000000_00000000_00000000_00000000_00000000_0-00000000_0000
    17  // 1 bit, as symbol, it always set as 0
    18  // 5 bits, as datacenter id
    19  // 5 bits, as machine id
    20  // 41 bits, the diff val between current ts and start ts
    21  // 12 bits, as internal sequence number, max value is 4096 (2 ^ 12)
    22  //
    23  // In this algorithm, it seems that the ID only has monotonic in single
    24  // machine, which is not an global and unique ID. But it is enough for
    25  // reducing the b+ tree page split in ORM database, such as MySQL innodb.
    26  // b+ tree page split is unfriendly to the IO performance. We have to create
    27  // new pages and copy the data to the new pages.
    28  // In this algorithm, the ID may still contains b+ tree page split, but
    29  // it will turn into stable after several split operations.
    30  // It is convergent algorithm!
    31  
    32  const (
    33  	xSnowflakeStartEpoch        = int64(1712336461000) // 2024-04-06 01:01:01 UTC+8
    34  	xSnowflakeDIDBits           = uint(5)              // DataCenter ID
    35  	xSnowflakeMIDBits           = uint(5)              // Machine ID
    36  	xSnowflakeTsDiffBits        = uint(41)
    37  	xSnowflakeSequenceBits      = uint(12) // max 4096
    38  	xSnowflakeTsDiffShiftLeft   = xSnowflakeSequenceBits
    39  	xSnowflakeMIDShiftLeft      = xSnowflakeTsDiffShiftLeft + xSnowflakeTsDiffBits
    40  	xSnowflakeDIDShiftLeft      = xSnowflakeMIDShiftLeft + xSnowflakeMIDBits
    41  	xSnowflakeSequenceMax       = int64(-1 ^ (-1 << xSnowflakeSequenceBits))
    42  	xSnowflakeMIDMax            = int64(-1 ^ (-1 << xSnowflakeMIDBits))
    43  	xSnowflakeDIDMax            = xSnowflakeMIDMax
    44  	xSnowflakeTsDiffMax         = int64(-1 ^ (-1 << xSnowflakeTsDiffBits))
    45  	xSnowflakeWorkerIDMax       = int64(-1 ^ (-1 << (xSnowflakeMIDBits + xSnowflakeDIDBits)))
    46  	xSnowflakeTsAndSequenceMask = int64(-1 ^ (-1 << (xSnowflakeTsDiffBits + xSnowflakeSequenceBits)))
    47  )
    48  
    49  const (
    50  	errSFInvalidWorkerID = sfError("worker id invalid")
    51  )
    52  
    53  // The now function could use the relative timestamp generator implementation.
    54  func xSnowFlakeID(dataCenterID, machineID int64, now func() time.Time) (UUIDGen, error) {
    55  	if dataCenterID < 0 || dataCenterID > xSnowflakeDIDMax {
    56  		return nil, infra.WrapErrorStackWithMessage(errSFInvalidDataCenterID, "dataCenterID: "+strconv.FormatInt(dataCenterID, 10)+
    57  			" (max: "+strconv.FormatInt(xSnowflakeDIDMax, 10)+")")
    58  	}
    59  
    60  	if machineID < 0 || machineID > xSnowflakeMIDMax {
    61  		return nil, infra.WrapErrorStackWithMessage(errSFInvalidMachineID, "machineID: "+strconv.FormatInt(machineID, 10)+
    62  			" (max: "+strconv.FormatInt(xSnowflakeMIDMax, 10)+")")
    63  	}
    64  
    65  	tsAndSequence := now().UnixNano() / 1e6
    66  	tsAndSequence <<= xSnowflakeTsDiffShiftLeft
    67  	waitIfNecessary := func() {
    68  		curTsAndSeq := atomic.LoadInt64(&tsAndSequence)
    69  		cur := curTsAndSeq >> xSnowflakeTsDiffShiftLeft
    70  		latest := now().UnixNano() / 1e6
    71  		if latest <= cur { // clock skew
    72  			time.Sleep(5 * time.Millisecond) // clock forward maybe blocked!!!
    73  		}
    74  	}
    75  
    76  	id := new(uuidDelegator)
    77  	id.number = func() uint64 {
    78  		waitIfNecessary()
    79  		tsAndSeq := atomic.AddInt64(&tsAndSequence, 1) & xSnowflakeTsAndSequenceMask
    80  		id := tsAndSeq |
    81  			(dataCenterID << xSnowflakeDIDShiftLeft) |
    82  			(machineID << xSnowflakeMIDShiftLeft)
    83  		return uint64(id)
    84  	}
    85  	id.str = func() string {
    86  		return strconv.FormatUint(id.number(), 10)
    87  	}
    88  	return id, nil
    89  }
    90  
    91  func XSnowFlakeID(dataCenterID, machineID int64, now func() time.Time) (UUIDGen, error) {
    92  	return xSnowFlakeID(dataCenterID, machineID, now)
    93  }
    94  
    95  func XSnowFlakeIDByWorkerID(workerID int64, now func() time.Time) (UUIDGen, error) {
    96  	if workerID < 0 || workerID > xSnowflakeWorkerIDMax {
    97  		return nil, infra.WrapErrorStackWithMessage(errSFInvalidWorkerID, "workerID: "+strconv.FormatInt(workerID, 10)+
    98  			" (max: "+strconv.FormatInt(xSnowflakeWorkerIDMax, 10)+")")
    99  	}
   100  	idMask := int64(0x1f)
   101  	mID := workerID & idMask
   102  	dID := (workerID >> 5) & idMask
   103  	return XSnowFlakeID(dID, mID, now)
   104  }