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 }