高并发分布式无碰撞ID生成机制

转载请注明出处:奇思漫想

由于用户管理(APP后端的订单号生成机制是采用 机构编号+时间戳+3位随机数+3位随机数)在高并发情况下碰撞几率很高。

正好诊疗卡支付的订单号需要完善,于是写了一套id生成机制。主要思路 机器(mac地址/IP)+时间戳+同一毫秒内的自增序列号(达到最大值等待等到下一毫秒重新从0开始)。底层主要依靠原子对象AtomicLong的CAS操作compareAndSet(expect,update),如果了解虚拟机的锁机制对这个应该会了解。

经测试自己的笔记本平均可支持一秒内生成30000条唯一ID。

拓展说明
  1. 本类会默认生成一个长度为19位的唯一ID,可支持同一局域网内多机超高并发不唯一,最长支持到2286年(请在此日期前更换id生成机制)

  2. 多局域网共享同一数据库的话,高并发情况下可能会产生碰撞(解决办法是依靠19位之外的数据位,假如你的订单是32位的你可以取其中两位作为机房编号,通过配置项的形式进行配置)

  3. 你可以在此订单号的基础上加入一些其他的标志位,用以拓展订单信息(日期等)或适应更复杂的编号生成环境,如多局域网(多机房)。

  4. 目前作者遇到的最复杂的 高并发分布式自增ID机制 也可以由此拓展而来。

代码如下
package com.yuantu.gateway.utils;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.NumberFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 唯一编号,经测试作者本人的笔记本平均每毫秒能生成30条左右唯一ID,单机平均每秒30000个唯一ID
 * 本类会默认生成一个长度为19位的唯一ID,可支持同一局域网内多机超高并发不唯一,最长支持到2286年(请在此日期前更换id生成机制)
 * 多局域网共享同一数据库的话,高并发情况下可能会产生碰撞(解决办法是依靠19位之外的数据位,假如你的订单是32位的你可以取其中两位作为机房编号,通过配置项的形式进行配置)
 * 你可以在此订单号的基础上加入一些其他的标志位,用以拓展订单信息(日期等)或适应更复杂的编号生成环境,如多局域网(多机房)。
 * IP后缀(192.168.20.12 -> 012)+ 时间戳 + 序列号
 * Created by zilue on 2017/4/22.
 */
public class UID {
    private static AtomicLong atomicLong = new AtomicLong(0L);
    private static AtomicLong timestamp = new AtomicLong(System.currentTimeMillis());
    private static final Long DEFAULT_MAX = 999L;
    private static String ip_postfix;
    private static NumberFormat numberFormat;

    static {
        try {
            InetAddress ia = InetAddress.getLocalHost();
            String[] split = ia.getHostAddress().split("\\.");
            ip_postfix = split[split.length - 1];
            NumberFormat numberFormat = NumberFormat.getNumberInstance();
            numberFormat.setGroupingUsed(false);
            numberFormat.setMinimumIntegerDigits(3);
            ip_postfix = numberFormat.format(Long.parseLong(ip_postfix));
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        numberFormat = NumberFormat.getNumberInstance();
        int seuquenceLen = String.valueOf(DEFAULT_MAX).length();
        numberFormat.setMinimumIntegerDigits(seuquenceLen);
        numberFormat.setGroupingUsed(false);


    }

    public static void main(String[] args) {
        System.out.println(getUniqueId());
        Long maxtimstamp=9999999999999L;
        Date date=new Date(maxtimstamp);
        System.out.println(date.toString());
    }

    public static String getUniqueId() {
        String formatSequence = numberFormat.format(getSequence(DEFAULT_MAX));
        return ip_postfix +System.currentTimeMillis()+ formatSequence;
    }

    private static String getUniqueId(Long maxPerMillisecond) {
        if (maxPerMillisecond == null)
            maxPerMillisecond = DEFAULT_MAX;
        NumberFormat numberFormat = NumberFormat.getNumberInstance();
        numberFormat.setMinimumIntegerDigits(String.valueOf(DEFAULT_MAX).length());
        Long sequence = getSequence(maxPerMillisecond);
        String formatSequence = numberFormat.format(sequence);
        return ip_postfix +System.currentTimeMillis()+ formatSequence;
    }

    /**
     * get a sequence(number) in a millisecond,if sequenceNumber equels maxPerMillisecond,
     * it will return at next millisecond with a sequenceNumber which will increase from zero again
     *
     * @param maxPerMillisecond
     * @return
     */
    public static Long getSequence(Long maxPerMillisecond) {
        long nextSequence = atomicLong.get();
        while (nextSequence >= maxPerMillisecond || !atomicLong.compareAndSet(nextSequence, ++nextSequence)) {
            if (nextSequence >= maxPerMillisecond && timestamp.longValue() >= System.currentTimeMillis()) {
                nextSequence = atomicLong.get();
            } else if (nextSequence >= maxPerMillisecond) {
                if (atomicLong.compareAndSet(maxPerMillisecond, 0)) {
                    timestamp.set(System.currentTimeMillis());
                    nextSequence = 0L;
                }
            }
        }
        return nextSequence;
    }


}

2017-05-07追加:

5月初恰好用户管理与his传的业务参数发生了id碰撞。

晓飞哥准备让用户管理用32位ID,便采用了12位全IP(高位补领)。过了几天又准备用64位ID,于是向师兄提出将Mac地址的10进制值、进程ID、全IP全部记录下来便可以支持非常高的高并发ID 。组成了固定值+其他值+mac地址+IP全路径+进程ID+时间戳+毫秒自增序列值。

下面说一下各个值的作用。

  • 固定值:可以用来拓展
  • 其他值:一些业务需要的值也可以看作拓展值
  • mac地址:10进制的mac地址,区分一台电脑
  • IP全路径:12位的ip全路径,区分同一个网络中不同的机器。
  • 进程ID:假如一台机器上有两个tomcat可以通过进程ID区分
  • 时间戳:这个不必说了吧(这里有个时间回拨的问题,如果你对机器时间做了调整,一定要注意 ,如何避免这又是一个问题)
  • 毫秒自增序列:一台机器毫秒内最大ID数量

如果出现以上全一样的情况那么祝贺你,你该买彩票了

其实主要的理念就是尽量区分开每台机器,每个tomcat程序,时间戳,再保证单机器原子循环自增。

你可能感兴趣的:(高并发分布式无碰撞ID生成机制)