生成随机 UUID 非阻塞

Posted

技术标签:

【中文标题】生成随机 UUID 非阻塞【英文标题】:Generate random UUID non blocking 【发布时间】:2020-05-22 14:04:22 【问题描述】:

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

是否有任何其他方法可以以非阻塞方式获取随机 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]

【问题讨论】:

这真的有问题吗?从操作系统随机数源获取足够的随机字节曾经是旧 Linux 版本的问题,但现在应该很快了。也许这只会在您第一次生成 UUID 时发生? 查看此主题以讨论为什么默认熵源可能很慢以及如何选择不同的源:***.com/questions/137212/… 如果你确实遇到了缓慢并且不想阻塞你的“反应”线程,你可以在一个专用的工作池上生成所有 UUID,就像你对其他阻塞的事情一样。但这似乎有点矫枉过正。 这没有问题(除非播种 SecureRandom 真的很慢)。类加载也是阻塞的。不要落入过早的“优化”陷阱,而是专注于可证明的性能问题。 您应该查看第一条评论,了解为什么通话需要这么多时间。但通常要包装同步阻塞调用 Mono.fromCallable(UUID::randomUUID).subscribeOn(Schedulers.boundedElastic()) 【参考方案1】:

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

另一个示例使用ulid-creator 生成Monotonic ULIDs,然后将它们转换为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 算法,而不是Linux 上的NativePRNGUuidCreatorSHA1PRNG 算法生成的 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

如何使用ThreadLocalRandom实现UUID生成器:

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

如何使用RandomBasedFactorySHA1PRNG算法的ana数组实现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();
        
    

RandomBasedFactorySHA1PRNG 算法的数组可以生成具有较少线程争用(较少阻塞)的 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-creator从Monotonic ULIDs生成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
-----------------------------------------------------------------------

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

【讨论】:

以上是关于生成随机 UUID 非阻塞的主要内容,如果未能解决你的问题,请参考以下文章

在 Mac OS X 上使用 Python 生成一个新的非阻塞进程

Java非阻塞读取[关闭]

将空数据写入非阻塞套接字会导致epoll_wait挂起

阻塞赋值和非阻塞赋值有何区别

IO阻塞非阻塞同步异步同步阻塞同步非阻塞异步阻塞异步非阻塞

verilog 阻塞和非阻塞啥区别啊?