diff --git a/pkg/uuid/internal_generator.go b/pkg/uuid/internal_generator.go new file mode 100644 index 00000000..db96dc43 --- /dev/null +++ b/pkg/uuid/internal_generator.go @@ -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, + } +} diff --git a/pkg/uuid/internal_generator_test.go b/pkg/uuid/internal_generator_test.go new file mode 100644 index 00000000..1f7b941e --- /dev/null +++ b/pkg/uuid/internal_generator_test.go @@ -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() +} diff --git a/pkg/uuid/uuid_container.go b/pkg/uuid/uuid_container.go new file mode 100644 index 00000000..2f9242c5 --- /dev/null +++ b/pkg/uuid/uuid_container.go @@ -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) +} diff --git a/pkg/uuid/uuid_generator.go b/pkg/uuid/uuid_generator.go new file mode 100644 index 00000000..e673a0ac --- /dev/null +++ b/pkg/uuid/uuid_generator.go @@ -0,0 +1,5 @@ +package uuid + +type UuidGenerator interface { + GenerateUuid(uuidType UuidType) int64 +} diff --git a/pkg/uuid/uuid_type.go b/pkg/uuid/uuid_type.go new file mode 100644 index 00000000..85add5a8 --- /dev/null +++ b/pkg/uuid/uuid_type.go @@ -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 +)