文章目录
- 前言
- 步骤
- 查看结果
前言
- 分布式系统常需要全局唯一的数字作为id,且该id要求有序,twitter的SnowFlake解决了这种需求,生成了符合条件的这种数字,本文将提供一个接口获取雪花算法数字。以下为代码。
步骤
-
SnowFlakeUtils 雪花算法工具类。
@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; } }
-
SystemClock工具类。
/** * 由于高并发,在同一毫秒中会多次获取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(); } }
-
ApplicationContextHelper Spring上下文工具类。
@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); } }
-
RedisOperation获取 RedisOperation,Redis操作工具类。
-
在Controller里编写接口,测试结果。
@RestController @RequestMapping("/part/util") public class UtilController { @ApiOperation("获取雪花数字") @GetMapping("/getSnowFlakeNo") public Result getSnowFlakeNo() { return Result.ok().data(String.valueOf(SnowFlakeUtils.getInstance().nextId())); } }
查看结果
- 启动项目,有postman访问接口,查看结果如下,返回结果中data的值即为雪花算法数字。