Scadb是一个分布式MySQL中间件,目前它支持的SQL语句是MySQL的一个子集,而且也没打算创造独特的语法出来,这样方便业务方可以在MySQL和Scadb之间无缝的切换。那么Scadb是否能够支持自增id呢?
一、Mongodb的ObjectID的启示
我们首先在Mongodb中产生一个ObjectID,
》 db.test.insert({foo:'bar'})
WriteResult({ "nInserted" : 1 })
》db.test.findOne()
{ "_id" : ObjectId("59197e923879ad7925b2a3f0"), "foo" : "bar" }
Mongodb没有支持自增id,缺省的文档都会产生一个ObjectID,是唯一ID,Mongodb的ObjectID格式如下所示,一个ObjectID主要由4个部分组成:
95----------63---------39--------23-----0
—————————————^
时间戳 --------机器 -----进程 --自增计数器
-
时间戳:将刚才生成的objectid的前4位进行提取“59197e92”,十进制为1494843026,这是我们产生数据时候的时间戳;
-
- 机器:接下来的三个字节就是“3879ad”,这三个字节是主机信息,一般的实现为组合所有的网卡信息成一个字符串,然后进行计算散列值;
-
- PID:上面的机器信息是为了确保在不同机器产生的objectId不冲突,而pid就是为了在同一台机器不同的mongodb进程产生了objectId不冲突,接下来的“7925”两位就是产生objectId的进程标识符,我们知道linux操作系统进程id是int型,所以两个字节是存储不下的,mongodb这里也是采用了进程信息的散列值然后取2个byte。
-
- 自增计数器:前面的九个字节是保证了一秒内不同机器不同进程生成objectId不冲突,这后面的三个字节“b2a3f0”是一个自动增加的计数器,用来确保在同一秒内产生的objectId也不会发现冲突,只要一个进程一秒内产生的数据少于16777216,就能保证记录的唯一性。
ObjectId的这个主键生成策略,很好地解决了在分布式环境下高并发情况主键唯一性问题,值得学习借鉴。Scadb也是一种分布式数据库,无法实现自增id。那么在设计之初,本来也考虑参考Mongodb的唯一ID方案,但是ObjectID是一个12个字节的数值,而Scadb是MySQL的中间件,MySQL没有一个一种合适的类型的来支持12个字节,当然,我们可以考虑用字符串,但是也从性能方面考虑,于是决定要采用整形BIGINT来存储唯一ID。
二、Scadb如何支持唯一ID
从上面的分析我们知道,Mongodb采用了3个byte存储机器ID,2个字节存储进程ID,就是为了保证每一个启动的Mongodb的进程能够产生一个唯一的进程信息(其实根据前面讲解的原理,还是无法100%保证进程标识的唯一性);
与此同时Scadb还要把整个ID号存储在一个BIGINT也就是8个字节的整数中,所以这种方案行不通。但是思路可以借鉴,我们只需要保证每个启动的进程有一个唯一的ID号。
进程号生成
由于Scadb的server进程不是多租户模式,也即是说所以我们只需要保证服务于一个业务的Scadb Server进程能够有一个唯一的进程号即可。Scadb的配置是基于Zookeeper的,这样我们很容易基于Zookeeper来生成一个唯一的进程号。
假设现在我们服务的业务名是test,那么它在zookeeper上的主路径是:/scadb/businesses/test,创建一个子目录:/scadb/businesses/test/runners。
在该子目录下,我们让每个Scadb server在启动的时候,在/scadb/businesses/test/runners目录下创建一个临时节点,节点创建成功,那么这个节点就作为Scadb server的进程号。由于是临时节点,一旦进程被杀死或者因为某种原因挂了,那么这个节点也会自动消失。
具体的逻辑为:
1、启动时,随机一个整数runnerId;
2、创建临时节点:/scadb/businesses/test/runners/${runnerId}
3、如果创建成功,结束;否则runnerId = (runnerId + 1) % 512,重复第2步。
唯一ID生成
有了进程号以后,那么唯一ID的生成就很简单了,Scadb的唯一ID的格式为:
63-62---------30----28------19--------- 0
—————————————
预留 时间戳 预留 进程号 自增计数器
最高位(63):我预留了,主要是因为Java等语言对无符号64位整数支持不友好,所以这一位没有使用;
时间戳(31-62):占用4个字节;
预留2位(30,29):主要考虑到以后其他的可扩展的功能;
进程号(20-28):占用了9位,每个业务可以支持512个进程,我压测过一个scadb可以支持5万次的普通数据库访问操作,所以512个进程对于一个业务来说够用了;
自增计数器(0-19):占用了20位,可以支持每秒1048576次不同值的生成,对于一个进程来说,这个数已经是非常高了。
具体代码不在这里列举了,大家可以参考源码中的ScadbUUID类。
三、Scadb唯一ID实战
首选我们创建一个有自增字段的表,如下所示:
mysql> /!scadb:partitionkey=id rule=rule10/CREATE TABLE
b
(
->id
bigint(20) NOT NULL AUTO_INCREMENT,
->name
varchar(50) COLLATE utf8_bin DEFAULT NULL,
->t
datetime DEFAULT NULL,
-> PRIMARY KEY (id
)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
-> ;
Query OK, 0 rows affected (0.92 sec)
注意:
- 1.虽然Scadb不支持唯一ID,但是为了保持与MySQL语法的统一性,这里仍然采用了AUTO_INCREMENT,这样b表就是一个支持唯一ID的分区表了;
- 2.这里自增字段只能是bigint型,不支持int型;
我们可以通过下面的语法来查看表的结构:
mysql> show create table b ;
+-------+------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+------------------------------------------------------------------------------------------------------------------+
| b | /!scadb:partitionkey=id rule=rule10/CREATE TABLEb
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
name
varchar(50) COLLATE utf8_bin DEFAULT NULL,
t
datetime DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin |
+-------+----------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
现在我们插入一条记录:
mysql> insert into b ( name ) values ( '123') ;
Query OK, 1 row affected (0.12 sec)
由于插入时没有指定ID,那么Scadb会自动为我们产生一个ID号,我们查询一下上次产生的自增ID:
mysql> select last_insert_id() ;
+---------------------+
| last_insert_id() |
+---------------------+
| 3208787689176782285 |
+---------------------+
1 row in set (0.03 sec)
现在我们根据该ID号查询一下,Scadb产生的整条记录:
mysql> select * from b where id = 3208787689176782285 ;
+---------------------+------+------+
| id | name | t |
+---------------------+------+------+
| 3208787689176782285 | 123 | NULL |
+---------------------+------+------+
1 row in set (0.07 sec)
四、最后
唯一ID不不仅仅数据库中需要用到,在业务开发中,我们也经常会碰到需要产生唯一ID的场景,希望本文的原理能够帮助到大家。
另外如果想了解scadb的架构,请阅读:scadb架构介绍。