{@code
19 | * +------+----------------------+----------------+-----------+
20 | * | sign | delta seconds | worker node id | sequence |
21 | * +------+----------------------+----------------+-----------+
22 | * 1bit 28bits 24bits 11bits
23 | * }
24 | *
25 | * You can also specified the bits by Spring property setting.
26 | *
31 | *
32 | * Note that: The total bits must be 64 -1
33 | *
34 | * @author yutianbao@baidu, john.huang
35 | */
36 | public class SnowflakeUidGenerator {
37 | private static final Logger LOGGER = LoggerFactory.getLogger(SnowflakeUidGenerator.class);
38 |
39 | public static final int TOTAL_BITS = 1 << 6;
40 |
41 | public static final String DEFAULT_BASE_DATE = "2018-07-01";
42 |
43 | /**
44 | * Bits for [sign-> second-> workId-> sequence]
45 | */
46 | protected int signBits = 1;
47 |
48 | // the start time, default is 2018-7-1
49 | protected long baseEpoch = 1530374400000L;
50 |
51 | // delta seconds
52 | protected int timeBits = 32;
53 |
54 | // worker node id bits
55 | protected int workerBits = 8;
56 |
57 | // sequence bits
58 | protected int seqBits = 15;
59 |
60 | /** Volatile fields caused by nextId() */
61 | protected long sequence = 0L;
62 | protected long lastSecond = -1L;
63 |
64 | protected long maxDeltaSeconds;
65 | protected long maxWorkerId;
66 | protected long maxSequence;
67 |
68 | protected int timestampShift;
69 | protected int workerIdShift;
70 |
71 | protected long workerId;
72 |
73 | /**
74 | * Initialize a uid generator with specified settings.
75 | *
76 | * @param workerId specify an id for the worker, the worker is the app to generate uid
77 | * @param dbId specify an id for the target db
78 | * @param baseDate the base date the generator begin with
79 | * @param timeBits time bits length
80 | * @param workerBits worker id bits length
81 | * @param dbBits target db id bits length
82 | * @param seqBits sequence bits length
83 | */
84 | public SnowflakeUidGenerator(long workerId, String baseDate, int timeBits, int workerBits, int seqBits) {
85 | this.workerId = workerId;
86 |
87 | Date date = DateUtils.parseByDayPattern(baseDate);
88 | this.baseEpoch = TimeUnit.MILLISECONDS.toSeconds(date.getTime());
89 |
90 | this.timeBits = timeBits;
91 | this.workerBits = workerBits;
92 | this.seqBits = seqBits;
93 |
94 | int allocateTotalBits = signBits + timeBits + workerBits + seqBits;
95 | Assert.isTrue(allocateTotalBits == TOTAL_BITS, "allocate not enough 64 bits");
96 |
97 | // initialize max value
98 | this.maxDeltaSeconds = ~(-1L << timeBits);
99 | this.maxWorkerId = ~(-1L << workerBits);
100 | this.maxSequence = ~(-1L << seqBits);
101 |
102 | Assert.isTrue(workerId <= maxWorkerId, String.format("workerId exceed the max value %d", maxWorkerId));
103 |
104 | // initialize shift
105 | this.timestampShift = workerBits + seqBits;
106 | this.workerIdShift = seqBits;
107 | }
108 |
109 | public SnowflakeUidGenerator(long workerId, int timeBits, int workerBits, int seqBits) {
110 | this(workerId, DEFAULT_BASE_DATE, timeBits, workerBits, seqBits);
111 | }
112 |
113 | public SnowflakeUidGenerator(long workerId) {
114 | this(workerId, DEFAULT_BASE_DATE, 28, 24, 11);
115 | }
116 |
117 | public long getUID() throws UidGenerateException {
118 | try {
119 | return nextId();
120 | } catch (Exception e) {
121 | LOGGER.error("Generate unique id exception. ", e);
122 | throw new UidGenerateException(e);
123 | }
124 | }
125 |
126 | public String parseUID(long uid) {
127 | // parse UID
128 | long sequence = (uid << (TOTAL_BITS - seqBits)) >>> (TOTAL_BITS - seqBits);
129 | long workerId = (uid << (timeBits + signBits)) >>> (TOTAL_BITS - workerBits);
130 | long deltaSeconds = uid >>> (workerBits + seqBits);
131 |
132 | Date thatTime = new Date(TimeUnit.SECONDS.toMillis(baseEpoch + deltaSeconds));
133 | String thatTimeStr = DateUtils.formatByDateTimePattern(thatTime);
134 |
135 | // format as string
136 | return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"workerId\":\"%d\",\"sequence\":\"%d\"}",
137 | uid, thatTimeStr, workerId, sequence);
138 | }
139 |
140 | /**
141 | * Get UID
142 | *
143 | * @return UID
144 | * @throws UidGenerateException in the case: Clock moved backwards; Exceeds the max timestamp
145 | */
146 | protected synchronized long nextId() {
147 | long currentSecond = getCurrentSecond();
148 |
149 | // Clock moved backwards, refuse to generate uid
150 | if (currentSecond < lastSecond) {
151 | long refusedSeconds = lastSecond - currentSecond;
152 | throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);
153 | }
154 |
155 | // At the same second, increase sequence
156 | if (currentSecond == lastSecond) {
157 | sequence = (sequence + 1) & maxSequence;
158 | // Exceed the max sequence, we wait the next second to generate uid
159 | if (sequence == 0) {
160 | currentSecond = getNextSecond(lastSecond);
161 | }
162 |
163 | // At the different second, sequence restart from zero
164 | } else {
165 | sequence = 0L;
166 | }
167 |
168 | lastSecond = currentSecond;
169 |
170 | // Allocate bits for UID
171 | long deltaSeconds = currentSecond - baseEpoch;
172 | return (deltaSeconds << timestampShift) | (workerId << workerIdShift) | sequence;
173 | }
174 |
175 | /**
176 | * Get next millisecond
177 | */
178 | private long getNextSecond(long lastTimestamp) {
179 | long timestamp = getCurrentSecond();
180 | while (timestamp <= lastTimestamp) {
181 | timestamp = getCurrentSecond();
182 | }
183 |
184 | return timestamp;
185 | }
186 |
187 | /**
188 | * Get current second
189 | */
190 | private long getCurrentSecond() {
191 | long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
192 | if (currentSecond - baseEpoch > maxDeltaSeconds) {
193 | throw new UidGenerateException("Timestamp bits is exhausted. Refusing UID generate. Now: " + currentSecond);
194 | }
195 |
196 | return currentSecond;
197 | }
198 |
199 | /**
200 | * Allocate bits for UID according to delta seconds & workerId & sequence
201 | * Note that: The highest bit will always be 0 for sign
202 | *
203 | * @param deltaSeconds
204 | * @param workerId
205 | * @param sequence
206 | * @return
207 | */
208 | public long allocate(long deltaSeconds, long workerId, long dbId, long sequence) {
209 | return (deltaSeconds << timestampShift) | (workerId << workerIdShift) | sequence;
210 | }
211 |
212 | public void setTimeBits(int timeBits) {
213 | if (timeBits > 0) {
214 | this.timeBits = timeBits;
215 | }
216 | }
217 |
218 | public void setWorkerBits(int workerBits) {
219 | if (workerBits > 0) {
220 | this.workerBits = workerBits;
221 | }
222 | }
223 |
224 | public void setSeqBits(int seqBits) {
225 | if (seqBits > 0) {
226 | this.seqBits = seqBits;
227 | }
228 | }
229 |
230 | /**
231 | * Get the worker id by using the last x bits of the local ip address
232 | * @throws UnknownHostException
233 | */
234 | public static long getWorkerIdByIP(int bits) throws UidGenerateException {
235 | int shift = 64 - bits;
236 | try {
237 | InetAddress address = InetAddress.getLocalHost();
238 | long ip = IpUtils.ipV4ToLong(address.getHostAddress());
239 | long workerId = (ip << shift) >>> shift;
240 | return workerId;
241 | } catch (UnknownHostException e) {
242 | LOGGER.error("Generate unique id exception. ", e);
243 | throw new UidGenerateException(e);
244 | }
245 | }
246 | }
--------------------------------------------------------------------------------