集群高并发分布式唯一全局Id重要么?

系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。

这篇文章就是给各位看官提供一个生成分布式唯一全局id生成方案的思路,希望能帮助到大家。

纯属娱乐之谈,大牛勿喷


一.为什么需要分布式全局唯一ID以及分布式ID的业务需求?

在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识,如在美团点评的金融、支付、餐饮、酒店
猫眼电影等产品的系统中数据逐渐增长,对数据库分库分表后需要有一个唯一ID来标识一条数据或信息;
特别Ian的订单、骑手、优惠券都需要有唯一ID做标识
此时一个能够生成全局唯一ID的系统是非常必要的

二.ID生成规则部分硬性要求

1.全局唯一

2.趋势递增

在MySQL的InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用Btree的数据结构来存储索引,在主键的选择上面我们应该尽量使用有序的主键保证写入性能

3.单调递增

保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求

4.信息安全

如果ID是连续,恶意用户的爬取工作就非常容易做了,直接按照顺序下载指定URL即可,如果是订单号就危险了,竞争对手可以直接知道我们一天的单量,所以在一些应用场景下,需要ID无规则不规则,让竞争对手不好猜

5.含时间戳

一样能够快速在开发中了解这个分布式ID什么时候生成的

三.ID号生成系统的可用性要求

1.高可用

发布一个获取分布式ID请求,服务器就要保证99.999%的情况下给我创建一个唯一分布式ID

2.低延迟

发一个获取分布式ID的请求,服务器就要快,极速

3.高QPS

例如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶得住且一下子成功创建10万个分布式ID

四.一般通用解决方案

UUID

UUID.randomUUID() , UUID的标准型包含32个16进制数字,以连字号分为五段,形式为 8-4-4-4-12的36个字符,性能非常高,本地生成,没有网络消耗。

五.存在问题

入数据库性能差,因为UUID是无序的

    无序,无法预测他的生成顺序,不能生成递增有序的数字
    首先分布式id一般都会作为逐渐,但是按照mysql官方推荐主键尽量越短越好,UUID每一个都很长,所以不是很推荐。
    主键,ID作为主键时,在特定的环境下会存在一些问题
      比如做DB主键的场景下,UUID就非常不适用MySQL官方有明确的说明


索引,B+树索引的分裂

      既然分布式ID是主键,然后主键是包含索引的,而mysql的索引是通过B+树来实现的,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的B+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键的B+树进行很大的修改,这一点很不好,插入完全无序,不但会导致一些中间节点产生分裂,也会白白创造出很多不饱和的节点,这样大大降低了数据库插入的性能。

UUID只能保证全局唯一性,不满足后面的趋势递增,单调递增

六.数据库自增主键

在分布式里面,数据库的自增ID机制的主要原理是:数据库自增ID和mysql数据库的replace into实现的,这里的replace into跟insert功能 类似,不同点在于:replace into首先尝试插入数据列表中,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,在插入,否则直接插入新数据。
REPLACE INTO的含义是插入一条记录,如果表中唯一索引的值遇到冲突,则替换老数据
REPLACE into t_test(stub) values('b');select LAST_INSERT_ID();
我们每次插入的时候,发现都会把原来的数据给替换,并且ID也会增加
这就满足了递增性、单调性、唯一性
在分布式情况下,并且并发量不多的情况,可以使用这种方案来解决,获得一个全局的唯一ID

七、集群分布式集群

    那数据库自增ID机制适合做分布式ID吗?答案是不太适合

    系统水平扩展比较困难,比如定义好步长和机器台数之后,如果要添加机器该怎么办,假设现在有一台机器发号是:1,2,3,4,5,(步长是1),这个时候需要扩容机器一台,可以这样做:把第二胎机器的初始值设置得比第一台超过很多,貌似还好,但是假设线上如果有100台机器,这个时候扩容要怎么做,简直是噩梦,所以系统水平扩展方案复杂难以实现。

    数据库压力还是很大,每次获取ID都得读写一次数据库,非常影响性能,不符合分布式ID里面的延迟低和高QPS的规则(在高并发下,如果都去数据库里面获取ID,那是非常影响性能的)

八、基于Redis生成全局ID策略

    因为Redis是单线程,天生保证原子性,可以使用原子操作INCR和INCRBY来实现

    INCRBY:设置增长步长

    注意:在Redis集群情况下,同样和MySQL一样需要设置不同的增长步长,同时key一定要设置有效期,可以使用Redis集群来获取更高的吞吐量。

    假设一个集群中有5台Redis,可以初始化每台Redis的值分别是 1,2,3,4,5 , 然后设置步长都是5

各个Redis生成的ID为:

A:1 6 11 16 21

B:2 7 12 17 22

C:3 8 13 18 23

D:4 9 14 19 24

E:5 10 15 20 25

    但是存在的问题是,就是Redis集群的维护和保养比较麻烦,配置麻烦。因为要设置单点故障,哨兵值守

    但是主要是的问题就是,为了一个ID,却需要引入整个Redis集群,有种杀鸡焉用牛刀的感觉

    那么所有的方案似乎都不是更棒的解决方案,难道我们的诉求得不到解决了么?

    今天的分享就先到这里,后续我们共同讨论一个全新的ID策略助我们解决问题~

你可能感兴趣的:(集群高并发分布式唯一全局Id重要么?)