统一ID生成服务,不是那么好去构建的,需要更深入的去进行思考,包括使用Redis生成ID,在高并发下多个服务去访问,读写分离的情况下如果延迟导致读写不一致,会不会出现其他的问题等等。
第一种:业界比较主流的分布式ID生成器的策略是提前加载,也就是预加载的机制,我们可以预先生成一批唯一的ID,然后直接放到内存里,由服务的调用方直接获取就好,获取之后就直接进行删除,当ID使用过半的时候或者达到临界值的时候再进行生成一批ID。这有点类似于高性能的一些框架中的内存预加载,或者一些内存计算、流式计算中spark里的预加载机制。看来在软件设计的领域,所有的思想都是相通的。
当然,如果统一ID生成服务的ID生成策略想要加入具体的业务规则,我们可以预先进行一些配置,比如订单服务、物流服务、支付ID等等,在获取ID的时候可以加一些生成的业务规则,这些都是可以在统一生成服务的后台进行配置。
批量生成的ID存储在内存中,内存技术可以是spring cache、ecache实现;
第二种(要考虑业务是否适合):使用单点唯一的ID生成策略,也就是我们固定的一个机器节点来生成一个唯一的ID,这种方式的好处是可以做到全局唯一,主要还是通过拼接的方式去实现的。
•比如我们每一个真实的线上服务节点都要有一个唯一的机器码,然后采用组合的方式进行生成:机器码 + 时间戳 + 自增序列 + 其他业务规则。当然也可以使用其他的组合方式,只不过大家需要注意的一点就是服务器时间的NTP问题,也就是服务器时间的同步问题.
NTP问题描述以及使用自增序列解决NTP问题:
•NTP是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议.
•在高并发的场景下,我们的生成ID请求量非常的巨大,就会暴露出NTP的问题。
举个例子,如果单纯的采用: 机器码 + 时间戳 或者 机器码 + UUID 这种组合,在高并发下可能会有订单重复的问题。
•机器码: ABK007
•当前时间戳:1528018092517
•ID生成为: ABK0071528018092517
•NTP就是你的服务器系统的时间会定时去获取,然后进行更新校准,也就是说,我们现在的系统时间是:1528018092517
生成了一个ID为ABK0071528018092517的订单,紧接着我们的服务器进行了NTP的时间校准,这个时候系统时间肯定是向前走的,比如我们在校准前的一刻,系统时间为:1528018092530 ,恰巧我们的服务器时间走的比较快,然后经过了一次NTP服务时间校准,我们的系统时间可能就会回到从前,回到过去.. 回到我们生成订单的时间之前,比如为1528018092513,那么高并发下在后续的订单id生成的时候可能还会生成一个订单为: ABK0071528018092517 ,这个时候我们就会产生订单id重复问题。
•所以,我们要解决高并发下的NTP问题就要考虑周全:
•比如使用:机器码 + 时间戳 + 自增序列,即时是我们的时间戳被NTP服务校准产生回调,那么我们自增系列永远会在单点下保证自增,比如AtomicLong就是很好的一种原子自增序列计数器。