The Real Initial Commit.

This commit is contained in:
Bruce Marriner 2016-06-01 14:59:26 -05:00
parent 9befef8908
commit cfa7691d14
3 changed files with 131 additions and 290 deletions

View File

@ -1,23 +1,43 @@
# flake # 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 ### Usage
PASS
BenchmarkGenerateChan-12 3000000 503 ns/op 0 B/op 0 allocs/op Import the package into your project.
BenchmarkGenerateChanParallel-12 2000000 743 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateNoSleep-12 5000000 244 ns/op 0 B/op 0 allocs/op ```go
BenchmarkGenerateNoSleepLock-12 5000000 283 ns/op 0 B/op 0 allocs/op import "github.com/bwmarrin/flake"
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 Construct a new flake Node that can be used to generate snowflake IDs then call
BenchmarkGenerateLocksParallel-12 5000000 368 ns/op 0 B/op 0 allocs/op the Generate method to get a unique ID. The only argument to the NewNode()
ok _/home/bruce/flake 15.291s method is a Node number. Each node you create must have it's own unique
go test -bench=. 16.88s user 7.37s system 151% cpu 15.981 total 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
View File

@ -1,102 +1,74 @@
// simple generator for.. // Package flake provides a very simple Twitter Snowflake generator and parser.
// Twitter Snowflake with custom definable epoch // You can optionally set a custom epoch for you use.
package flake package flake
import "encoding/base64" import (
import "fmt" "encoding/base64"
import "strconv" "fmt"
import "sync" "strconv"
import "time" "strings"
"sync"
const ( "time"
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
) )
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 { type Node struct {
sync.Mutex // TODO: find a way to avoid locks? sync.Mutex
time int64
// configurable values node int64
epoch int64 step int64
node int64
// runtime tracking values
lastTime int64
step int64
} }
// Start a new Flake factory / server node using the given node number // An ID is a custom type used for a snowflake ID. This is used so we can
// sets with default settings, use helper functions to change // attach methods onto the ID.
// node, epoch, etc. type ID int64
func NewFlakeNode(node int64) (*Node, error) {
if node < 0 || node > NodeMax { // NewNode returns a new Flake node that can be used to generate flake IDs
return nil, fmt.Errorf("Invalid node number.") 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{ return &Node{
epoch: time.Date(2016, 1, 0, 0, 0, 0, 0, time.UTC).UnixNano() / int64(time.Millisecond), time: 0,
node: node, node: node,
lastTime: 0, step: 0,
step: 0,
}, nil }, nil
} }
// high performance generator // Generate creates and returns a unique snowflake ID
// well, that w as the idea... func (n *Node) Generate() (ID, error) {
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) {
n.Lock() n.Lock()
defer n.Unlock() defer n.Unlock()
now := time.Now().UnixNano() / 1000000 now := time.Now().UnixNano() / 1000000
if n.lastTime > now { if n.time == now {
return 0, fmt.Errorf("Invalid system time.") n.step = (n.step + 1) & stepMask
}
if n.lastTime == now {
n.step = (n.step + 1) & StepMask
if n.step == 0 { if n.step == 0 {
for now <= n.lastTime { for now <= n.time {
time.Sleep(100 * time.Microsecond)
now = time.Now().UnixNano() / 1000000 now = time.Now().UnixNano() / 1000000
} }
} }
@ -104,129 +76,75 @@ func (n *Node) LockedGenerate() (Flake, error) {
n.step = 0 n.step = 0
} }
n.lastTime = now n.time = now
return Flake((now-n.epoch)<<TimeShift | return ID((now-Epoch)<<timeShift |
(n.node << NodeShift) | (n.node << nodeShift) |
(n.step), (n.step),
), nil ), nil
} }
// Return a freshly generated Flake ID // Int64 returns an int64 of the snowflake ID
func (n *Node) Generate() (Flake, error) { func (f ID) Int64() int64 {
return int64(f)
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
} }
// Return a freshly generated Flake ID // String returns a string of the snowflake ID
func (n *Node) GenerateNoSleep() (Flake, error) { func (f ID) String() string {
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 {
return fmt.Sprintf("%d", f) 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) 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) return strconv.FormatInt(int64(f), 36)
} }
func (f Flake) Base64() string { // Base64 returns a base64 string of the snowflake ID
return base64.StdEncoding.EncodeToString(f.Byte()) 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()) return []byte(f.String())
} }
func (f Flake) Time() int64 { // Time returns an int64 unix timestamp of the snowflake ID time
// ugh.. TODO func (f ID) Time() int64 {
// epoch is supposed to be configurable.....
Epoch := time.Date(2016, 1, 0, 0, 0, 0, 0, time.UTC).UnixNano() / int64(time.Millisecond)
return (int64(f) >> 22) + Epoch 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 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 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
}

View File

@ -4,41 +4,9 @@ import (
"testing" "testing"
) )
////////////////////////////////////////////////////////////////////////////// func BenchmarkGenerate(b *testing.B) {
// Benchmarks Presence Update event with fake data. node, _ := NewNode(1)
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)
b.ReportAllocs() b.ReportAllocs()
for n := 0; n < b.N; n++ { 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()
}
})
}