The Real Initial Commit.
This commit is contained in:
parent
9befef8908
commit
cfa7691d14
52
README.md
52
README.md
@ -1,23 +1,43 @@
|
||||
# flake
|
||||
Very simple snowflake generator
|
||||
====
|
||||
[![GoDoc](https://godoc.org/github.com/bwmarrin/flake?status.svg)](https://godoc.org/github.com/bwmarrin/flake) [![Go report](http://goreportcard.com/badge/bwmarrin/flake)](http://goreportcard.com/report/bwmarrin/flake) [![Build Status](https://travis-ci.org/bwmarrin/flake.svg?branch=master)](https://travis-ci.org/bwmarrin/flake)
|
||||
[![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23flake.svg)](https://discord.gg/0f1SbxBZjYoCtNPP)
|
||||
|
||||
Finally, hit my goal of 244ns/op.
|
||||
flake is a Google Go (golang) package that provides a very simple twitter
|
||||
snowflake generator.
|
||||
|
||||
But, interesting comparison of different methods.
|
||||
====
|
||||
|
||||
flake is a [Go](https://golang.org/) package that provides a very simple twitter
|
||||
snowflake ID generator along with several functions to convert an ID into
|
||||
different formats.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installing
|
||||
|
||||
This assumes you already have a working Go environment, if not please see
|
||||
[this page](https://golang.org/doc/install) first.
|
||||
|
||||
```sh
|
||||
go get github.com/bwmarrin/flake
|
||||
```
|
||||
time go test -bench=.
|
||||
testing: warning: no tests to run
|
||||
PASS
|
||||
BenchmarkGenerateChan-12 3000000 503 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGenerateChanParallel-12 2000000 743 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGenerateNoSleep-12 5000000 244 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGenerateNoSleepLock-12 5000000 283 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGenerateNoSleepLockParallel-12 5000000 348 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGenerate-12 5000000 283 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGenerateLocks-12 5000000 293 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGenerateLocksParallel-12 5000000 368 ns/op 0 B/op 0 allocs/op
|
||||
ok _/home/bruce/flake 15.291s
|
||||
go test -bench=. 16.88s user 7.37s system 151% cpu 15.981 total
|
||||
|
||||
### Usage
|
||||
|
||||
Import the package into your project.
|
||||
|
||||
```go
|
||||
import "github.com/bwmarrin/flake"
|
||||
```
|
||||
|
||||
Construct a new flake Node that can be used to generate snowflake IDs then call
|
||||
the Generate method to get a unique ID. The only argument to the NewNode()
|
||||
method is a Node number. Each node you create must have it's own unique
|
||||
Node number. A node number can be any number from 0 to 1023.
|
||||
|
||||
```go
|
||||
node, err := flake.NewNode(1)
|
||||
id := node.Generate()
|
||||
fmt.Printf("ID: %d, %s\n", id, id.String())
|
||||
```
|
||||
|
268
flake.go
268
flake.go
@ -1,102 +1,74 @@
|
||||
// simple generator for..
|
||||
// Twitter Snowflake with custom definable epoch
|
||||
|
||||
// Package flake provides a very simple Twitter Snowflake generator and parser.
|
||||
// You can optionally set a custom epoch for you use.
|
||||
package flake
|
||||
|
||||
import "encoding/base64"
|
||||
import "fmt"
|
||||
import "strconv"
|
||||
import "sync"
|
||||
import "time"
|
||||
|
||||
const (
|
||||
TimeBits = 41
|
||||
NodeBits = 10
|
||||
StepBits = 12
|
||||
|
||||
TimeMask int64 = -1 ^ (-1 << TimeBits)
|
||||
NodeMask int64 = -1 ^ (-1 << NodeBits)
|
||||
StepMask int64 = -1 ^ (-1 << StepBits)
|
||||
|
||||
NodeMax = -1 ^ (-1 << NodeBits)
|
||||
|
||||
TimeShift uint8 = NodeBits + StepBits
|
||||
NodeShift uint8 = StepBits
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
timeBits = 41
|
||||
nodeBits = 10
|
||||
stepBits = 12
|
||||
|
||||
timeMask int64 = -1 ^ (-1 << timeBits)
|
||||
nodeMask int64 = -1 ^ (-1 << nodeBits)
|
||||
stepMask int64 = -1 ^ (-1 << stepBits)
|
||||
|
||||
nodeMax = -1 ^ (-1 << nodeBits)
|
||||
|
||||
timeShift uint8 = nodeBits + stepBits
|
||||
nodeShift uint8 = stepBits
|
||||
)
|
||||
|
||||
// Epoch is set to the twitter snowflake epoch of 2006-03-21:20:50:14 GMT
|
||||
// You may customize this to set a different epoch for your application.
|
||||
var Epoch int64 = 1288834974657
|
||||
|
||||
// A Node struct holds the basic information needed for a flake generator node
|
||||
type Node struct {
|
||||
sync.Mutex // TODO: find a way to avoid locks?
|
||||
|
||||
// configurable values
|
||||
epoch int64
|
||||
node int64
|
||||
|
||||
// runtime tracking values
|
||||
lastTime int64
|
||||
step int64
|
||||
sync.Mutex
|
||||
time int64
|
||||
node int64
|
||||
step int64
|
||||
}
|
||||
|
||||
// Start a new Flake factory / server node using the given node number
|
||||
// sets with default settings, use helper functions to change
|
||||
// node, epoch, etc.
|
||||
func NewFlakeNode(node int64) (*Node, error) {
|
||||
// An ID is a custom type used for a snowflake ID. This is used so we can
|
||||
// attach methods onto the ID.
|
||||
type ID int64
|
||||
|
||||
if node < 0 || node > NodeMax {
|
||||
return nil, fmt.Errorf("Invalid node number.")
|
||||
// NewNode returns a new Flake node that can be used to generate flake IDs
|
||||
func NewNode(node int64) (*Node, error) {
|
||||
|
||||
if node < 0 || node > nodeMax {
|
||||
return nil, fmt.Errorf("Node number must be between 0 and 1023")
|
||||
}
|
||||
|
||||
return &Node{
|
||||
epoch: time.Date(2016, 1, 0, 0, 0, 0, 0, time.UTC).UnixNano() / int64(time.Millisecond),
|
||||
node: node,
|
||||
lastTime: 0,
|
||||
step: 0,
|
||||
time: 0,
|
||||
node: node,
|
||||
step: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// high performance generator
|
||||
// well, that w as the idea...
|
||||
func (n *Node) Generator(c chan Flake) {
|
||||
|
||||
ticker := time.NewTicker(time.Millisecond)
|
||||
now := int64(time.Now().UnixNano() / 1000000)
|
||||
for {
|
||||
|
||||
n.step = 0
|
||||
|
||||
select {
|
||||
case c <- Flake((now-n.epoch)<<TimeShift | (n.node << NodeShift) | (n.step)):
|
||||
|
||||
n.step = (n.step + 1) & StepMask
|
||||
|
||||
if n.step == 0 {
|
||||
// wait for ticker..
|
||||
// haha, funny, this isn't fast enough to ever get here.
|
||||
}
|
||||
case <-ticker.C:
|
||||
now++
|
||||
// continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return a freshly generated Flake ID
|
||||
func (n *Node) LockedGenerate() (Flake, error) {
|
||||
// Generate creates and returns a unique snowflake ID
|
||||
func (n *Node) Generate() (ID, error) {
|
||||
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
now := time.Now().UnixNano() / 1000000
|
||||
|
||||
if n.lastTime > now {
|
||||
return 0, fmt.Errorf("Invalid system time.")
|
||||
}
|
||||
|
||||
if n.lastTime == now {
|
||||
n.step = (n.step + 1) & StepMask
|
||||
if n.time == now {
|
||||
n.step = (n.step + 1) & stepMask
|
||||
|
||||
if n.step == 0 {
|
||||
for now <= n.lastTime {
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
for now <= n.time {
|
||||
now = time.Now().UnixNano() / 1000000
|
||||
}
|
||||
}
|
||||
@ -104,129 +76,75 @@ func (n *Node) LockedGenerate() (Flake, error) {
|
||||
n.step = 0
|
||||
}
|
||||
|
||||
n.lastTime = now
|
||||
n.time = now
|
||||
|
||||
return Flake((now-n.epoch)<<TimeShift |
|
||||
(n.node << NodeShift) |
|
||||
return ID((now-Epoch)<<timeShift |
|
||||
(n.node << nodeShift) |
|
||||
(n.step),
|
||||
), nil
|
||||
}
|
||||
|
||||
// Return a freshly generated Flake ID
|
||||
func (n *Node) Generate() (Flake, error) {
|
||||
|
||||
now := time.Now().UnixNano() / 1000000
|
||||
|
||||
if n.lastTime > now {
|
||||
return 0, fmt.Errorf("Invalid system time.")
|
||||
}
|
||||
|
||||
if n.lastTime == now {
|
||||
n.step = (n.step + 1) & StepMask
|
||||
|
||||
if n.step == 0 {
|
||||
for now <= n.lastTime {
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
now = time.Now().UnixNano() / 1000000
|
||||
}
|
||||
}
|
||||
} else {
|
||||
n.step = 0
|
||||
}
|
||||
|
||||
n.lastTime = now
|
||||
|
||||
return Flake((now-n.epoch)<<TimeShift |
|
||||
(n.node << NodeShift) |
|
||||
(n.step),
|
||||
), nil
|
||||
// Int64 returns an int64 of the snowflake ID
|
||||
func (f ID) Int64() int64 {
|
||||
return int64(f)
|
||||
}
|
||||
|
||||
// Return a freshly generated Flake ID
|
||||
func (n *Node) GenerateNoSleep() (Flake, error) {
|
||||
|
||||
now := time.Now().UnixNano() / 1000000
|
||||
|
||||
if n.lastTime == now {
|
||||
n.step = (n.step + 1) & StepMask
|
||||
|
||||
if n.step == 0 {
|
||||
for now <= n.lastTime {
|
||||
now = time.Now().UnixNano() / 1000000
|
||||
}
|
||||
}
|
||||
} else {
|
||||
n.step = 0
|
||||
}
|
||||
|
||||
n.lastTime = now
|
||||
|
||||
return Flake((now-n.epoch)<<TimeShift |
|
||||
(n.node << NodeShift) |
|
||||
(n.step),
|
||||
), nil
|
||||
}
|
||||
|
||||
// Return a freshly generated Flake ID
|
||||
func (n *Node) GenerateNoSleepLock() (Flake, error) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
now := time.Now().UnixNano() / 1000000
|
||||
|
||||
if n.lastTime == now {
|
||||
n.step = (n.step + 1) & StepMask
|
||||
|
||||
if n.step == 0 {
|
||||
for now <= n.lastTime {
|
||||
now = time.Now().UnixNano() / 1000000
|
||||
}
|
||||
}
|
||||
} else {
|
||||
n.step = 0
|
||||
}
|
||||
|
||||
n.lastTime = now
|
||||
|
||||
return Flake((now-n.epoch)<<TimeShift |
|
||||
(n.node << NodeShift) |
|
||||
(n.step),
|
||||
), nil
|
||||
}
|
||||
|
||||
type Flake int64
|
||||
type Flakes []*Flake
|
||||
|
||||
func (f Flake) String() string {
|
||||
// String returns a string of the snowflake ID
|
||||
func (f ID) String() string {
|
||||
return fmt.Sprintf("%d", f)
|
||||
}
|
||||
|
||||
func (f Flake) Base2() string {
|
||||
// Base2 returns a string base2 of the snowflake ID
|
||||
func (f ID) Base2() string {
|
||||
return strconv.FormatInt(int64(f), 2)
|
||||
}
|
||||
func (f Flake) Base36() string {
|
||||
|
||||
// Base36 returns a base36 string of the snowflake ID
|
||||
func (f ID) Base36() string {
|
||||
return strconv.FormatInt(int64(f), 36)
|
||||
}
|
||||
|
||||
func (f Flake) Base64() string {
|
||||
return base64.StdEncoding.EncodeToString(f.Byte())
|
||||
// Base64 returns a base64 string of the snowflake ID
|
||||
func (f ID) Base64() string {
|
||||
return base64.StdEncoding.EncodeToString(f.Bytes())
|
||||
}
|
||||
|
||||
func (f Flake) Byte() []byte {
|
||||
// Bytes returns a byte array of the snowflake ID
|
||||
func (f ID) Bytes() []byte {
|
||||
return []byte(f.String())
|
||||
}
|
||||
|
||||
func (f Flake) Time() int64 {
|
||||
// ugh.. TODO
|
||||
// epoch is supposed to be configurable.....
|
||||
Epoch := time.Date(2016, 1, 0, 0, 0, 0, 0, time.UTC).UnixNano() / int64(time.Millisecond)
|
||||
// Time returns an int64 unix timestamp of the snowflake ID time
|
||||
func (f ID) Time() int64 {
|
||||
return (int64(f) >> 22) + Epoch
|
||||
}
|
||||
|
||||
func (f Flake) Node() int64 {
|
||||
// Node returns an int64 of the snowflake ID node number
|
||||
func (f ID) Node() int64 {
|
||||
return int64(f) & 0x00000000003FF000 >> 12
|
||||
}
|
||||
|
||||
func (f Flake) Sequence() int64 {
|
||||
// Step returns an int64 of the snowflake step (or sequence) number
|
||||
func (f ID) Step() int64 {
|
||||
return int64(f) & 0x0000000000000FFF
|
||||
}
|
||||
|
||||
// MarshalJSON returns a json byte array string of the snowflake ID.
|
||||
func (f ID) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + f.String() + `"`), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a json byte array of a snowflake ID into an ID type.
|
||||
func (f *ID) UnmarshalJSON(b []byte) error {
|
||||
|
||||
s := strings.Replace(string(b), `"`, ``, 2)
|
||||
i, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
*f = ID(i)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
101
flake_test.go
101
flake_test.go
@ -4,41 +4,9 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
func BenchmarkGenerate(b *testing.B) {
|
||||
|
||||
// Benchmarks Presence Update event with fake data.
|
||||
func BenchmarkGenerateChan(b *testing.B) {
|
||||
|
||||
node, _ := NewFlakeNode(1)
|
||||
c := make(chan Flake)
|
||||
go node.Generator(c)
|
||||
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
<-c
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Benchmarks Presence Update event with fake data.
|
||||
func BenchmarkGenerateChanParallel(b *testing.B) {
|
||||
|
||||
node, _ := NewFlakeNode(1)
|
||||
c := make(chan Flake)
|
||||
go node.Generator(c)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
<-c
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmarks Presence Update event with fake data.
|
||||
func BenchmarkGenerateNoSleep(b *testing.B) {
|
||||
|
||||
node, _ := NewFlakeNode(1)
|
||||
node, _ := NewNode(1)
|
||||
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
@ -46,68 +14,3 @@ func BenchmarkGenerateNoSleep(b *testing.B) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Benchmarks Presence Update event with fake data.
|
||||
func BenchmarkGenerateNoSleepLock(b *testing.B) {
|
||||
|
||||
node, _ := NewFlakeNode(1)
|
||||
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = node.GenerateNoSleepLock()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Benchmarks Presence Update event with fake data.
|
||||
func BenchmarkGenerateNoSleepLockParallel(b *testing.B) {
|
||||
|
||||
node, _ := NewFlakeNode(1)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, _ = node.GenerateNoSleepLock()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// Benchmarks Presence Update event with fake data.
|
||||
|
||||
// Benchmarks Presence Update event with fake data.
|
||||
func BenchmarkGenerate(b *testing.B) {
|
||||
|
||||
node, _ := NewFlakeNode(1)
|
||||
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = node.Generate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Benchmarks Presence Update event with fake data.
|
||||
func BenchmarkGenerateLocks(b *testing.B) {
|
||||
|
||||
node, _ := NewFlakeNode(1)
|
||||
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = node.LockedGenerate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Benchmarks Presence Update event with fake data.
|
||||
func BenchmarkGenerateLocksParallel(b *testing.B) {
|
||||
|
||||
node, _ := NewFlakeNode(1)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, _ = node.LockedGenerate()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user