基于雪花算法的单机版

雪花算法是基于时间戳的一种生成随机数的算法。网上的改变版也很多,当前基于我们的业务场景,改变了使用于我们业务场景的算法。

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;
    }

}

 

posted @ 2020-01-14 23:37  woniu4  阅读(883)  评论(0编辑  收藏  举报