initial commit
This commit is contained in:
parent
b47a20bc89
commit
f1a31db501
166
flake.go
Normal file
166
flake.go
Normal file
@ -0,0 +1,166 @@
|
||||
// generates IDs for Tasktic
|
||||
// Based on the common flake/snowflake implementations
|
||||
// 41bit timestamp, 10bit node, 12bit sequence
|
||||
// can build 4096 unique IDs per millisecond per node.
|
||||
// node ID must be defined and you should insure the
|
||||
// system can maintaine accurate time on all nodes.
|
||||
|
||||
// simple generator for..
|
||||
// Twitter Snowflake with custom definable epoch
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
if node < 0 || node > NodeMax {
|
||||
return nil, fmt.Errorf("Invalid node number.")
|
||||
}
|
||||
|
||||
return &Node{
|
||||
epoch: time.Date(2016, 1, 0, 0, 0, 0, 0, time.UTC).UnixNano() / int64(time.Millisecond),
|
||||
node: node,
|
||||
lastTime: 0,
|
||||
step: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// high performance generator
|
||||
func (n *Node) Generator(c chan Flake) {
|
||||
|
||||
}
|
||||
|
||||
// Return a freshly generated Flake ID
|
||||
func (n *Node) LockedGenerate() (Flake, 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.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
|
||||
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
|
||||
}
|
||||
|
||||
type Flake int64
|
||||
type Flakes []*Flake
|
||||
|
||||
func (f Flake) String() string {
|
||||
return fmt.Sprintf("%d", f)
|
||||
}
|
||||
|
||||
func (f Flake) Base2() string {
|
||||
return strconv.FormatInt(int64(f), 2)
|
||||
}
|
||||
func (f Flake) Base36() string {
|
||||
return strconv.FormatInt(int64(f), 36)
|
||||
}
|
||||
|
||||
func (f Flake) Base64() string {
|
||||
return base64.StdEncoding.EncodeToString(f.Byte())
|
||||
}
|
||||
|
||||
func (f Flake) Byte() []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)
|
||||
return (int64(f) >> 22) + Epoch
|
||||
}
|
||||
|
||||
func (f Flake) Node() int64 {
|
||||
return int64(f) & 0x00000000003FF000 >> 12
|
||||
}
|
||||
|
||||
func (f Flake) Sequence() int64 {
|
||||
return int64(f) & 0x0000000000000FFF
|
||||
}
|
44
flake_test.go
Normal file
44
flake_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
package flake
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 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