早期单机
早期单机系统习惯的主键有两种方式:整数的自增主键和字符串主键
整数自增主键,数据库自己维护,每次+1,优点快速简单,具有顺序,方便排序,缺点高并发时会有主键冲突问题。
字符串方式有两种,一种主键含业务意义,一种主键和业务无关
含业务意义的:如每三位代表一级部门 001001001,一级支持999个同级部门。这样方便按部门编号排序,但当业务变化就死翘翘了。
和业务无关的:UUID 全球唯一值,采用hash算法,根据网卡编号,根据当前时间生成一个32位的值,有的36位(36中间4个-),无法排序。
SQL:select uuid()
248b7d7c-175d-11ec-a483-005056c00001
字符串方式相对整数速度慢,乱序,但和业务无关更加灵活,长短自己定义。
现今分布式
一台服务器的存储容量有限,于是大型项目都采用多台服务器,通过分布式结构把它们相连。存储容量是不愁了,但又产生诸多新的问题。
多台数据库服务器,多个数据库,写数据写哪去呢?
多个业务同时请求申请新的ID造成高并发下可能获得是同一个ID
数据库难以合并和迁移(主要指整数类型)
早期解决办法是分库分表,如分段解决:1100,101200,201~300三段。这样的办法是不很可笑,如果数量满了呢?是不又乱套了。
于是分布式ID的解决方案应用而生!
生成分布式ID的方法很多:
Redis它的INCR()和INCRBY()是原子性操作命令,高并发时不会出错,常用来产生整数自增ID支持。
ZooKeeper主要通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。很少会使用ZK来生成唯一ID。主要是由于需要依赖ZK,并且是多步调用API,如果在竞争较大的情况下,需要考虑使用分布式锁。因此,性能在高并发的分布式环境下,也不甚理想。
MongoDB的ObjectId。它设计成轻量型的,不同的机器都能用全局唯一的同种方法方便地生成它。MongoDB 从一开始就设计用来作为分布式数据库,处理多个节点是一个核心要求,使其在分片环境中要容易生成得多。
雪花算法 Snowflake
但上面的解决方案都需要引入重量级的产品,使用起来极其不方便。于是Twitter开源的分布式ID生成算法snowflake脱颖而出。
SnowFlake算法用来生成64位的ID,刚好可以用long整型存储,能够用于分布式系统中生产唯一的ID, 并且生成的ID有大致的顺序。 在这次实现中,生成的64位ID可以分成5个部分:
0 - 41位时间戳 - 5位数据中心标识 - 5位机器标识 - 12位序列号
5位数据中心标识跟5位机器标识这样的分配仅仅是当前实现中分配的,如果业务有其实的需要,可以按其它的分配比例分配,如10位机器标识,不需要数据中心标识。
snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)ID按照时间在单机上是递增的。
3)纯数字,支持排序。
缺点:
在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。
MybatisPlus 中的ID_WORKER就是使用的这种算法