-
Notifications
You must be signed in to change notification settings - Fork 287
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature saga :support generate id by Snowflake (#670)
- Loading branch information
1 parent
540dab4
commit d1fad74
Showing
3 changed files
with
146 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package sequence | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
"time" | ||
|
||
"github.com/seata/seata-go/pkg/util/log" | ||
) | ||
|
||
// SnowflakeSeqGenerator snowflake gen ids | ||
// ref: https://en.wikipedia.org/wiki/Snowflake_ID | ||
|
||
var ( | ||
// set the beginning time | ||
epoch = time.Date(2024, time.January, 01, 00, 00, 00, 00, time.UTC).UnixMilli() | ||
) | ||
|
||
const ( | ||
// timestamp occupancy bits | ||
timestampBits = 41 | ||
// dataCenterId occupancy bits | ||
dataCenterIdBits = 5 | ||
// workerId occupancy bits | ||
workerIdBits = 5 | ||
// sequence occupancy bits | ||
seqBits = 12 | ||
|
||
// timestamp max value, just like 2^41-1 = 2199023255551 | ||
timestampMaxValue = -1 ^ (-1 << timestampBits) | ||
// dataCenterId max value, just like 2^5-1 = 31 | ||
dataCenterIdMaxValue = -1 ^ (-1 << dataCenterIdBits) | ||
// workId max value, just like 2^5-1 = 31 | ||
workerIdMaxValue = -1 ^ (-1 << workerIdBits) | ||
// sequence max value, just like 2^12-1 = 4095 | ||
seqMaxValue = -1 ^ (-1 << seqBits) | ||
|
||
// number of workId offsets (seqBits) | ||
workIdShift = 12 | ||
// number of dataCenterId offsets (seqBits + workerIdBits) | ||
dataCenterIdShift = 17 | ||
// number of timestamp offsets (seqBits + workerIdBits + dataCenterIdBits) | ||
timestampShift = 22 | ||
|
||
defaultInitValue = 0 | ||
) | ||
|
||
type SnowflakeSeqGenerator struct { | ||
mu *sync.Mutex | ||
timestamp int64 | ||
dataCenterId int64 | ||
workerId int64 | ||
sequence int64 | ||
} | ||
|
||
// NewSnowflakeSeqGenerator initiates the snowflake generator | ||
func NewSnowflakeSeqGenerator(dataCenterId, workId int64) (r *SnowflakeSeqGenerator, err error) { | ||
if dataCenterId < 0 || dataCenterId > dataCenterIdMaxValue { | ||
err = fmt.Errorf("dataCenterId should between 0 and %d", dataCenterIdMaxValue-1) | ||
return | ||
} | ||
|
||
if workId < 0 || workId > workerIdMaxValue { | ||
err = fmt.Errorf("workId should between 0 and %d", dataCenterIdMaxValue-1) | ||
return | ||
} | ||
|
||
return &SnowflakeSeqGenerator{ | ||
mu: new(sync.Mutex), | ||
timestamp: defaultInitValue - 1, | ||
dataCenterId: dataCenterId, | ||
workerId: workId, | ||
sequence: defaultInitValue, | ||
}, nil | ||
} | ||
|
||
// GenerateId timestamp + dataCenterId + workId + sequence | ||
func (S *SnowflakeSeqGenerator) GenerateId(entity string, ruleName string) string { | ||
S.mu.Lock() | ||
defer S.mu.Unlock() | ||
|
||
now := time.Now().UnixMilli() | ||
|
||
if S.timestamp > now { // Clock callback | ||
log.Errorf("Clock moved backwards. Refusing to generate ID, last timestamp is %d, now is %d", S.timestamp, now) | ||
return "" | ||
} | ||
|
||
if S.timestamp == now { | ||
// generate multiple IDs in the same millisecond, incrementing the sequence number to prevent conflicts | ||
S.sequence = (S.sequence + 1) & seqMaxValue | ||
if S.sequence == 0 { | ||
// sequence overflow, waiting for next millisecond | ||
for now <= S.timestamp { | ||
now = time.Now().UnixMilli() | ||
} | ||
} | ||
} else { | ||
// initialized sequences are used directly at different millisecond timestamps | ||
S.sequence = defaultInitValue | ||
} | ||
tmp := now - epoch | ||
if tmp > timestampMaxValue { | ||
log.Errorf("epoch should between 0 and %d", timestampMaxValue-1) | ||
return "" | ||
} | ||
S.timestamp = now | ||
|
||
// combine the parts to generate the final ID and convert the 64-bit binary to decimal digits. | ||
r := (tmp)<<timestampShift | | ||
(S.dataCenterId << dataCenterIdShift) | | ||
(S.workerId << workIdShift) | | ||
(S.sequence) | ||
|
||
return fmt.Sprintf("%d", r) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package sequence | ||
|
||
import ( | ||
"strconv" | ||
"testing" | ||
) | ||
|
||
func TestSnowflakeSeqGenerator_GenerateId(t *testing.T) { | ||
var dataCenterId, workId int64 = 1, 1 | ||
generator, err := NewSnowflakeSeqGenerator(dataCenterId, workId) | ||
if err != nil { | ||
t.Error(err) | ||
return | ||
} | ||
var x, y string | ||
for i := 0; i < 100; i++ { | ||
y = generator.GenerateId("", "") | ||
if x == y { | ||
t.Errorf("x(%s) & y(%s) are the same", x, y) | ||
} | ||
x = y | ||
} | ||
} | ||
|
||
func TestEpoch(t *testing.T) { | ||
t.Log(epoch) | ||
t.Log(len(strconv.FormatInt(epoch, 10))) | ||
} |