6行代码实现一个 id 发号器

id 发号器的问题,@一乐 的这篇文章说的很透彻了:http://weibo.com/p/1001603800404851831206 但参考实现就显得有些复杂。最近在雪球工作中正好需要用到发号器,于是用 Lua 在 Redis 上实现了一个最简单的:


-- usage: redis-cli -h 10.10.201.100 -p 10401 EVAL "$(cat getID.lua)" 1 XID:01:02
local arg = KEYS[1]
-- project id must has 2 digits, 01 - 15
local pid = tonumber(string.sub(arg, 5, 6))
-- instance id must has 2 digits, 01 - 15
local iid = tonumber(string.sub(arg, 8, 9))
local idnum = redis.call("INCR", "ID_IDX") % 65536
local sec = redis.call("TIME")[1] - 1420041600
return sec*16777216 + pid*1048576 + iid*65536 + idnum

解释:

  • id 总长度 52bit,为了兼容 js,php,flex 等语言 long 类型最长只能 52 bit
  • 最高 28 bit 为秒级时间戳,因为位数限制,不能从 1970.1.1 开始,-1420041600 表示从 2015.1.1 开始,大约可以使用10年(3106天)
  • 接下来4个bit为 project id,客户端传入,区分业务
  • 再接下来4个bit为 instance id,HA 用的,支持最多16个instance。如果业务只需要“秒级粗略有序”,比如发微博或发评论,则可以多个 instance 同时使用,不需要做任何特殊处理;如果业务需要“因果有序”,比如某个user短期内快速做的多个操作必须有因果顺序(程序化交易,必须先卖再买,几个毫秒内完成),那么就需要做一些特殊处理,要么用 uid 做一致性hash,或者像我们这样偷懒:一段时间内固定使用一个 instance
  • 最低16个bit是 sequence id,每个 instance 支持每秒 65535 个 id。bit数再大,redis 该成为瓶颈了。
  • twitter和微博都是把 instance id 写死到 server 端,这样 server端就变成有状态的了:16个instance,每个 instance 都与其它的配置不一样。在雪球我们不希望 server 端有状态,于是设计成 instance id 由客户端传入,server 端退化成一个普通的 redis cache server (不需要 rdb,不需要 aof,宕机重启即可)
  • 几个注意点:宕机不能自动立即重启,必须间隔1秒以上,避免 sequence id 重复导致 id 重复;迁移时必须先 kill 老的 instance,再启动新的,也是为了避免 sequence id 重复。

id 发号器说简单也确实挺简单。任何一个技术点,只要理解了本质,大约都是这么简单罢。