在单体应用中,我们可以通过数据库的主键ID来生成唯一的ID,但是如果数据量变大,就需要进行分库分表,在分库分表之后,如何生成一个全局唯一的ID,就是一个关键的问题。
通常情况下,对于分布式ID来说,我们一般希望他具有以下几个特点:
通常,在分布式ID的生成方案主要有以下6种:
UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。
标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12),共32个字符,通常由以下几部分的组合而成:当前日期和时间,时钟序列,全局唯一的IEEE机器识别号
UUID的优点就是他的性能比较高,不依赖网络,本地就可以生成,使用起来也比较简单。
但是他也有两个比较明显的缺点,那就是长度过长和没有任何含义。长度自然不必说,他有32位16进制数字。对于"550e8400-e29b-41d4-a716-446655440000"这个字符串来说,我想任何一个程序员都看不出其表达的含义。一旦使用它作为全局唯一标识,就意味着在日后的问题排查和开发调试过程中会遇到很大的困难。
用UUID当做分布式ID,存在着不适合范围查询、不方便展示以及查询效率低等问题。
分布式ID也可以使用数据库的自增ID,但是这种实现中就要求一定是一个单库单表才能保证ID自增且不重复,这就带来了一个单点故障的问题。
一旦这个数据库挂了,那整个分布式ID的生成服务就挂了。而且还存在一个性能问题,如果高并发访问数据库的话,就会带来阻塞问题。
号段模式是在数据库的基础上,为了解决性能问题而产生的一种方案。他的意思就是每次去数据库中取ID的时候取出来一批,并放在缓存中,然后下一次生成新ID的时候就从缓存中取。这一批用完了再去数据库中拿新的。
而为了防止多个实例之间发生冲突,需要采用号段的方式,即给每个客户端发放的时候按号段分开,如客户端A取的号段是1-1000,客户端B取的是1001-2000,客户端C取的是2001-3000。当客户端A用完之后,再来取的时候取到的是3001-4000。
号段模式的好处是在同一个客户端中,生成的ID是顺序递增的。并且不需要频繁的访问数据库,也能提升获取ID的性能。缺点是没办法保证全局顺序递增,也存在数据库的单点故障问题。
其实很多分库分表的中间件的主键ID的生成,主要采用的也是号段模式,如TDDL Sequence
基于数据库可以实现,那么基于Redis也是可以的,我们可以依赖Redis的incr命令实现ID的原子性自增。
Redis的好处就是可以借助集群解决单点故障的问题,并且他基于内存性能也比较高。
但是Redis存在数据丢失的情况,无论是那种持久化机制,都无法完全避免。
除了以上方案以外,还有一些第三方的工具可以用来实现分布式ID,如百度的UidGenerator、美团的Leaf以及滴滴的Tinyid等等。
这些框架在功能上有的是整合了我们前面提到的多种实现方式,有的是针对不同的方式做了改进,如解决雪花算法的时钟拨回问题等。