分布式ID-雪花算法工具类IdUtils

1. ID号的要求有哪些呢?

  1. 全局唯一性:不重复、唯一标识。
  2. 趋势递增:有序方便主键索引,插入查询性能。
  3. 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
  4. 信息安全:可能暴露信息,例如:订单数量、用户数量

2. 分布式ID都有哪些生成方式?

  • UUID 随机数
  • 数据库自增ID;数据库特性
  • Redis 生成 ID
  • 雪花算法(SnowFlake)等算法
生成算法优点缺点
UUIDID 是无序、无业务含义、太长、查询慢,不适合建立索引ID 是无序的,查询慢,不适合建立索引
数据库自增代码简单,数据递增DB 单点故障、并发瓶颈等,需 DBA 专业维护
snowflake雪花算法低位趋势递增,不占带宽,性能高依赖于服务器时间,时钟回拨问题
Redis自增 incr无单点故障(集群),性能高,递增占用带宽,redis 集群维护

3. ID生成

mybatis plus

QQ图片20201127170759.png

工具类

public class Snowflake {
    public static final int NODE_SHIFT = 10;
    public static final int SEQ_SHIFT = 12;
    public static final short MAX_NODE = 1024;
    public static final short MAX_SEQUENCE = 4096;
    private short sequence;
    private long referenceTime;
    private int node;

    public Snowflake(int node) {
        if (node >= 0 && node <= MAX_NODE) {
            this.node = node;
        } else {
            throw new IllegalArgumentException(String.format("node must be between %s and %s", 0, MAX_NODE));
        }
    }

    public synchronized long next() {
        long currentTime = System.currentTimeMillis();
        long counter;
        if (currentTime < this.referenceTime) {
            throw new RuntimeException(String.format("Last referenceTime %s is after reference time %s", this.referenceTime, currentTime));
        }

        if (currentTime > this.referenceTime) {
            this.sequence = 0;
        } else {
            if (this.sequence >= MAX_SEQUENCE) {
                throw new RuntimeException("Sequence exhausted at " + this.sequence);
            }
            ++this.sequence;
        }

        counter = this.sequence;
        this.referenceTime = currentTime;

        return currentTime << NODE_SHIFT << SEQ_SHIFT | (long) (this.node << SEQ_SHIFT) | counter;
    }
}

public class IdUtils {
    private final static Logger logger = LoggerFactory.getLogger(IdUtils.class);
    final static Snowflake snowflake = new Snowflake(getNode());

    /**
     * 获取ID
     */
    public static Long getId() {
        return snowflake.next();
    }

    /**
     * 获取节点
     */
    private static int getNode() {
        //获取本地主机
        InetAddress address;
        try {
            address = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            logger.error("获取本地主机出现异常", e);
            return new Random().nextInt(1024) + 1;
        }

        //获取ip地址
        String ip = address.getHostAddress();
        if (ValidateUtils.isEmpty(ip) || ip.equals("127.0.0.1") || ip.equals("0.0.0.0")) {
            try {
                ip = getIP();
            } catch (SocketException e) {
                logger.error("获取IP地址出现异常", e);
                try {
                    return getNodeByMacAddress();
                } catch (IOException e1) {
                    logger.error("获取IP地址出现异常", e);
                }
                //随机数得到节点
                return new Random().nextInt(1024) + 1;
            }
        }

        if (ip == null) {
            return -1;
        }
        //得到ip地址的每一位
        String[] bs = ip.split("\\.");

        //ip地址第3位
        int b3 = Integer.parseInt(bs[2]);
        //ip地址第4位
        int b4 = Integer.parseInt(bs[3]);
        //计算节点号
        int node = b3 % 4 * 256 + b4 + 1;

        return node;
    }

    /**
     * Unix下获取本地IP
     */
    private static String getIP() throws SocketException {
        Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces();
        while (allNetInterfaces.hasMoreElements()) {
            NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
            Enumeration addresses = netInterface.getInetAddresses();
            while (addresses.hasMoreElements()) {
                InetAddress ip = (InetAddress) addresses.nextElement();
                if (ip != null && ip instanceof Inet4Address) {
                    if (!ip.isLoopbackAddress() && ip.isSiteLocalAddress()) {
                        return ip.getHostAddress();
                    }
                }
            }
        }
        return null;
    }

    /**
     * 通过Mac地址计算节点
     */
    private static int getNodeByMacAddress() throws UnknownHostException, SocketException {
        InetAddress ip = InetAddress.getLocalHost();
        NetworkInterface network = NetworkInterface.getByInetAddress(ip);
        byte[] mac = network.getHardwareAddress();

        if (ValidateUtils.isEmpty(mac)) {
            mac = NetworkInterface.getByName("eth0").getHardwareAddress();
        }

        int sum = 0;

        for (int i = 0; i < mac.length; i++) {
            sum += mac[i] & 0xff;
        }

        int node = sum % 1024 + 1;

        return node;
    }
}

参考资料:https://tech.meituan.com/2017/04/21/mt-leaf.html
https://mp.weixin.qq.com/s/rgy3FmF-r-GEyBfT-w47vQ

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值