1+ package com .example .springsocial .controller ;
2+
3+
4+ import java .net .NetworkInterface ;
5+ import java .security .SecureRandom ;
6+ import java .time .Instant ;
7+ import java .util .Enumeration ;
8+
9+ /**
10+ * Distributed Sequence Generator.
11+ * Inspired by Twitter snowflake: https://github.com/twitter/snowflake/tree/snowflake-2010
12+ *
13+ * This class should be used as a Singleton.
14+ * Make sure that you create and reuse a Single instance of SequenceGenerator per machine in your distributed system cluster.
15+ */
16+ public class SequenceGenerator {
17+ private static final int TOTAL_BITS = 64 ;
18+ private static final int EPOCH_BITS = 42 ;
19+ private static final int MACHINE_ID_BITS = 10 ;
20+ private static final int SEQUENCE_BITS = 12 ;
21+
22+ private static final int maxMachineId = (int )(Math .pow (2 , MACHINE_ID_BITS ) - 1 );
23+ private static final int maxSequence = (int )(Math .pow (2 , SEQUENCE_BITS ) - 1 );
24+
25+ // Custom Epoch (January 1, 2015 Midnight UTC = 2015-01-01T00:00:00Z)
26+ private static final long CUSTOM_EPOCH = 1420070400000L ;
27+
28+ private final int machineId ;
29+
30+ private long lastTimestamp = -1L ;
31+ private long sequence = 0L ;
32+
33+ // Create Snowflake with a machineId
34+ public SequenceGenerator (int machineId ) {
35+ if (machineId < 0 || machineId > maxMachineId ) {
36+ throw new IllegalArgumentException (String .format ("MachineId must be between %d and %d" , 0 , maxMachineId ));
37+ }
38+ this .machineId = machineId ;
39+ }
40+
41+ // Let Snowflake generate a machineId
42+ public SequenceGenerator () {
43+ this .machineId = createMachineId ();
44+ }
45+
46+
47+ public long nextId () {
48+ long currentTimestamp = timestamp ();
49+
50+ synchronized (this ) {
51+ if (currentTimestamp < lastTimestamp ) {
52+ throw new IllegalStateException ("Invalid System Clock!" );
53+ }
54+
55+ if (currentTimestamp == lastTimestamp ) {
56+ sequence = (sequence + 1 ) & maxSequence ;
57+ if (sequence == 0 ) {
58+ // Sequence Exhausted, wait till next millisecond.
59+ currentTimestamp = waitNextMillis (currentTimestamp );
60+ }
61+ } else {
62+ // reset sequence for next millisecond
63+ sequence = 0 ;
64+ }
65+
66+ lastTimestamp = currentTimestamp ;
67+ }
68+
69+ long id = currentTimestamp << (TOTAL_BITS - EPOCH_BITS );
70+ id |= (machineId << (TOTAL_BITS - EPOCH_BITS - MACHINE_ID_BITS ));
71+ id |= sequence ;
72+ return id ;
73+ }
74+
75+
76+ // Get current timestamp in milliseconds, adjust for the custom epoch.
77+ private static long timestamp () {
78+ return Instant .now ().toEpochMilli () - CUSTOM_EPOCH ;
79+ }
80+
81+ // Block and wait till next millisecond
82+ private long waitNextMillis (long currentTimestamp ) {
83+ while (currentTimestamp == lastTimestamp ) {
84+ currentTimestamp = timestamp ();
85+ }
86+ return currentTimestamp ;
87+ }
88+
89+ private int createMachineId () {
90+ int machineId ;
91+ try {
92+ StringBuilder sb = new StringBuilder ();
93+ Enumeration <NetworkInterface > networkInterfaces = NetworkInterface .getNetworkInterfaces ();
94+ while (networkInterfaces .hasMoreElements ()) {
95+ NetworkInterface networkInterface = networkInterfaces .nextElement ();
96+ byte [] mac = networkInterface .getHardwareAddress ();
97+ if (mac != null ) {
98+ for (int i = 0 ; i < mac .length ; i ++) {
99+ sb .append (String .format ("%02X" , mac [i ]));
100+ }
101+ }
102+ }
103+ machineId = sb .toString ().hashCode ();
104+ } catch (Exception ex ) {
105+ machineId = (new SecureRandom ().nextInt ());
106+ }
107+ machineId = machineId & maxMachineId ;
108+ return machineId ;
109+ }
110+ }
0 commit comments