diff --git a/README.md b/README.md index 5a8b2c6..1ab4e0a 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ trade/create 性能问题,缩短事务时间,考虑其他方式实现可靠分布式事务 -需要确认以下 ID.GenSerial 的分布式并发安全性 - jsonb 类型转换问题,考虑一个高效的 any 到 struct 转换工具 端口资源池的 gc 实现 diff --git a/web/services/id.go b/web/services/id.go index 3373bec..c394928 100644 --- a/web/services/id.go +++ b/web/services/id.go @@ -44,48 +44,28 @@ var ( ) func (s *IdService) GenSerial() (string, error) { - var ctx = context.Background() - - // 构造Redis键 now := time.Now().Unix() - key := idSerialKey(now) - // 使用Redis事务确保原子操作 - var sequence int64 - err := g.Redis.Watch(ctx, func(tx *redis.Tx) error { + // 脚本实现原子操作 + script := redis.NewScript(` + local current = tonumber(redis.call('GET', KEYS[1])) or 0 + if current >= tonumber(ARGV[1]) then + return redis.error_reply('sequence overflow') + end - // 获取当前序列号 - currentVal, err := tx.Get(ctx, key).Int64() - if err != nil && !errors.Is(err, redis.Nil) { - return err - } + local sequence = current + 1 + redis.call('SET', KEYS[1], sequence, 'EX', ARGV[2]) - if errors.Is(err, redis.Nil) { - currentVal = 0 - } - sequence = currentVal + 1 - - // 检查序列号是否溢出 - if sequence > maxSequence { - return ErrSequenceOverflow - } - - // 将更新后的序列号保存回Redis,设置5秒过期时间 - pipe := tx.Pipeline() - pipe.Set(ctx, key, sequence, redisTTL*time.Second) - _, err = pipe.Exec(ctx) - return err - }, key) + return sequence + `) + sequence, err := script.Run(context.Background(), g.Redis, []string{idSerialKey(now)}, maxSequence, redisTTL).Int64() if err != nil { return "", err } // 组装最终ID id := uint64((now << timestampShift) | sequence) - - idStr := strconv.FormatUint(id, 10) - - return idStr, nil + return strconv.FormatUint(id, 10), nil } // ParseSerial 解析ID,返回其组成部分 @@ -98,7 +78,7 @@ func (s *IdService) ParseSerial(id uint64) (timestamp int64, sequence int64) { // idSerialKey 根据时间戳生成Redis键 func idSerialKey(timestamp int64) string { - return fmt.Sprintf("global:id:serial:%d", timestamp) + return fmt.Sprintf("id:serial:%d", timestamp) } // endregion