0

0

c# 在 C# 中实现一个简单的分布式ID生成器(雪花算法)

月夜之吻

月夜之吻

发布时间:2026-02-08 08:14:09

|

570人浏览过

|

来源于php中文网

原创

直接用SnowflakeIdGenerator类易出错,根本原因是未保证workerId和datacenterId全局唯一;需用Redis等中心化服务动态分配,统一用DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()防时钟回拨,sequence溢出时应阻塞等待而非重置。

c# 在 c# 中实现一个简单的分布式id生成器(雪花算法)

为什么直接用 SnowflakeIdGenerator 类容易出错

很多人照着网上的 C# 雪花算法实现抄一个 SnowflakeIdGenerator 类,运行起来 ID 看似递增,但一上多节点就重复——根本原因是没处理好 **机器 ID(workerId)和数据中心 ID(datacenterId)的全局唯一分配**。雪花算法要求同一毫秒内,不同节点的 workerId 必须不重叠,否则 timestamp + workerId + sequence 组合就可能碰撞。

常见错误包括:

  • 编码 workerId = 1,本地跑得通,部署到 3 台服务器全设成 1,必然冲突
  • 用 IP 哈希取模算 workerId,但 IPv4 地址段有限,哈希后碰撞概率高
  • 依赖配置文件手动填 workerId,运维漏配或填错,服务启动失败或 ID 冲突静默发生

ZooKeeperRedis 自动分配 workerId 更可靠

生产环境推荐用中心化协调服务动态分配,避免人工干预。Redis 是多数团队已有组件,接入成本低:

  • 每个实例启动时,用 Lua 脚本在 Redis 中原子性地申请一个未被占用的 workerId(比如从 0–1023 范围取)
  • 成功后写入临时 key(带 TTL),并监听该 key 过期或主动释放
  • 若申请不到可用 ID,应阻塞等待或快速失败,不能降级为随机数——那就不叫雪花算法了

示例 Lua 脚本逻辑(用于 redis-cli --eval):

local used = redis.call('smembers', 'snowflake:used_worker_ids')
local all = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
for _, id in ipairs(all) do
  local is_used = false
  for _, u in ipairs(used) do
    if tonumber(u) == id then is_used = true; break end
  end
  if not is_used then
    redis.call('sadd', 'snowflake:used_worker_ids', id)
    redis.call('setex', 'snowflake:worker:' .. id, 300, 'alive')
    return id
  end
end
return -1

DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()DateTime.Now 更安全

雪花算法依赖时间戳左移,一旦系统时钟回拨(NTP 校正、虚拟机休眠恢复),DateTime.Now 可能跳变,导致生成重复 ID 或序列号溢出。C# 中应统一用 UTC 时间戳:

HiDream AI
HiDream AI

全中文AIGC创作平台和AI社区

下载
  • DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() 返回 long,精度毫秒,无时区歧义
  • 不要用 DateTime.Now.Ticks / 10000,它依赖本地时区且易受夏令时影响
  • 建议在 ID 生成器内部缓存上一次时间戳,若新时间 ≤ 上次值,则用 sequence++;若差值 > 5s,可 panic 或抛异常——说明时钟严重异常

序列号 sequence 溢出时必须阻塞等待,不能重置

标准雪花算法中,序列号占 12 bit(0–4095),意味着每毫秒最多生成 4096 个 ID。如果业务峰值超过该速率,常见错误是“超了就清零”,这会破坏单调递增性,且在分布式下极易撞 ID。

  • 正确做法:当 sequence == 4095 且当前毫秒未变时,线程 sleep 到下一毫秒再继续
  • sleep 时间不宜用 Thread.Sleep(1),而应计算目标时间戳后调用 SpinWait.SpinUntil 或基于 Stopwatch 的 busy-wait,减少上下文切换开销
  • 注意:.NET 6+ 中 System.TimeProvider 可用于测试时模拟时间推进,方便压测序列号阻塞逻辑

简单阻塞示意(非完整实现):

while (currentTimestamp == _lastTimestamp)
{
    currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
    if (currentTimestamp == _lastTimestamp)
    {
        // 自旋等待,直到下一毫秒
        while (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() <= currentTimestamp) { }
        currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
    }
}

时间戳和序列号的协同边界非常敏感,任何想“绕过限制”的优化(比如扩展 sequence 位数、改用 hybrid logical clock)都会脱离雪花协议,下游系统可能无法识别。保持原生 64 位结构,才是兼容性和可维护性的底线。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

381

2023.08.11

分布式和微服务的区别
分布式和微服务的区别

分布式和微服务的区别在定义和概念、设计思想、粒度和复杂性、服务边界和自治性、技术栈和部署方式等。本专题为大家提供分布式和微服务相关的文章、下载、课程内容,供大家免费下载体验。

241

2023.10.07

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

613

2023.08.10

Java 并发编程高级实践
Java 并发编程高级实践

本专题深入讲解 Java 在高并发开发中的核心技术,涵盖线程模型、Thread 与 Runnable、Lock 与 synchronized、原子类、并发容器、线程池(Executor 框架)、阻塞队列、并发工具类(CountDownLatch、Semaphore)、以及高并发系统设计中的关键策略。通过实战案例帮助学习者全面掌握构建高性能并发应用的工程能力。

89

2025.12.01

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

431

2023.08.14

常用的数据库软件
常用的数据库软件

常用的数据库软件有MySQL、Oracle、SQL Server、PostgreSQL、MongoDB、Redis、Cassandra、Hadoop、Spark和Amazon DynamoDB。更多关于数据库软件的内容详情请看本专题下面的文章。php中文网欢迎大家前来学习。

988

2023.11.02

内存数据库有哪些
内存数据库有哪些

内存数据库有Redis、Memcached、Apache Ignite、VoltDB、TimesTen、H2 Database、Aerospike、Oracle TimesTen In-Memory Database、SAP HANA和ache Cassandra。更多关于内存数据库相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

652

2023.11.14

mongodb和redis哪个读取速度快
mongodb和redis哪个读取速度快

redis 的读取速度比 mongodb 更快。原因包括:1. redis 使用简单的键值存储,而 mongodb 存储 json 格式的数据,需要解析和反序列化。2. redis 使用哈希表快速查找数据,而 mongodb 使用 b-tree 索引。因此,redis 在需要高性能读取操作的应用程序中是一个更好的选择。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

494

2024.04.02

Golang处理数据库错误教程合集
Golang处理数据库错误教程合集

本专题整合了Golang数据库错误处理方法、技巧、管理策略相关内容,阅读专题下面的文章了解更多详细内容。

67

2026.02.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
进程与SOCKET
进程与SOCKET

共6课时 | 0.4万人学习

Redis+MySQL数据库面试教程
Redis+MySQL数据库面试教程

共72课时 | 6.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号