晓夏

北漂的女孩

Good Luck To You!

高并发分布式系统如何做到唯一id

浏览量:385

最近公司用到这个,和几个小伙伴一起想到几个方案,希望大家能多提出意见,一起交流和分享

  1. 数据库自增id最为唯一标识

    1.  优点:操作简单,不用考虑唯一性

    2.  缺点:在大表准备水平拆分的时候,每一个表都会生成一个自增的id,对分开的表来说是重复的,对以后的查询和删除有很大的影响

                 对表进行高并发的时候,需要添加事物,否则就会产生重复的id

                     在操作父子关系表(关联表),需要获取MAX(Id)来关联父子表的关系,如果存在并发关系,获取的Max(Id)很有可能被线程获取到

    结论:适合小应用,无需分表,没有高并发性能要求。

  2.  单独开一个数据库,获取全局唯一的自增序列号或各表的MaxId

     a.使用自增序列表

      专门使用一个数据库,用于生成唯一的MAX(id)。开启事务,每次想要插入的数据先插入到自增表里面,然后返回一个唯一的id

      注意:需要定期维护自增序列表,提高查询效率,开启事务

    b.使用Max表,存储每一个表的MAX(id)的值

        专门一个数据库,记录各个表的max(id)的值,建立一个存储过程来获取id;

         逻辑:开启事务,对于表中不存在的记录直接返回一个默认值为1的键值,同事插入该记录到table_key表里面,如果存在记录,key值直接加1更新的到MaxId中并返回key。

    缺点:每次查询MaxId都是一个损耗,不过不会像自增序列表那么容易爆,因为他是摆表进行划分的

    结论:适用中型应用,此方案解决了分表,关联表插入记录的问题。但是无法满足高并发性能要求。同时也存在单点问题,如果这个数据库cash掉的话……

    解决方案:

     建立两台以上的数据库ID生成服务器,每个服务器都有一张记录各表当前ID的MaxId表,但是MaxId表中Id的增长步长是服务器的数量,起始值依次错开,这样相当于把ID的生成散列到每个服务器节点上。

    例如:如果我们设置两台数据库ID生成服务器,那么就让一台的MaxId表的Id起始值为1(或当前最大Id+1),每次增长步长为2,另一台的MaxId表的ID起始值为2(或当前最大Id+2),每次步长也为2。这样就将产生ID的压力均匀分散到两台服务器上,同时配合应用程序控制,当一个服务器失效后,系统能自动切换到另一个服务器上获取ID,从而解决的单点问题保证了系统的容错。

    注意:1、多服务器就必须面临负载均衡的问题;

              2、倘若添加新节点,需要对原有数据重新根据步长计算迁移数据。

    结论:适合大型应用,生成Id较短,友好性比较好。(强烈推荐)

  3. Sequence特性

    这个是数据级别的,允许在多个表之间共享序列号,他解决表在同一个数据库的情况,但是要放在不同的数据库,那不能共享

    结论:适用中型应用,此方案不能完全解决分表问题,而且无法满足高并发性能要求。同时也存在单点问题,如果这个数据库cash掉的话……

  4. 通过数据库集群编号+集群内自增类型两个字段共同组成唯一的主键

    优点:实现比较简单,维护也简单

    缺点:关联表相对复杂,需要两个字段,并且业务逻辑必须一开始就设计为处理复合主键的逻辑,后期由单主键转换为复合主键改动成本太大

    结论:适合大型应用,但需要业务逻辑配合处理复合主键

  5. GUID  全局唯一标识符号

    GUID通常标识成32个16进制的数字(0-9,A-F)组成的字符串,如{23ED4520-3EDE-1096-A2EE-09093C48492E},它实质上是一个128位长的二进制整数。

    GUID算法使用到用户的网卡MAC地址上,以保证计算机集群生成唯一的一个GUID,在相同计算机上随机生成两个相同的GUid的可能性是非常小的,但并不为0。所以,用于生成GUID算法都加入非随机的参数(时间),保证重复的事情不发生

    优点:GUID是最简单的方案,跨平台,跨语言,跨业务逻辑,全局唯一的id,数据间同步、迁移都能简单实现

    缺点:存储占32位,并且无可读性,返回GUID给客户显得不专业

               占用珍贵的聚集索引,一般我们不会根据GUID去查单据,并且插入因为GUID是无需的,在聚集索引的排序规则下可能移动大量的记录

    结论:适合大型应用。生成的ID不够友好,占据32位,索引效率较低

  6. 通过设置每个集群中自增ID起始点,将各自的集群的ID进行绝对的分段来实现全局唯一。当遇见某个集群数据增长过快后,通过命令调整下一个ID起始位置跳过可能存在的冲突。

    结论:适合大型应用,但需要高度关注各个集群的iD增长状况

  7. GUID转换成INT64

      对于GUID的可读取性,直接把GUID转换成int64    

       即将GUID转为了19位数字,数字反馈给客户可以一定程度上缓解友好性问题。EG:

       GUID: cfdab168-211d-41e6-8634-ef5ba6502a22    (不友好)

       Int64: 5717212979449746068                                      (友好性还行)

     不过我的小伙伴说ToInt64后就不唯一了。我专门写了个并发测试程序,后文将给出测试结果

       (唯一性、业务适合性是可以权衡的,这个唯一性肯定比不过GUID的,一般程序上都会安排错误处理机制,比如异常后执行一次重插的方案……)

    结论:适合大型应用,生成相对友好的Id(纯数字)------因简单和业务友好性而推荐。

  8. 雪花算法

<?php

function createOnlyId($mId = 0)
{
    $beginTime = 1479533469598;//随便定义一个开始时间
    $max4bit = 1099511627775;//最大的四字节
    $machineId = null;//机器id
    // 时间戳 42字节
    $time = floor(microtime(true) * 1000);
    // 当前时间 与 开始时间 差值
    $time -= $beginTime;
    // 二进制的 毫秒级时间戳
    $base = decbin($max4bit + $time);
    // 机器id  10 字节
   if(!$machineId)
    {
        $machineId = 1;//没有机器id默认为1 暂时
    }
    if($mId)
        $machineNumberId = $mId;
    else
        $machineNumberId = 1;
    $machineid = str_pad(decbin($machineNumberId), 10, "0", STR_PAD_LEFT);
    // 序列数 12字节
    $random = str_pad(decbin(mt_rand(0, 4095)), 12, "0", STR_PAD_LEFT);
    // 拼接
    $base = $base.$machineid.$random;
    // 转化为 十进制 返回
    return bindec($base);
}
$id = createOnlyId();
var_dump($id);
die();


神回复

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。