订单号生成方式

发布时间 2023-04-15 13:11:37作者: smars1990

1. reids 实现

利用Redis特有的原子性的特性,在Redis中实现自增。
在Redis中设置数据的自动递增,同时设置数据的到期时间,在业务流程中,单号是每天自动递增的,同时加上一些特有的前缀组成。
本文设计的单号的格式为:YSD20221111000066

 

1.1 获取次日凌晨日期

public long getNextDay() {
Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
//今天凌晨
return c.getTimeInMillis() / 1000 * (24 * 60 * 60);
}

 

 

1.2. 生成订单号工具类

@Component
public class RedisCode {


    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 依赖Redis的原子性,设置数据中的自动递增,有效期到12点
     *
     * @param key
     */
    public String getCode(String key) {
        StringBuilder orderNo = new StringBuilder();
        String format = new SimpleDateFormat("yyyyMMdd").format(new Date());
        orderNo.append("YSD").append(format);
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        //当缓存中没有时,默认时0
        long increment = entityIdCounter.getAndIncrement();
        if (increment == 0) {
            //当数据为0的时候,设置有效期,10秒后过期
            //entityIdCounter.expire(3600, TimeUnit.SECONDS);
            //设置到什么时间过期.例:次日凌晨过期
            entityIdCounter.expireAt(new Date(getNextDay()));
        }
        //当日返回的id最低六位
        increment = increment + 1;
        String count = increment <= 9 ? "00000" : increment <= 99 ? "0000" : increment <= 999 ? "000" : increment <= 9999 ? "00" : increment <= 99999 ? "0" : "";
        orderNo.append(count).append(increment);
        return orderNo.toString();
    }

    /**
     * 获取次日凌晨的时间戳
     *
     * @return
     */
    public long getNextDay() {
        Calendar c = Calendar.getInstance();
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        //今天凌晨
        return c.getTimeInMillis() / 1000 * (24 * 60 * 60);
    }
}

1.3 测试方法

    @Test
    public void d() {
        long a = System.currentTimeMillis();
        for (int i = 0; i < 20; i++) {
            System.out.println(redisCode.getCode("code"));
        }
        long b = System.currentTimeMillis();
        System.out.println("耗时:" + (b - a) / 1000);
    }

 

 

2.雪花算法实现

雪花算法简介

SnowFlake 雪花算法
SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。在2014年开源 scala 语言版本。

雪花算法(Snowflake Algorithm)是一种用于生成唯一ID的算法,它基于时间戳和随机数生成器。以下是雪花算法的基本原理:

  1. 时间戳:使用当前时间作为时间戳,并将其转换为秒数。
  2. 随机数生成器:使用当前时间和一个随机数种子作为随机数生成器的种子,以确保每次生成的随机数都不同。
  3. 生成唯一ID:将时间戳和随机数生成器的结果组合起来,生成一个唯一的ID。
  4. 将ID转换为十六进制字符串:将生成的ID转换为十六进制字符串,以便在不同的系统中使用。

雪花算法的优点是生成的ID具有高度的唯一性和稳定性,可以在不同的系统中使用。然而,由于其使用了随机数生成器,因此生成的ID可能会受到时间和硬件的影响,从而导致生成的ID不够稳定。

 

 

public class SnowflakeIdWorker {

    //开始时间截 (2015-01-01)
    private final long START_TIME_STAMP = 1420041600000L;

    /**
     * 每一部分占用的位数
     */
    //序列号占用的位数
    private final long SEQUENCE_BIT = 12L;
    //机器标识占用的位数
    private final long MACHINE_BIT = 5L;
    //数据中心占用的位数
    private final long DATACENTER_BIT = 5L;


    /**
     * 每一部分的最大值
     */
    //最大数据中心数量,结果是31
    private final long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);

    //最大机器数量,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
    private final long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);

    //最大序列,这里为4095 (0b111111111111=0xfff=4095)
    private final long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);


    /**
     * 每一部分向左的位移
     */
    //机器ID向左移12位
    private final long MACHINE_ID_LEFT = SEQUENCE_BIT;

    //数据中心id向左移17位(12+5)
    private final long DATACENTER_ID_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    //时间截向左移22位(5+5+12)
    private final long TIME_STAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT + DATACENTER_BIT;


    //数据中心ID(0~31)
    private long datacenterId;

    //机器ID(0~31)
    private long machineId;

    //序列号 { 毫秒内序列(0~4095)}
    private long sequence = 0L;

    //上一次时间戳
    private long lastTimestamp = -1L;


    /**
     * 构造函数
     *
     * @param machineId    工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowflakeIdWorker(long machineId, long datacenterId) {
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_MACHINE_NUM));
        }
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_NUM));
        }
        this.machineId = machineId;
        this.datacenterId = datacenterId;
    }


    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {

        long currentTimeStamp = getCurrentTimeStamp();
        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (currentTimeStamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - currentTimeStamp));
        }
        //如果是同一时间生成的,则进行毫秒内序列
        if (currentTimeStamp == lastTimestamp) {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                currentTimeStamp = getNewTimeStamp(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }
        //上次生成ID的时间截
        lastTimestamp = currentTimeStamp;
        //移位并通过或运算拼到一起组成64位的ID
        return ((currentTimeStamp - START_TIME_STAMP) << TIME_STAMP_LEFT) //时间戳部分
                | (datacenterId << DATACENTER_ID_LEFT) //数据中心部分
                | (machineId << MACHINE_ID_LEFT) //机器标识部分
                | sequence;  //序列号部分
    }


    /**
     * 返回以毫秒为单位的当前时间
     */
    protected long getCurrentTimeStamp() {
        return System.currentTimeMillis();
    }

    /**
     * 获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     */
    protected long getNewTimeStamp(long lastTimestamp) {
        long timestamp = getCurrentTimeStamp();
        while (timestamp <= lastTimestamp) {
            timestamp = getCurrentTimeStamp();
        }
        return timestamp;
    }

}

2.1 测试

@Test
    public void c() {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 2);
        for (int i = 0; i < 10; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }

    }

 

 

 

 

参考:【https://blog.csdn.net/weixin_43682323/article/details/127808447】