Skip to content

Commit

Permalink
feature saga :support generate id by Snowflake (#670)
Browse files Browse the repository at this point in the history
  • Loading branch information
CocaineCong authored Mar 16, 2024
1 parent 540dab4 commit d1fad74
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package engine

import (
"context"
"time"

"github.com/pkg/errors"
"github.com/seata/seata-go/pkg/saga/statemachine/constant"
"github.com/seata/seata-go/pkg/saga/statemachine/engine/events"
"github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl"
"github.com/seata/seata-go/pkg/saga/statemachine/statelang"
"time"
)

type ProcessCtrlStateMachineEngine struct {
Expand Down
116 changes: 116 additions & 0 deletions pkg/saga/statemachine/engine/sequence/snowflake.go
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)
}
28 changes: 28 additions & 0 deletions pkg/saga/statemachine/engine/sequence/snowflake_test.go
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)))
}

0 comments on commit d1fad74

Please sign in to comment.