当前位置:   article > 正文

雪花主键_在postgres的实现

雪花主键

一、主键的属性:

1. 全局唯一,不重复

2. 趋势有序,后面的值大于前面的值

3. 高性能, 读写效率高。qps不能太低,否则容易造成线程堵塞

4. 可用性好,业务对ID的可用性高,不存在单点故障


二、常见的主键方式

2.1、自增主键

利用数据库表自增主键

优点: 1. 简单

            2. 查询和插入的性能高

            3. 保证有序

缺点:1. 无法保证全局唯一, 分库分表

2.2、UUID

以java的UUID为例

优点: 1. 基本能保证全部唯一(重复概率低),如果加上时间戳和机器码

缺点: 1. 无序,无法保证后来的消息主键值大于前面的主键

            2. 因为乱序,所以对数据的存储,以mysql为例,B+树形结构的存储不友好。读写效率低

2.3、雪花主键

优点: 1. 基本能保证全部唯一(重复概率低)

            2. 主键值有序

            3. 读取性能高

            4. 可用性好,他适合分布式主键,不担心单点故障。

缺点: 1. 写入性能没自增主键好 (自增主键是int类型)

            2. 存在乱序或 重复的可能性,即机器的时钟回调


三、雪花主键的原理

一共64位,8个字节。它的值分别有, 符号位,时间戳,机器码,序列号组成。

基础的格式是,位数是从低向高(从右往左数)

1. 第64位,符号位, 0代表正数,1代表负数。所以默认为0

2. 第23-63位,一共41位,是时间戳的值,毫秒值。2^41 = 2199023255552 毫秒。除以 24*60*60*1000,约为69年。标准时间戳基准是1970-1-1,即可以用到 2039-1-1, 为了增加使用时间 ,可以修改时间基准值。

3. 第13-23位,一共10位,是机器ID值, 2^10, 1024台机器。集群可以有1024个节点。

4. 第1-12位,一共12位,是序列值,2^12 = 4096, 即每个节点每毫秒最大的并发,生成4096个雪花值。再多就会重复了。如果想要增加并发值, 可以降低机器ID值的位数,增加序列值的位数(一般集群也不会有1024个节点)。 

例如修改成23-15,一共8位是机器ID值,2^8 = 256 ,也有256个节点值。 1-14位是序列值,则16384,每个节点每毫秒的最大并发数就有16384个了。

下图为标准的雪花位数示意图


四、雪花主键的实现

华为高斯dws数据仓库,postgres的sql语法实现。

4.1、序列值

创建一个序列 

  1. SET search_path = test;
  2. CREATE SEQUENCE seq_table_id_1
  3. START WITH 0 -- 序列值开始值
  4. INCREMENT BY 1 -- 步长
  5. MINVALUE 0 -- 最小值
  6. NO MAXVALUE -- 为了保证 序列有序, 如果设定了最大值,则要打开cycle
  7. CACHE 100; -- 缓存,加快速度

4.2、雪花主键的实现

  1. /**
  2. 本例是按照标准的雪花位数分配,即机器ID值是10位,序列号是12位。
  3. 本例没有规范机器ID值,因为我的集群实际也只有一台。 你可以依据自己实际情况,指定机器ID值,例如mac码,ip地址等。
  4. 获取机器ID值后调用函数传入参数
  5. */
  6. CREATE OR REPLACE FUNCTION test.f_generate_snow_id(input_machine_id integer)
  7. RETURNS bigint
  8. LANGUAGE plpgsql
  9. NOT FENCED SHIPPABLE
  10. AS $$
  11. DECLARE
  12. -- our_epoch bigint := 1672502400000; --2023-1-1 00:00:00 的时间戳 , 修改基准时间为2023-01-01. 这样就可以使用到 2023 +69 = 2092年。
  13. seq_id bigint;
  14. machine_id int;
  15. now_millis bigint;
  16. result bigint := 0 ; --确保符号位是0,正数
  17. BEGIN
  18. seq_id := nextval('test.seq_table_id_1') % 4096; -- 序列号值大于4096,需要循环
  19. machine_id := input_machine_id % 1024 ;
  20. -- SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis;
  21. -- result := result | ((now_millis - our_epoch) << 22) ; -- 调整基准时间的实现
  22. SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis;
  23. result := result | (now_millis << 22) ;
  24. result := result | (machine_id << 10) ;
  25. result := result | (seq_id&4095);
  26. return result;
  27. END $$
  28. /

 五、测试写入性能。

5.1、目标表,建表时指定主键,并使用雪花

  1. create table test.t_test(
  2. id bigint not null default test.f_generate_snow_id(20) -- 建表时,传入 0-1023 的 任意值
  3. , age int
  4. ,PRIMARY key (id)
  5. )

 数据来源表: 200w

  1. create table test.t_source(
  2. id int
  3. ,name varchar2
  4. )

生产200w数据插入来源表 t_source.  这个语句效率有点低跑了30+分钟

  1. DECLARE
  2. i bigint := 0 ;
  3. sql text;
  4. begin
  5. loop
  6. sql := 'insert into test.t_source(age) values (1) ;' ;
  7. EXECUTE sql;
  8. i := i+1 ;
  9. exit when i > 2000000 ;
  10. end loop ;
  11. end ;

 5.2、插入性能

  1. insert into test.t_test(age)
  2. select age from test.t_source

费时: 2min37s

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/470306
推荐阅读
相关标签
  

闽ICP备14008679号