add uuid generator
This commit is contained in:
@@ -0,0 +1,100 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mayswind/lab/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
INTERNAL_UUID_UNIX_TIME_BITS = 32
|
||||||
|
INTERNAL_UUID_UNIX_TIME_MASK = (1 << INTERNAL_UUID_UNIX_TIME_BITS) - 1
|
||||||
|
|
||||||
|
INTERNAL_UUID_TYPE_BITS = 4
|
||||||
|
INTERNAL_UUID_TYPE_MASK = (1 << INTERNAL_UUID_TYPE_BITS) - 1
|
||||||
|
|
||||||
|
INTERNAL_UUID_SERVER_ID_BITS = 8
|
||||||
|
INTERNAL_UUID_SERVER_ID_MASK = (1 << INTERNAL_UUID_SERVER_ID_BITS) - 1
|
||||||
|
|
||||||
|
INTERNAL_UUID_SEQ_ID_BITS = 19
|
||||||
|
INTERNAL_UUID_SEQ_ID_MASK = (1 << INTERNAL_UUID_SEQ_ID_BITS) - 1
|
||||||
|
|
||||||
|
SEQ_NUMBER_ID_BITS = 32
|
||||||
|
SEQ_NUMBER_ID_MASK = (1 << SEQ_NUMBER_ID_BITS) - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type InternalUuidInfo struct {
|
||||||
|
UnixTime uint32
|
||||||
|
UuidType uint8
|
||||||
|
UuidServerId uint8
|
||||||
|
SequentialId uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalUuidGenerator struct {
|
||||||
|
uuidServerId uint8
|
||||||
|
uuidSeqNumbers [1 << INTERNAL_UUID_TYPE_BITS]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInternalUuidGenerator(config *settings.Config) (*InternalUuidGenerator, error) {
|
||||||
|
generator := &InternalUuidGenerator{
|
||||||
|
uuidServerId: config.UuidServerId,
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *InternalUuidGenerator) GenerateUuid(idType UuidType) int64 {
|
||||||
|
// 63bits = unixTime(32bits) + uuidType(4bits) + uuidServerId(8bits) + sequentialNumber(19bits)
|
||||||
|
|
||||||
|
var unixTime uint64
|
||||||
|
var newSeqId uint64
|
||||||
|
uuidType := uint8(idType)
|
||||||
|
|
||||||
|
for {
|
||||||
|
unixTime = uint64(time.Now().Unix())
|
||||||
|
newSeqId = atomic.AddUint64(&u.uuidSeqNumbers[uuidType], 1)
|
||||||
|
|
||||||
|
if newSeqId>>SEQ_NUMBER_ID_BITS == unixTime {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSeqId := newSeqId
|
||||||
|
newSeqId = unixTime << SEQ_NUMBER_ID_BITS
|
||||||
|
|
||||||
|
if atomic.CompareAndSwapUint64(&u.uuidSeqNumbers[uuidType], currentSeqId, newSeqId) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seqId := newSeqId & SEQ_NUMBER_ID_MASK
|
||||||
|
|
||||||
|
unixTimePart := (int64(unixTime) & INTERNAL_UUID_UNIX_TIME_MASK) << (INTERNAL_UUID_TYPE_BITS + INTERNAL_UUID_SERVER_ID_BITS + INTERNAL_UUID_SEQ_ID_BITS)
|
||||||
|
uuidTypePart := (int64(uuidType) & INTERNAL_UUID_TYPE_MASK) << (INTERNAL_UUID_SERVER_ID_BITS + INTERNAL_UUID_SEQ_ID_BITS)
|
||||||
|
uuidServerIdPart := (int64(u.uuidServerId) & INTERNAL_UUID_SERVER_ID_MASK) << INTERNAL_UUID_SEQ_ID_BITS
|
||||||
|
seqIdPart := int64(seqId) & INTERNAL_UUID_SEQ_ID_MASK
|
||||||
|
|
||||||
|
uuid := unixTimePart | uuidTypePart | uuidServerIdPart | seqIdPart
|
||||||
|
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *InternalUuidGenerator) ParseUuidInfo(uuid int64) *InternalUuidInfo {
|
||||||
|
seqId := uint32(uuid & INTERNAL_UUID_SEQ_ID_MASK)
|
||||||
|
uuid = uuid >> INTERNAL_UUID_SEQ_ID_BITS
|
||||||
|
|
||||||
|
uuidServerId := uint8(uuid & INTERNAL_UUID_SERVER_ID_MASK)
|
||||||
|
uuid = uuid >> INTERNAL_UUID_SERVER_ID_BITS
|
||||||
|
|
||||||
|
uuidType := uint8(uuid & INTERNAL_UUID_TYPE_MASK)
|
||||||
|
uuid = uuid >> INTERNAL_UUID_TYPE_BITS
|
||||||
|
|
||||||
|
unixTime := uint32(uuid & INTERNAL_UUID_UNIX_TIME_MASK)
|
||||||
|
|
||||||
|
return &InternalUuidInfo{
|
||||||
|
UnixTime: unixTime,
|
||||||
|
UuidType: uuidType,
|
||||||
|
UuidServerId: uuidServerId,
|
||||||
|
SequentialId: seqId,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/lab/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateUuid(t *testing.T) {
|
||||||
|
expectedUnixTime := time.Now().Unix()
|
||||||
|
expectedUuidServerId := uint8(90)
|
||||||
|
expectedUuidType := UUID_TYPE_JOURNAL
|
||||||
|
|
||||||
|
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId})
|
||||||
|
uuid := generator.GenerateUuid(expectedUuidType)
|
||||||
|
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||||
|
|
||||||
|
actualUnixTime := uuidInfo.UnixTime
|
||||||
|
assert.Equal(t, uint32(expectedUnixTime), actualUnixTime)
|
||||||
|
|
||||||
|
actualUuidServerId := uuidInfo.UuidServerId
|
||||||
|
assert.Equal(t, expectedUuidServerId, actualUuidServerId)
|
||||||
|
|
||||||
|
actualUuidType := uuidInfo.UuidType
|
||||||
|
assert.Equal(t, uint8(expectedUuidType), actualUuidType)
|
||||||
|
|
||||||
|
expectedSeqId := 0
|
||||||
|
actualSeqId := uuidInfo.SequentialId
|
||||||
|
assert.Equal(t, uint32(expectedSeqId), actualSeqId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateUuid_MultiType(t *testing.T) {
|
||||||
|
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 1})
|
||||||
|
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||||
|
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||||
|
|
||||||
|
expectedSeqId := 0
|
||||||
|
actualSeqId := uuidInfo.SequentialId
|
||||||
|
assert.Equal(t, uint32(expectedSeqId), actualSeqId)
|
||||||
|
|
||||||
|
uuid = generator.GenerateUuid(UUID_TYPE_ACCOUNT)
|
||||||
|
uuidInfo = generator.ParseUuidInfo(uuid)
|
||||||
|
actualSeqId = uuidInfo.SequentialId
|
||||||
|
assert.Equal(t, uint32(expectedSeqId), actualSeqId)
|
||||||
|
|
||||||
|
uuid = generator.GenerateUuid(UUID_TYPE_JOURNAL)
|
||||||
|
uuidInfo = generator.ParseUuidInfo(uuid)
|
||||||
|
actualSeqId = uuidInfo.SequentialId
|
||||||
|
assert.Equal(t, uint32(expectedSeqId), actualSeqId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateUuid_1000Times(t *testing.T) {
|
||||||
|
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 2})
|
||||||
|
expectedUnixTime := time.Now().Unix()
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||||
|
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||||
|
|
||||||
|
assert.Equal(t, uint32(expectedUnixTime), uuidInfo.UnixTime)
|
||||||
|
assert.Equal(t, uint32(i), uuidInfo.SequentialId)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
expectedUnixTime = time.Now().Unix()
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||||
|
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||||
|
|
||||||
|
assert.Equal(t, uint32(expectedUnixTime), uuidInfo.UnixTime)
|
||||||
|
assert.Equal(t, uint32(i), uuidInfo.SequentialId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateUuid_1000000TimesConcurrent(t *testing.T) {
|
||||||
|
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 3})
|
||||||
|
var mutex sync.Mutex
|
||||||
|
var generatedIds sync.Map
|
||||||
|
var waitGroup sync.WaitGroup
|
||||||
|
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
go func() {
|
||||||
|
waitGroup.Add(1)
|
||||||
|
|
||||||
|
for j := 0; j < 40000; j++ {
|
||||||
|
if j%10000 == 0 { // echo server can only generate 500,000 (50 * 10000) uuids in one second
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedUnixTime := time.Now().Unix()
|
||||||
|
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||||
|
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||||
|
|
||||||
|
if uint32(expectedUnixTime) != uuidInfo.UnixTime {
|
||||||
|
mutex.Lock()
|
||||||
|
assert.Equal(t, uint32(expectedUnixTime), uuidInfo.UnixTime)
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if uuidInfo.SequentialId == 0 {
|
||||||
|
if existedUnixTime, exists := generatedIds.Load(uuid); exists {
|
||||||
|
mutex.Lock()
|
||||||
|
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, seq id is %d, existed unixtime is %d, current unix time is %d", uuid, uuidInfo.SequentialId, existedUnixTime, uuidInfo.UnixTime))
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedIds.Store(uuid, uuidInfo.UnixTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waitGroup.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
waitGroup.Wait()
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/lab/pkg/errs"
|
||||||
|
"github.com/mayswind/lab/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UuidContainer struct {
|
||||||
|
Current UuidGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
Container = &UuidContainer{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitializeUuidGenerator(config *settings.Config) error {
|
||||||
|
if config.UuidGeneratorType == settings.UUID_GENERATOR_TYPE_INTERNAL {
|
||||||
|
generator, err := NewInternalUuidGenerator(config)
|
||||||
|
Container.Current = generator
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs.ErrInvalidUuidMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UuidContainer) GenerateUuid(uuidType UuidType) int64 {
|
||||||
|
return u.Current.GenerateUuid(uuidType)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
type UuidGenerator interface {
|
||||||
|
GenerateUuid(uuidType UuidType) int64
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package uuid
|
||||||
|
|
||||||
|
type UuidType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
UUID_TYPE_DEFAULT UuidType = 0
|
||||||
|
UUID_TYPE_USER UuidType = 1
|
||||||
|
UUID_TYPE_ACCOUNT UuidType = 2
|
||||||
|
UUID_TYPE_JOURNAL UuidType = 3
|
||||||
|
UUID_TYPE_CATEGORY UuidType = 4
|
||||||
|
UUID_TYPE_TAG UuidType = 5
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user