You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@seata.apache.org by yi...@apache.org on 2024/03/16 09:46:53 UTC

(incubator-seata-go) branch feature/saga updated: feature saga :support generate id by Snowflake (#670)

This is an automated email from the ASF dual-hosted git repository.

yixia pushed a commit to branch feature/saga
in repository https://gitbox.apache.org/repos/asf/incubator-seata-go.git


The following commit(s) were added to refs/heads/feature/saga by this push:
     new d1fad744 feature saga :support generate id by Snowflake (#670)
d1fad744 is described below

commit d1fad744aa7b6acc01efcc87a0a1315bec72a18d
Author: FanOne <co...@outlook.com>
AuthorDate: Sat Mar 16 17:46:49 2024 +0800

    feature saga :support generate id by Snowflake (#670)
---
 .../engine/process_ctrl_statemachine_engine.go     |   3 +-
 pkg/saga/statemachine/engine/sequence/snowflake.go | 116 +++++++++++++++++++++
 .../statemachine/engine/sequence/snowflake_test.go |  28 +++++
 3 files changed, 146 insertions(+), 1 deletion(-)

diff --git a/pkg/saga/statemachine/engine/process_ctrl_statemachine_engine.go b/pkg/saga/statemachine/engine/process_ctrl_statemachine_engine.go
index afec63cb..aae9fe89 100644
--- a/pkg/saga/statemachine/engine/process_ctrl_statemachine_engine.go
+++ b/pkg/saga/statemachine/engine/process_ctrl_statemachine_engine.go
@@ -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 {
diff --git a/pkg/saga/statemachine/engine/sequence/snowflake.go b/pkg/saga/statemachine/engine/sequence/snowflake.go
new file mode 100644
index 00000000..f5622b06
--- /dev/null
+++ b/pkg/saga/statemachine/engine/sequence/snowflake.go
@@ -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)
+}
diff --git a/pkg/saga/statemachine/engine/sequence/snowflake_test.go b/pkg/saga/statemachine/engine/sequence/snowflake_test.go
new file mode 100644
index 00000000..451f478d
--- /dev/null
+++ b/pkg/saga/statemachine/engine/sequence/snowflake_test.go
@@ -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)))
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@seata.apache.org
For additional commands, e-mail: notifications-help@seata.apache.org