This commit is contained in:
bryanqiu 2022-11-23 16:02:30 +08:00
parent ea81f5a3a6
commit 6f7e10aecb
2 changed files with 99 additions and 58 deletions

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=

151
uid.go
View File

@ -8,12 +8,14 @@
package uid package uid
import ( import (
"errors"
"fmt" "fmt"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/gammazero/deque" "github.com/gammazero/deque"
"qoobing.com/gomod/log"
) )
// IdCreator // IdCreator
@ -24,35 +26,42 @@ type IdCreator interface {
// IdCreatorHelper // IdCreatorHelper
type IdCreatorHelper interface { type IdCreatorHelper interface {
// CreatePrefix create id prefix // CreatePrefix create id prefix
CreatePrefix(parts ...interface{}) CreatePrefix(parts ...interface{}) string
// CreateRange create id range from database table t_id. // CreateRange create id range from database table t_id.
CreateRange(prefix string) (start, length int64, err error) CreateRange(prefix string) (start int64, length int, err error)
// CreateMixedId create finnal transaction id // CreateMixedId create finnal transaction id
CreateMixedId(prefix string, id int64, parts ...interface{}) string CreateMixedId(prefix string, id int64, parts ...interface{}) string
} }
// idCreater type rangeQueue struct {
type idCreater struct { deque.Deque[*Range]
lock sync.Mutex
Type string
Ranges map[string]*deque.Deque
Helper IdCreateHelper
} }
// NewIdCreater to new a id creator by helper. // idCreator
func NewIdCreater(typ string, helper IdCreatorHelper) IdCreater { type idCreator struct {
lock sync.Mutex
Type string
Ranges map[string]*rangeQueue
RangesLocks map[string]*sync.Mutex
Helper IdCreatorHelper
}
// NewIdCreator to new a id creator by helper.
func NewIdCreator(typ string, helper IdCreatorHelper) IdCreator {
// Step 1. basic value // Step 1. basic value
var idg = &idCreater{ var idg = &idCreator{
lock: sync.Mutex, lock: sync.Mutex{},
Type: typ, Type: typ,
Ranges: map[string]*deque.Deque{}, Ranges: map[string]*rangeQueue{},
RangesLocks: map[string]*sync.Mutex{},
Helper: helper, Helper: helper,
} }
// Step 2. generate the first range. // Step 2. generate the first range.
if prefix, err = idg.createDefaultPrefix(); err == nil { if prefix, err := idg.createDefaultPrefix(); err == nil {
if err := idg.createNewRange(prefix); err != nil { if err := idg.createNewRange(prefix); err != nil {
log.Errorf("generate the first range failed: %s", err) log.Errorf("generate the first range failed: %s", err)
panic("NewIdCreator error")
} }
} }
@ -61,20 +70,20 @@ func NewIdCreater(typ string, helper IdCreatorHelper) IdCreater {
for { for {
for prefix, queue := range idg.Ranges { for prefix, queue := range idg.Ranges {
var qlen = queue.Len() var qlen = queue.Len()
var timeoutMs = 1 var timeoutMs = 1 * time.Millisecond
if queueLen <= 0 { if qlen <= 0 {
timeoutMs = 1 timeoutMs = 1 * time.Millisecond
} else if queueLen <= 2 { } else if qlen <= 2 {
timeoutMs = 50 timeoutMs = 50 * time.Millisecond
} else { } else {
timeoutMs = -1000 timeoutMs = -1000 * time.Millisecond
} }
if timeoutMs > 0 { if int(timeoutMs) > 0 {
idg.createNewRange(prefix) idg.createNewRange(prefix)
time.Sleep(timeoutMs * time.Millisecond) time.Sleep(timeoutMs * time.Millisecond)
} else if timeoutMs < 0 { } else if int(timeoutMs) < 0 {
time.Sleep((-timeoutMs) * time.Millisecond) time.Sleep((-timeoutMs) * time.Millisecond)
} }
} }
@ -85,47 +94,50 @@ func NewIdCreater(typ string, helper IdCreatorHelper) IdCreater {
return idg return idg
} }
func (idg *idCreater) GetId(idparts ...interface{}) string { func (idg *idCreator) GetId(idparts ...interface{}) string {
var prefix = idg.Creator.CreatePrefix(idparts...) var prefix = idg.Helper.CreatePrefix(idparts...)
for { for {
var id int64 = 0 var id int64 = 0
var cur = idg.getCurrentRange(prefix) var currange = idg.getCurrentRange(prefix)
log.Infof("success get current range:(%s, %d)", prefix, currange.Start)
// Step 1. Optimistic[no lock] to add id in current range. // Step 1. Optimistic[no lock] to add id in current range.
for { for {
id = atomic.LoadInt64(&cur.Next) id = atomic.LoadInt64(&currange.Next)
if id > cur.End { if id > currange.End {
//next range //next range
break break
} }
if atomic.CompareAndSwapInt64(&cur.Next, id, id+1) { if atomic.CompareAndSwapInt64(&currange.Next, id, id+1) {
//success //success
break break
} }
} }
// Step 2. No lock add failed, try add id in next range. // Step 2. No lock add failed, try add id in next range.
if id > cur.End { if id > currange.End {
//next range //next range
idg.turnToNextRange(cur) log.Infof("failed get id from current range, try next")
idg.turnToNextRange(currange)
continue continue
} }
// Step 3. Success, return mixed id. // Step 3. Success, return mixed id.
return idg.MixId(cur, id) log.Infof("success get id(not miexed):(%s, %d)", prefix, id)
return idg.Helper.CreateMixedId(prefix, id, idparts...)
} }
} }
// Range Range define the range [start, end] of ids. // Range Range define the range [start, end] of ids.
type Range struct { type Range struct {
Id int64 // id Next int64 // next id
Status int // 0:invlid; 1:ok; 2:used Status int // 0:invlid; 1:ok; 2:used
Prefix string // prefix Prefix string // prefix
Start int64 // begin id of this range(include) Start int64 // begin id of this range(include)
End int64 // end id of this range(include) End int64 // end id of this range(include)
Length int64 // lenght of this range Length int // lenght of this range
} }
func (idg *idCreater) createDefaultPrefix() (prefix string, err error) { func (idg *idCreator) createDefaultPrefix() (prefix string, err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
err = fmt.Errorf("default prefix not surport by user") err = fmt.Errorf("default prefix not surport by user")
@ -137,76 +149,103 @@ func (idg *idCreater) createDefaultPrefix() (prefix string, err error) {
return prefix, err return prefix, err
} }
func (idg *idCreater) createNewRange(prefix string) error { func (idg *idCreator) removeOldRange(prefix string) error {
if lock, ok := idg.RangesLocks[prefix]; ok {
lock.Lock()
defer lock.Unlock()
queue := idg.Ranges[prefix]
for {
r := queue.Front()
if r.Status == 1 {
break
} else if r.Status == 2 {
queue.PopFront()
} else {
panic("invalid status, maybe somethong bug")
}
}
}
return nil
}
func (idg *idCreator) createNewRange(prefix string) error {
// Step 1. create range from db or something else. // Step 1. create range from db or something else.
start, length, err := idg.Creator.CreateRange(idg.Type, prefix) start, length, err := idg.Helper.CreateRange(prefix)
if err != nil { if err != nil {
return fmt.Errorf( str := fmt.Sprintf(
"idCreater '%s' generate range err:'%s'", "idCreator '%s' generate range err:'%s'",
idg.Type, err.Error()) idg.Type, err.Error())
log.Errorf("%s", str)
return errors.New(str)
} }
// Step 2. lock generator. // Step 2. lock generator.
idg.lock.Lock() idg.lock.Lock()
defer idg.lock.Unlock() defer idg.lock.Unlock()
lock, ok := idg.RangesLocks[prefix] if _, ok := idg.RangesLocks[prefix]; !ok {
if !ok { idg.Ranges[prefix] = &rangeQueue{Deque: *deque.New[*Range]()}
lock = &sync.Mutex{} idg.RangesLocks[prefix] = &sync.Mutex{}
queue = deque.New[*Range]()
idg.Ranges[prefix] = queue
idg.RangesLocks[prefix] = lock
} }
var lock = idg.RangesLocks[prefix]
var queue = idg.Ranges[prefix]
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
// Step 3. push to ranges queue. // Step 3. push to ranges queue.
queue.PushBack( queue.PushBack(
&Range{ &Range{
Id: 0, Next: start,
Status: 1, Status: 1,
Prefix: prefix, Prefix: prefix,
Start: start, Start: start,
End: start + length - 1, End: start + int64(length) - 1,
Length: length, Length: length,
}) })
return nil return nil
} }
func (idg *idCreater) getCurrentRange(prefix string) (r *Range) { func (idg *idCreator) getCurrentRange(prefix string) (r *Range) {
// Step 0. get ranges queue by prefix. // Step 0. get ranges queue by prefix.
var queue, ok = idg.Ranges[prefix] var queue, ok = idg.Ranges[prefix]
if !ok { if !ok {
idg.createNewRange(prefix) idg.createNewRange(prefix)
} }
var lock, ok = idg.RangesLocks[prefix] var lock, okk = idg.RangesLocks[prefix]
if !ok { if !okk {
panic("unreachable code: lock is not exist") panic("unreachable code: lock is not exist")
} }
lock.Lock()
defer lock.Unlock()
// Step 1. try read from queue directly. // Step 1. try read from queue directly.
lock.Lock()
if queue.Len() >= 1 { if queue.Len() >= 1 {
return queue.Front() r = queue.Front()
lock.Unlock()
return r
} }
lock.Unlock()
// Step 2. try create a new range & try again. // Step 2. try create a new range & try again.
idg.createNewRange(prefix) idg.createNewRange(prefix)
lock.Lock()
defer lock.Unlock()
if queue.Len() >= 1 { if queue.Len() >= 1 {
return queue.Front() r = queue.Front()
return r
} }
panic("get current range failed, too many pressure???") panic("get current range failed, too many pressure???")
} }
func (idg *idCreater) turnToNextRange(r *Range) { func (idg *idCreator) turnToNextRange(r *Range) {
// We donot lock the range, because there is no too much // We donot lock the range, because there is no too much
// side effect even if 'createNewRange' execute twice. // side effect even if 'createNewRange' execute twice.
switch r.Status { switch r.Status {
case 1: case 1:
r.Status = 2 r.Status = 2
idg.removeOldRange(r.Prefix)
idg.createNewRange(r.Prefix) idg.createNewRange(r.Prefix)
case 2: case 2:
//have turn by others, we do nothing. //have turn by others, we do nothing.
log.Debugf("have turn by others, we do nothing.")
default: default:
panic("invalid status, maybe somethong bug") panic("invalid status, maybe somethong bug")
} }
@ -218,13 +257,13 @@ func (idg *idCreater) turnToNextRange(r *Range) {
// **DONOT** use in product enviroment. // **DONOT** use in product enviroment.
type demo struct{} type demo struct{}
func (d *demo) GetRange(typ, prefix string) (start, length int64, err error) { func (d *demo) CreateRange(typ, prefix string) (start int64, length int, err error) {
var curdate string = time.Now().Format("20060102") var curdate string = time.Now().Format("20060102")
if prefix != curdate { if prefix != curdate {
return 0, 0, fmt.Errorf("demo just support prefix is current date") return 0, 0, fmt.Errorf("demo just support prefix is current date")
} }
start = uint64((time.Now().Unix() % 86400) * 10000) start = int64((time.Now().Unix() % 86400) * 10000)
length = 10000 length = 10000
return start, length, nil return start, length, nil
} }