Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature saga :support generate id by Snowflake #670

Merged
merged 5 commits into from
Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)))
}
Loading