学习笔记

实习学习笔记


雪花算法

<h1><center>雪花算法</center></h1> <h2>雪花算法是什么</h2> <p>SnowFlake是Twitter公司采用的一种算法,目的是在分布式系统中产生全局唯一且趋势递增的ID。</p> <h2>雪花算法概要</h2> <h3>雪花算法能够保证</h3> <p>所有生成的id按时间趋势递增 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)</p> <h3>生成的ID格式</h3> <center>![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/8bc2ee653f9e7ce3d5ac329eda97c2f8)</center> <h3>ID组成部分</h3> <p>每个ID是一个长度为64bit的数字</p> <h4>1.二进制正负数表示</h4> <p>第一位 占用1bit,其值始终是0,没有实际作用。</p> <h4>2.时间戳</h4> <p>时间戳 占用41bit,精确到毫秒,总共可以容纳约69年的时间</p> <h4>3.工作机器ID</h4> <p>工作机器id 占用10bit,其中高位5bit是数据中心ID,低位5bit是工作节点ID,做多可以容纳1024个节点。</p> <h4>4.序列号</h4> <p>序列号 占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID。</p> <h3>一毫秒能够生成的ID</h3> <p>SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢:: 同一毫秒的ID数量 = 1024 X 4096 = 4194304</p> <h2>算法实现</h2> <pre><code class="language-java">public class IdWorker{ /**长度5位的工作id */ private long workerId; /** * 长度5位的数据id */ private long datacenterId; /** * 长度12位的序列号 */ private long sequence; /** * id生成器构造方法,初始化 工作id、数据id、序列号 长度共12位 * @param workerId 工作id * @param datacenterId 数据id * @param sequence 序列号 */ public IdWorker(long workerId, long datacenterId, long sequence){ // sanity check for workerId if (workerId &gt; maxWorkerId || workerId &lt; 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0",maxWorkerId)); } if (datacenterId &gt; maxDatacenterId || datacenterId &lt; 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId)); } System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId); this.workerId = workerId; this.datacenterId = datacenterId; this.sequence = sequence; } /** * 初始时间戳 */ private long twepoch = 1288834974657L; /** * 工作id长度为5位 */ private long workerIdBits = 5L; /** * 数据id长度为5位 */ private long datacenterIdBits = 5L; /** * 工作id最大值 */ private long maxWorkerId = -1L ^ (-1L &lt;&lt; workerIdBits); /** * 数据id最大值 */ private long maxDatacenterId = -1L ^ (-1L &lt;&lt; datacenterIdBits); /** * 序列号长度 */ private long sequenceBits = 12L; //序列号最大值 /** * 序列号最大值 */ private long sequenceMask = -1L ^ (-1L &lt;&lt; sequenceBits); //工作id需要左移的位数,12位 /** * 工作id需要左移的位数,12位 * 即排在其前的 序列号长度sequenceBits */ private long workerIdShift = sequenceBits; //数据id需要左移位数 12+5=17位 /** * 数据id需要左移位数 12+5=17位 * 即排在其前的 序列号长度sequenceBits + 工作id长度workerIdBits */ private long datacenterIdShift = sequenceBits + workerIdBits; //时间戳需要左移位数 12+5+5=22位 /** * 时间戳需要左移位数 12+5+5=22位 * 即排在其前的 序列号长度sequenceBits + 工作id长度workerIdBits + 数据id长度datacenterIdBits */ private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; /** * 上次时间戳,初始值为负数 */ private long lastTimestamp = -1L; public long getWorkerId(){ return workerId; } public long getDatacenterId(){ return datacenterId; } public long getTimestamp(){ return System.currentTimeMillis(); } /** * 下一个ID的生成算法 * @return 生成的id */ public synchronized long nextId() { long timestamp = timeGen(); //获取当前时间戳如果小于上次时间戳,则表示时间戳获取出现异常 if (timestamp &lt; lastTimestamp) { System.err.printf("clock is moving backwards. Rejecting requests until %d.", lastTimestamp); throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加一;否则序列号赋值为0,从0开始。 if (lastTimestamp == timestamp) { sequence = (sequence + 1) &amp; sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0; } //将上次时间戳值刷新 lastTimestamp = timestamp; /** * 返回结果: * (timestamp - twepoch) &lt;&lt; timestampLeftShift) 表示将时间戳减去初始时间戳,再左移相应位数 * (datacenterId &lt;&lt; datacenterIdShift) 表示将数据id左移相应位数 * (workerId &lt;&lt; workerIdShift) 表示将工作id左移相应位数 * | 是按位或运算符,例如:x | y,只有当x,y都为0的时候结果才为0,其它情况结果都为1。 * 因为个部分只有相应位上的值有意义,其它位上都是0,所以将各部分的值进行 | 运算就能得到最终拼接好的id */ return ((timestamp - twepoch) &lt;&lt; timestampLeftShift) | (datacenterId &lt;&lt; datacenterIdShift) | (workerId &lt;&lt; workerIdShift) | sequence; } /** * 获取时间戳,并与上次时间戳比较 * @param lastTimestamp * @return */ private long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp &lt;= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 获取系统时间戳 * @return */ private long timeGen(){ return System.currentTimeMillis(); } /** * IdWorker生成测试 */ public static void main(String[] args) { IdWorker worker = new IdWorker(1,1,1); for (int i = 0; i &lt; 30; i++) { System.out.println(worker.nextId()); } } }</code></pre>

页面列表

ITEM_HTML