关于雪花
雪花(snowflake
)在自然界中,是极具独特美丽,又变幻莫测的东西:
- 雪花属于六方晶系,它具有四个结晶轴,其中三个辅轴在一个基面上,互相以60度的角度相交,第四轴(主晶轴)与三个辅轴所形成的基面垂直;
- 雪花的基本形状是六角形,但是大自然中却几乎找不出两朵完全相同的雪花,每一个雪花都拥有自己的独有图案,就象地球上找不出两个完全相同的人一样。许多学者用显微镜观测过成千上万朵雪花,这些研究最后表明,形状、大小完全一样和各部分完全对称的雪花,在自然界中是无法形成的。
雪花算法
雪花算法的原始版本是scala版,用于生成分布式ID(纯数字,时间顺序),订单编号,骑手,优惠券
等。
- 自增ID:对于数据敏感场景不宜使用,且不适合于分布式场景。
- GUID:采用无意义字符串,数据量增大时造成访问过慢,且不宜排序。
ID生成规则部分硬性要求:
-
全局唯一
:不能出现重复的ID号,既然是唯一-标识,这是最基本的要求 -
趋势递增
:在MySQL的InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用Btree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。 -
单调递增
:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求 -
信息安全
:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可。如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,需要ID无规则不规则,让竞争对手否好猜。 -
含时间戳
:这样就能够在开发中快速了解这个分布式id的生成时间。
ID号生成系统的可用性要求:
-
高可用
:发一个获取分布式ID的请求,服务器就要保证99.999%的情况下给我创建一个唯一分布式ID。 -
低延迟
:发一个获取分布式ID的请求,服务器就要快,极速。 -
高QPS
:假如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶的住且一下子成功创建10万个分布式ID。
一般通用方案:
方法1:
UUID(Universally Unique ldentifer)
的标准型式包含32个16进制数字
,以连字号分为五段
,形式为8位-4位-4位-4位-12位
的36
个字符, 示例:550e8400-e29b-41d4-a716-446655440000
- 优点:性能非常高、本地生成,没有网络消耗,唯一性也是OK的。
- 缺点:入数据库性能差,生产环境下不适合使用。(此方法不可行,只满足了5个硬性要求里的1个要求:全局唯一要求)
无序
,无法预测他的生成顺序,不能生成递增有序的数字
。首先分布式ID一般都会作为主键, 但是MySQL官方推荐主键要尽量越短越好,UUID每一个都很长,所以不是很推荐。主键
,ID作为主键时在特定的环境会存在一些问题。比如做DB主键的场景下,UUID就非常不适用MySQL官方有明确的建议主键要尽量越短越好
,而36个字符长度的UUID不符合要求。索引
,既然分布式ID是主键
,然后主键是包含索引的
,然后MySQL的索引是通过B+树来实现的
,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的B+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键地械的B+树进行很大的修改,这一点很不好。插入完全无序,不但会导致一些中间节点产生分裂,也会白白创造出很多不饱和的节点,这样大大降低了数据库插入的性能
。
方法2:
数据库自增主键方法,又分了2
种形式:单机
、集群分布式
。
单机
(此方法可行,生产规模不大的项目中可使用):在单机里面,数据库的自增ID机制的主要原理是:数据库自增ID
和MySQL数据库的replace into实现
。REPLACE INTO的含义
是插入一条记录
,如果表中唯一索引的值遇到冲突,则替换就数据
。
replace into
跟inset
功能类似,不同点
在于:replace into首先尝试插入数据列表中
,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,再插入。否则直接插入新数据
。
-- 创建一张测试表,stub字段设置了唯一索引。
CREATE TABLE t_test(
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
stub CHAR(1) NOT NULL DEFAULT '',
UNIQUE KEY stub(stub)
)
-- 查询表数据
SELECT * FROMt_ test;
-- 第一次插入一条数据,主键索引的值是1,第二次再次执行会根据主键或唯一索引判断,是否存在,存在则先删除,再插入。
REPLACE INTO t_test (stub) VALUES('b');
-- 查询当前主键索引
SELECT LAST_INSERT_ID();
集群分布式
(此方法不可行):数据库自增ID机制不太适合作分布式ID,理由如下:
- 系统水平扩展比较困难,比如定义好了步长和机器台数之后,如果要添加机器该怎么做?假设现在只有一台机器发号是1,2,3,4,5(步长是1),这个时候需要扩容机器一台。可以这样做:把第二台机器的初始值设置得比第一台超过很多,貌似还好,现在想象一下如果我们线上有100台机器,这个时候要扩容该怎么做?简直是噩梦,所以系统水平扩展方案复杂难以实现。
- 数据库压力还是很大,每次获取ID都得读写一次数据库, 非常影响性能,不符合分布式ID里面的延迟低和要高QPS的规则(在高并发下,如果都去数据库里面获取id,那是非常影响性能的)。
方法3:
基于Redis
生成全局ID
策略,Redis是单线的天生保证原子性
,可以使用原子操作INCR和INCRBY来实现
。(此方法可行,但配置麻烦要依赖第三方来实现,中途某台redis服务宕机了,会出现不连号的现象)
注意
:在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
推荐方案:
雪花算法
,它的起源snowflake
中文的意思是 雪花,雪片,所以翻译成雪花算法。它最早是twitter内部使用的分布式环境下的唯一ID生成算法
。在2014年开源,开源的版本由scala编写。
雪花算法产生的背景当然是twitter高并发环境下对唯一ID生成的需求