引言
原文链接:
https://yunsonbai.top/2020/01/21/snowflake_deploy/
ID在实际生产中起着非常重要的作用,任务追踪,任务查询等等等,尤其是在数据库层面,如果使用自增 Int 做唯一索引,那查询速度将变得快(常数级)的惊人。很多人可能有疑问,为什么唯一索引要建议使用自增int呢,建议大家查阅一下MySQL是怎么插入数据的。本文将分享一下我的snowflake分布式ID生成算法的部署方案。
关于snowflake
官方介绍: https://github.com/twitter-archive/snowflake
优势:
基于时间戳只能递增,并且能持续使用60多年(那会业务还有的话,肯定要优化)
分布式,几乎完全的水平扩展(1024个,实测go版单机(2C+4G)能到3万qps)
需要考虑的问题
怎么部署
有时间回滚怎么办
服务的依赖包
snowflake: https://github.com/bwmarrin/snowflake
web框架: https://github.com/gin-gonic/gin
接下来的名词说明
节点: 提供服务的服务器
节点ID: 雪花算法启动需要一个number, ID就是这个number
node资源队列: 存放节点ID的队列
心跳: 定时上报节点信息的协程, 2秒一次
本服务数据结构
图示

说明(以上数据存在redis)
node资源队列: 即将启动的node ID(1-1024), 存放单纯的数字
nodeInfo表: hash表, 存放心跳信息。key: id, value: jsonStr jsonStr: ‘{“t”: <时间戳>, “sid”: <上报节点机器唯一标识>}’
部署说明
节点id发放
流程图

说明
投放的总节点数n不能超过1024
n依次递减得到ID, 如果在信息表存在该ID则跳过,投放其他ID
如需减少实例,可直接杀死实例,实例会回收ID到资源队列,等待下次使用
节点的启动/恢复
流程图

流程图说明
nodeInfo有在该ID的心跳, 上报节点不是自己且最新上报时间距离现在<20秒
20秒的意义: 避免时间回滚(一般集群定时同步时间, 若出现出现20秒以上回滚, 问题有点大)
重试的意义: 尽可能拾起死掉的ID
尝试从node资源队列里获取ID, 最多重试3次, 每次间隔1秒, 若取到返回ID
若1失败, 尝试从nodeInfo表里获取20秒以上没有心跳的节点ID, 最多重试3次,每次间隔5秒, 若取到返回ID
如果1和2都没拿到ID, 服务终止。否则启动节点服务并尝试发心跳
当满足以下情况不能发起心跳, 且终止服务, 并销毁该ID
关键点说明
ID的回收: 服务终止时, 由退出信号监听协程负责把ID重新放回node资源队列,如果ID为0, 则不再放回(所谓的销毁)。
ID销毁: 把本节点拿到的ID置0
节点增加/更新
增加:
确定目前node资源队列和nodeInfo表中有的ID
假定增加后节点数为N, 目前所有的节点数为n。若N>n, 需要重新投放, 投放总数为N(注意看前边的投放规则), 若N<=n, 不用投放直接启动剩余节点
更新: 采取先终止服务(终止时节点会回收ID), 在启动新节点。可以一个或多个操作,注意不要全部杀死, 否则会影响线上服务。
结束语
分布式ID生成算法snowflake部署的最大难点在于同一时刻不能存在两个一样的node ID运行和如何避免时间回滚, 本文几个解决方法:
两个辅助协程加一个恢复规则, 快速帮助系统恢复正常状态。
欢迎大家批评指正。




