Java--Spring项目生成雪花算法数字(Twitter SnowFlake)

文章目录

前言
  • 分布式系统常需要全局唯一的数字作为id,且该id要求有序,twitter的SnowFlake解决了这种需求,生成了符合条件的这种数字,本文将提供一个接口获取雪花算法数字。以下为代码。
步骤
  1. SnowFlakeUtils 雪花算法工具类。

    java 复制代码
    @Slf4j
    public class SnowFlakeUtils {
    
        private static final RedisOperation REDIS_OPERATION = ApplicationContextHelper.getBean(RedisOperation.class);
    
        private static final String LOCAL_IP = getLocalIp();
    
        private static volatile SnowFlakeUtils instance;
        /**
         * 该任务开始时间,必须手动设置(差值的唯一性)
         * 建议在生产部署时选择某一日的0时0分0秒0毫秒的时间戳,方便计算
         */
        private static final long START_TIME = 1588733692671L;
        /**
         * 各个位的位数,Timestamp为41L(无需定义)
         */
        private static final long DATA_CENTER_ID_BITS = 5L;
        private static final long WORKER_ID_BITS = 1L;
        private static final long SEQUENCE_BITS = 16L;
        /**
         * 各位的最大值
         */
        private static final long DATA_CENTER_ID_MAX = ~(-1 << DATA_CENTER_ID_BITS);
        private static final long WORKER_ID_MAX = ~(-1 << WORKER_ID_BITS);
        private static final long SEQUENCE_MAX = ~(-1 << SEQUENCE_BITS);
        /**
         * 各位应该向左移动位数
         */
        private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
        private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
        private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
        /**
         * 数据中心ID
         */
        private final long dataCenterId;
        private static final String DATA_CENTER_ID = "DATACENTERID";
        /**
         * 工作线程ID
         */
        private final long workerId;
        private static final String WORKER_ID = "WORKERID";
        /**
         * 序列号
         */
        private long sequence = 0L;
        /**
         * 上次时间(保证不回退)
         */
        private long lastTimestamp = -1L;
        /***
         * 是否在高并发下
         */
        private boolean isClock = false;
    
        public static SnowFlakeUtils getInstance() {
            if (instance == null) {
                synchronized (SnowFlakeUtils.class) {
                    if (instance == null) {
                        int dataCenterId = 0;
                        int workerId = 0;
                        while (true) {
                            // tryCatch保证即使redis等出现问题也可以保证当前线程阻塞,重启redis即可处理继续处理
                            try {
                                String replace = RedisKeyConstant.SNOW_FLAKE_KEY.
                                        replace(DATA_CENTER_ID, String.valueOf(dataCenterId)).
                                        replace(WORKER_ID, String.valueOf(workerId));
                                if (REDIS_OPERATION.setnx(replace, LOCAL_IP, 1, TimeUnit.MINUTES)) {
                                    instance = new SnowFlakeUtils(dataCenterId, workerId);
                                    break;
                                }
                                // 进行重新set直至成功,目前只运用dataCenterId
                                if (dataCenterId++ == DATA_CENTER_ID_MAX) {
                                    log.error("SnowFlake is getting CacheLock, please checkDATACENTERID_MAX={}", DATA_CENTER_ID_MAX);
                                    dataCenterId = 0;
                                }
                            } catch (Exception e) {
                                log.error("SnowFlakeUtils get CacheLock Error, errorMsg:", e);
                                try {
                                    Thread.sleep(MagicNum.THOUSAND);
                                } catch (InterruptedException ex) {
                                    log.error(ex.getMessage(), ex);
                                }
                            }
                        }
                    }
                }
            }
            return instance;
        }
    
        public SnowFlakeUtils(long dataCenterId, long workerId) {
            if (dataCenterId > DATA_CENTER_ID_MAX || dataCenterId < 0) {
                throw new IllegalArgumentException(String.format("data center id can't be greater than %d or less than 0", DATA_CENTER_ID_MAX));
            }
            if (workerId > WORKER_ID_MAX || workerId < 0) {
                throw new IllegalArgumentException(String.format("worker id can't be greater than %d or less than 0", WORKER_ID_MAX));
            }
            this.dataCenterId = dataCenterId;
            this.workerId = workerId;
            String key = RedisKeyConstant.SNOW_FLAKE_KEY.
                    replace(DATA_CENTER_ID, String.valueOf(dataCenterId)).
                    replace(WORKER_ID, String.valueOf(workerId));
            log.info("SnowFlakeUtils Cache Key={}", key);
            // 起线程保证workerId和dataCenter组合不重复
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            log.debug("SnowFlakeUtils is keep geting CacheLock-{}", key);
                            String localIp = REDIS_OPERATION.get(key);
                            if (LOCAL_IP.equals(localIp)) {
                                REDIS_OPERATION.setex(key, LOCAL_IP, 1, TimeUnit.MINUTES);
                            } else if (!REDIS_OPERATION.setnx(key, LOCAL_IP, 1, TimeUnit.MINUTES)) {
                                throw new ProcessException(CommonConstants.ENUM_PROCESSING_EXCEPTION,"SnowFlakeUtils losed CacheLock-" + key + "." +
                                        "CacheLockKeeperThread broken!" +
                                        "Reday to retrieve CacheLock and Single Instance!");
                            }
                            Thread.sleep(MagicNum.FIFTY * MagicNum.THOUSAND);
                        } catch (Exception e) {
                            // 发生异常 将单例清除 并退出循环结束子线程
                            synchronized (SnowFlakeUtils.class) {
                                instance = null;
                            }
                            log.error(e.getMessage(),e);
                            break;
                        }
                    }
                }
            });
            thread.setName("SnowFlake-CacheLockKeeper-" + dataCenterId + "-" + workerId);
            thread.start();
        }
    
        public void setClock(boolean clock) {
            this.isClock = clock;
        }
    
        public synchronized long nextId() {
            long timestamp = this.getTime();
    
            if (timestamp < lastTimestamp) {
                long offset = lastTimestamp - timestamp;
                if (offset <= MagicNum.FIVE) {
                    try {
                        this.wait(offset << 1);
                        timestamp = this.getTime();
                        if (timestamp < lastTimestamp) {
                            throw new RuntimeException(String.format("Clock moved backwards, Refusing to generate id for %d milliseconds", offset));
                        }
                    } catch (InterruptedException e) {
                        log.error(e.getMessage(), e);
                    }
                } else {
                    throw new RuntimeException(String.format("Clock moved backwards, Refusing to generate id for %d milliseconds", offset));
                }
            }
    
            if (lastTimestamp == timestamp) {
                sequence = sequence + 1;
                if (sequence > SEQUENCE_MAX) {
                    timestamp = tilNextMillis(timestamp);
                    sequence = 0;
                }
            } else {
                sequence = 0;
            }
    
            lastTimestamp = timestamp;
    
            return ((timestamp - START_TIME) << TIMESTAMP_SHIFT) |
                    (dataCenterId << DATA_CENTER_ID_SHIFT) |
                    (workerId << WORKER_ID_SHIFT) |
                    sequence;
        }
    
        /**
         * 该毫秒达到上限,等待到下1毫秒
         */
        private long tilNextMillis(long timestamp) {
            while (getTime() <= timestamp) {
                log.debug("单毫秒主键生成达到上限");
            }
            return this.getTime();
        }
    
        private long getTime() {
            if (isClock) {
                return SystemClock.currentTimeMillis();
            } else {
                return System.currentTimeMillis();
            }
        }
    
        private static String getLocalIp() {
            String ip = "";
            try {
                InetAddress addr = InetAddress.getLocalHost();
                ip += addr.getHostAddress();
            } catch (Exception e) {
                ip += "127.0.0.1";
            }
            ip += "_" + System.currentTimeMillis() + "_" + Math.random();
            log.info("SnowFlakeUtils Cache Value={}", ip);
            return ip;
        }
    }
  2. SystemClock工具类。

    java 复制代码
    /**
     * 由于高并发,在同一毫秒中会多次获取currentTimeMillis,而每次使用System.currentTimeMillis都会占用CPU(native方法).
     * 于是自定义类(single)来获取currentTimeMillis,实现方法是在此类中定义时间并设置一个周期任务(定时线程)1毫秒更新类中的时间
     */
    public final class SystemClock {
    
      private static final SystemClock INSTANCE = new SystemClock(1);
    
      public static SystemClock getInstance() {
          return INSTANCE;
      }
    
      /**
       * 更新时间的时间间隔,默认为1毫秒
       */
      private final long period;
      /**
       * 当前时间
       */
      private final AtomicLong now;
    
      private SystemClock(long period) {
          this.period = period;
          this.now = new AtomicLong(System.currentTimeMillis());
          scheduleClockUpdate();
      }
    
      /**
       * 定时任务(设置为守护线程,1毫秒后开始更新)
       * scheduleAtFixedRate: 每次开始间隔为1毫秒
       * scheduleWithFixedDelay: 每次结束与开始为1毫秒
       */
      private void scheduleClockUpdate() {
          ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
              @Override
              public Thread newThread(Runnable r) {
                  Thread thread = new Thread(r, "System Clock");
                  thread.setDaemon(true);
                  return thread;
              }
          });
          executorService.scheduleAtFixedRate(new Runnable() {
              @Override
              public void run() {
                  now.set(System.currentTimeMillis());
              }
          }, period, period, TimeUnit.MILLISECONDS);
      }
    
      public static long currentTimeMillis() {
          return getInstance().now.get();
      }
    }
  3. ApplicationContextHelper Spring上下文工具类。

    java 复制代码
    @Slf4j
    @Component
    public class ApplicationContextHelper implements ApplicationContextAware {
    /**
    * Spring上下文
    */
    private static ApplicationContext applicationContext;
    
        /**
         * @return ApplicationContext
         */
        public static ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    
        /**
         * 获取ApplicationContextAware
         *
         */
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            ApplicationContextHelper.applicationContext = applicationContext;
        }
    
        /**
         * 根据Class获取对应实例
         *
         */
        public static <T> T getBean(Class<T> clz) {
            return applicationContext.getBean(clz);
        }
    
        /**
         * 根据beanName获取对应实例
         */
        public static <T> T getBean(String name, Class<T> requiredType) {
            return applicationContext.getBean(name, requiredType);
        }
    
    
        public static Object getBean(String name) {
            return applicationContext.getBean(name);
        }
    }
  4. RedisOperation获取 RedisOperation,Redis操作工具类

  5. 在Controller里编写接口,测试结果。

    java 复制代码
    @RestController
    @RequestMapping("/part/util")
    public class UtilController {
         @ApiOperation("获取雪花数字")
         @GetMapping("/getSnowFlakeNo")
         public Result getSnowFlakeNo() {
             return Result.ok().data(String.valueOf(SnowFlakeUtils.getInstance().nextId()));
         }
    }
查看结果
  • 启动项目,有postman访问接口,查看结果如下,返回结果中data的值即为雪花算法数字。
相关推荐
芸尚非14 分钟前
idea添加版权信息
java·ide·intellij-idea
岳轩子15 分钟前
23种设计模式之单例模式
java·单例模式·设计模式
冬天vs不冷28 分钟前
SpringBoot源码解析(五):准备应用环境
java·spring boot·后端
liuxin3344556635 分钟前
中小企业人事管理:SpringBoot技术深度解析
java·spring boot·后端
薯条不要番茄酱1 小时前
【JavaEE初阶】枫叶经霜艳,梅花透雪香-计算机是如何运行的?
java·开发语言·后端·java-ee·学习方法
希忘auto2 小时前
详解Servlet的使用
java·servlet·tomcat
ygl61503733 小时前
Vue3+SpringBoot3+Sa-Token+Redis+mysql8通用权限系统
java·spring boot·vue
Code哈哈笑3 小时前
【Java 学习】构造器、static静态变量、static静态方法、static构造器、
java·开发语言·学习
是老余3 小时前
Java三大特性:封装、继承、多态【详解】
java·开发语言
鸽鸽程序猿3 小时前
【JavaEE】Maven的介绍及配置
java·java-ee·maven