一个好的订单号是如何诞生的 - ZhangTory's NoteBlog - 张耀誉的笔记博客

一个好的订单号是如何诞生的

最近新的项目需要生成一个独一无二的订单号,很简单对吧?
但是生成一个“好”的订单号真的简单吗?

首选要明确怎么才叫“好”。

  1. 顾订单号思义,看到订单号就能从中得到业务相关的信息。
  2. 在分布式、高并发的情况下能有良好的性能,并且生成的订单号能保证独一无二。
    根据这两条要求,UUID和数据库自增的方案就被排除掉了。

首先来看看如何做到“顾订单号思义”。

  1. 简短
    首先要知道,订单号大部分是用于售后,或者我们排查问题时才会用到,所以简短的订单号能够减少客户到客服,客服到工程师的过程中出错的可能。所以后面的各种要求都会考虑到所占用的长度。
  2. 订单的生成时间
    看一眼就能知道订单生成的时间对我们来说很重要,不论是查数据库记录,还是查日志,都可以帮我们缩小范围。年月日一般来说会使用8位的长度,但是一般可以忽略年份的前2为,一共只占用6位的长度。

在某些并发量大的公司会要求精确到时分秒,对应的又会占用6位长度。需要精确到时分秒的哪一位,需要根据业务情况决定。

  1. 业务渠道/业务类型
    有的公司可能不同的业务最终创建订单和支付都在同一个系统,用于方便记账等等。那么如果能一眼看出具体是哪个业务过来的订单,能帮助我们更快定位问题。

这里举个例子,客服接到大量投诉,通过订单号看到投诉几乎全是某个业务的订单,那么基本可以判断是该业务系统的故障;同理,如果各个业务过来的订单都有问题,那么就可能是当前系统出了bug。
一般来说占用1到2个字符的长度。

  1. 支付渠道
    同上,我们可能对接微信、支付宝、银联等等,如果能从订单号直接看到支付渠道,也可以帮助我们快速定位问题。一般占用1个字符的长度。
  2. 其他必要信息
    不同的业务还有不同的一些信息需要展示,比如我们公司的SUP系统,还有不同的上游渠道,那么展示出来对我们排查问题也很有帮助。一般占用1到2个字符的长度。

    针对以上5点我们就可以得到一个基本的格式。

以我们公司的业务情况来说,订单号的格式为:6位的日期 + 2位的上游渠道编号 + 2位的业务渠道编号 + 6位订单流水号。
因为这次做的项目是SUP系统,算是一个中台服务,后期可能会对接多个上游和业务渠道,所以都才用了2位长度。
6位订单流水号可以支持每日百万条订单,很显然对我们已经绰绰有余了。
目前总体长度为16位,这个长度已经足够长了。
如果你做的业务更加单一,可以去掉或使用1位的上游渠道、业务渠道,订单号能更短。

我们再从技术方面来看看。

  1. 唯一性
    这个不用解释。但要强调的是,在分布式、高并发场景下,也能够保证唯一性。
  2. 高性能
    生成订单号不应该太消耗性能,只有高性能,才能支持高并发的数据量。

同时要注意,数据库中订单号的字段肯定是会做索引的,一般都是用的B+树索引,所以生成的订单号最好保证趋势递增,从而减小插入数据库时的消耗。
2.信息安全
订单号不能透露运营信息。比如你的竞争对手上午下一笔订单,下午再下一笔订单,就能知道你们的这段时间的订单数量,这就暴露了公司机密。

那么在技术上我们怎么保证以上3点呢?
唯一性和高性能应该放在一起说。
对于趋势递增,其实通过日期就能保证总体上是趋势递增的,毕竟分布式情况下,要做到绝对的递增是比较麻烦的。
如果要借助外部,并且并发量不会非常非常大,那么我们可以使用redis来实现。
因为redis的incr是原子性的,所以可以通过incr来保证分布式、高并发下的唯一性。
一般来说,redis的性能足以满足通常情况下的高并发,但是如果并发量更大,那么redis就会成为瓶颈。

如果真的并发量非常大,那么可以考虑Twitter的SlowFlake算法。

其实SlowFlake非常简单,通过减去一个设定的开始时间,减小了时间戳的长度。再通过工作机器id,保证每台机器生成的订单号都是唯一的。剩下12bit的序列号是每台机器内存中维护的一个递增序列。
理论上讲单台机器SlowFlake每毫秒能产生4096个订单号,性能已经非常强了。
SlowFlake算法非常巧妙,时间戳保证了总体递增,虽然不能一眼看出订单创建的时间,但是也可以算出来,当然一般是查出来。此外竞争对手不可能通过订单号推出运营情况。
虽说SlowFlake很强,但要说缺点也是有的,最大的一个缺点来自于时间回拨。
运行程序的机器时间,总会走得快一点或者慢一点,所以服务器都会做自动对时。
如果这台机器走得快一点,那么在对时后,就会出现时间回拨的情况,那么在这么几毫秒的时间内是无法生成订单号的。
一般来说时间回拨都不会造成订单号重复,除非在回拨前停止程序,回拨后再启动程序,那么就会出现生成的订单号重复。不过这种巧合实际出现的概率为0,除非你遇到了一个SB队友。
还有很多公司基于SlowFlake做了针对性的一些修改,都算是类SlowFlake算法,有需要再去研究。

针对我们公司的情况,我选择了redis来辅助生成订单号。
因为这个系统大概率之后较长一段时间都不会有太大的并发量,如果选择SlowFlake,那么会出现所有的订单号都是以1结尾...会很尴尬,而redis的性能完全满足我们的要求。

最后是安全性,根据之前我们说的:6位的日期 + 2位的上游渠道编号 + 2位的业务渠道编号 + 6位订单流水号,竞争对手就能知道我们的运营情况。
为了保证安全性,一般来说是加入随机数。但是随机数不能加入太多,因为目前已经有16位长了。
因为前期每个渠道都不会有太大的订单量,所以我打算只加入2位的随机数,放在低位,保证运营数据安全。

最终的订单号格式:6位的日期 + 2位的上游渠道编号 + 2位的业务渠道编号 + 流水号[2-5] + 1位随机数 + 流水号[1] + 1位随机数 + 流水号[0]

添加新评论

电子邮件地址不会被公开,评论内容可能需要管理员审核后显示。