不少大的互联网公司数据量很大,都采用分库分表,那么分库后就须要统一的惟一ID进行存储。这个ID能够是数字递增的,也能够是UUID类型的。mysql
若是是递增的话,那么拆分了数据库后,能够按照id的hash,均匀的分配到数据库中,而且mysql数据库若是将递增的字段做为主键存储的话会大大提升存储速度。可是若是把订单ID按照数字递增的话,别人可以很容易猜到你有多少订单了,这种状况就能够须要一种非数字递增的方式进行ID的生成。算法
想到分布式ID的生成,你们可能想到采用Redis进行生成ID,使用Redis的INCR命令去生成和获取这个自增的ID,这个没有问题,可是这个INCR的生成QPS速度为200400(官网发布的测试结果),也就是20W这样子,若是QPS没有超过这些的话,显然使用Redis比较合适。sql
那么咱们对于要达到高可用,高QPS,低延迟咱们有没有更好的想法呢。接下来一块儿看一下snowflake算法,由twitter公司开源的雪花算法。数据库
snowflake一共64位:缓存
1. 第一位不用。网络
2. 41位是时间戳。 2^41以毫秒为单位的话,可获得69年,很是够用了。分布式
3. 10位位工做机器,能够有2^10=1024个工做节点,有的公司将其拆分为5位工做中心编码,5位分给工做机器。性能
4. 最后12位用于生成递增数据共4096个数。测试
若是用这个理论上的QPS上的QPS为409W/S。编码
这种方式的优势为:
1. QPS很是高,性能也很是够。高性能条件也知足了。
2. 不须要依赖其余第三方的中间件,好比Redis。少了依赖,可用率提升了。
3. 能够根据本身定制进行调节。也就是里边的10位进行自由分配。
缺点:
1. 此种算法很依赖时钟,假如时钟进行回拨了,将有可能生成相同的ID。
UUID是采用32位二进制数据生成的,它生成的性能很是好,可是它是基于机器MAC地址生成的,并且不是分布式的,因此不是我们讨论的范畴。
下面我们看一下一些大公司的分布式ID实现机制,经过生成建立一张表,采用8个Byte, 64位进行存储使用,用这张表记录所产生ID的位置,好比ID从0开始,而后使用了1000个,那么数据库里边记录里边的最大值是一千,同时还有个步长值,好比1000,那么获取下一个值得时候最大值为2001,即最大的没有使用的值。
具体的实现步骤以下:
1. 提供一个生成分布式ID的服务,这个ID的服务是读取数据库里边的值和步长值计算生成须要的值和范围,而后服务消费方拿到后进行将号段存储到缓存中使用。
2.当给到服务调用方以后,数据库当即更新数据。
这种状况下的优势为:
1. 容灾性能好,若是DB出现问题,由于数据放到内存中,仍是能够支撑一段时间。
2. 8个Byte能够知足业务生成ID使用。
3. 最大值能够本身定义,这样有些迁移的业务还能够本身定义最大值继续使用。
固然缺点也存在:
1. 当数据库挂了整个系统将不能使用。
2. 号段递增的,容易被其余人猜到。
3. 若是不少服务同时访问获取这个ID或者网络波动致使数据库IO升高的时候,系统稳定性会出现问题。
而后针对上述状况的解决方法是他们采用了双缓存机制,即将号码段读取到内存中以后开始使用,当使用到了10%的时候从新启动一个新线程,而后当一个缓存用完了以后去用另外一块缓存的数据。当另外一个缓存的数据达到10%的时候再重启激动一个新线程获取,依次反复。
这样作的好处是避免同时访问大量数据库,致使I/O增多。同时能够经过两个缓存段解决了单一缓存致使很快用完的状况。固然把这个号段设置成QPS大小的600倍,这样数据库挂了10-20分钟内仍是能够继续提供服务的。
以上一直提到了一个问题,就是ID递增,我们如何解决这个问题呢。就是采用snowflake,而后解决里边的时钟问题,有些公司采用ZK去比较当前workerId也就是节点ID使用的时间是否有回拨,若是有回拨就进行休眠固定时间,看是否能遇上时间,若是能遇上的话,继续生成ID,若是一直没有遇上达到某个值得话,那么就报错处理。由于中间10位是表示不一样的节点,那么不一样的节点生成的ID就不会存在递增的状况。
这些思路都是某公司已经实现了的,若是有兴趣继续研究的话,那么在GITHUB上搜索下开源的Leaf能够直接拿着使用的。
若是有不对的地方,还望指正。