initial version
This commit is contained in:
commit
ea81f5a3a6
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module qoobing.com/gomod/uid
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/gammazero/deque v0.2.1
|
230
uid.go
Normal file
230
uid.go
Normal file
@ -0,0 +1,230 @@
|
||||
// Copyright 2022 @qoobing.com. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// Unique id creator tools, It can be used to create 'userid' or
|
||||
// 'transaction id' or something else need global unique id in a
|
||||
// service cluster.
|
||||
package uid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gammazero/deque"
|
||||
)
|
||||
|
||||
// IdCreator
|
||||
type IdCreator interface {
|
||||
GetId(parts ...interface{}) string
|
||||
}
|
||||
|
||||
// IdCreatorHelper
|
||||
type IdCreatorHelper interface {
|
||||
// CreatePrefix create id prefix
|
||||
CreatePrefix(parts ...interface{})
|
||||
// CreateRange create id range from database table t_id.
|
||||
CreateRange(prefix string) (start, length int64, err error)
|
||||
// CreateMixedId create finnal transaction id
|
||||
CreateMixedId(prefix string, id int64, parts ...interface{}) string
|
||||
}
|
||||
|
||||
// idCreater
|
||||
type idCreater struct {
|
||||
lock sync.Mutex
|
||||
Type string
|
||||
Ranges map[string]*deque.Deque
|
||||
Helper IdCreateHelper
|
||||
}
|
||||
|
||||
// NewIdCreater to new a id creator by helper.
|
||||
func NewIdCreater(typ string, helper IdCreatorHelper) IdCreater {
|
||||
// Step 1. basic value
|
||||
var idg = &idCreater{
|
||||
lock: sync.Mutex,
|
||||
Type: typ,
|
||||
Ranges: map[string]*deque.Deque{},
|
||||
Helper: helper,
|
||||
}
|
||||
|
||||
// Step 2. generate the first range.
|
||||
if prefix, err = idg.createDefaultPrefix(); err == nil {
|
||||
if err := idg.createNewRange(prefix); err != nil {
|
||||
log.Errorf("generate the first range failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3. backgroud task for generate range
|
||||
go func() {
|
||||
for {
|
||||
for prefix, queue := range idg.Ranges {
|
||||
var qlen = queue.Len()
|
||||
var timeoutMs = 1
|
||||
|
||||
if queueLen <= 0 {
|
||||
timeoutMs = 1
|
||||
} else if queueLen <= 2 {
|
||||
timeoutMs = 50
|
||||
} else {
|
||||
timeoutMs = -1000
|
||||
}
|
||||
|
||||
if timeoutMs > 0 {
|
||||
idg.createNewRange(prefix)
|
||||
time.Sleep(timeoutMs * time.Millisecond)
|
||||
} else if timeoutMs < 0 {
|
||||
time.Sleep((-timeoutMs) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
return idg
|
||||
}
|
||||
|
||||
func (idg *idCreater) GetId(idparts ...interface{}) string {
|
||||
var prefix = idg.Creator.CreatePrefix(idparts...)
|
||||
for {
|
||||
var id int64 = 0
|
||||
var cur = idg.getCurrentRange(prefix)
|
||||
// Step 1. Optimistic[no lock] to add id in current range.
|
||||
for {
|
||||
id = atomic.LoadInt64(&cur.Next)
|
||||
if id > cur.End {
|
||||
//next range
|
||||
break
|
||||
}
|
||||
if atomic.CompareAndSwapInt64(&cur.Next, id, id+1) {
|
||||
//success
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2. No lock add failed, try add id in next range.
|
||||
if id > cur.End {
|
||||
//next range
|
||||
idg.turnToNextRange(cur)
|
||||
continue
|
||||
}
|
||||
|
||||
// Step 3. Success, return mixed id.
|
||||
return idg.MixId(cur, id)
|
||||
}
|
||||
}
|
||||
|
||||
// Range Range define the range [start, end] of ids.
|
||||
type Range struct {
|
||||
Id int64 // id
|
||||
Status int // 0:invlid; 1:ok; 2:used
|
||||
Prefix string // prefix
|
||||
Start int64 // begin id of this range(include)
|
||||
End int64 // end id of this range(include)
|
||||
Length int64 // lenght of this range
|
||||
}
|
||||
|
||||
func (idg *idCreater) createDefaultPrefix() (prefix string, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("default prefix not surport by user")
|
||||
}
|
||||
}()
|
||||
if prefix = idg.Helper.CreatePrefix(); len(prefix) == 0 {
|
||||
err = fmt.Errorf("default prefix not surport by user")
|
||||
}
|
||||
return prefix, err
|
||||
}
|
||||
|
||||
func (idg *idCreater) createNewRange(prefix string) error {
|
||||
// Step 1. create range from db or something else.
|
||||
start, length, err := idg.Creator.CreateRange(idg.Type, prefix)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"idCreater '%s' generate range err:'%s'",
|
||||
idg.Type, err.Error())
|
||||
}
|
||||
|
||||
// Step 2. lock generator.
|
||||
idg.lock.Lock()
|
||||
defer idg.lock.Unlock()
|
||||
lock, ok := idg.RangesLocks[prefix]
|
||||
if !ok {
|
||||
lock = &sync.Mutex{}
|
||||
queue = deque.New[*Range]()
|
||||
idg.Ranges[prefix] = queue
|
||||
idg.RangesLocks[prefix] = lock
|
||||
}
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
// Step 3. push to ranges queue.
|
||||
queue.PushBack(
|
||||
&Range{
|
||||
Id: 0,
|
||||
Status: 1,
|
||||
Prefix: prefix,
|
||||
Start: start,
|
||||
End: start + length - 1,
|
||||
Length: length,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (idg *idCreater) getCurrentRange(prefix string) (r *Range) {
|
||||
// Step 0. get ranges queue by prefix.
|
||||
var queue, ok = idg.Ranges[prefix]
|
||||
if !ok {
|
||||
idg.createNewRange(prefix)
|
||||
}
|
||||
var lock, ok = idg.RangesLocks[prefix]
|
||||
if !ok {
|
||||
panic("unreachable code: lock is not exist")
|
||||
}
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
// Step 1. try read from queue directly.
|
||||
if queue.Len() >= 1 {
|
||||
return queue.Front()
|
||||
}
|
||||
|
||||
// Step 2. try create a new range & try again.
|
||||
idg.createNewRange(prefix)
|
||||
if queue.Len() >= 1 {
|
||||
return queue.Front()
|
||||
}
|
||||
panic("get current range failed, too many pressure???")
|
||||
}
|
||||
|
||||
func (idg *idCreater) turnToNextRange(r *Range) {
|
||||
// We donot lock the range, because there is no too much
|
||||
// side effect even if 'createNewRange' execute twice.
|
||||
switch r.Status {
|
||||
case 1:
|
||||
r.Status = 2
|
||||
idg.createNewRange(r.Prefix)
|
||||
case 2:
|
||||
//have turn by others, we do nothing.
|
||||
default:
|
||||
panic("invalid status, maybe somethong bug")
|
||||
}
|
||||
}
|
||||
|
||||
// demo demo range creator, return current timestamp % 86400 * lenght.
|
||||
// **DONOT** use in product enviroment.
|
||||
// **DONOT** use in product enviroment.
|
||||
// **DONOT** use in product enviroment.
|
||||
type demo struct{}
|
||||
|
||||
func (d *demo) GetRange(typ, prefix string) (start, length int64, err error) {
|
||||
var curdate string = time.Now().Format("20060102")
|
||||
if prefix != curdate {
|
||||
return 0, 0, fmt.Errorf("demo just support prefix is current date")
|
||||
}
|
||||
|
||||
start = uint64((time.Now().Unix() % 86400) * 10000)
|
||||
length = 10000
|
||||
return start, length, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user