首页 > 解决方案 > 生成随机 UUID 非阻塞

问题描述

通过使用 Blockhound io.projectreactor.tools blockhound-junit-platform我发现 UUID.randomUUID 是一个阻塞调用,这对我们来说是个问题,因为我们使用的是 Spring boot Webflux 版本 2.2.2.RELEASE

是否有任何其他方法可以以非阻塞方式获取随机 uuid,或者是否有任何其他 java 库推荐用于非阻塞生成随机字符串。

来自blockhound的堆栈跟踪:

java.lang.Error: Blocking call! java.io.FileInputStream#readBytes
at reactor.blockhound.BlockHound$Builder.lambda$new$0(BlockHound.java:196) ~[blockhound-1.0.1.RELEASE.jar:na]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain][ExceptionHandlingWebHandler]
Stack trace:
    at reactor.blockhound.BlockHound$Builder.lambda$new$0(BlockHound.java:196) ~[blockhound-1.0.1.RELEASE.jar:na]
    at reactor.blockhound.BlockHound$Builder.lambda$install$6(BlockHound.java:318) ~[blockhound-1.0.1.RELEASE.jar:na]
    at reactor.blockhound.BlockHoundRuntime.checkBlocking(BlockHoundRuntime.java:46) ~[na:na]
    at java.base/java.io.FileInputStream.readBytes(FileInputStream.java) ~[na:na]
    at java.base/java.io.FileInputStream.read(FileInputStream.java:279) ~[na:na]
    at java.base/java.io.FilterInputStream.read(FilterInputStream.java:133) ~[na:na]
    at java.base/sun.security.provider.NativePRNG$RandomIO.readFully(NativePRNG.java:424) ~[na:na]
    at java.base/sun.security.provider.NativePRNG$RandomIO.ensureBufferValid(NativePRNG.java:526) ~[na:na]
    at java.base/sun.security.provider.NativePRNG$RandomIO.implNextBytes(NativePRNG.java:545) ~[na:na]
    at java.base/sun.security.provider.NativePRNG.engineNextBytes(NativePRNG.java:220) ~[na:na]
    at java.base/java.security.SecureRandom.nextBytes(SecureRandom.java:741) ~[na:na]
    at java.base/java.util.UUID.randomUUID(UUID.java:150) ~[na:na]

标签: javareactive-programminguuidspring-webfluxproject-reactor

解决方案


这是关于如何使用uuid-creator减少 Linux 上的线程争用的三个示例的列表。

另一个示例使用ulid-creator生成单调 ULID,然后将它们转换为 RFC-4122 UUID(标准)。单调 ULID 生成速度非常快

基准代码可在GitHub Gist上获得。

示例 1:使用UuidCreatorSHA1PRNG

如何设置供SHA1PRNG以下人员使用的算法UuidCreator

# Append to /etc/environment or ~/.profile
# Use the the algorithm SHA1PRNG for SecureRandom
export UUIDCREATOR_SECURERANDOM="SHA1PRNG"
// generate a random-based UUID
UUID uuid = UuidCreator.getRandomBased();

UuidCreator使用固定的SecureRandom. 该变量UUIDCREATOR_SECURERANDOM告诉UuidCreator使用另一种SecureRandom算法,而不是NativePRNG在 Linux 上。UuidCreatorSHA1PRNG算法一起生成的 UUID 的线程争用(更少阻塞)比UUID.randomUUID().

查看使用 4 个线程的基准测试:

----------------------------------------------------------------------
Benchmark                      Mode  Cnt      Score     Error   Units
----------------------------------------------------------------------
UUID.randomUUID()             thrpt    5   1423,060 ±  30,125  ops/ms
UuidCreator.getRandomBased()  thrpt    5  10616,016 ± 281,486  ops/ms
----------------------------------------------------------------------

示例 2:使用ThreadLocalRandom

如何使用以下方法实现 UUID 生成器ThreadLocalRandom

public class UuidGenerator {

    private static final RandomBasedFactory UUID_FACTORY;

    static {
        UUID_FACTORY = new RandomBasedFactory((int length) -> {
            final byte[] bytes = new byte[length];
            ThreadLocalRandom.current().nextBytes(bytes);
            return bytes;
        });
    }

    public static UUID generate() {
        return UUID_FACTORY.create();
    }
}

ThreadLocalRandom是一个快速(且不安全)的随机生成器,没有线程争用(非阻塞)。

查看使用 4 个线程的基准测试:

----------------------------------------------------------------------
Benchmark                     Mode  Cnt      Score      Error   Units
----------------------------------------------------------------------
UUID.randomUUID()            thrpt    5   1423,060 ±   30,125  ops/ms
UuidGenerator.generate()     thrpt    5  85390,979 ± 1564,589  ops/ms
----------------------------------------------------------------------

示例 3:使用RandomBasedFactory[]SHA1PRNG

如何使用 ana 数组RandomBasedFactorySHA1PRNG算法实现 UUID 生成器:

    public static class UuidGenerator {

        private static final int SIZE = 8; // you choose
        private static final RandomBasedFactory[] FACTORIES;

        static {
            FACTORIES = new RandomBasedFactory[SIZE];
            try {
                for (int i = 0; i < FACTORIES.length; i++) {
                    // SHA1PRNG or DRBG can be used to reduce thread contention.
                    SecureRandom argument = SecureRandom.getInstance("SHA1PRNG");
                    FACTORIES[i] = new RandomBasedFactory(argument);
                }
            } catch (NoSuchAlgorithmException e) {
                // oops!
            }
        }

        public static UUID generate() {
            // calculate the factory index given the current thread ID
            final int index = (int) Thread.currentThread().getId() % SIZE;
            return FACTORIES[index].create();
        }
    }

一组RandomBasedFactorywith 和SHA1PRNG算法可以生成具有较少线程争用(较少阻塞)的 UUID。

查看使用 4 个线程的基准测试:

----------------------------------------------------------------------
Benchmark                     Mode  Cnt      Score      Error   Units
----------------------------------------------------------------------
UUID.randomUUID()            thrpt    5   1423,060 ±   30,125  ops/ms
UuidGenerator.generate()     thrpt    5  10048,747 ±  195,209  ops/ms
----------------------------------------------------------------------

示例 4:使用UlidCreatorMonotonic ULIDs

如何使用ulid-creatorMonotonic ULID生成 RFC-4122 UUID :

// create a Monotonic ULID and convert it to RFC-4122 UUID v4
UUID uuid = UlidCreator.getMonotonicUlid().toRfc4122().toUuid();

查看使用 4 个线程的基准测试:

-----------------------------------------------------------------------
Benchmark                       Mode  Cnt     Score      Error   Units
-----------------------------------------------------------------------
UUID.randomUUID()              thrpt    5  1423,060 ±   30,125  ops/ms
UlidCreator.getMonotonicUlid() thrpt    5  7391,917 ±  871,799  ops/ms
-----------------------------------------------------------------------

编辑:添加了使用单调 ULID 的第四个示例。


推荐阅读