分布式系统常见的一种场景就是要求id全局唯一,以下介绍七种,并分析优劣。
1、利用数据库自增id特性。
优点:
1)简单。
2)ID有序排列,分页和排序方便,方便统计数据增长。
缺点:
1)可用性难以保证:数据库常见架构是一主多从+读写分离,生成自增ID是写请求,主库挂了就玩不转了
2)扩展性差,性能有上限:因为写入是单点,数据库主库的写性能决定ID的生成性能上限,并且难以扩展
改进方法:
1)增加主库,避免写入单点
2)数据水平切分,保证各主库生成的ID不重复
2、UUID
常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。
优点:
1)简单,代码方便。
2)生成ID性能非常好,基本不会有性能问题。
3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。
缺点:
1)没有排序,无法保证趋势递增。
2)UUID往往是使用字符串存储,查询的效率比较低。
3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。
4)传输数据量大
5)不可读。
3、取当前毫秒数
uuid是一个本地算法,生成性能高,但无法保证趋势递增,且作为字符串ID检索效率低
取当前毫秒数是一种常见方案:uint64 ID = GenTimeMS();
优点:
(1)本地生成ID,不需要进行远程调用,时延低
(2)生成的ID趋势递增
(3)生成的ID是整数,建立索引后查询效率高
缺点:
如果并发量超过1000,会生成重复的ID(1s=1000ms)
不能保证ID的唯一性。当然,使用微秒可以降低冲突概率,但每秒最多只能生成1000000个ID,再多的话就一定会冲突了,所以使用微秒并不从根本上解决问题。
优化:使用GUID生成前十位+GUID生成的时间(6位)
4、Redis生成id
当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。
优点:
1)性能优。
2)ID有序排列,分页和排序方便,方便统计数据增长。
缺点:
1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
2)需要编码和配置的工作量比较大。
5、流弊到不行的twitter开源的的snowflake算法
这是目前比较好的一个分布式唯一id的生成办法。
开源地址:https://github.com/twitter-archive/snowflake
建议在大型架构过程中,id生成器单独抽离出来做成一个服务。
我自己写的一个使用snowflake的服务: https://github.com/skyywj/Id-Service
核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0
使用:
//获取一个id接口
long snowflakeNextId()
//获取count个id接口
long[] snowflakeNextIds(int count) throws InvalidSystemClock;
优点:
快,一秒大约可生成。
没有啥依赖,实现也特别简单。
知道原理之后可以根据实际情况调整各各位段,方便灵活。
缺点:
只能趋势递增。(有些也不叫缺点,网上有些如果绝对递增,竞争对手中午下单,第二天在下单即可大概判断该公司的订单量,危险!!!)
依赖机器时间,如果发生回拨会导致可能生成id重复。
6. 利用zookeeper生成唯一ID
zookeeper主要通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。很少会使用zookeeper来生成唯一ID。主要是由于需要依赖zookeeper,并且是多步调用API,如果在竞争较大的情况下,需要考虑使用分布式锁。因此,性能在高并发的分布式环境下,也不甚理想。
7. MongoDB的ObjectId
MongoDB的ObjectId和snowflake算法类似。它设计成轻量型的,不同的机器都能用全局唯一的同种方法方便地生成它。MongoDB 从一开始就设计用来作为分布式数据库,处理多个节点是一个核心要求。使其在分片环境中要容易生成得多。