例如,用户发一个文章,要生成一个文章ID,假定用户ID是14532,则
time <-- math.currentMiliseconds(); shardindId <-- 14532 % 1024; //即196 articleId <-- incrby idgenerator_next_196 1 //1是增长的步长用lua脚本表示是:
local step = redis.call('GET', 'idgenerator_step');
local shardId = KEYS[1] % 1024;
local next = redis.call('INCRBY', 'idgenerator_next_' .. shardId, step);
return {math.currentMiliseconds(), shardId, next};“idgenerator_step"这个key用来存放增长的步长。
客户端用eva l执行上面的脚本,得到三元组之后,可以自由组合成64bit的全局ID。
上面只是一个服务器,那么如何解决单点问题呢?
上面的“idgenerator_step"的作用就体现出来了。
比如,要部署三台redis做为ID生成服务器,分别是A,B,C。那么在启动时设置redis-A下面的键值:
idgenerator_step = 3 idgenerator_next_1, idgenerator_next_2, idgenerator_next_3 ... idgenerator_next_1024 = 1
设置redis-B下面的键值:
idgenerator_step = 3 idgenerator_next_1, idgenerator_next_2, idgenerator_next_3 ... idgenerator_next_1024 = 2设置redis-C下面的键值:
idgenerator_step = 3 idgenerator_next_1, idgenerator_next_2, idgenerator_next_3 ... idgenerator_next_1024 = 3
那么上面三台ID生成服务器之间就是完全独立的,而且平等关系的。任意一台服务器挂掉都不影响,客户端只要随机选择一台去用eva l命令得到三元组即可。
我测试了下单台的redis服务器每秒可以生成3万个ID。那么部署三台ID服务器足以支持任何的应用了。
测试程序见这里:
https://gist.github.com/hengyunabc/9032295
如果不熟悉lua脚本,可以定制自己的ID规则等比较麻烦。
优点:
非常的快,而且可以线性部署。
可以随意定制自己的Lua脚本,生成各种业务的ID。
其它的东东:
MongoDB的Objectid,这个实在是太长了要12个字节:
ObjectId is a 12-byte BSON type, constructed using: a 4-byte value representing the seconds since the Unix epoch, a 3-byte machine identifier, a 2-byte process id, and a 3-byte counter, starting with a random value.
总结:
生成全局ID并不很难实现的东东,不过从各个网络的做法,及演进还是可以学到很多东东。有时候一些简单现成的组件就可以解决问题,只是缺少思路而已。
参考:
http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/
http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram
https://github.com/twitter/snowflake/
http://docs.mongodb.org/manual/reference/object-id/
http://www.redisdoc.com/en/latest/script/eva l.html redis脚本参考