Skip to content

Commit

Permalink
[type:feat] support snowflake. (#103)
Browse files Browse the repository at this point in the history
* [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
yunlongn authored Jul 14, 2022
1 parent d890071 commit f9e7ca8
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 0 deletions.
143 changes: 143 additions & 0 deletions core/lang/snowflake.go
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
}
92 changes: 92 additions & 0 deletions core/lang/snowflake_test.go
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")
}

0 comments on commit f9e7ca8

Please sign in to comment.