From f9e7ca83a6f542e508ebe1fc62edd13064d66deb Mon Sep 17 00:00:00 2001 From: yunlongn Date: Thu, 14 Jul 2022 10:56:43 +0800 Subject: [PATCH] [type:feat] support snowflake. (#103) * [type:feat] support xmap and lru. (acmestack#76) * [type:feat] support xmap and lru. * [type:feat] support xlist * [type:feat] support xlist * [type:feat] support snowflake. --- core/lang/snowflake.go | 143 ++++++++++++++++++++++++++++++++++++ core/lang/snowflake_test.go | 92 +++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 core/lang/snowflake.go create mode 100644 core/lang/snowflake_test.go diff --git a/core/lang/snowflake.go b/core/lang/snowflake.go new file mode 100644 index 0000000..00713fd --- /dev/null +++ b/core/lang/snowflake.go @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022, AcmeStack + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lang + +import ( + "fmt" + "github.com/acmestack/godkits/log" + "sync" + "time" +) + +const ( + epoch = int64(1640966400000) // Set start time (timestamp / MS): 2022-01-01 00:00:00, valid for 69 years + timestampBits = uint(41) // Time stamp occupied digits + dataCenterIdBits = uint(5) // Bytes occupied by data center ID + workerIdBits = uint(5) // Number of bytes occupied by machine ID + sequenceBits = uint(12) // Number of bytes occupied by the sequence + timestampMax = int64(-1 ^ (-1 << timestampBits)) // Timestamp maximum + dataCenterIdMax = int64(-1 ^ (-1 << dataCenterIdBits)) // Maximum number of data center IDS supported + workerIdMax = int64(-1 ^ (-1 << workerIdBits)) // Maximum number of machine IDS supported + sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // Maximum number of sequence ids supported + workerIdShift = sequenceBits // machine id left shift number + dataCenterIdShift = sequenceBits + workerIdBits // Data center id left shift number + timestampShift = sequenceBits + workerIdBits + dataCenterIdBits // Timestamp left shift +) + +// Snowflake Snowflake +type Snowflake struct { + sync.Mutex + timestamp int64 + workerId int64 + dataCenterId int64 + sequence int64 +} + +// NewSnowflake NewSnowflake +// @param dataCenterId +// @param workerId +// @return *Snowflake +// @return error +func NewSnowflake(dataCenterId, workerId int64) (*Snowflake, error) { + if dataCenterId < 0 || dataCenterId > dataCenterIdMax { + return nil, fmt.Errorf("dataCenterId must be between 0 and %d", dataCenterIdMax-1) + } + if workerId < 0 || workerId > workerIdMax { + return nil, fmt.Errorf("workerId must be between 0 and %d", workerIdMax-1) + } + return &Snowflake{ + timestamp: 0, + dataCenterId: dataCenterId, + workerId: workerId, + sequence: 0, + }, nil +} + +// NextVal +// @receiver s +// @return int64 +func (s *Snowflake) NextVal() int64 { + s.Lock() + now := time.Now().UnixNano() / 1000000 // 转毫秒 + if s.timestamp == now { + // The same timestamp generates different data + s.sequence = (s.sequence + 1) & sequenceMask + if s.sequence == 0 { + // Exceeded 12bit length, need to wait for the next millisecond + // next millisecond will use sequence:0 + for now <= s.timestamp { + now = time.Now().UnixNano() / 1000000 + } + } + } else { + // different timestamps are recounted using sequence:0 + s.sequence = 0 + } + t := now - epoch + if t > timestampMax { + s.Unlock() + log.Error("epoch must be between 0 and %d", timestampMax-1) + return 0 + } + s.timestamp = now + r := int64((t)<> dataCenterIdShift) & dataCenterIdMax + workerId = (sid >> workerIdShift) & workerIdMax + return +} + +// GetTimestamp +// @param sid +// @return timestamp +func GetTimestamp(sid int64) (timestamp int64) { + timestamp = (sid >> timestampShift) & timestampMax + return +} + +// GetGenTimestamp +// @param sid +// @return timestamp +func GetGenTimestamp(sid int64) (timestamp int64) { + timestamp = GetTimestamp(sid) + epoch + return +} + +// GetGenTime +// @param sid +// @return t +func GetGenTime(sid int64) (t string) { + // The timestamp/1000 obtained by GetGenTimestamp needs to be converted into seconds + t = time.Unix(GetGenTimestamp(sid)/1000, 0).Format("2006-01-02 15:04:05") + return +} + +// GetTimestampStatus Get the percentage of timestamps used: range (0.0 - 1.0) +// @return state +func GetTimestampStatus() (state float64) { + state = float64(time.Now().UnixNano()/1000000-epoch) / float64(timestampMax) + return +} diff --git a/core/lang/snowflake_test.go b/core/lang/snowflake_test.go new file mode 100644 index 0000000..0d884a4 --- /dev/null +++ b/core/lang/snowflake_test.go @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022, AcmeStack + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lang + +import ( + "github.com/acmestack/godkits/assert" + "sync" + "testing" + "time" +) + +func TestNewSnowflake(t *testing.T) { + var i, j int64 + for i = 0; i < 32; i++ { + for j = 0; j < 32; j++ { + _, err := NewSnowflake(i, j) + assert.IsTrue(t, err == nil, err) + } + } + _, err := NewSnowflake(0, -1) + assert.IsTrue(t, err != nil, err) + _, err2 := NewSnowflake(-1, 0) + assert.IsTrue(t, err2 != nil, err) +} + +func TestNextVal(t *testing.T) { + s, err := NewSnowflake(0, 0) + assert.IsTrue(t, err == nil, err) + var i int64 + for i = 0; i < sequenceMask*10; i++ { + val := s.NextVal() + assert.IsFalse(t, val == 0, err) + } +} + +func TestUnique(t *testing.T) { + var wg sync.WaitGroup + var check sync.Map + s, err := NewSnowflake(0, 0) + assert.IsTrue(t, err == nil, err) + for i := 0; i < 1000000; i++ { + wg.Add(1) + // Simulate multithreading to generate data + go func() { + defer wg.Add(-1) + val := s.NextVal() + _, ok := check.Load(val) + assert.IsTrue(t, !ok, "Data already exists in map") + check.Store(val, 0) + assert.IsTrue(t, val != 0, "Unique NextVal Error") + }() + } + wg.Wait() +} + +func TestGetTime(t *testing.T) { + s, err := NewSnowflake(0, 1) + assert.IsTrue(t, err == nil, err) + val := s.NextVal() + formatDate := time.Now().Format("2006-01-02 15:04:05") + assert.IsTrue(t, formatDate == GetGenTime(val), err) +} + +func TestGetDeviceID(t *testing.T) { + s, err := NewSnowflake(28, 11) + assert.IsTrue(t, err == nil, err) + val := s.NextVal() + dataCenterId, workerId := GetDeviceID(val) + if dataCenterId != 28 || workerId != 11 { + t.Fail() + } +} + +func TestGetTimestampStatus(t *testing.T) { + status := GetTimestampStatus() + assert.IsTrue(t, status < 100, "epoch exceeded current time") +}