基于雪花算法的单机版
雪花算法是基于时间戳的一种生成随机数的算法。网上的改变版也很多,当前基于我们的业务场景,改变了使用于我们业务场景的算法。
1、生成的Id长度不能超过17,,最大值为:160111892926110,即前端支持的最大数字类型长度
2、没有统一的服务来产生ID,需要将Id在各自服务中自主实现
3、一台服务器可能部署多台实例
4、实例随时会根据业务量的扩展,进行扩容。
基于上述方式改变的算法如下:
基于Mysql将服务所在机器的IP和PORT作为唯一标识存储在数据库中,这样就会在库中形成唯一的ID。之后根据升序即可获取当前IP和PORT所在列表中的位置,这样就可以固有一套IP对应表,同时,机器标识信息占用位数较少,目前定在了127位,仅需要7个BIT位。序列值为单秒最多支持创建31个,也就是需要5个BIT位。这样就有了12个BIT位,其余位数均为时间戳所在位数。剩余的41位可以支持69年,对于一个普通产品来说,足够了。下图为具体展示
11111111111111111111111111111111111111111 1111111 11111
时间戳 机器 序列号
具体代码实现:
/** * <p> * 基于雪花算法改造的ID生成器 * <p> * * @author woniu * @date 2020年01月14日 * @version 1.0 */ public class IdUtils { private static final Logger LOGGER = LoggerFactory.getLogger(IdUtils.class); private static IdUtils idUtils; /** * 上次更新时间 */ private static Long lastStamp = -1L; private static Long sqlNUm = 0L; private static Integer hostId = -1; private IpListService ipListService = AppContext.getAppObject(IpListService.class); /** * 基于2020年1月1日开始计算 */ private static final Long BEGIN_TIME = 1577808000000L; private static final Integer SEQ_NUM_LEN = 5; /** * 机器占位符,当前最大支持127个实例 */ private static final Integer HOST_LEN = 7; /** * 序列号的最大值,2的SEQ_NUM_LEN(5)次方减1 : 2^5 -1 = 31 */ private static final Integer MAX_SEQ_NUM = 31; /** * 时间戳的偏移位 * 前端支持最大值为:9007199254740991,去除后11位,还有41位,支持69年 */ private static final Integer TIME_STAMP_SHIFT = HOST_LEN + SEQ_NUM_LEN; /** * 初始化实例 * @return */ private static IdUtils getInstance() { if (idUtils == null) { idUtils = new IdUtils(); } return idUtils; } /** * 生成Id的入库 */ public static synchronized Long genId() { if (hostId == -1L) { initIp(); } Long currentTime = System.currentTimeMillis(); if (sqlNUm > MAX_SEQ_NUM) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } currentTime = System.currentTimeMillis(); sqlNUm = 0L; } if (currentTime.longValue() == lastStamp.longValue()) { return ((currentTime - BEGIN_TIME) << 11) | (hostId << 6) | (sqlNUm ++); } else { sqlNUm = 0L; lastStamp = currentTime; return ((currentTime - BEGIN_TIME) << 11) | (hostId << 6) | (sqlNUm ++); } } /** * 初始化IP信息 */ private static void initIp() { try { String localIp = IpPortUtils.getLocalIp(); String port = String.valueOf(IpPortUtils.getHttpPort()); if (!getHostId(localIp, port)) { IpList localIpList = new IpList(); localIpList.setIp(localIp); localIpList.setPort(port); localIpList.setCreateTime(new Date()); localIpList.setUpdateTime(new Date()); getInstance().ipListService.saveOrUpdate(localIpList); getHostId(localIp, port); } } catch (Exception e) { LOGGER.error("init ip error", e); throw new RuntimeException(); } } /** * 获取hostId信息,如果获取则返回true,反之false * @param localIp * @param port * @return */ private static boolean getHostId(String localIp, String port) { List<IpList> ipList = getInstance().ipListService.findAllByAsc(); for (int i = 0; i < ipList.size(); i++) { IpList ip = ipList.get(i); if (ip.getIp().equals(localIp) && ip.getPort().equals(port)) { hostId = i; return true; } } return false; } }