Skip to content

Commit f9e7ca8

Browse files
authored
[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.
1 parent d890071 commit f9e7ca8

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed

core/lang/snowflake.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright (c) 2022, AcmeStack
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package lang
19+
20+
import (
21+
"fmt"
22+
"github.com/acmestack/godkits/log"
23+
"sync"
24+
"time"
25+
)
26+
27+
const (
28+
epoch = int64(1640966400000) // Set start time (timestamp / MS): 2022-01-01 00:00:00, valid for 69 years
29+
timestampBits = uint(41) // Time stamp occupied digits
30+
dataCenterIdBits = uint(5) // Bytes occupied by data center ID
31+
workerIdBits = uint(5) // Number of bytes occupied by machine ID
32+
sequenceBits = uint(12) // Number of bytes occupied by the sequence
33+
timestampMax = int64(-1 ^ (-1 << timestampBits)) // Timestamp maximum
34+
dataCenterIdMax = int64(-1 ^ (-1 << dataCenterIdBits)) // Maximum number of data center IDS supported
35+
workerIdMax = int64(-1 ^ (-1 << workerIdBits)) // Maximum number of machine IDS supported
36+
sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // Maximum number of sequence ids supported
37+
workerIdShift = sequenceBits // machine id left shift number
38+
dataCenterIdShift = sequenceBits + workerIdBits // Data center id left shift number
39+
timestampShift = sequenceBits + workerIdBits + dataCenterIdBits // Timestamp left shift
40+
)
41+
42+
// Snowflake Snowflake
43+
type Snowflake struct {
44+
sync.Mutex
45+
timestamp int64
46+
workerId int64
47+
dataCenterId int64
48+
sequence int64
49+
}
50+
51+
// NewSnowflake NewSnowflake
52+
// @param dataCenterId
53+
// @param workerId
54+
// @return *Snowflake
55+
// @return error
56+
func NewSnowflake(dataCenterId, workerId int64) (*Snowflake, error) {
57+
if dataCenterId < 0 || dataCenterId > dataCenterIdMax {
58+
return nil, fmt.Errorf("dataCenterId must be between 0 and %d", dataCenterIdMax-1)
59+
}
60+
if workerId < 0 || workerId > workerIdMax {
61+
return nil, fmt.Errorf("workerId must be between 0 and %d", workerIdMax-1)
62+
}
63+
return &Snowflake{
64+
timestamp: 0,
65+
dataCenterId: dataCenterId,
66+
workerId: workerId,
67+
sequence: 0,
68+
}, nil
69+
}
70+
71+
// NextVal
72+
// @receiver s
73+
// @return int64
74+
func (s *Snowflake) NextVal() int64 {
75+
s.Lock()
76+
now := time.Now().UnixNano() / 1000000 // 转毫秒
77+
if s.timestamp == now {
78+
// The same timestamp generates different data
79+
s.sequence = (s.sequence + 1) & sequenceMask
80+
if s.sequence == 0 {
81+
// Exceeded 12bit length, need to wait for the next millisecond
82+
// next millisecond will use sequence:0
83+
for now <= s.timestamp {
84+
now = time.Now().UnixNano() / 1000000
85+
}
86+
}
87+
} else {
88+
// different timestamps are recounted using sequence:0
89+
s.sequence = 0
90+
}
91+
t := now - epoch
92+
if t > timestampMax {
93+
s.Unlock()
94+
log.Error("epoch must be between 0 and %d", timestampMax-1)
95+
return 0
96+
}
97+
s.timestamp = now
98+
r := int64((t)<<timestampShift | (s.dataCenterId << dataCenterIdShift) | (s.workerId << workerIdShift) | (s.sequence))
99+
s.Unlock()
100+
return r
101+
}
102+
103+
// GetDeviceID
104+
// @param sid
105+
// @return dataCenterId
106+
// @return workerId
107+
func GetDeviceID(sid int64) (dataCenterId, workerId int64) {
108+
dataCenterId = (sid >> dataCenterIdShift) & dataCenterIdMax
109+
workerId = (sid >> workerIdShift) & workerIdMax
110+
return
111+
}
112+
113+
// GetTimestamp
114+
// @param sid
115+
// @return timestamp
116+
func GetTimestamp(sid int64) (timestamp int64) {
117+
timestamp = (sid >> timestampShift) & timestampMax
118+
return
119+
}
120+
121+
// GetGenTimestamp
122+
// @param sid
123+
// @return timestamp
124+
func GetGenTimestamp(sid int64) (timestamp int64) {
125+
timestamp = GetTimestamp(sid) + epoch
126+
return
127+
}
128+
129+
// GetGenTime
130+
// @param sid
131+
// @return t
132+
func GetGenTime(sid int64) (t string) {
133+
// The timestamp/1000 obtained by GetGenTimestamp needs to be converted into seconds
134+
t = time.Unix(GetGenTimestamp(sid)/1000, 0).Format("2006-01-02 15:04:05")
135+
return
136+
}
137+
138+
// GetTimestampStatus Get the percentage of timestamps used: range (0.0 - 1.0)
139+
// @return state
140+
func GetTimestampStatus() (state float64) {
141+
state = float64(time.Now().UnixNano()/1000000-epoch) / float64(timestampMax)
142+
return
143+
}

core/lang/snowflake_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright (c) 2022, AcmeStack
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package lang
19+
20+
import (
21+
"github.com/acmestack/godkits/assert"
22+
"sync"
23+
"testing"
24+
"time"
25+
)
26+
27+
func TestNewSnowflake(t *testing.T) {
28+
var i, j int64
29+
for i = 0; i < 32; i++ {
30+
for j = 0; j < 32; j++ {
31+
_, err := NewSnowflake(i, j)
32+
assert.IsTrue(t, err == nil, err)
33+
}
34+
}
35+
_, err := NewSnowflake(0, -1)
36+
assert.IsTrue(t, err != nil, err)
37+
_, err2 := NewSnowflake(-1, 0)
38+
assert.IsTrue(t, err2 != nil, err)
39+
}
40+
41+
func TestNextVal(t *testing.T) {
42+
s, err := NewSnowflake(0, 0)
43+
assert.IsTrue(t, err == nil, err)
44+
var i int64
45+
for i = 0; i < sequenceMask*10; i++ {
46+
val := s.NextVal()
47+
assert.IsFalse(t, val == 0, err)
48+
}
49+
}
50+
51+
func TestUnique(t *testing.T) {
52+
var wg sync.WaitGroup
53+
var check sync.Map
54+
s, err := NewSnowflake(0, 0)
55+
assert.IsTrue(t, err == nil, err)
56+
for i := 0; i < 1000000; i++ {
57+
wg.Add(1)
58+
// Simulate multithreading to generate data
59+
go func() {
60+
defer wg.Add(-1)
61+
val := s.NextVal()
62+
_, ok := check.Load(val)
63+
assert.IsTrue(t, !ok, "Data already exists in map")
64+
check.Store(val, 0)
65+
assert.IsTrue(t, val != 0, "Unique NextVal Error")
66+
}()
67+
}
68+
wg.Wait()
69+
}
70+
71+
func TestGetTime(t *testing.T) {
72+
s, err := NewSnowflake(0, 1)
73+
assert.IsTrue(t, err == nil, err)
74+
val := s.NextVal()
75+
formatDate := time.Now().Format("2006-01-02 15:04:05")
76+
assert.IsTrue(t, formatDate == GetGenTime(val), err)
77+
}
78+
79+
func TestGetDeviceID(t *testing.T) {
80+
s, err := NewSnowflake(28, 11)
81+
assert.IsTrue(t, err == nil, err)
82+
val := s.NextVal()
83+
dataCenterId, workerId := GetDeviceID(val)
84+
if dataCenterId != 28 || workerId != 11 {
85+
t.Fail()
86+
}
87+
}
88+
89+
func TestGetTimestampStatus(t *testing.T) {
90+
status := GetTimestampStatus()
91+
assert.IsTrue(t, status < 100, "epoch exceeded current time")
92+
}

0 commit comments

Comments
 (0)