-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[type:feat] support snowflake. (#103)
* [type:feat] support xmap and lru. (#76) * [type:feat] support xmap and lru. * [type:feat] support xlist * [type:feat] support xlist * [type:feat] support snowflake.
- Loading branch information
Showing
2 changed files
with
235 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)<<timestampShift | (s.dataCenterId << dataCenterIdShift) | (s.workerId << workerIdShift) | (s.sequence)) | ||
s.Unlock() | ||
return r | ||
} | ||
|
||
// GetDeviceID | ||
// @param sid | ||
// @return dataCenterId | ||
// @return workerId | ||
func GetDeviceID(sid int64) (dataCenterId, workerId int64) { | ||
dataCenterId = (sid >> 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 | ||
} |
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,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") | ||
} |