From 7443e8a5320b85e1d66ec09d8dbefed30efa9ee3 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sun, 26 Mar 2023 23:58:30 +0800 Subject: [PATCH] uuid generator supports generating more than 1 uuid in one time --- pkg/services/base.go | 5 ++ pkg/uuid/internal_generator.go | 52 ++++++++---- pkg/uuid/internal_generator_test.go | 123 ++++++++++++++++++++++++++++ pkg/uuid/uuid_container.go | 5 ++ pkg/uuid/uuid_generator.go | 1 + 5 files changed, 170 insertions(+), 16 deletions(-) diff --git a/pkg/services/base.go b/pkg/services/base.go index 691310c5..e9cc267d 100644 --- a/pkg/services/base.go +++ b/pkg/services/base.go @@ -45,3 +45,8 @@ type ServiceUsingUuid struct { func (s *ServiceUsingUuid) GenerateUuid(uuidType uuid.UuidType) int64 { return s.container.GenerateUuid(uuidType) } + +// GenerateUuids generates new uuids according to given uuid type and count +func (s *ServiceUsingUuid) GenerateUuids(uuidType uuid.UuidType, count uint8) []int64 { + return s.container.GenerateUuids(uuidType, count) +} diff --git a/pkg/uuid/internal_generator.go b/pkg/uuid/internal_generator.go index c01c974e..b37ec619 100644 --- a/pkg/uuid/internal_generator.go +++ b/pkg/uuid/internal_generator.go @@ -48,40 +48,51 @@ func NewInternalUuidGenerator(config *settings.Config) (*InternalUuidGenerator, return generator, nil } -// GenerateUuid returns a new uuid +// GenerateUuid generates a new uuid func (u *InternalUuidGenerator) GenerateUuid(idType UuidType) int64 { + uuids := u.GenerateUuids(idType, 1) + return uuids[0] +} + +// GenerateUuids generates new uuids +func (u *InternalUuidGenerator) GenerateUuids(idType UuidType, count uint8) []int64 { // 63bits = unixTime(32bits) + uuidType(4bits) + uuidServerId(8bits) + sequentialNumber(19bits) + uuids := make([]int64, count) + + if count < 1 { + return uuids + } + var unixTime uint64 - var newSeqId uint64 + var newFirstSeqId uint64 + var newLastSeqId uint64 uuidType := uint8(idType) for { unixTime = uint64(time.Now().Unix()) - newSeqId = atomic.AddUint64(&u.uuidSeqNumbers[uuidType], 1) + newLastSeqId = atomic.AddUint64(&u.uuidSeqNumbers[uuidType], uint64(count)) - if newSeqId>>seqNumberIdBits == unixTime { + if newLastSeqId>>seqNumberIdBits == unixTime { + newFirstSeqId = newLastSeqId - uint64(count-1) break } - currentSeqId := newSeqId - newSeqId = unixTime << seqNumberIdBits + currentSeqId := newLastSeqId + newFirstSeqId = unixTime << seqNumberIdBits + newLastSeqId = newFirstSeqId + uint64(count-1) - if atomic.CompareAndSwapUint64(&u.uuidSeqNumbers[uuidType], currentSeqId, newSeqId) { + if atomic.CompareAndSwapUint64(&u.uuidSeqNumbers[uuidType], currentSeqId, newLastSeqId) { break } } - seqId := newSeqId & seqNumberIdMask + for i := 0; i < int(count); i++ { + seqId := (newFirstSeqId + uint64(i)) & seqNumberIdMask + uuids[i] = u.assembleUuid(unixTime, uuidType, seqId) + } - unixTimePart := (int64(unixTime) & internalUuidUnixTimeMask) << (internalUuidTypeBits + internalUuidServerIdBits + internalUuidSeqIdBits) - uuidTypePart := (int64(uuidType) & internalUuidTypeMask) << (internalUuidServerIdBits + internalUuidSeqIdBits) - uuidServerIdPart := (int64(u.uuidServerId) & internalUuidServerIdMask) << internalUuidSeqIdBits - seqIdPart := int64(seqId) & internalUuidSeqIdMask - - uuid := unixTimePart | uuidTypePart | uuidServerIdPart | seqIdPart - - return uuid + return uuids } // ParseUuidInfo returns a info struct which contains all information in uuid @@ -104,3 +115,12 @@ func (u *InternalUuidGenerator) ParseUuidInfo(uuid int64) *InternalUuidInfo { SequentialId: seqId, } } + +func (u *InternalUuidGenerator) assembleUuid(unixTime uint64, uuidType uint8, seqId uint64) int64 { + unixTimePart := (int64(unixTime) & internalUuidUnixTimeMask) << (internalUuidTypeBits + internalUuidServerIdBits + internalUuidSeqIdBits) + uuidTypePart := (int64(uuidType) & internalUuidTypeMask) << (internalUuidServerIdBits + internalUuidSeqIdBits) + uuidServerIdPart := (int64(u.uuidServerId) & internalUuidServerIdMask) << internalUuidSeqIdBits + seqIdPart := int64(seqId) & internalUuidSeqIdMask + + return unixTimePart | uuidTypePart | uuidServerIdPart | seqIdPart +} diff --git a/pkg/uuid/internal_generator_test.go b/pkg/uuid/internal_generator_test.go index c5e156d6..88f87485 100644 --- a/pkg/uuid/internal_generator_test.go +++ b/pkg/uuid/internal_generator_test.go @@ -121,3 +121,126 @@ func TestGenerateUuid_1000000TimesConcurrent(t *testing.T) { waitGroup.Wait() } + +func TestGenerateUuids_Count0(t *testing.T) { + expectedUuidServerId := uint8(90) + expectedUuidType := UUID_TYPE_TRANSACTION + + generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId}) + uuids := generator.GenerateUuids(expectedUuidType, 0) + + assert.NotEqual(t, nil, uuids) + assert.Equal(t, 0, len(uuids)) +} + +func TestGenerateUuids_Count255(t *testing.T) { + expectedUnixTime := time.Now().Unix() + expectedUuidServerId := uint8(90) + expectedUuidType := UUID_TYPE_TRANSACTION + expectedUuidCount := uint8(255) + + generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId}) + uuids := generator.GenerateUuids(expectedUuidType, expectedUuidCount) + + for i := 0; i < int(expectedUuidCount); i++ { + uuidInfo := generator.ParseUuidInfo(uuids[i]) + + 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 := i + actualSeqId := uuidInfo.SequentialId + assert.Equal(t, uint32(expectedSeqId), actualSeqId) + } + + assert.Equal(t, int(expectedUuidCount), len(uuids)) +} + +func TestGenerateUuids_30TimesIn3Seconds(t *testing.T) { + expectedUuidServerId := uint8(90) + expectedUuidType := UUID_TYPE_TRANSACTION + expectedUuidCount := uint8(255) + + generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: expectedUuidServerId}) + + for cycle := 0; cycle < 30; cycle++ { + expectedUnixTime := time.Now().Unix() + uuids := generator.GenerateUuids(expectedUuidType, expectedUuidCount) + var firstSeqId uint32 + + for i := 0; i < int(expectedUuidCount); i++ { + uuidInfo := generator.ParseUuidInfo(uuids[i]) + + 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) + + if i == 0 { + firstSeqId = uuidInfo.SequentialId + } else { + expectedSeqId := firstSeqId + uint32(i) + actualSeqId := uuidInfo.SequentialId + assert.Equal(t, expectedSeqId, actualSeqId) + } + } + + time.Sleep(100 * time.Millisecond) + } +} + +func TestGenerateUuids_1000000TimesConcurrent(t *testing.T) { + concurrentCount := 50 + expectedUuidCount := uint8(100) + generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 3}) + var mutex sync.Mutex + var generatedIds sync.Map + var waitGroup sync.WaitGroup + + for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ { + go func() { + waitGroup.Add(1) + + for cycle := 0; cycle < 400; cycle++ { + if cycle%100 == 0 { // each server can only generate 500,000 (50 * 10000) uuids in one second + time.Sleep(1000 * time.Millisecond) + } + + expectedUnixTime := time.Now().Unix() + uuids := generator.GenerateUuids(UUID_TYPE_USER, expectedUuidCount) + + for i := 0; i < int(expectedUuidCount); i++ { + uuidInfo := generator.ParseUuidInfo(uuids[i]) + + if uint32(expectedUnixTime) != uuidInfo.UnixTime { + mutex.Lock() + assert.Equal(t, uint32(expectedUnixTime), uuidInfo.UnixTime) + mutex.Unlock() + } + + if existedUnixTime, exists := generatedIds.Load(uuids[i]); exists { + mutex.Lock() + assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, seq id is %d, existed unixtime is %d, current unix time is %d", uuids[i], uuidInfo.SequentialId, existedUnixTime, uuidInfo.UnixTime)) + mutex.Unlock() + } + + generatedIds.Store(uuids[i], uuidInfo.UnixTime) + } + } + + waitGroup.Done() + }() + } + + waitGroup.Wait() +} diff --git a/pkg/uuid/uuid_container.go b/pkg/uuid/uuid_container.go index e442d65f..058bd2ee 100644 --- a/pkg/uuid/uuid_container.go +++ b/pkg/uuid/uuid_container.go @@ -31,3 +31,8 @@ func InitializeUuidGenerator(config *settings.Config) error { func (u *UuidContainer) GenerateUuid(uuidType UuidType) int64 { return u.Current.GenerateUuid(uuidType) } + +// GenerateUuids returns new uuids by the current uuid generator +func (u *UuidContainer) GenerateUuids(uuidType UuidType, count uint8) []int64 { + return u.Current.GenerateUuids(uuidType, count) +} diff --git a/pkg/uuid/uuid_generator.go b/pkg/uuid/uuid_generator.go index f8a545f6..3faf6779 100644 --- a/pkg/uuid/uuid_generator.go +++ b/pkg/uuid/uuid_generator.go @@ -3,4 +3,5 @@ package uuid // UuidGenerator is common uuid generator interface type UuidGenerator interface { GenerateUuid(uuidType UuidType) int64 + GenerateUuids(uuidType UuidType, count uint8) []int64 }