发号器是所有公司通用的功能,目的是为了产生分布式环境下全局唯一的id,在有赞,其中间件为March。
有赞的发号器有两种运行模式,一种是基于计数器自增;另一种是基于时间戳与计数器结合方式。

发号器的三个必要条件

  1. crash safe,服务崩溃不影响发号器使用,如id不能重叠。
  2. 不受外部服务器变化影响,如服务器的时间戳,有可能会发生回跳。
  3. 高可用,高吞吐量。

    发号器的实现

    Sequence

    March在启动时会从etcd中载入之前持久化的已经发过的id作为起点,然后执行一次持久化,将id+batch保存下来,[id,id+batch]的区间就是缓存,客户端请求的id都是从这个缓存中获取的,同时会启动一个goroutine来做持久化,缓存容量低于50%时会再次通知持久化,上界就扩容到了id+batch*2,如果有突发的流量,将缓存池进行耗空,那么会发生阻塞。同时主备切换时,可能会调空一段id,并没有影响。

    Timestamp

    这个模式下,id分为3段,node,timestamp,sequence。通过配置各个段的长度和偏移以及时间戳的精度,就可以兼容各种已有的基于时间戳的发号器实现。多个请求到来时,如果timestamp相同,则增长sequence,timestamp改变时就清空sequence。另外,如果sequence段溢出,则将溢出部分加入到timestamp上,所以即使sequence耗空了,也不会阻塞发号器。它会定时持久化。March启动时,如果获取当前时间大于保存的时间,就使用当前时间作为起点,否则以保存的时间作为起点。同样的,如果请求时发现当前时间小于保存的时间,就使用保存时间,根本上避免了机器时间戳回跳问题。

    高可用

    March的高可用是利用etcd的ttl和watch实现,启动时,创建一个新的带ttl的Node,如果成功,就称为主节点,否则称为备节点。
  • 主节点

定时用前一次请求返回的index刷新Node的ttl,保持自己的主节点角色,刷新失败时说明主节点已被抢走,则重新进行抢主流程。与此同时,还会等待demote请求,收到请求时,会等待新的主节点信息。

  • 备节点

先查询主节点信息,备节点收到发号请求时,会按Redis Cluster协议重定向到主节点,之后开始Watch Node的变化,检测到变化时,也开始抢主节点过程。
这样实现的好处是,最多一个ttl就能检测到,并完成切换,可以做到完全无损。