mirror of
https://github.com/godruoyi/go-snowflake.git
synced 2025-12-23 05:25:15 +00:00
🚛 first commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.code
|
||||||
|
.git
|
||||||
|
.idea
|
||||||
|
*.DS_Store
|
||||||
|
/vendor
|
||||||
31
atomic_resolver.go
Normal file
31
atomic_resolver.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package snowflake
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
|
var lastTime int64
|
||||||
|
var lastSeq uint32
|
||||||
|
|
||||||
|
// AtomicResolver define as atomic sequence resolver, base on standard sync/atomic.
|
||||||
|
func AtomicResolver(ms int64) (uint16, error) {
|
||||||
|
var last int64
|
||||||
|
var seq, localSeq uint32
|
||||||
|
|
||||||
|
for {
|
||||||
|
last = atomic.LoadInt64(&lastTime)
|
||||||
|
localSeq = atomic.LoadUint32(&lastSeq)
|
||||||
|
if last > ms {
|
||||||
|
return MaxSequence, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if last == ms {
|
||||||
|
seq = MaxSequence & (localSeq + 1)
|
||||||
|
if seq == 0 {
|
||||||
|
return MaxSequence, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if atomic.CompareAndSwapInt64(&lastTime, last, ms) && atomic.CompareAndSwapUint32(&lastSeq, localSeq, seq) {
|
||||||
|
return uint16(seq), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
atomic_resolver_test.go
Normal file
29
atomic_resolver_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package snowflake_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAtomicResolver(t *testing.T) {
|
||||||
|
id, _ := snowflake.AtomicResolver(1)
|
||||||
|
|
||||||
|
if id != 0 {
|
||||||
|
t.Error("Sequence should be equal 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCombinationParallel(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
snowflake.AtomicResolver(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAtomicResolver(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
snowflake.AtomicResolver(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
21
example/example.go
Normal file
21
example/example.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
id := snowflake.ID()
|
||||||
|
fmt.Println(id)
|
||||||
|
|
||||||
|
sid := snowflake.ParseID(id)
|
||||||
|
// SID {
|
||||||
|
// Sequence: 0
|
||||||
|
// MachineID: 0
|
||||||
|
// Timestamp: x
|
||||||
|
// ID: x
|
||||||
|
// }
|
||||||
|
fmt.Println(sid)
|
||||||
|
}
|
||||||
8
package.go
Normal file
8
package.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Package snowflake is a network service for generating unique ID numbers at high scale with some simple guarantees.
|
||||||
|
// The first bit is unused sign bit.
|
||||||
|
// The second part consists of a 41-bit timestamp (milliseconds) whose value is the offset of the current time relative to a certain time.
|
||||||
|
// The 5 bits of the third and fourth parts represent data center and worker, and max value is 2^5 -1 = 31.
|
||||||
|
// The last part consists of 12 bits, its means the length of the serial number generated per millisecond per working node, a maximum of 2^12 -1 = 4095 IDs can be generated in the same millisecond.
|
||||||
|
// In a distributed environment, five-bit datacenter and worker mean that can deploy 31 datacenters, and each datacenter can deploy up to 31 nodes.
|
||||||
|
// The binary length of 41 bits is at most 2^41 -1 millisecond = 69 years. So the snowflake algorithm can be used for up to 69 years, In order to maximize the use of the algorithm, you should specify a start time for it.
|
||||||
|
package snowflake
|
||||||
65
privateip.go
Normal file
65
privateip.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package snowflake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivateIPToMachineID convert private ip to machine id.
|
||||||
|
// From https://github.com/sony/sonyflake/blob/master/sonyflake.go
|
||||||
|
func PrivateIPToMachineID() uint16 {
|
||||||
|
ip, err := lower16BitPrivateIP()
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// private function defined.
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
func privateIPv4() (net.IP, error) {
|
||||||
|
as, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range as {
|
||||||
|
ipnet, ok := a.(*net.IPNet)
|
||||||
|
if !ok || ipnet.IP.IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := ipnet.IP.To4()
|
||||||
|
if isPrivateIPv4(ip) {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("no private ip address")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPrivateIPv4(ip net.IP) bool {
|
||||||
|
return ip != nil &&
|
||||||
|
(ip[0] == 10 || ip[0] == 172 && (ip[1] >= 16 && ip[1] < 32) || ip[0] == 192 && ip[1] == 168)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lower16BitPrivateIP() (uint16, error) {
|
||||||
|
ip, err := privateIPv4()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snowflake macnineID max length is 10, max value is 1023
|
||||||
|
// If ip[2] > 3, return ip[2] + ip[3], but:
|
||||||
|
// 10.21.5.211 => return 5 + 211 = 216
|
||||||
|
// 10.21.4.212 => return 4 + 212 = 216
|
||||||
|
// need help if you can do this.
|
||||||
|
if ip[2] > 3 {
|
||||||
|
return uint16(ip[2] + ip[3]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint16(ip[2])<<8 + uint16(ip[3]), nil
|
||||||
|
}
|
||||||
14
privateip_test.go
Normal file
14
privateip_test.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package snowflake_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrivateIPToMachineID(t *testing.T) {
|
||||||
|
mid := snowflake.PrivateIPToMachineID()
|
||||||
|
if mid <= 0 {
|
||||||
|
t.Error("MachineID should be > 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
187
readme.md
Normal file
187
readme.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<div>
|
||||||
|
<p align="center">
|
||||||
|
<image src="https://www.pngkey.com/png/full/105-1052235_snowflake-png-transparent-background-snowflake-with-clear-background.png" width="250" height="250">
|
||||||
|
</p>
|
||||||
|
<p align="center">An Lock Free ID Generator for Golang based on Snowflake Algorithm (Twitter announced).</p>
|
||||||
|
<!-- <p align="center">
|
||||||
|
<a href="https://scrutinizer-ci.com/g/godruoyi/go-snowflake/">
|
||||||
|
<image src="https://scrutinizer-ci.com/g/godruoyi/go-snowflake/badges/quality-score.png?b=master" alt="quality score">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/godruoyi/go-snowflake">
|
||||||
|
<image src="https://github.styleci.io/repos/201936013/shield?branch=master" alt="godruoyi go-snowflake">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/godruoyi/go-snowflake">
|
||||||
|
<image src="https://poser.pugx.org/godruoyi/go-snowflake/license" alt="License">
|
||||||
|
</a>
|
||||||
|
<a href="https://packagist.org/packages/godruoyi/go-snowflake">
|
||||||
|
<image src="https://poser.pugx.org/godruoyi/go-snowflake/v/stable" alt="Packagist Version">
|
||||||
|
</a>
|
||||||
|
<a href="https://scrutinizer-ci.com/g/godruoyi/go-snowflake/">
|
||||||
|
<image src="https://scrutinizer-ci.com/g/godruoyi/go-snowflake/badges/build.png?b=master" alt="build passed">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/godruoyi/go-snowflake">
|
||||||
|
<image src="https://poser.pugx.org/godruoyi/go-snowflake/downloads" alt="Total Downloads">
|
||||||
|
</a>
|
||||||
|
</p> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
An Lock Free ID Generator for Golang implementation.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Snowflake is a network service for generating unique ID numbers at high scale with some simple guarantees.
|
||||||
|
|
||||||
|
* The first bit is unused sign bit.
|
||||||
|
* The second part consists of a 41-bit timestamp (milliseconds) whose value is the offset of the current time relative to a certain time.
|
||||||
|
* The 10 bits machineID, and max value is 2^10 -1 = 1023.
|
||||||
|
* The last part consists of 12 bits, its means the length of the serial number generated per millisecond per working node, a maximum of 2^12 -1 = 4095 IDs can be generated in the same millisecond.
|
||||||
|
* The binary length of 41 bits is at most 2^41 -1 millisecond = 69 years. So the snowflake algorithm can be used for up to 69 years, In order to maximize the use of the algorithm, you should specify a start time for it.
|
||||||
|
|
||||||
|
> You must know, The ID generated by the snowflake algorithm is not guaranteed to be unique.
|
||||||
|
> For example, when two different requests enter the same machine at the same time, and the sequence generated by the node is the same, the generated ID will be duplicated.
|
||||||
|
|
||||||
|
So if you want use the snowflake algorithm to generate unique ID, You must ensure: The sequence-number generated in the same millisecond of the same node is unique.
|
||||||
|
Based on this, we created this package and integrated multiple sequence-number providers into it.
|
||||||
|
|
||||||
|
* AtomicResolver (base sync/atmoic)
|
||||||
|
* Custom (you can custom)
|
||||||
|
|
||||||
|
> Each provider only needs to ensure that the serial number generated in the same millisecond is different. You can get a unique ID.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ go get github.com/godruoyi/go-snowflake
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useage
|
||||||
|
|
||||||
|
1. simple to use.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
id := snowflake.ID()
|
||||||
|
fmt.Println(id)
|
||||||
|
// 1537200202186752
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Specify the MachineID.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
snowflake.SetMachineID(1)
|
||||||
|
id := snowflake.ID()
|
||||||
|
fmt.Println(id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Specify start time.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
snowflake.SetStartTime(time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
id := snowflake.ID()
|
||||||
|
fmt.Println(id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Parse ID.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
id := snowflake.ID()
|
||||||
|
sid := snowflake.ParseID(id)
|
||||||
|
|
||||||
|
fmt.Println(sid.ID) //
|
||||||
|
fmt.Println(sid.MachineID) //
|
||||||
|
fmt.Println(sid.Sequence) //
|
||||||
|
fmt.Println(sid.Timestamp) //
|
||||||
|
fmt.Println(sid.GenerateTime()) //
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best practices
|
||||||
|
|
||||||
|
> ⚠️⚠️ All SetXXX method is thread-unsafe, recommended you call him in the main function.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
snowflake.SetMachineID(1) // change to your machineID
|
||||||
|
snowflake.SetStartTime(time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
|
||||||
|
http.HandleFunc("/order", submitOrder)
|
||||||
|
http.ListenAndServe(":8090", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func submitOrder(w http.ResponseWriter, req *http.Request) {
|
||||||
|
orderId := snowflake.ID()
|
||||||
|
// save order
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
1. Custom sequence resolver.
|
||||||
|
|
||||||
|
You can customize the sequence-number resolver by following way:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func yourSequenceNumber(ms int64) (uint16, error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// usage
|
||||||
|
|
||||||
|
snowflake.SetSequenceResolver(yourSequenceNumber)
|
||||||
|
snowflake.ID()
|
||||||
|
```
|
||||||
|
|
||||||
|
And you can use closure:
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
172
snowflake.go
Normal file
172
snowflake.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package snowflake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These constants are the bit lengths of snowflake ID parts.
|
||||||
|
const (
|
||||||
|
TimestampLength = 41
|
||||||
|
MachineIDLength = 10
|
||||||
|
SequenceLength = 12
|
||||||
|
MaxSequence = 1<<SequenceLength - 1
|
||||||
|
MaxTimestamp = 1<<TimestampLength - 1
|
||||||
|
MaxMachineID = 1<<MachineIDLength - 1
|
||||||
|
|
||||||
|
machineIDMoveLength = SequenceLength
|
||||||
|
timestampMoveLength = MachineIDLength + SequenceLength
|
||||||
|
)
|
||||||
|
|
||||||
|
// SequenceResolver the snowflake sequence resolver.
|
||||||
|
//
|
||||||
|
// When you want use the snowflake algorithm to generate unique ID, You must ensure: The sequence-number generated in the same millisecond of the same node is unique.
|
||||||
|
// Based on this, we create this interface provide following reslover:
|
||||||
|
// AtomicResolver : base sync/atomic (by default).
|
||||||
|
type SequenceResolver func(ms int64) (uint16, error)
|
||||||
|
|
||||||
|
// default start time is 2014-09-01 00:00:00UTC
|
||||||
|
// default machineID is 0
|
||||||
|
// default resolver is AtomicResolver
|
||||||
|
var (
|
||||||
|
resolver SequenceResolver
|
||||||
|
machineID = 0
|
||||||
|
startTime = time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ID use ID to generate snowflake id and it will ignore error. if you want error info, you need use NextID method.
|
||||||
|
// This function is thread safe.
|
||||||
|
func ID() uint64 {
|
||||||
|
id, _ := NextID()
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextID use NextID to generate snowflake id and return an error.
|
||||||
|
// This function is thread safe.
|
||||||
|
func NextID() (uint64, error) {
|
||||||
|
c := currentMillis()
|
||||||
|
seqResolver := callSequenceResolver()
|
||||||
|
seq, err := seqResolver(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for seq >= MaxSequence {
|
||||||
|
c = waitForNextMillis(c)
|
||||||
|
seq, err = seqResolver(c)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
df := int(elapsedTime(startTime))
|
||||||
|
if df < 0 || df > MaxTimestamp {
|
||||||
|
return 0, errors.New("The maximum life cycle of the snowflake algorithm is 2^41-1(millis), please check starttime")
|
||||||
|
}
|
||||||
|
|
||||||
|
id := uint64(df<<timestampMoveLength | machineID<<machineIDMoveLength | int(seq))
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStartTime set the start time for snowflake algorithm.
|
||||||
|
//
|
||||||
|
// It will panic when:
|
||||||
|
// s IsZero
|
||||||
|
// s > current millisecond
|
||||||
|
// current millisecond - s > 2^41(69 years).
|
||||||
|
// This function is thread-unsafe, recommended you call him in the main function.
|
||||||
|
func SetStartTime(s time.Time) {
|
||||||
|
s = s.UTC()
|
||||||
|
|
||||||
|
if s.IsZero() {
|
||||||
|
panic("The start time cannot be a zero value")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.After(time.Now()) {
|
||||||
|
panic("The s cannot be greater than the current millisecond")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because s must after now, so the `df` not < 0.
|
||||||
|
df := elapsedTime(s)
|
||||||
|
if df > MaxTimestamp {
|
||||||
|
panic("The maximum life cycle of the snowflake algorithm is 69 years")
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMachineID specify the machine ID. It will panic when machineid > max limit for 2^10-1.
|
||||||
|
// This function is thread-unsafe, recommended you call him in the main function.
|
||||||
|
func SetMachineID(m uint16) {
|
||||||
|
if m > MaxMachineID {
|
||||||
|
panic("The machineid cannot be greater than 1023")
|
||||||
|
}
|
||||||
|
machineID = int(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSequenceResolver set an custom sequence resolver.
|
||||||
|
// This function is thread-unsafe, recommended you call him in the main function.
|
||||||
|
func SetSequenceResolver(seq SequenceResolver) {
|
||||||
|
if seq != nil {
|
||||||
|
resolver = seq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SID snowflake id
|
||||||
|
type SID struct {
|
||||||
|
Sequence uint64
|
||||||
|
MachineID uint64
|
||||||
|
Timestamp uint64
|
||||||
|
ID uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateTime snowflake generate at, return a UTC time.
|
||||||
|
func (id *SID) GenerateTime() time.Time {
|
||||||
|
ms := startTime.UTC().UnixNano()/1e6 + int64(id.Timestamp)
|
||||||
|
|
||||||
|
return time.Unix(0, (ms * int64(time.Millisecond))).UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseID parse snowflake it to SID struct.
|
||||||
|
func ParseID(id uint64) SID {
|
||||||
|
time := id >> (SequenceLength + MachineIDLength)
|
||||||
|
sequence := id & MaxSequence
|
||||||
|
machineID := (id & (MaxMachineID << SequenceLength)) >> SequenceLength
|
||||||
|
|
||||||
|
return SID{
|
||||||
|
ID: id,
|
||||||
|
Sequence: sequence,
|
||||||
|
MachineID: machineID,
|
||||||
|
Timestamp: time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// private function defined.
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
func waitForNextMillis(last int64) int64 {
|
||||||
|
now := currentMillis()
|
||||||
|
for now == last {
|
||||||
|
now = currentMillis()
|
||||||
|
}
|
||||||
|
return now
|
||||||
|
}
|
||||||
|
|
||||||
|
func callSequenceResolver() SequenceResolver {
|
||||||
|
if resolver == nil {
|
||||||
|
return AtomicResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func elapsedTime(s time.Time) int64 {
|
||||||
|
return currentMillis() - s.UTC().UnixNano()/1e6
|
||||||
|
}
|
||||||
|
|
||||||
|
// currentMillis get current millisecond.
|
||||||
|
func currentMillis() int64 {
|
||||||
|
return time.Now().UTC().UnixNano() / 1e6
|
||||||
|
}
|
||||||
202
snowflake_test.go
Normal file
202
snowflake_test.go
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
package snowflake_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/godruoyi/go-snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestID(t *testing.T) {
|
||||||
|
id := snowflake.ID()
|
||||||
|
|
||||||
|
if id <= 0 {
|
||||||
|
t.Error("The snowflake should't < 0.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetStartTime(t *testing.T) {
|
||||||
|
t.Run("A nil time", func(tt *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e == nil {
|
||||||
|
tt.Error("Should throw a error when start time is zero")
|
||||||
|
} else if e.(string) != "The start time cannot be a zero value" {
|
||||||
|
tt.Error("The error message should equal [The start time cannot be a zero value]")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var time time.Time
|
||||||
|
snowflake.SetStartTime(time)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Start time too big", func(tt *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e == nil {
|
||||||
|
tt.Error("Should throw a error when start time is too big")
|
||||||
|
} else if e.(string) != "The s cannot be greater than the current millisecond" {
|
||||||
|
tt.Error("The error message should equal [The s cannot be greater than the current millisecond]")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time := time.Date(2035, 1, 1, 1, 0, 0, 0, time.UTC)
|
||||||
|
snowflake.SetStartTime(time)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Start time too small", func(tt *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e == nil {
|
||||||
|
tt.Error("Should throw a error when starttime is too small")
|
||||||
|
} else if e.(string) != "The maximum life cycle of the snowflake algorithm is 69 years" {
|
||||||
|
tt.Error("The error message should equal [The maximum life cycle of the snowflake algorithm is 69 years]")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// because 2021-69 = 1952, set df time > 69 years to test.
|
||||||
|
time := time.Date(1951, 1, 1, 1, 0, 0, 0, time.UTC)
|
||||||
|
snowflake.SetStartTime(time)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Default start time", func(tt *testing.T) {
|
||||||
|
defaultTime := time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
defaultNano := defaultTime.UTC().UnixNano() / 1e6
|
||||||
|
|
||||||
|
sid := snowflake.ParseID(snowflake.ID())
|
||||||
|
currentTime := sid.Timestamp + uint64(defaultNano)
|
||||||
|
|
||||||
|
nowNano := time.Now().UTC().UnixNano() / 1e6
|
||||||
|
|
||||||
|
// approximate equality, Assuming that the program is completed in one second.
|
||||||
|
if currentTime/1000 != uint64(nowNano)/1000 {
|
||||||
|
t.Error("The timestamp should be equal")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Basic", func(tt *testing.T) {
|
||||||
|
date := time.Date(2002, 1, 1, 1, 0, 0, 0, time.UTC)
|
||||||
|
snowflake.SetStartTime(date)
|
||||||
|
|
||||||
|
nowNano := time.Now().UTC().UnixNano() / 1e6
|
||||||
|
startNano := date.UTC().UnixNano() / 1e6
|
||||||
|
df := nowNano - startNano
|
||||||
|
|
||||||
|
sid := snowflake.ParseID(snowflake.ID())
|
||||||
|
|
||||||
|
// approximate equality, Assuming that the program is completed in one second.
|
||||||
|
if sid.Timestamp/1000 != uint64(df)/1000 {
|
||||||
|
t.Error("The timestamp should be equal")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetMachineID(t *testing.T) {
|
||||||
|
// first test,
|
||||||
|
sid := snowflake.ParseID(snowflake.ID())
|
||||||
|
if sid.MachineID != 0 {
|
||||||
|
t.Error("MachineID should be equal 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("No Panic", func(tt *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
t.Error("An error should not be returned")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
snowflake.SetMachineID(1)
|
||||||
|
id := snowflake.ID()
|
||||||
|
sid := snowflake.ParseID(id)
|
||||||
|
|
||||||
|
if sid.MachineID != 1 {
|
||||||
|
tt.Error("The machineID should be equal 1")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Panic", func(tt *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err == nil {
|
||||||
|
tt.Error("Should throw a error")
|
||||||
|
} else if err.(string) != "The machineid cannot be greater than 1023" {
|
||||||
|
tt.Error("The error message should be eq 「The machineid cannot be greater than 1023」")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
snowflake.SetMachineID(1024)
|
||||||
|
})
|
||||||
|
|
||||||
|
snowflake.SetMachineID(100)
|
||||||
|
sid = snowflake.ParseID(snowflake.ID())
|
||||||
|
if sid.MachineID != 100 {
|
||||||
|
t.Error("MachineID should be equal 100")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetSequenceResolver(t *testing.T) {
|
||||||
|
snowflake.SetSequenceResolver(func(c int64) (uint16, error) {
|
||||||
|
return 100, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
id := snowflake.ID()
|
||||||
|
sid := snowflake.ParseID(id)
|
||||||
|
|
||||||
|
if sid.Sequence != 100 {
|
||||||
|
t.Error("The snowflake number part of sequence should be equal 100")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
|
||||||
|
id = snowflake.ID()
|
||||||
|
sid2 := snowflake.ParseID(id)
|
||||||
|
|
||||||
|
if sid2.Sequence != 100 {
|
||||||
|
t.Error("The snowflake number part of sequence should be equal 100")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sid2.Timestamp <= sid.Timestamp {
|
||||||
|
t.Error("It should be bigger than the previous time")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextID(t *testing.T) {
|
||||||
|
_, err := snowflake.NextID()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
snowflake.SetSequenceResolver(func(ms int64) (uint16, error) {
|
||||||
|
return 0, errors.New("test error")
|
||||||
|
})
|
||||||
|
_, e := snowflake.NextID()
|
||||||
|
if e == nil {
|
||||||
|
t.Error("Should be throw error")
|
||||||
|
} else if e.Error() != "test error" {
|
||||||
|
t.Error("NextID error message should be equal [test error]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseID(t *testing.T) {
|
||||||
|
time := 101 << (snowflake.MachineIDLength + snowflake.SequenceLength)
|
||||||
|
machineid := 1023 << snowflake.SequenceLength
|
||||||
|
seq := 999
|
||||||
|
|
||||||
|
id := uint64(time | machineid | seq)
|
||||||
|
|
||||||
|
d := snowflake.ParseID(id)
|
||||||
|
if d.Sequence != 999 {
|
||||||
|
t.Error("Sequence should be equal 999")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.MachineID != 1023 {
|
||||||
|
t.Error("MachineID should be equal 1023")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Timestamp != 101 {
|
||||||
|
t.Error("Timestamp should be equal 101")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSID_GenerateTime(t *testing.T) {
|
||||||
|
sid := snowflake.ParseID(snowflake.ID())
|
||||||
|
|
||||||
|
if sid.GenerateTime().UTC().Second() != time.Now().UTC().Second() {
|
||||||
|
t.Error("The id generate time should be equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user