Implementation
Some details of this implementation borrow from sonyflake.
Here using 8 bits to represent time in units of 500ms, 4 bits to represent sequence, 4 bits to represent machine.
As a result:
- The lifetime is (2^8 imes 500ms = 128s)
- It can work in (2^4 = 16) distributed machines
- It can generate (2^4 = 16) IDs per (500ms) at most in a single thread
Code
server
:
package main
import (
"errors"
"net/http"
"os"
"sync"
"time"
"github.com/gin-gonic/gin"
logger "github.com/sirupsen/logrus"
"github.com/x-cray/logrus-prefixed-formatter"
)
func init() {
logger.SetFormatter(&prefixed.TextFormatter{
TimestampFormat: "2006-01-02 15:04:05",
FullTimestamp: true,
ForceFormatting: true,
DisableColors: true,
})
logger.SetOutput(os.Stdout)
logger.SetLevel(logger.DebugLevel)
}
const (
BitLenTick = 8
BitLenSequence = 4
BitLenMachineID = 4
tickTimeUnit = time.Millisecond * 500
)
var idFlake *IDFlake
type IDFlake struct {
mu sync.Mutex
machineID int64
startTick int64 // ticks of the start time of IDFlake from UNIX epoch
elapsedTick int64 // elapsed ticks from start tick
sequence int64
}
func toFlakeTime(t time.Time) int64 {
return t.UTC().UnixNano() / int64(tickTimeUnit)
}
func NewIDFlake(machineID int64, startTime time.Time) *IDFlake {
return &IDFlake{
machineID: machineID,
startTick: toFlakeTime(startTime),
elapsedTick: 0,
sequence: 0,
}
}
func (f *IDFlake) toID() (uint64, error) {
if f.elapsedTick >= 1<<BitLenTick {
return 0, errors.New("over the time limit")
}
return uint64(f.elapsedTick<<(BitLenMachineID+BitLenSequence)) | uint64(f.machineID<<BitLenSequence) | uint64(f.sequence), nil
}
func (f *IDFlake) getID() (uint64, error) {
f.mu.Lock()
defer f.mu.Unlock()
currentTick := toFlakeTime(time.Now()) - f.startTick
if f.elapsedTick < currentTick {
f.sequence = 0
f.elapsedTick = currentTick
} else {
f.sequence = (f.sequence + 1) & (1<<BitLenSequence - 1)
if f.sequence == 0 {
f.elapsedTick++
sleepFor := sleepTime(f.elapsedTick - currentTick)
time.Sleep(sleepFor)
logger.Infof("slept for %.8fms", sleepFor.Seconds()*1000)
}
}
return f.toID()
}
func sleepTime(overTick int64) time.Duration {
return time.Duration(overTick)*tickTimeUnit - time.Duration(time.Now().UTC().UnixNano())%tickTimeUnit
}
func Decompose(id uint64) map[string]uint64 {
return map[string]uint64{
"id": id,
"tick": id >> (BitLenMachineID + BitLenSequence),
"machine": (id >> BitLenSequence) & (1<<BitLenMachineID - 1),
"sequence": id & (1<<BitLenSequence - 1),
}
}
func getID(c *gin.Context) {
id, err := idFlake.getID()
if err != nil {
logger.Warnf("getID error: %v", err)
c.JSON(http.StatusOK,
map[string]interface{}{
"error": err.Error(),
},
)
return
}
result := Decompose(id)
c.JSON(http.StatusOK, result)
}
func httpRun() {
r := gin.Default()
r.GET("/get_id", getID)
r.Run(":8080")
}
func main() {
idFlake = NewIDFlake(8, time.Now())
httpRun()
}
client
:
# coding: utf-8
import requests
def main():
while True:
res = requests.get('http://127.0.0.1:8080/get_id').json()
print res
if res.get('error'):
break
if __name__ == '__main__':
main()
Run
server
:
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] Listening and serving HTTP on :8080
[2020-04-01 16:11:05] INFO slept for 285.71300000ms
[2020-04-01 16:11:06] INFO slept for 446.46500000ms
[2020-04-01 16:11:06] INFO slept for 450.56500000ms
[2020-04-01 16:11:07] INFO slept for 449.93100000ms
......
[2020-04-01 16:13:08] INFO slept for 449.52100000ms
[2020-04-01 16:13:09] INFO slept for 441.43400000ms
[2020-04-01 16:13:09] INFO slept for 434.80600000ms
[2020-04-01 16:13:10] INFO slept for 445.62500000ms
[2020-04-01 16:13:10] INFO slept for 444.83400000ms
[2020-04-01 16:13:11] INFO slept for 437.82700000ms
[2020-04-01 16:13:11] WARN getID error: over the time limit
client
:
{u'machine': 0, u'tick': 4, u'id': 1024, u'sequence': 0}
{u'machine': 0, u'tick': 4, u'id': 1025, u'sequence': 1}
{u'machine': 0, u'tick': 4, u'id': 1026, u'sequence': 2}
{u'machine': 0, u'tick': 4, u'id': 1027, u'sequence': 3}
{u'machine': 0, u'tick': 4, u'id': 1028, u'sequence': 4}
{u'machine': 0, u'tick': 4, u'id': 1029, u'sequence': 5}
{u'machine': 0, u'tick': 4, u'id': 1030, u'sequence': 6}
{u'machine': 0, u'tick': 4, u'id': 1031, u'sequence': 7}
{u'machine': 0, u'tick': 4, u'id': 1032, u'sequence': 8}
{u'machine': 0, u'tick': 4, u'id': 1033, u'sequence': 9}
{u'machine': 0, u'tick': 4, u'id': 1034, u'sequence': 10}
{u'machine': 0, u'tick': 4, u'id': 1035, u'sequence': 11}
{u'machine': 0, u'tick': 4, u'id': 1036, u'sequence': 12}
{u'machine': 0, u'tick': 4, u'id': 1037, u'sequence': 13}
{u'machine': 0, u'tick': 4, u'id': 1038, u'sequence': 14}
{u'machine': 0, u'tick': 4, u'id': 1039, u'sequence': 15}
{u'machine': 0, u'tick': 5, u'id': 1280, u'sequence': 0}
{u'machine': 0, u'tick': 5, u'id': 1281, u'sequence': 1}
{u'machine': 0, u'tick': 5, u'id': 1282, u'sequence': 2}
{u'machine': 0, u'tick': 5, u'id': 1283, u'sequence': 3}
{u'machine': 0, u'tick': 5, u'id': 1284, u'sequence': 4}
......
{u'machine': 0, u'tick': 254, u'id': 65037, u'sequence': 13}
{u'machine': 0, u'tick': 254, u'id': 65038, u'sequence': 14}
{u'machine': 0, u'tick': 254, u'id': 65039, u'sequence': 15}
{u'machine': 0, u'tick': 255, u'id': 65280, u'sequence': 0}
{u'machine': 0, u'tick': 255, u'id': 65281, u'sequence': 1}
{u'machine': 0, u'tick': 255, u'id': 65282, u'sequence': 2}
{u'machine': 0, u'tick': 255, u'id': 65283, u'sequence': 3}
{u'machine': 0, u'tick': 255, u'id': 65284, u'sequence': 4}
{u'machine': 0, u'tick': 255, u'id': 65285, u'sequence': 5}
{u'machine': 0, u'tick': 255, u'id': 65286, u'sequence': 6}
{u'machine': 0, u'tick': 255, u'id': 65287, u'sequence': 7}
{u'machine': 0, u'tick': 255, u'id': 65288, u'sequence': 8}
{u'machine': 0, u'tick': 255, u'id': 65289, u'sequence': 9}
{u'machine': 0, u'tick': 255, u'id': 65290, u'sequence': 10}
{u'machine': 0, u'tick': 255, u'id': 65291, u'sequence': 11}
{u'machine': 0, u'tick': 255, u'id': 65292, u'sequence': 12}
{u'machine': 0, u'tick': 255, u'id': 65293, u'sequence': 13}
{u'machine': 0, u'tick': 255, u'id': 65294, u'sequence': 14}
{u'machine': 0, u'tick': 255, u'id': 65295, u'sequence': 15}
{u'error': u'over the time limit'}