Java高性能全局ID生成器

一款高性能的类雪花算法的全局ID生成器,并发环境下QPS可达820万+。

实际生成规则可根据具体需求更改

package com.kosmian.fastid;

import java.util.Calendar;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;

/**
 * 流水号生成器
 * <p>
 * 总长度(22位):G + 17位时间戳 + 4位自增因子
 * <p>
 * 类雪花算法,适用于高并发环境。QPS实测值可达820万
 */
public final class GlobalNumberGenerator {
    private final static Generator GENERATOR = new Generator();

    public static String nextId() {
        return GENERATOR.nextId();
    }

    private final static class Generator {

        // ID前缀
        private static final String PREFIX = "G";

        // 序列号配置(13位=8192个/ms)
        private static final int SEQUENCE_BITS = 13;
        // 自增序列号最大值(8191)
        private static final long SEQUENCE_MASK = (1L << SEQUENCE_BITS) - 1;
        // 时间戳左移位
        private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS;

        // 时间戳格式工具
        private static final char[] DIGITS = "0123456789".toCharArray();
        private static final long[] DIVISORS = {1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000};

        // ID生成缓存器
        private static final int ID_LENGTH = PREFIX.length() + 17 + 4;
        private static final ThreadLocal<StringBuilder> ID_COMBINE = ThreadLocal.withInitial(
                () -> new StringBuilder(ID_LENGTH).append(PREFIX));

        // 时间戳缓存器
        private static final ThreadLocal<Calendar> CALENDAR = ThreadLocal.withInitial(
                Calendar::getInstance);

        // 合并状态:高51位=时间戳,低13位=序列号
        private final AtomicLong state = new AtomicLong(0L);

        public String nextId() {
            for (; ; ) {
                long oldState = state.get();
                long lastMs = oldState >>> TIMESTAMP_SHIFT;
                long currentMs = System.currentTimeMillis();

                // 时钟回拨处理
                if (currentMs < lastMs) {
                    handleClockBackward(lastMs, currentMs);
                    continue;
                }

                // 时间戳相同序列号自增
                long newMs, newSeq;
                if (currentMs == lastMs) {
                    newSeq = (oldState & SEQUENCE_MASK) + 1;
                    if (newSeq > SEQUENCE_MASK) {
                        newMs = waitNextMillis(lastMs);
                        newSeq = 1;
                    } else {
                        newMs = lastMs;
                    }
                } else {
                    newMs = currentMs;
                    newSeq = 1;
                }

                // 返回ID
                if (state.compareAndSet(oldState, (newMs << TIMESTAMP_SHIFT) | newSeq)) {
                    return formatId(newMs, newSeq);
                }

                // CAS失败时
                LockSupport.parkNanos(1_000);
            }
        }

        private String formatId(long currentTimestamp, long sequence) {
            StringBuilder sb = ID_COMBINE.get();
            sb.setLength(PREFIX.length());// 保留前缀

            // 时间戳部分(共19位)
            Calendar cal = CALENDAR.get();
            cal.setTimeInMillis(currentTimestamp);
            // 年(4位)
            sb.append(cal.get(Calendar.YEAR));
            // 月日时分秒(各2位)
            formatNumber(sb, cal.get(Calendar.MONTH) + 1, 2);
            formatNumber(sb, cal.get(Calendar.DAY_OF_MONTH), 2);
            formatNumber(sb, cal.get(Calendar.HOUR_OF_DAY), 2);
            formatNumber(sb, cal.get(Calendar.MINUTE), 2);
            formatNumber(sb, cal.get(Calendar.SECOND), 2);
            // 毫秒(3位)
            formatNumber(sb, cal.get(Calendar.MILLISECOND), 3);

            // 自增序列号(4位)
            formatNumber(sb, sequence, 4);
            return sb.toString();
        }

        private void formatNumber(StringBuilder sb, long number, int numberDigits) {
            for (int i = numberDigits - 1; i >= 0; i--) {
                sb.append(DIGITS[(int) ((number / DIVISORS[i]) % 10)]);
            }
        }

        private void handleClockBackward(long lastMs, long currentMs) {
            final long maxBackwardMs = 1_000;
            final long backwardMs = lastMs - currentMs;

            if (backwardMs > maxBackwardMs) {
                throw new IllegalStateException(
                        String.format("Clock moved backwards. Refusing to generate ID for %d ms", backwardMs));
            }

            long startWait = System.currentTimeMillis();

            while (true) {
                long nowSys = System.currentTimeMillis();

                if (nowSys >= lastMs) {
                    return;
                }

                long remaining = lastMs - nowSys;
                if (remaining > 0) {
                    LockSupport.parkNanos(Math.min(remaining, 1) * 1_000_000);
                }

                if (System.currentTimeMillis() - startWait > maxBackwardMs * 2) {
                    throw new IllegalStateException("Clock backward recovery timeout");
                }
            }
        }

        private long waitNextMillis(long lastTimestamp) {
            long timestamp = System.currentTimeMillis();
            int spins = 0;
            while (timestamp <= lastTimestamp) {
                if (++spins % 1_000 == 0) {
                    Thread.yield();
                }
                timestamp = System.currentTimeMillis();
            }
            return timestamp;
        }

    }
}

QPS压测

package com.kosmian.fastid;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Supplier;

public class QPSCounter {
    private final AtomicLong counter = new AtomicLong(0);
    private volatile long lastQPS = 0;
    Runtime runtime = Runtime.getRuntime();
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

    private final boolean isCountFreeInfo;

    public QPSCounter() {
        this(false);
    }

    public QPSCounter(boolean isCountFreeInfo) {
        this.isCountFreeInfo = isCountFreeInfo;
        // 每秒执行一次清零和记录QPS
        scheduler.scheduleAtFixedRate(() -> {
            lastQPS = counter.getAndSet(0); // 获取当前计数并清零
            if (isCountFreeInfo) {
                System.err.printf("内存: used=%,dMB, free=%,dMB, total=%,dMB%n",
                        (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024,
                        runtime.freeMemory() / 1024 / 1024,
                        runtime.totalMemory() / 1024 / 1024);
            }
        }, 1, 1, TimeUnit.SECONDS);
    }

    // 记录一次请求
    public void increment(Supplier<?> supplier) {
        if (supplier != null) {
            supplier.get();
        }
        counter.incrementAndGet();
    }

    // 获取上一秒的QPS
    public long getQPS() {
        return lastQPS;
    }

    // 关闭计数器
    public void shutdown() {
        scheduler.shutdown();
    }

    // 手动重置计数器(通常不需要,因为会自动每秒重置)
    public void reset() {
        counter.set(0);
        lastQPS = 0;
    }

    public static void main(String[] args) {
        QPSCounter counter = new QPSCounter(true);

        AtomicLong secCounter = new AtomicLong(0);

        int threadCount = 8;
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (;;) {
                    counter.increment(GlobalNumberGenerator::nextId);
                }
            }).start();
        }
        while (true) {
            // 每秒打印QPS
            LockSupport.parkNanos(1_000_000_000);
            System.out.println("执行第 " + secCounter.incrementAndGet() + "s, threads: " + threadCount + ", QPS: " + counter.getQPS());
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流沙QS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值