snowflake核心源码解读

Snowflake是twitter开源的一款提供产生UID的网络服务软件,简介请猛击这里,另外笔者会另外写文章介绍其特性与使用,本文旨在描述snowflake是如何实现UID生成的。

Snowflake ID组成

Snowflake ID有64bits长,由以下三部分组成:

  • time---42bits,精确到ms,那就意味着其可以表示长达(2^42-1)/(1000360024*365)=139.5年,另外使用者可以自己定义一个开始纪元(epoch),然后用(当前时间-开始纪元)算出time,这表示在time这个部分在140年的时间里是不会重复的,官方文档在这里写成了41bits,应该是写错了。另外,这里用time还有一个很重要的原因,就是可以直接更具time进行排序,对于twitter这种更新频繁的应用,时间排序就显得尤为重要了。

  • machine id---10bits,该部分其实由datacenterId和workerId两部分组成,这两部分是在配置文件中指明的。

    • datacenterId的作用(个人看法)

      1.方便搭建多个生成uid的service,并保证uid不重复,比如在datacenter0将机器0,1,2组成了一个生成uid的service,而datacenter1此时也需要一个生成uid的service,从本中心获取uid显然是最快最方便的,那么它可以在自己中心搭建,只要保证datacenterId唯一。如果没有datacenterId,即用10bits,那么在搭建一个新的service前必须知道目前已经在用的id,否则不能保证生成的id唯一,比如搭建的两个uid service中都有machine id为100的机器,如果其server时间相同,那么产生相同id的情况不可避免。

      2.加快server启动速度。启动一台uid server时,会去检查zk同workerId目录中其他机器的情况,如其在zk上注册的id和向它请求返回的work_id是否相同,是否处同一个datacenter下,另外还会检查该server的时间与目前已有机器的平均时间误差是否在10s范围内等,这些检查是会耗费一定时间的。将一个datacenter下的机器数限制在32台(5bits)以内,在一定程度上也保证了server的启动速度。

    • workerId是实际server机器的代号,最大到32,同一个datacenter下的workerId是不能重复的。它会被注册到zookeeper上,确保workerId未被其他机器占用,并将host:port值存入,注册成功后就可以对外提供服务了。

  • sequence id ---12bits,该id可以表示4096个数字,它是在time相同的情况下,递增该值直到为0,即一个循环结束,此时便只能等到下一个ms到来,一般情况下4096/ms的请求是不太可能出现的,所以足够使用了。

Snowflake ID便是通过这三部分实现了UID的产生,策略也并不复杂。下面我们来看看它的一些关键源码。

IdWorker.scala(com.twitter.service.snowflake)

UID生成的核心代码都在这个文件里,我们先来看看其中的一些常量定义。

常量

//自定义的开始纪元,这里貌似是tweet纪元,但我算出的结果是Nov 04 09:42:54 CST 2010,不大对
  val twepoch = 1288834974657L
  //workerID的字节数
  private[this] val workerIdBits = 5L
  //datacenterId的字节数
  private[this] val datacenterIdBits = 5L
  //workerId和datacenterId的最大表示值
  private[this] val maxWorkerId = -1L ^ (-1L << workerIdBits)//2^5-1
  private[this] val maxDatacenterId = -1L ^ (-1L << datacenterIdBits)//2^5-1
  //sequenceId的字节数
  private[this] val sequenceBits = 12L
  private[this] val sequenceMask = -1L ^ (-1L << sequenceBits)
  //各个id对应的偏移值
  private[this] val workerIdShift = sequenceBits//12
  private[this] val datacenterIdShift = sequenceBits + workerIdBits//12+5=17
  private[this] val timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits//12+5+5=22

  private[this] var lastTimestamp = -1L


各个变量的含义请看注释,workerId和datacenterId都是配置文件定义好的,没什么可说的,下面我们看下time和sequence id的产生源码。


protected[snowflake] def nextId(): Long = synchronized { //获取当前时间,ms var timestamp = timeGen() //lastTimestamp中记录着上一次产生id的时间戳 if (timestamp < lastTimestamp) { //小于,机器时间出问题了 exceptionCounter.incr(1) log.error("clock is moving backwards. Rejecting requests until %d.", lastTimestamp); throw new InvalidSystemClock("Clock moved backwards. Refusing to generate id for %d milliseconds".format( lastTimestamp - timestamp)) } //相等,递增sequence id if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask //递增过程中sequence为0了,表明sequence 值用尽了,等待下一个ms的到来。 if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp) } } else { //大于,将sequence设为0,从头递增 sequence = 0 } //记录此次产生id的时间戳 lastTimestamp = timestamp //通过shift组装返回id ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence }

通过上面的注释相信大家已经明白time和sequence id产生的过程了,是不是特别简单?!优雅的设计带来的也往往是简洁的代码。

小结

本文主要阐述了snowflake uid的组成和产生策略,希望对大家有所帮助。

标签: snowflake, id_generator

添加新评论