This commit is contained in:
bryanqiu 2022-12-03 19:56:56 +08:00
parent c1b56c8472
commit f6c68dec7c

155
uid.go
View File

@ -33,10 +33,24 @@ type IdCreatorHelper interface {
CreateMixedId(prefix string, id int64, parts ...interface{}) string CreateMixedId(prefix string, id int64, parts ...interface{}) string
} }
// Range Range define the range [start, end] of ids.
type Range struct {
Next int64 // next id
Status int // 0:invlid; 1:ok; 2:useout
Prefix string // prefix
Start int64 // begin id of this range(include)
End int64 // end id of this range(include)
Length int // lenght of this range
}
type rangeQueue struct { type rangeQueue struct {
deque.Deque[*Range] deque.Deque[*Range]
} }
const (
RANGE_STATUS_OK = 1
RANGE_STATUS_USEOUT = 2
)
// idCreator // idCreator
type idCreator struct { type idCreator struct {
lock sync.Mutex lock sync.Mutex
@ -59,10 +73,7 @@ func NewIdCreator(typ string, helper IdCreatorHelper) IdCreator {
// 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 { idg.createNewRangeWithErrorPanic(prefix)
log.Errorf("generate the first range failed: %s", err)
panic("NewIdCreator error")
}
} }
// Step 3. backgroud task for generate range // Step 3. backgroud task for generate range
@ -81,7 +92,7 @@ func NewIdCreator(typ string, helper IdCreatorHelper) IdCreator {
} }
if int(timeoutMs) > 0 { if int(timeoutMs) > 0 {
idg.createNewRange(prefix) idg.createNewRangeWithErrorPanic(prefix)
time.Sleep(timeoutMs * time.Millisecond) time.Sleep(timeoutMs * time.Millisecond)
} else if int(timeoutMs) < 0 { } else if int(timeoutMs) < 0 {
time.Sleep((-timeoutMs) * time.Millisecond) time.Sleep((-timeoutMs) * time.Millisecond)
@ -99,7 +110,8 @@ func (idg *idCreator) GetId(idparts ...interface{}) string {
for { for {
var id int64 = 0 var id int64 = 0
var currange = idg.getCurrentRange(prefix) var currange = idg.getCurrentRange(prefix)
log.Infof("success get current range:(%s, %d)", prefix, currange.Start) log.Infof("success get current range:(%s, %d-%d)",
prefix, currange.Start, currange.End)
// 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(&currange.Next) id = atomic.LoadInt64(&currange.Next)
@ -127,16 +139,6 @@ func (idg *idCreator) GetId(idparts ...interface{}) string {
} }
} }
// Range Range define the range [start, end] of ids.
type Range struct {
Next int64 // next 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 int // lenght of this range
}
func (idg *idCreator) 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 {
@ -149,25 +151,18 @@ func (idg *idCreator) createDefaultPrefix() (prefix string, err error) {
return prefix, err return prefix, err
} }
func (idg *idCreator) removeOldRange(prefix string) error { func (idg *idCreator) createNewRangeWithErrorPanic(prefix string) {
if lock, ok := idg.RangesLocks[prefix]; ok { if err := idg.createNewRange(prefix); err != nil {
lock.Lock() panic("create new range error:" + err.Error())
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) removeOldRangeWithErrorPanic(prefix string) {
if err := idg.removeOldRange(prefix); err != nil {
panic("remove old range error:" + err.Error())
}
}
func (idg *idCreator) createNewRange(prefix string) error { 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.Helper.CreateRange(prefix) start, length, err := idg.Helper.CreateRange(prefix)
@ -186,47 +181,68 @@ func (idg *idCreator) createNewRange(prefix string) error {
idg.Ranges[prefix] = &rangeQueue{Deque: *deque.New[*Range]()} idg.Ranges[prefix] = &rangeQueue{Deque: *deque.New[*Range]()}
idg.RangesLocks[prefix] = &sync.Mutex{} idg.RangesLocks[prefix] = &sync.Mutex{}
} }
var lock = idg.RangesLocks[prefix]
var queue = idg.Ranges[prefix] var queue = idg.Ranges[prefix]
var lock = idg.RangesLocks[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( var r = &Range{
&Range{
Next: start, Next: start,
Status: 1, Status: RANGE_STATUS_OK,
Prefix: prefix, Prefix: prefix,
Start: start, Start: start,
End: start + int64(length) - 1, End: start + int64(length) - 1,
Length: length, Length: length,
}) }
queue.PushBack(r)
log.Infof("success createNewRange (%s, %d-%d)", prefix, start, r.End)
return nil
}
func (idg *idCreator) removeOldRange(prefix string) error {
if lock, ok := idg.RangesLocks[prefix]; ok {
lock.Lock()
defer lock.Unlock()
queue := idg.Ranges[prefix]
for queue.Len() > 0 {
r := queue.Front()
if r.Status == RANGE_STATUS_OK {
break
} else if r.Status == RANGE_STATUS_USEOUT {
queue.PopFront()
log.Infof("success remove old range:(%s,%d-%d)",
prefix, r.Start, r.End)
} else {
panic("invalid status, maybe somethong bug")
}
}
} else {
log.Infof("rare case: rangequeue:(%s) have remove by others", prefix)
}
return nil return nil
} }
func (idg *idCreator) getCurrentRange(prefix string) (r *Range) { func (idg *idCreator) getCurrentRange(prefix string) (r *Range) {
// Step 0. get ranges queue by prefix.
var ( var (
trycount = 0
queue *rangeQueue = nil
ok = false ok = false
lock *sync.Mutex = nil
queue *rangeQueue = nil
) )
for trycount = 0; !ok && trycount < 3; trycount++ {
if queue, ok = idg.Ranges[prefix]; !ok {
idg.createNewRange(prefix)
}
}
if !ok {
panic("maybe something bug, cannot found range queue after createNewRange")
}
queue, ok = idg.Ranges[prefix] // Step 0. get ranges queue by prefix.
if !ok { for trycount := 0; !ok && trycount < 3; trycount++ {
if queue, ok = idg.Ranges[prefix]; ok {
lock = idg.RangesLocks[prefix]
break
}
// try create new range
idg.createNewRange(prefix) idg.createNewRange(prefix)
} }
var lock, okk = idg.RangesLocks[prefix] if !ok {
if !okk { panic("maybe something bug or too many pressures," +
panic("unreachable code: lock is not exist") "cannot found range queue after createNewRange retry 3 times")
} }
// Step 1. try read from queue directly. // Step 1. try read from queue directly.
@ -238,7 +254,7 @@ func (idg *idCreator) getCurrentRange(prefix string) (r *Range) {
} }
lock.Unlock() lock.Unlock()
// Step 2. try create a new range & try again. // Step 2. retry create a new range & try again.
idg.createNewRange(prefix) idg.createNewRange(prefix)
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
@ -253,31 +269,14 @@ 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 RANGE_STATUS_OK:
r.Status = 2 r.Status = RANGE_STATUS_USEOUT
idg.removeOldRange(r.Prefix) idg.createNewRangeWithErrorPanic(r.Prefix)
idg.createNewRange(r.Prefix) idg.removeOldRangeWithErrorPanic(r.Prefix)
case 2: case RANGE_STATUS_USEOUT:
//have turn by others, we do nothing. // have turn by others, we do nothing.
log.Debugf("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")
} }
} }
// 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) CreateRange(typ, prefix string) (start int64, length int, 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 = int64((time.Now().Unix() % 86400) * 10000)
length = 10000
return start, length, nil
}