Skip to content

Commit

Permalink
initial implementation (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
g8rswimmer authored Dec 1, 2020
1 parent 39ee217 commit 7c68d02
Show file tree
Hide file tree
Showing 5 changed files with 439 additions and 1 deletion.
72 changes: 71 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,71 @@
# go-flake
# go-flake

This is a simple go implementation of the twitter [snowflake](https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake.html) id generator.

The id breakdown is:
* 48 bits of a epoch time in milliseconds
* 5 bits of a worker id
* 12 bits of sequence

There are two ways to use the generator
* straight call to generate the id
* this method will allow the user to pass the worker and seqeunce
* use the generator struct
* this is for one worker id and will handle auto update of the sequence and check for rollover
* Errors can help with identifying possible id collision

## Examples
The following are the two implementation examples.

### Function Generation
```go

import (
"fmt"

"github.com/g8rswimmer/go-flake"
)

const (
worker = uint64(10)
seq = uint64(1000)
)

func main() {
id, err := flake.Generate(worker, seq)
if err != nil {
panic(err)
}
fmt.Printf("The generated id %v\n", id)
}
```

### Generator
```go
package main

import (
"fmt"

"github.com/g8rswimmer/go-flake"
)

const (
worker = uint64(10)
)

func main() {
gen, err := flake.New(worker)
if err != nil {
panic(err)
}

for i := 0; i < 10; i++ {
id, err := gen.Generate()
if err != nil {
panic(err)
}
fmt.Printf("The generated id %v\n", id)
}
}
```
20 changes: 20 additions & 0 deletions _examples/basic/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"fmt"

"github.com/g8rswimmer/go-flake"
)

const (
worker = uint64(10)
seq = uint64(1000)
)

func main() {
id, err := flake.Generate(worker, seq)
if err != nil {
panic(err)
}
fmt.Printf("The generated id %v\n", id)
}
26 changes: 26 additions & 0 deletions _examples/generator/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"fmt"

"github.com/g8rswimmer/go-flake"
)

const (
worker = uint64(10)
)

func main() {
gen, err := flake.New(worker)
if err != nil {
panic(err)
}

for i := 0; i < 10; i++ {
id, err := gen.Generate()
if err != nil {
panic(err)
}
fmt.Printf("The generated id %v\n", id)
}
}
101 changes: 101 additions & 0 deletions generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package flake

import (
"errors"
"fmt"
"sync"
"time"
)

// ID is the flake id 46 bits of time, 6 bits of worker and 12 bits of sequence
type ID uint64

// Decimal is the ID decimal string
func (i ID) Decimal() string {
return fmt.Sprintf("%d", i)
}

func (i ID) String() string {
t := uint64(i) >> 18
w := uint64(i) >> 12
w &= 0x3F
s := uint64(i) & 0xFFF

return fmt.Sprintf("%012X-%02X-%03X", t, w, s)
}

// ErrSequenceRollover is an error when the sequence number rolls over and the epoch time has not incremented
var ErrSequenceRollover = errors.New("flake generator sequence rollover")

const (
// InvalidID is an invalid id
InvalidID ID = 0
)

// Generate will create an id from the worker and sequence requested
func Generate(worker, seq uint64) (ID, error) {
switch {
case seq == 0 || seq >= 4096:
return InvalidID, fmt.Errorf("flake generator: sequence can not be zero or over 4095 [%d]", seq)
case worker == 0 || worker >= 64:
return InvalidID, fmt.Errorf("flake generator: worker can not be zero or over 63 [%d]", worker)
default:
}

epoch := uint64(time.Now().UnixNano() / int64(time.Millisecond))

return generate(epoch, worker, seq), nil
}

func generate(epoch, worker, seq uint64) ID {
id := epoch & 0x3FFFFFFFFFFF
id <<= 6
id |= uint64(worker & 0x3F)
id <<= 12
id |= uint64(seq) & 0xFFF
return ID(id)
}

// New will create a new generator for the worker
func New(worker uint64) (*Generator, error) {
switch {
case worker == 0 || worker >= 64:
return nil, fmt.Errorf("flake generator: worker can not be zero or over 63 [%d]", worker)
default:
}
return &Generator{
worker: worker,
mutex: sync.Mutex{},
}, nil
}

// Generator will create a new id from the worker
type Generator struct {
epoch uint64
seq uint64
worker uint64
mutex sync.Mutex
}

// Generate will create a new id
func (g *Generator) Generate() (ID, error) {
g.mutex.Lock()
defer g.mutex.Unlock()

epoch := uint64(time.Now().UnixNano() / int64(time.Millisecond))
if epoch != g.epoch {
g.epoch = epoch
g.seq = 0
}
g.seq++
if g.seq >= 4096 {
return InvalidID, ErrSequenceRollover
}

return generate(g.epoch, g.worker, g.seq), nil
}

// Wait will sleep for a millisecond, if there was an error on generate, this will help make sure that the sequence rollover will be handled.
func (g *Generator) Wait() {
time.Sleep(time.Millisecond)
}
Loading

0 comments on commit 7c68d02

Please sign in to comment.