回源码主页即时通讯网 - 即时通讯开发者社区!
1   /*
2    * Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.baidu.fsg.uid.impl;
17  
18  import java.util.Date;
19  import java.util.concurrent.TimeUnit;
20  
21  import org.apache.commons.lang.StringUtils;
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  import org.springframework.beans.factory.InitializingBean;
25  
26  import com.baidu.fsg.uid.BitsAllocator;
27  import com.baidu.fsg.uid.UidGenerator;
28  import com.baidu.fsg.uid.exception.UidGenerateException;
29  import com.baidu.fsg.uid.utils.DateUtils;
30  import com.baidu.fsg.uid.worker.WorkerIdAssigner;
31  
32  /**
33   * Represents an implementation of {@link UidGenerator}
34   *
35   * The unique id has 64bits (long), default allocated as blow:<br>
36   * <li>sign: The highest bit is 0
37   * <li>delta seconds: The next 28 bits, represents delta seconds since a customer epoch(2016-05-20 00:00:00.000).
38   *                    Supports about 8.7 years until to 2024-11-20 21:24:16
39   * <li>worker id: The next 22 bits, represents the worker's id which assigns based on database, max id is about 420W
40   * <li>sequence: The next 13 bits, represents a sequence within the same second, max for 8192/s<br><br>
41   *
42   * The {@link DefaultUidGenerator#parseUID(long)} is a tool method to parse the bits
43   *
44   * <pre>{@code
45   * +------+----------------------+----------------+-----------+
46   * | sign |     delta seconds    | worker node id | sequence  |
47   * +------+----------------------+----------------+-----------+
48   *   1bit          28bits              22bits         13bits
49   * }</pre>
50   *
51   * You can also specified the bits by Spring property setting.
52   * <li>timeBits: default as 28
53   * <li>workerBits: default as 22
54   * <li>seqBits: default as 13
55   * <li>epochStr: Epoch date string format 'yyyy-MM-dd'. Default as '2016-05-20'<p>
56   *
57   * <b>Note that:</b> The total bits must be 64 -1
58   *
59   * @author yutianbao
60   */
61  public class DefaultUidGenerator implements UidGenerator, InitializingBean {
62      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUidGenerator.class);
63  
64      /** Bits allocate */
65      protected int timeBits = 28;
66      protected int workerBits = 22;
67      protected int seqBits = 13;
68  
69      /** Customer epoch, unit as second. For example 2016-05-20 (ms: 1463673600000)*/
70      protected String epochStr = "2016-05-20";
71      protected long epochSeconds = TimeUnit.MILLISECONDS.toSeconds(1463673600000L);
72  
73      /** Stable fields after spring bean initializing */
74      protected BitsAllocator bitsAllocator;
75      protected long workerId;
76  
77      /** Volatile fields caused by nextId() */
78      protected long sequence = 0L;
79      protected long lastSecond = -1L;
80  
81      /** Spring property */
82      protected WorkerIdAssigner workerIdAssigner;
83  
84      @Override
85      public void afterPropertiesSet() throws Exception {
86          // initialize bits allocator
87          bitsAllocator = new BitsAllocator(timeBits, workerBits, seqBits);
88  
89          // initialize worker id
90          workerId = workerIdAssigner.assignWorkerId();
91          if (workerId > bitsAllocator.getMaxWorkerId()) {
92              throw new RuntimeException("Worker id " + workerId + " exceeds the max " + bitsAllocator.getMaxWorkerId());
93          }
94  
95          LOGGER.info("Initialized bits(1, {}, {}, {}) for workerID:{}", timeBits, workerBits, seqBits, workerId);
96      }
97  
98      @Override
99      public long getUID() throws UidGenerateException {
100         try {
101             return nextId();
102         } catch (Exception e) {
103             LOGGER.error("Generate unique id exception. ", e);
104             throw new UidGenerateException(e);
105         }
106     }
107 
108     @Override
109     public String parseUID(long uid) {
110         long totalBits = BitsAllocator.TOTAL_BITS;
111         long signBits = bitsAllocator.getSignBits();
112         long timestampBits = bitsAllocator.getTimestampBits();
113         long workerIdBits = bitsAllocator.getWorkerIdBits();
114         long sequenceBits = bitsAllocator.getSequenceBits();
115 
116         // parse UID
117         long sequence = (uid << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits);
118         long workerId = (uid << (timestampBits + signBits)) >>> (totalBits - workerIdBits);
119         long deltaSeconds = uid >>> (workerIdBits + sequenceBits);
120 
121         Date thatTime = new Date(TimeUnit.SECONDS.toMillis(epochSeconds + deltaSeconds));
122         String thatTimeStr = DateUtils.formatByDateTimePattern(thatTime);
123 
124         // format as string
125         return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"workerId\":\"%d\",\"sequence\":\"%d\"}",
126                 uid, thatTimeStr, workerId, sequence);
127     }
128 
129     /**
130      * Get UID
131      *
132      * @return UID
133      * @throws UidGenerateException in the case: Clock moved backwards; Exceeds the max timestamp
134      */
135     protected synchronized long nextId() {
136         long currentSecond = getCurrentSecond();
137 
138         // Clock moved backwards, refuse to generate uid
139         if (currentSecond < lastSecond) {
140             long refusedSeconds = lastSecond - currentSecond;
141             throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);
142         }
143 
144         // At the same second, increase sequence
145         if (currentSecond == lastSecond) {
146             sequence = (sequence + 1) & bitsAllocator.getMaxSequence();
147             // Exceed the max sequence, we wait the next second to generate uid
148             if (sequence == 0) {
149                 currentSecond = getNextSecond(lastSecond);
150             }
151 
152         // At the different second, sequence restart from zero
153         } else {
154             sequence = 0L;
155         }
156 
157         lastSecond = currentSecond;
158 
159         // Allocate bits for UID
160         return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);
161     }
162 
163     /**
164      * Get next millisecond
165      */
166     private long getNextSecond(long lastTimestamp) {
167         long timestamp = getCurrentSecond();
168         while (timestamp <= lastTimestamp) {
169             timestamp = getCurrentSecond();
170         }
171 
172         return timestamp;
173     }
174 
175     /**
176      * Get current second
177      */
178     private long getCurrentSecond() {
179         long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
180         if (currentSecond - epochSeconds > bitsAllocator.getMaxDeltaSeconds()) {
181             throw new UidGenerateException("Timestamp bits is exhausted. Refusing UID generate. Now: " + currentSecond);
182         }
183 
184         return currentSecond;
185     }
186 
187     /**
188      * Setters for spring property
189      */
190     public void setWorkerIdAssigner(WorkerIdAssigner workerIdAssigner) {
191         this.workerIdAssigner = workerIdAssigner;
192     }
193 
194     public void setTimeBits(int timeBits) {
195         if (timeBits > 0) {
196             this.timeBits = timeBits;
197         }
198     }
199 
200     public void setWorkerBits(int workerBits) {
201         if (workerBits > 0) {
202             this.workerBits = workerBits;
203         }
204     }
205 
206     public void setSeqBits(int seqBits) {
207         if (seqBits > 0) {
208             this.seqBits = seqBits;
209         }
210     }
211 
212     public void setEpochStr(String epochStr) {
213         if (StringUtils.isNotBlank(epochStr)) {
214             this.epochStr = epochStr;
215             this.epochSeconds = TimeUnit.MILLISECONDS.toSeconds(DateUtils.parseByDayPattern(epochStr).getTime());
216         }
217     }
218 }