diff --git a/idGenerator/assets/双缓存号段分配.webp b/idGenerator/assets/双缓存号段分配.webp new file mode 100644 index 0000000..d04b30c Binary files /dev/null and b/idGenerator/assets/双缓存号段分配.webp differ diff --git a/idGenerator/buffer.go b/idGenerator/buffer.go new file mode 100644 index 0000000..0ef78ba --- /dev/null +++ b/idGenerator/buffer.go @@ -0,0 +1,72 @@ +package idgenerator + +import ( + "sync" + "time" + + "github.com/charlienet/go-mixed/idGenerator/store" +) + +type obtainFunc func() (*store.Segment, error) + +type doubleBuffer struct { + current *store.Segment // 当前 + backup *store.Segment // 备用 + obtain obtainFunc // 数据段获取函数 + inFill bool // 备用缓冲填充中 + isReadly bool // 备用缓冲区填充完成 + mu sync.Mutex +} + +func newDoubleBuffer(obtainFunc obtainFunc) *doubleBuffer { + b := &doubleBuffer{obtain: obtainFunc} + b.current, _ = b.obtain() + + return b +} + +func (b *doubleBuffer) allot() (int64, bool) { + if !b.inFill && b.current.IsEnding() { + go b.full() // 填充备用缓冲 + } + + // 缓冲区耗尽时切换 + if b.current.IsEmpty() { + // 检查备用缓冲是否已经填充完成,已完成时切换,否则等待 + for !b.isReadly { + time.Sleep(time.Microsecond * 100) + } + + b.switchBuf() + } + + return b.current.Allot(), b.current.Reback() +} + +func (b *doubleBuffer) full() { + b.mu.Lock() + defer b.mu.Unlock() + + if !b.inFill { + var err error + b.inFill = true + + b.backup, err = b.obtain() + if err != nil { + println("填充失败:", err.Error()) + panic(err) + } + b.isReadly = true + } +} + +func (b *doubleBuffer) switchBuf() { + b.mu.Lock() + defer b.mu.Unlock() + + if b.isReadly { + b.current, b.backup = b.backup, b.current + b.inFill = false + b.isReadly = false + } +} diff --git a/idGenerator/buffer_test.go b/idGenerator/buffer_test.go new file mode 100644 index 0000000..a672550 --- /dev/null +++ b/idGenerator/buffer_test.go @@ -0,0 +1,25 @@ +package idgenerator + +import ( + "testing" + + "github.com/charlienet/go-mixed/idGenerator/store" + "github.com/charlienet/go-mixed/redis" + "github.com/charlienet/go-mixed/tests" +) + +func TestBufferAlloc(t *testing.T) { + + tests.RunOnRedis(t, func(rdb redis.Client) { + f := func() (*store.Segment, error) { + return store.NewRedisStore("sss", rdb).Assign(3, 99, 10) + } + + b := newDoubleBuffer(f) + + for i := 0; i < 80; i++ { + t.Log(b.allot()) + } + + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"}) +} diff --git a/idGenerator/formater.go b/idGenerator/formater.go new file mode 100644 index 0000000..c0b330e --- /dev/null +++ b/idGenerator/formater.go @@ -0,0 +1,132 @@ +package idgenerator + +import ( + "errors" + "math" + "strconv" + "time" +) + +const ( + YYYYMMDDHHmmss = "20060102150405" + YYYYMMDDHHmm = "200601021504" + YYYYMMDDHH = "2006010215" + YYYYMMDD = "20060102" +) + +type Layout int + +const ( + Binary Layout = iota + Decimal +) + +type formater interface { + MaxinalMachineCode() int64 + MaximalSequence() int64 + Format(machine, serial int64, reback bool) Id +} + +type Id int64 + +func (i Id) String() string { + if i == 0 { + return "" + } + + return strconv.FormatInt(int64(i), 10) +} + +func (i Id) Int64() int64 { + return int64(i) +} + +// 标识生成器 +type generator struct { + maximalSequence int64 // 序列段的最大值 + maxinalMachineCode int64 // 机器码的最大值 + sequenceLength int64 // 序列段长度 + machineCodeLength int64 // 机器码长度 + lastTimestamp int64 // 最后回绕时间 + getTimestampFunc func() int64 // 获取时间段的方法 +} + +func (g generator) MaxinalMachineCode() int64 { + return g.maxinalMachineCode +} + +func (g generator) MaximalSequence() int64 { + return g.maximalSequence +} + +func (g *generator) getTimestamp(reback bool) int64 { + newTimestamp := g.getTimestampFunc() + + for reback && g.lastTimestamp == newTimestamp { + time.Sleep(time.Microsecond * 10) + newTimestamp = g.getTimestampFunc() + } + + g.lastTimestamp = newTimestamp + return newTimestamp +} + +type binaryFormater struct { + generator +} + +func newBinaryFormatter(start int64, sequenceLength, machineCodeLength int64) (*binaryFormater, error) { + return &binaryFormater{ + generator: generator{ + maximalSequence: int64(-1 ^ (-1 << sequenceLength)), + maxinalMachineCode: int64(-1 ^ (-1 << machineCodeLength)), + sequenceLength: sequenceLength, + machineCodeLength: machineCodeLength, + getTimestampFunc: func() int64 { return time.Now().Unix() - start }, + }, + }, nil +} + +func (f *binaryFormater) Format(machine, serial int64, reback bool) Id { + timestamp := f.getTimestamp(reback) + return Id(timestamp<<(f.sequenceLength+f.machineCodeLength) | machine< decimalMaxLength { + return nil, errors.New("the data length is out of limit") + } + + serialShift := int64(math.Pow10(serialLength)) + machineShift := int64(math.Pow10(machineLength)) + + return &decimalFormater{ + generator: generator{ + sequenceLength: serialShift, + maximalSequence: serialShift - 1, + machineCodeLength: machineShift, + maxinalMachineCode: machineShift - 1, + getTimestampFunc: func() int64 { + now := time.Now() + v := now.Format(format) + r, _ := strconv.ParseInt(v, 10, 64) + return r + }, + }, + supportReback: len(format) == 14, + }, nil +} + +func (f *decimalFormater) Format(machine, serial int64, reback bool) Id { + timestamp := f.getTimestamp(reback) + return Id(timestamp*f.sequenceLength*f.machineCodeLength + machine*f.sequenceLength + serial) +} diff --git a/idGenerator/formater_test.go b/idGenerator/formater_test.go new file mode 100644 index 0000000..764e4b4 --- /dev/null +++ b/idGenerator/formater_test.go @@ -0,0 +1,34 @@ +package idgenerator + +import ( + "testing" +) + +func TestBinary(t *testing.T) { + f, _ := newBinaryFormatter(DefaultStartTimeStamp, 16, 12) + t.Log(f.maxinalMachineCode, f.maximalSequence) +} + +func TestDecimal(t *testing.T) { + f, _ := newDecimalFormater(YYYYMMDDHHmmss, 4, 1) + t.Log(f.maxinalMachineCode, f.maxinalMachineCode) + t.Log(f.Format(22333, 9, false)) +} + +func TestDecimalMonth111(t *testing.T) { + f, _ := newDecimalFormater(YYYYMMDD, 4, 1) + t.Log(f.maxinalMachineCode, f.maxinalMachineCode) + + t.Log(f.Format(233, 9, false)) +} + +func TestBinaryTimestamp(t *testing.T) { + f, _ := newBinaryFormatter(DefaultStartTimeStamp, 10, 4) + for i := 0; i < 100; i++ { + if i%7 == 0 { + t.Log(f.Format(int64(i), 0xF, true)) + } else { + t.Log(f.Format(int64(i), 0xF, false)) + } + } +} diff --git a/idGenerator/generator.go b/idGenerator/generator.go new file mode 100644 index 0000000..ac9a8ee --- /dev/null +++ b/idGenerator/generator.go @@ -0,0 +1,124 @@ +package idgenerator + +import ( + "time" + + "github.com/charlienet/go-mixed/idGenerator/store" + "github.com/charlienet/go-mixed/mathx" + "github.com/charlienet/go-mixed/redis" +) + +const ( + defaultDoubleBufferStep int64 = 50 +) + +var DefaultStartTimeStamp = time.Date(2023, 1, 1, 0, 0, 0, 0, time.Local).Unix() + +type opt func(*idGenerator) error + +type Option struct { + TimeFormat string + SerialLength int + MachineLength int +} + +type idGenerator struct { + store storage // 外部存储 + formater formater // 格式化器 + buffer *doubleBuffer // 序列缓冲 +} + +func WithMem(machineCode int64) opt { + return WithStore(store.NewMemStore(machineCode)) +} + +func WithRedis(key string, rdb redis.Client) opt { + return WithStore(store.NewRedisStore(key, rdb)) +} + +func WithStore(s storage) opt { + return func(ig *idGenerator) error { + ig.store = s + return nil + } +} + +func WithDecimalFormater(format string, serialLength, machineLength int) opt { + return func(ig *idGenerator) error { + f, err := newDecimalFormater(format, serialLength, machineLength) + if err != nil { + return err + } + + ig.formater = f + return nil + } +} + +func WithBinaryFormatter(start int64, serialLength, machineLength int64) opt { + return func(ig *idGenerator) error { + f, err := newBinaryFormatter(start, serialLength, machineLength) + if err != nil { + return err + } + + ig.formater = f + return nil + } +} + +func New(opts ...opt) (*idGenerator, error) { + g := &idGenerator{} + + for _, o := range opts { + err := o(g) + if err != nil { + return nil, err + } + } + + if g.store == nil { + g.store = store.NewMemStore(0) + } + + _, err := g.store.UpdateMachineCode(g.formater.MaxinalMachineCode()) // 初始化机器码 + if err != nil { + return nil, err + } + + g.buffer = newDoubleBuffer(g.obtain) // 初始化序列缓冲 + + return g, nil +} + +func (g *idGenerator) WithRedis(key string, rdb redis.Client) *idGenerator { + return g.WithStore(store.NewRedisStore(key, rdb)) +} + +func (g *idGenerator) WithStore(s storage) *idGenerator { + g.store = s + return g +} + +func (g *idGenerator) Next() Id { + serial, reback := g.buffer.allot() + id := g.formater.Format(g.store.MachineCode(), serial, reback) + + return id +} + +func (g *idGenerator) Close() { + if g.store != nil { + g.store.Close() + } +} + +func (g *idGenerator) obtain() (*store.Segment, error) { + step := mathx.Min(defaultDoubleBufferStep, g.formater.MaximalSequence()) + s, err := g.store.Assign(0, g.formater.MaximalSequence(), step) + if err != nil { + println("分配失败", err.Error()) + } + + return s, err +} diff --git a/idGenerator/genterator_test.go b/idGenerator/genterator_test.go new file mode 100644 index 0000000..1080559 --- /dev/null +++ b/idGenerator/genterator_test.go @@ -0,0 +1,151 @@ +package idgenerator_test + +import ( + "sync" + "testing" + + idgenerator "github.com/charlienet/go-mixed/idGenerator" + "github.com/charlienet/go-mixed/redis" + "github.com/charlienet/go-mixed/sets" + "github.com/charlienet/go-mixed/tests" +) + +var redisOption = redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"} + +func TestGenerator(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + generator, err := idgenerator.New( + idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 1, 1), + idgenerator.WithRedis("idgen_test", rdb)) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 20; i++ { + t.Log(generator.Next()) + } + }) +} + +func TestDecimalGenerator(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + generator, err := idgenerator.New( + idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1), + idgenerator.WithRedis("idgen_test", rdb)) + if err != nil { + t.Fatal(err) + } + for i := 0; i < 200; i++ { + t.Log(generator.Next()) + } + + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"}) +} + +func TestDecimalMonth(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + generator, err := idgenerator.New( + idgenerator.WithDecimalFormater(idgenerator.YYYYMMDD, 2, 1), + idgenerator.WithRedis("idgen_test", rdb)) + if err != nil { + t.Fatal(err) + } + for i := 0; i < 105; i++ { + t.Log(generator.Next()) + } + + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"}) +} + +func TestParallelCreate(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + var wg sync.WaitGroup + + wg.Add(2) + go func() { + defer wg.Done() + + g1, err := idgenerator.New( + idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1), + idgenerator.WithRedis("idgen_testcccc", rdb)) + if err != nil { + panic(err) + } + + _ = g1.Next().Int64() + }() + + go func() { + defer wg.Done() + + g2, err := idgenerator.New( + idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1), + idgenerator.WithRedis("idgen_testcccc", rdb)) + if err != nil { + panic(err) + } + + _ = g2.Next().Int64() + }() + + wg.Wait() + + }, redisOption) +} + +func TestParallel(t *testing.T) { + set := sets.NewHashSet[int64]().Sync() + opt := redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"} + + _ = set + f := func() { + tests.RunOnRedis(t, func(rdb redis.Client) { + generator, err := idgenerator.New( + idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1), + idgenerator.WithRedis("idgen_testcccc", rdb)) + if err != nil { + t.Fatal(err) + } + defer generator.Close() + + generator.Next() + for i := 0; i < 50000; i++ { + id := generator.Next().Int64() + if set.Contains(id) { + panic("生成重复") + } + set.Add(id) + } + + }, opt) + } + + var wg sync.WaitGroup + for i := 0; i < 6; i++ { + wg.Add(1) + go func() { + defer wg.Done() + f() + }() + } + + wg.Wait() +} + +func BenchmarkGenerator(b *testing.B) { + tests.RunOnRedis(b, func(rdb redis.Client) { + b.Run("bbb", func(b *testing.B) { + generator, err := idgenerator.New( + idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1), + idgenerator.WithRedis("idgen_test", rdb)) + if err != nil { + b.Fatal(err) + } + + for i := 0; i < 999; i++ { + generator.Next() + } + + }) + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"}) +} diff --git a/idGenerator/id_generator.go b/idGenerator/id_generator.go deleted file mode 100644 index ce7f741..0000000 --- a/idGenerator/id_generator.go +++ /dev/null @@ -1,118 +0,0 @@ -package idgenerator - -import ( - "fmt" - "math" - "time" - - _ "unsafe" -) - -// 时间段开始时间 2022-01-01 -const startTimeStamp = 1640966400 - -const ( - MachineIdBits = uint(8) //机器id所占的位数 - SequenceBits = uint(12) //序列所占的位数 - MachineIdMax = int64(-1 ^ (-1 << MachineIdBits)) //支持的最大机器id数量 - SequenceMask = uint64(-1 ^ (-1 << SequenceBits)) // - MachineIdShift = uint64(SequenceBits) //机器id左移位数 - TimestampShift = uint64(SequenceBits + MachineIdBits) //时间戳左移位数 -) - -type TimePrecision int - -const ( - Second TimePrecision = iota // 秒 - Minute // 分 - Day // 日 -) - -type Config struct { - Machine int - TimeScope TimePrecision -} - -type Generator struct { - machine uint64 - timeScope TimePrecision // 时间段精度 - timestamp uint64 // 上次生成时间 - sequence uint64 // 上次使用序列 -} - -type Id struct { - machine uint64 // 机器标识 - Scope int // 时间精度 - Timestamp uint64 // 生成时间 - Sequence uint64 // 标识序列 -} - -func New(cfg Config) *Generator { - return &Generator{ - machine: uint64(cfg.Machine), - timeScope: cfg.TimeScope, - } -} - -func (g *Generator) Next() Id { - - now := currentTimestamp() - - if g.timestamp == now && g.sequence == 0 { - fmt.Println(time.Now().Format("2006-01-02 15:04:05.000"), "下一个时间点") - for now <= g.timestamp { - // runtime.Gosched() - now = currentTimestamp() - } - } - - g.timestamp = now // 标识生成时间 - g.sequence = (g.sequence + 1) & SequenceMask // 生成下一序列,超过最大值时返回零 - - return Id{ - machine: g.machine, - Timestamp: g.timestamp, - Sequence: g.sequence, - } -} - -func (i Id) Id() uint64 { - return i.Timestamp< max_seq,将分配上限提升一个步长 max_seq += step,并持久化 max_seq;重启时,读出持久化的 max_seq,赋值给 cur_seq。此种处理方式可以降低持久化的硬盘 IO 次数,可以系统的整体吞吐量。 + +2)分号段共享存储:引入号段 section 的概念,uin 相邻的一段用户属于一个号段,共享一个 max_seq。该处理方式可以大幅减少 max_seq 数据的大小,同时可以进一步地降低 IO 次数。 + + +二进制模式 +按照二进制模式处理序列号生成,各数据段按照各自的位长度组装 + +十进制模式 + + +缺陷 + +当发生序列回绕时,不同的时间序列有不同的处理。如时间序列精度为日,那么在发生回绕时认为此时的回绕不能发生。如果应用在当日内被重启,并且发生了回绕,当前机制无法检测此问题。 +序列记录可以依赖于外部存储,如Redis,DB等。当外部存储失效时序列可能会发生冲突。此时当时间段没有前进时可能发生重复。一般认为DB为不失效存储。 + + +需要烦精确的选择时间段和序列的相对关系,一般情况下 + + + + +ID分配器工作流程 + +初始化 +1. 创建生成器 +2. 创建外部存储 +3. 指定生成器生成规则,机器码长度,序列长度,序列化格式。 +4. 创建双缓存,使用外部存储分配数据段。 + + +标识分配 +1. 向序列缓冲器获取下一序列 +2. 向时间段分配器获取下一时间戳(传入是否回旋) +4. 由标识组装器获取 + +格式化器 + + 十进制格式化,时间段格式串 + 二进制格式化,起始时间,时间精度 \ No newline at end of file diff --git a/idGenerator/store.go b/idGenerator/store.go new file mode 100644 index 0000000..93152d3 --- /dev/null +++ b/idGenerator/store.go @@ -0,0 +1,11 @@ +package idgenerator + +import "github.com/charlienet/go-mixed/idGenerator/store" + +// 序列存储分配器 +type storage interface { + MachineCode() int64 // 当前机器码 + UpdateMachineCode(max int64) (int64, error) // 更新机器标识 + Assign(min, max, step int64) (*store.Segment, error) // 分配号段 + Close() +} diff --git a/idGenerator/store/mem_store.go b/idGenerator/store/mem_store.go new file mode 100644 index 0000000..a36c691 --- /dev/null +++ b/idGenerator/store/mem_store.go @@ -0,0 +1,60 @@ +package store + +import ( + "sync" + + "github.com/charlienet/go-mixed/mathx" +) + +type memStore struct { + mu sync.Mutex + machine int64 + current int64 +} + +func NewMemStore(machineCode int64) *memStore { + return &memStore{machine: machineCode} +} + +func (s *memStore) UpdateMachineCode(max int64) (int64, error) { + return s.machine, nil +} +func (s *memStore) MachineCode() int64 { + return s.machine +} + +func (s *memStore) Assign(min, max, step int64) (*Segment, error) { + s.mu.Lock() + defer s.mu.Unlock() + + step = mathx.Min(step, max) + start := mathx.Max(s.current, min) + end := start + step + + reback := false + if start >= max { + start = min + end = step + s.current = end + + reback = true + } + + if end > max { + end = max + s.current = end + } + + s.current = end + + return &Segment{ + start: start, + current: start, + end: end, + reback: reback, + }, nil +} + +func (s *memStore) Close() { + +} diff --git a/idGenerator/store/mem_store_test.go b/idGenerator/store/mem_store_test.go new file mode 100644 index 0000000..6e38beb --- /dev/null +++ b/idGenerator/store/mem_store_test.go @@ -0,0 +1,21 @@ +package store_test + +import ( + "testing" + + "github.com/charlienet/go-mixed/idGenerator/store" +) + +func TestMemSmall(t *testing.T) { + s := store.NewMemStore(2) + for i := 0; i < 10; i++ { + t.Log(s.Assign(1, 9, 20)) + } +} + +func TestMemBig(t *testing.T) { + s := store.NewMemStore(2) + for i := 0; i < 10; i++ { + t.Log(s.Assign(0, 99, 18)) + } +} diff --git a/idGenerator/store/mysql_store.go b/idGenerator/store/mysql_store.go new file mode 100644 index 0000000..3ed177b --- /dev/null +++ b/idGenerator/store/mysql_store.go @@ -0,0 +1,2 @@ +package store + diff --git a/idGenerator/store/redis_store.go b/idGenerator/store/redis_store.go new file mode 100644 index 0000000..79ae021 --- /dev/null +++ b/idGenerator/store/redis_store.go @@ -0,0 +1,194 @@ +package store + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "github.com/charlienet/go-mixed/rand" + "github.com/charlienet/go-mixed/redis" +) + +const ( + allocatedKey = "allocated" + sequenceKey = "sequence" + delaySecond = 10 +) + +const ( + // 机器码分配及保活,检查机器码键值是否匹配,如匹配成功对键进行延时。如不匹配需要重新申请机器码 + // 请求参数 机器码,机器识别码,机器码最大值 + machineLua = ` + local newKey = KEYS[1]..":"..ARGV[1] + if redis.call("GET", newKey) == ARGV[2] then + redis.call("EXPIRE", newKey, 10) + return tonumber(ARGV[1]) + else + for i = 0, ARGV[3], 1 do + newKey = KEYS[1]..":"..tostring(i) + + if redis.call("EXISTS", newKey) == 0 then + redis.call("SET", newKey, ARGV[2], "EX", 10) + return i + end + end + return -1 + end` + + // 序列分配min, max, step + segmentLua = ` +local key = KEYS[1] +local min = tonumber(ARGV[1]) +local max = tonumber(ARGV[2]) +local step = tonumber(ARGV[3]) + +if step > max then + step = max +end + +if redis.call("EXISTS", key) == 0 then + redis.call("SET", key, step) + return {min, step, 0} +end + +local begin = tonumber(redis.call("GET", key)) +local increase = redis.call("INCRBY", key, step) +local reback = 0 + +if begin >= max then + begin = min + increase = step + + redis.call("SET", key, step) + reback = 1 +end + +if increase > max then + increase = max + redis.call("SET", key, increase) +end + +return {begin, increase, reback} +` +) + +type redisStore struct { + rdb redis.Client + machinekey string // 机器码键 + sequenceKey string // 序列键 + value string // 随机键值 + machineCode int64 // 机器码 + close chan struct{} // 关闭保活协程 + isRunning bool // 是否已经关闭 + mu sync.Mutex +} + +func NewRedisStore(key string, rdb redis.Client) *redisStore { + return &redisStore{ + rdb: rdb, + machinekey: rdb.JoinKeys(key, allocatedKey), + sequenceKey: rdb.JoinKeys(key, sequenceKey), + value: rand.Hex.Generate(24), + close: make(chan struct{}), + } +} + +// 分配机器标识,分配值为-1时表示分配失败 +func (s *redisStore) UpdateMachineCode(max int64) (int64, error) { + err := s.updateMachine(max) + if err != nil { + return -1, err + } + + // 关闭原协程,开启新的保活协程 + + // if s.isRunning { + // s.close <- struct{}{} + // } + + // if !s.isRunning { + // s.close <- struct{}{} + go s.keepAlive(max) + // } + + return s.machineCode, nil +} + +func (s *redisStore) MachineCode() int64 { + return s.machineCode +} + +func (s *redisStore) Assign(min, max, step int64) (*Segment, error) { + s.mu.Lock() + defer s.mu.Unlock() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + + // 序列段分配 + key := s.rdb.JoinKeys(s.sequenceKey, fmt.Sprintf("%v", s.machineCode)) + r, err := s.rdb.Eval(ctx, segmentLua, []string{key}, min, max, step).Result() + if err != nil { + return &Segment{}, err + } + + start, end, reback := split(r) + return &Segment{start: start, end: end, current: start, reback: reback}, err +} + +func split(r any) (start, end int64, reback bool) { + if result, ok := r.([]any); ok { + start = result[0].(int64) + end = result[1].(int64) + reback = result[2].(int64) == 1 + } + + return +} + +func (s *redisStore) updateMachine(max int64) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + r, err := s.rdb.Eval(ctx, machineLua, []string{s.machinekey}, s.machineCode, s.value, max).Result() + if err != nil { + return err + } + + if r == nil { + return errors.New("failed to obtain machine code") + } + + s.machineCode = r.(int64) + if s.machineCode == -1 { + return errors.New("machine code allocation failed") + } + + return nil +} + +func (s *redisStore) Close() { + s.close <- struct{}{} + s.isRunning = false +} + +func (s *redisStore) keepAlive(max int64) { + t := time.NewTicker(time.Second * (delaySecond / 3)) + defer t.Stop() + + for { + select { + case <-t.C: + // println("当前机器码:", s.machineCode) + err := s.updateMachine(max) + if err != nil { + fmt.Println("err:", err.Error()) + } + case <-s.close: + println("保活停止") + return + } + } +} diff --git a/idGenerator/store/redis_store_test.go b/idGenerator/store/redis_store_test.go new file mode 100644 index 0000000..965ee4c --- /dev/null +++ b/idGenerator/store/redis_store_test.go @@ -0,0 +1,106 @@ +package store_test + +import ( + "testing" + "time" + + "github.com/charlienet/go-mixed/idGenerator/store" + "github.com/charlienet/go-mixed/redis" + "github.com/charlienet/go-mixed/tests" +) + +func TestSmallSerail(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + s := store.NewRedisStore("sss", rdb) + for i := 0; i < 5; i++ { + t.Log(s.Assign(0, 9, 20)) + } + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"}) +} + +func TestSmallAssign(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + + s := store.NewRedisStore("sss", rdb) + + for i := 0; i < 10; i++ { + t.Log(s.Assign(0, 9, 30)) + } + + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"}) +} + +func TestBigAssign(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + + s := store.NewRedisStore("sss", rdb) + + for i := 0; i < 102; i++ { + t.Log(s.Assign(0, 99, 10)) + } + + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"}) +} + +func TestRedisAssign(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + + s := store.NewRedisStore("sss", rdb) + + for i := 0; i < 10; i++ { + t.Log(s.Assign(21, 99, 30)) + } + + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"}) +} + +func TestFullRedisAssign(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + + s := store.NewRedisStore("sss", rdb) + + for i := 0; i < 10; i++ { + t.Log(s.Assign(0, 999, 99)) + } + + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456"}) +} + +func TestUpdateMachineCode(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + + for i := 0; i < 20; i++ { + s := store.NewRedisStore("id", rdb) + code, err := s.UpdateMachineCode(99) + t.Log("获取到机器标识:", code, err) + + if err != nil { + return + } + + time.Sleep(time.Millisecond * 100) + + // s.Close() + } + + time.Sleep(time.Second * 10) + + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456", Prefix: "cacc"}) + +} + +func TestUpdate(t *testing.T) { + tests.RunOnRedis(t, func(rdb redis.Client) { + s := store.NewRedisStore("id", rdb) + s.UpdateMachineCode(99) + t.Log(s.MachineCode()) + + s.UpdateMachineCode(99) + t.Log(s.MachineCode()) + + s2 := store.NewRedisStore("id", rdb) + s2.UpdateMachineCode(99) + t.Log(s2.MachineCode()) + + }, redis.ReidsOption{Addr: "192.168.123.50:6379", Password: "123456", Prefix: "cacc"}) +} diff --git a/idGenerator/store/segment.go b/idGenerator/store/segment.go new file mode 100644 index 0000000..2307182 --- /dev/null +++ b/idGenerator/store/segment.go @@ -0,0 +1,33 @@ +package store + +import "fmt" + +// 号段 +type Segment struct { + start int64 + end int64 + current int64 + reback bool +} + +func (s *Segment) Allot() int64 { + s.current++ + return s.current +} + +func (s *Segment) IsEnding() bool { + return (s.current - s.start) > (s.end - s.current) +} + +func (s *Segment) IsEmpty() bool { + return s.current == s.end +} + +func (s *Segment) Reback() bool { + // println("回旋确认:", s.reback, s.current == (s.start+1)) + return s.reback && s.current == (s.start+1) +} + +func (s *Segment) String() string { + return fmt.Sprintf("start:%d-%d(%v)", s.start, s.end, s.reback) +}