From cfa7691d14cc43194249ffec9e7592a5a1d47fd6 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 1 Jun 2016 14:59:26 -0500 Subject: [PATCH] The Real Initial Commit. --- README.md | 52 +++++++--- flake.go | 268 ++++++++++++++++++-------------------------------- flake_test.go | 101 +------------------ 3 files changed, 131 insertions(+), 290 deletions(-) diff --git a/README.md b/README.md index a706e2e..0958923 100644 --- a/README.md +++ b/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()) ``` diff --git a/flake.go b/flake.go index 9001032..a0212e7 100644 --- a/flake.go +++ b/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)< 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)< 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)<> 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 +} diff --git a/flake_test.go b/flake_test.go index a1d248e..08c00fb 100644 --- a/flake_test.go +++ b/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() - } - }) -}