订单号的生成

234 阅读3分钟

规则:

业务前缀 + 订单类型 + 年月日 + 秒级时间戳的后5位 + 微秒时间戳微秒部分前5位 + 随机值M

业务前缀 + 2(买家单类型) + 241018 + 83239 + 39048 + 7

秒级控制:

一天的秒数是86400 ,所以取5位数,能满足秒级别的不重复

00000-86400

00001 -86401

13599 - 99999

13600 - 1 00000

微秒级别控制

微秒时间戳 前5位 + 随机值 能一定程度上避免微级别的冲突,

但是不能百分之百,所以肯定要用锁来去重一下

锁控制

jiuwu_genUniOn_[order_number] 做锁,incr操作如果是返回不为1 ,sleep一毫秒,递归调用自己;否则设置缓存过期时间,直接返回

代码实现示例:

func GetOrderNum(ctx context.Context, orderType string) string {
    prefix := "start"

    num := 5 //因为一天的秒数是86400(5位) 如果只取4位会有重复的风险 比如 同一天获取秒级时间戳可能会有 XXX00000 和 XXX10000 如果只取4位 得到的值是一样的都是0000,取5位就不会
 
    // 获取当前时间
    now := time.Now()
    // 转换时间为字符串
    ymd := now.Format("060102")

    // 将时间戳转换为字符串
    timestampStr := strconv.FormatInt(now.Unix(), 10)

    // 获取微秒
    microTs := strconv.FormatInt(now.UnixNano()/1e3, 10)

    // 生成随机数
    randomNum := strconv.Itoa(rand.Intn(9) + 1)

    //秒级后5位 毫秒级前5位  95 + 订单类型 + 年月日 + 秒级时间戳的后N位 + 微秒时间戳微秒部分前5位 + 随机值M
    orderNumber := prefix + orderType + ymd + timestampStr[len(timestampStr)-num:] + microTs[10:][:5] + randomNum

    key := fmt.Sprintf("jiuwu_genUniOn_%s", orderNumber)
    cnt, err := gd.Redis.Incr(ctx, key).Result()

    //出错或者碰撞都要重新生成
    if err != nil || cnt != 1 {
       time.Sleep(1 * time.Microsecond)//因为上面是控制在微秒级别的,所以这里休息一微秒
       msg := fmt.Sprintf("生成订单号出错或者出现碰撞 类型 (%d) err(%+v) 是否碰撞(%t)", orderType, err, cnt != 1)
       go SendFeiShuMsg(msg)
       return GetOrderNumV2(ctx, orderType)
    }
    //设置过期时间1天  1天后不可能有碰撞
    gd.Redis.Expire(ctx, key, 86400)
    return orderNumber
}

思考:

1.如果出现异常,一直递归怎么办

每次获取当前时间方法是底层的一般不会出现问题,递归前sleep了一微秒,避免了无用的循环,所以第二次递归的时候肯定不在同一微秒了,不会出现多次递归的情况;如果出现了有飞书告警,可以人工介入

2.生成订单 的QPS是多少

提交订单接口的响应99线耗时200ms以内,QPS 个位数(好吧...)

吐槽

1.微妙是 16位,前面10位是秒级别,后面6位是微妙部分,没必要只取5位再加一个随机数,完全可以取6位,控制微妙级别不重复,+锁 避免重复

  • 1 秒 = 1000 毫秒
  • 1 毫秒 = 1000 微秒
  • 1 微秒 = 1000 纳秒