扶沟县建设局网站,网站用html做框架asp做主页,网站关键字描述,六盘水网站开发1. 为什么说自增ID在分布式时代“不够用了”#xff1f; 如果你用过PostgreSQL#xff0c;肯定对SERIAL或BIGSERIAL数据类型不陌生。建表时随手一写#xff0c;一个自带自增序列的主键字段就有了#xff0c;简单省心。我自己在早期做单体应用时#xff0c;也特别爱用这个接下来是核心的函数。我重新写了一个加了更详细的注释并修正了位运算的位数分配使其更接近标准的41位时间戳、10位节点ID、12位序列号的划分。CREATE OR REPLACE FUNCTION generate_snowflake_id( node_id INT DEFAULT 1 -- 默认节点ID为1可按需传入 ) RETURNS BIGINT AS $$ DECLARE -- 自定义纪元时间戳毫秒。这里示例为 2020-01-01 00:00:00 UTC our_epoch BIGINT : 1577836800000; seq_id BIGINT; -- 序列号部分 now_millis BIGINT; -- 当前时间戳毫秒 result BIGINT; -- 最终结果 BEGIN -- 1. 获取序列号从序列取下一个值并取模40962^12得到0-4095的序列号 seq_id : nextval(global_id_seq) % 4096; -- 2. 获取当前毫秒时间戳 SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis; -- 3. 计算时间差毫秒并左移22位为10位节点ID和12位序列号腾出位置 -- 标准是左移22位 (41位时间戳 | 10位节点 | 12位序列) result : (now_millis - our_epoch) 22; -- 4. 放入节点ID左移12位后与结果合并 result : result | (node_id 12); -- 5. 放入序列号 result : result | seq_id; RETURN result; END; $$ LANGUAGE PLPGSQL;现在我们可以用这个函数来创建表了。注意看主键id的默认值不再是nextval(某个序列)而是我们自定义的generate_snowflake_id()函数。你可以为不同的分片表传入不同的node_id参数。-- 创建表主键默认使用雪花ID并指定节点ID为5假设这是第5个分片 CREATE TABLE public.user_orders ( id BIGINT NOT NULL DEFAULT generate_snowflake_id(5), user_id BIGINT NOT NULL, amount DECIMAL(10, 2), created_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT user_orders_pkey PRIMARY KEY (id) ); -- 插入数据id会自动生成 INSERT INTO public.user_orders (user_id, amount) VALUES (1001, 99.99); INSERT INTO public.user_orders (user_id, amount) VALUES (1002, 150.50); -- 查询一下看看生成的ID SELECT * FROM public.user_orders;你会看到生成的id是一串长长的、趋势递增的数字这就是我们的雪花ID。它完全由数据库函数在插入时实时计算生成不依赖任何中心化的序列状态同步完美解决了分布式环境下的ID冲突问题。4. 避坑指南与高级调优让雪花算法真正落地无忧把函数跑起来只是第一步真正在生产环境用稳还得注意几个关键细节。这些都是我踩过坑之后总结出来的经验。第一个大坑序列号耗尽与取模运算。原始文章里提到了当写入量极大时可能会出现主键冲突。问题就出在seq_id : nextval(table_id_seq) % 1024;这一行。% 1024意味着序列号范围只有0-1023。如果单个节点比如一个数据库分片的写入QPS极高一毫秒内插入超过1024条记录那么取模后序列号就会重复结合相同的时间戳和节点ID就会生成重复的ID。解决方案就是扩大序列号位数。原始文章后来改成了% 4096这是对的。我上面的函数直接用了% 409612位序列号。你需要根据业务峰值吞吐量来评估假设你单个分片最高峰时每秒写入1万条QPS10000那么平均每毫秒是10条4096的容量绰绰有余。公式很简单(2 ^ 序列号位数) 单节点每毫秒最大写入量。第二个要点节点ID的管理。node_id或叫shard_id是全局唯一的标识。你必须确保分配给每个数据库实例、每个微服务、每个分片的ID是唯一的并且在整个系统生命周期内不冲突。通常的做法是把这个配置放在配置中心如Nacos, Apollo或者利用ZK/Etcd这样的协调服务来分配。绝对不要硬编码在函数里更不要出现重复。一个实用的技巧是可以用服务器的IP地址最后一段的某个映射或者用部署时指定的环境变量来动态设置。时间回拨问题这是雪花算法的“阿喀琉斯之踵”。如果服务器时钟发生回拨比如NTP同步、人工误操作可能导致生成的时间戳比上一次还小进而可能产生重复ID。在数据库函数层面我们很难做太复杂的补救比如等待。一个比较务实的防御策略是监控告警严格监控所有数据库服务器的时间同步状态NTP一旦发现时钟偏移告警。在应用层处理更常见的做法是在应用服务层使用雪花算法库如Java的Hutool的Snowflake这些库通常内置了时钟回拨检测机制比如发现回拨就抛出异常由业务代码决定是重试还是降级。数据库函数作为兜底将数据库的生成函数视为一个兜底方案。当应用层生成失败或迁移老旧数据时使用。这样可以将时钟回拨的风险隔离在更可控的应用层。性能考量与函数优化。每次插入都调用这个函数会有一点点性能开销。对于超高性能要求的场景你可以考虑使用PostgreSQL的IMMUTABLE函数标记如果确保节点ID不变且时间戳逻辑不变但需谨慎因为clock_timestamp()是易变的。或者像一些大厂的做法在应用层批量生成一批ID缓存在本地插入时直接使用彻底避免每次插入都计算。数据库函数则用于补漏或特定场景。分库分表下的使用姿势。在分库分表场景下雪花算法简直是绝配。假设你按用户ID哈希分成了10个表user_orders_0 到 user_orders_9。你可以给这10个物理表分配不同的node_id比如0-9。插入数据时根据用户ID路由到对应的分表该表使用其专属的node_id生成ID。这样所有分表生成的ID不仅全局唯一而且由于高位的节点ID不同数据在不同分片间也能做到大致均匀分布避免了热点。迁移老数据时你需要为已有的记录批量生成新的雪花ID并更新。这里务必注意更新主键是个危险操作所有关联的外键都要一并处理。建议在业务低峰期通过精心编排的脚本分批次完成。从自增ID迁移到雪花算法看似只是换了个主键生成方式实则是对系统分布式架构认知的一次升级。它要求你更清晰地定义数据的边界、节点的身份和时间的权威。这个过程可能会遇到一些麻烦比如序列号位数设小了或者节点ID配重了但一旦趟平这条路你的系统就在水平扩展的能力上迈出了坚实的一步。我现在负责的几个核心业务表全都换成了雪花ID再也没为数据合并、分库分表时的ID问题熬过夜。技术选型没有银弹但雪花算法在分布式唯一ID这个点上确实是一个经过海量业务验证的、非常靠谱的选择。