☰
雪花算法 ID 生成器(Mahjong Snowflake)
2026/10/1 8:07:21 网站建设 项目流程
-- ============================================================================-- 雪花算法 ID 生成器(Mahjong Snowflake)-- 支持动态传入类型 ID,服务器 ID 在初始化时固定。-- 默认布局控制在 53 位以内,避免旧 Lua double-number 运行时丢失低位精度。-- 默认纪元:2026-01-01 00:00:00 UTC---- 当同一秒内序列号耗尽时,借用未来时间戳继续生成,避免死循环-- ============================================================================localtostring=(mahjong_dumpandmahjong_dump.dump_null)ortostringlocaltoString=(mahjong_dumpandmahjong_dump.dump_null)ortoString-- 局部化系统库函数(提升性能,避免全局查找)localos_time=os.time-- 获取系统时间(秒)localmath_floor=math.floor-- 取整函数localmath_max=math.max-- 最大值函数(用于位数裁剪)localLogRichDebug=MJLOGTlocalLogRichWarning=MJLOGWlocalLogRichErr=MJLOGE-- 定义雪花算法模块(全局可见)-- 定义雪花算法模块(全局可见;热更新时直接替换重建,禁 or 留旧表)mahjong_snowflake={}-- ============================================================================-- 默认配置常量-- ============================================================================-- 默认配置常量(注意:在 Lua 中直接使用数字字面量而不是 local 变量,-- 遵循 C++ 宏全大写下划线命名风格,全局可见。)SNOWFLAKE_DEFAULT_EPOCH=1767225600-- 默认纪元秒数:2026-01-01 00:00:00 UTCSNOWFLAKE_DEFAULT_TIMESTAMP_BITS=28-- 时间戳占用的位数(从2026年起约可覆盖8.5年)SNOWFLAKE_DEFAULT_SERVER_ID_BITS=7-- 服务器ID占用的位数(最大128个服务器)SNOWFLAKE_DEFAULT_TYPE_BITS=6-- 类型ID占用的位数(最大64种类型)SNOWFLAKE_DEFAULT_SEQUENCE_BITS=12-- 序列号占用的位数(每秒每类型最多4096个ID)-- 进程内兜底持久化(key: server_id:type_id)g_tMahjongSnowflakeLastSecStore=g_tMahjongSnowflakeLastSecStoreor{}-- ============================================================================-- 根据位数计算最大值-- ============================================================================--- 根据给定的位数计算可表示的最大值--- @param bits integer 位数--- @return integer 最大值(2^bits - 1)localfunctionmax_value(bits)return(2^bits)-1end-- ============================================================================-- 创建雪花 ID 生成器实例-- ============================================================================--- 创建雪花 ID 生成器实例--- @param server_id integer 服务器ID(必须唯一,取值范围根据 server_id_bits 决定)--- @param opts table|nil 可选配置--- @field epoch integer 纪元秒数(默认为2026-01-01 00:00:00)--- @field timestamp_bits integer 时间戳位数--- @field server_id_bits integer 服务器ID位数--- @field type_bits integer 类型ID位数--- @field sequence_bits integer 序列号位数--- @field get_time function 获取当前秒数的函数(默认为 os.time)--- @return table 包含 next_id 方法的对象functionmahjong_snowflake.new(server_id,opts)opts=optsor{}localepoch=opts.epochorSNOWFLAKE_DEFAULT_EPOCHlocalts_bits=opts.timestamp_bitsorSNOWFLAKE_DEFAULT_TIMESTAMP_BITSlocalsid_bits=opts.server_id_bitsorSNOWFLAKE_DEFAULT_SERVER_ID_BITSlocaltype_bits=opts.type_bitsorSNOWFLAKE_DEFAULT_TYPE_BITSlocalseq_bits=opts.sequence_bitsorSNOWFLAKE_DEFAULT_SEQUENCE_BITS-- 检查总位数是否超过53位,保证旧 Lua double-number 运行时也能精确表示每个整数ID。ifts_bits+sid_bits+type_bits+seq_bits>53then-- 修复:降级为警告+裁剪而非直接崩溃,防止服务因配置错误被中断LogRichWarning(string.format("total bits exceed 53 (got %d), clamping to 53 to preserve integer precision",ts_bits+sid_bits+type_bits+seq_bits))localnOverflow=ts_bits+sid_bits+type_bits+seq_bits-53seq_bits=math_max(1,seq_bits-nOverflow)nOverflow=ts_bits+sid_bits+type_bits+seq_bits-53ifnOverflow>0thentype_bits=math_max(1,type_bits-nOverflow)nOverflow=ts_bits+sid_bits+type_bits+seq_bits-53endifnOverflow>0thensid_bits=math_max(1,sid_bits-nOverflow)endend-- 检查服务器ID是否超出范围localmax_server_id=max_value(sid_bits)ifserver_id<0orserver_id>max_server_idthenerror(string.format("server_id out of range [0, %d]",max_server_id))endlocalmax_type_id=max_value(type_bits)-- 类型ID最大值localmax_sequence=max_value(seq_bits)-- 序列号最大值-- 移位量(左移位数),用于组合各部分localseq_shift=0-- 序列号放在最低位,无需移位localtype_shift=seq_bits-- 类型ID左移 seq_bits 位localsid_shift=seq_bits+type_bits-- 服务器ID左移 (seq_bits + type_bits) 位localts_shift=seq_bits+type_bits+sid_bits-- 时间戳左移 (seq_bits + type_bits + sid_bits) 位localtype_multiplier=2^type_shift-- 类型ID段乘数,替代左移语法localsid_multiplier=2^sid_shift-- 服务器ID段乘数,替代左移语法localts_multiplier=2^ts_shift-- 时间戳段乘数,替代左移语法-- 获取当前时间的函数(可自定义,用于测试)localget_time=opts.get_timeoros_timelocalfnLoadLastSec=opts.load_last_seclocalfnSaveLastSec=opts.save_last_sec-- 存储每个类型的私有状态(用于序列号)-- state 结构:{ last_sec = 上次时间戳(秒), sequence = 当前序列号 }localstates={}localfunctionget_store_key(type_id)returnstring.format("%d:%d",server_id,type_id)end--- 加载上次记录的秒数和序列号(从内存+磁盘恢复)--- @param type_id integer 类型ID--- @return integer nSec, integer nSeq 上次记录的秒数和恢复的序列号localfunctionload_last_sec(type_id)localnDisk=niliffnLoadLastSecthenlocalbOk,nVal=pcall(fnLoadLastSec,server_id,type_id)ifbOkandtype(nVal)=="number"thennDisk=math_floor(nVal)endendlocaltMem=g_tMahjongSnowflakeLastSecStore[get_store_key(type_id)]localnMem,nMemSeq=nil,0iftype(tMem)=="table"thennMem=type(tMem.sec)=="number"andmath_floor(tMem.sec)ornilnMemSeq=type(tMem.seq)=="number"andmath_floor(tMem.seq)or0elseiftype(tMem)=="number"thennMem=math_floor(tMem)nMemSeq=0endifnDiskandnMemthenlocalnSec=nDisk>nMemandnDiskornMemreturnnSec,(nSec==nMemandnMemSeqor0)endreturn(nDiskornMemor0),(nDiskand0ornMemSeq)endlocalfunctionpersist_last_sec(type_id,nSec,nSeq)localnSecInt=math_floor(nSec)localnSeqInt=math_floor(nSeqor0)g_tMahjongSnowflakeLastSecStore[get_store_key(type_id)]={sec=nSecInt,seq=nSeqInt}iffnSaveLastSecthenlocalbOk,err=pcall(fnSaveLastSec,server_id,type_id,nSecInt)ifnotbOkthenLogRichErr(string.format("Snowflake: persist_last_sec failed for server %d type %d: %s",server_id,type_id,tostring(err)))endendend--- 生成下一个 ID--- @param type_id integer 类型ID(如匹配类型),范围 0 ~ max_type_id--- @return integer 64位整数ID(实际使用53位有效位,适配 Lua double-number 精度范围)localfunctionnext_id(type_id)-- 检查类型ID是否有效iftype_id<0ortype_id>max_type_idthenerror(string.format("type_id out of range [0, %d]",max_type_id))end-- 获取该类型的状态localstate=states[type_id]ifnotstatethenlocalnRecovered,nRecoveredSeq=load_last_sec(type_id)localnNow=get_time()ifnRecovered<nNowthennRecovered=nNow nRecoveredSeq=0elseifnRecoveredSeq==0then-- 崩溃重启在同一秒内且序列号已丢失:推进1秒避免ID碰撞nRecovered=nRecovered+1end-- 修复崩溃重启后 nRecoveredSeq 为 nil 的问题:确保序列号有初始值state={last_sec=nRecovered,sequence=(type(nRecoveredSeq)=="number"andnRecoveredSeqor0)}states[type_id]=stateendlocalcur_sec=get_time()-- 当前秒数(相对于系统时间)ifcur_sec<state.last_secthen-- 时钟回拨:使用上次的时间戳,并递增序列号cur_sec=state.last_sec state.sequence=state.sequence+1ifstate.sequence>max_sequencethen-- 序列号耗尽,进入下一秒cur_sec=cur_sec+1state.sequence=0state.last_sec=cur_secpersist_last_sec(type_id,state.last_sec,state.sequence)else-- 序列号未耗尽,但时钟已回拨,仍需持久化以防止崩溃后ID重复persist_last_sec(type_id,state.last_sec,state.sequence)endelseifcur_sec==state.last_secthen-- 同一秒内序列号递增state.sequence=state.sequence+1ifstate.sequence>max_sequencethen-- 序列号耗尽,借用下一秒cur_sec=cur_sec+1state.sequence=0state.last_sec=cur_secpersist_last_sec(type_id,state.last_sec,state.sequence)endelse-- 新的一秒,序列号重置state.sequence=0state.last_sec=cur_sec-- HACK: persist_last_sec 如绑定磁盘IO,每秒调用在高并发下可能成为瓶颈-- 若使用进程内持久化(仅内存),则无此问题persist_last_sec(type_id,state.last_sec,state.sequence)end-- 计算相对于纪元的偏移localts_offset=cur_sec-epochifts_offset<0thenLogRichErr(string.format("mahjong_snowflake: current time %d before epoch %d, clamping to 0",cur_sec,epoch))ts_offset=0end-- 组合各部分生成ID:使用算术乘法替代位运算,避免旧 Lua 运行时语法失败。localid=ts_offset*ts_multiplier+server_id*sid_multiplier+type_id*type_multiplier+state.sequencereturnidendreturn{next_id=next_id}end

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询