再談隨機數引起的阻塞問題


Java的隨機數實現有很多坑,記錄一下這次使用jdk1.8里新增的加強版隨機數實現SecureRandom.getInstanceStrong() 遇到的問題。

之前在維護ali-tomcat的時候曾發現過jvm隨機數算法選用不當導致tomcat的SessionID生成非常慢的情況,可以參考JVM上的隨機數與熵池策略 和 Docker中apache-tomcat啟動慢的問題 這兩篇文章。不過當時沒有太追究,以為使用了-Djava.security.egd=file:/dev/./urandom就可以避免了,在這次項目里再次遇到隨機數導致所有線程阻塞之后發現這塊還挺多規則。

本次項目中使用的是jdk1.8,啟動參數里設置了

-Djava.security.egd=file:/dev/./urandom

使用的隨機數方式是Java8新增的:

SecureRandom.getInstanceStrong();

碰到故障時,線程阻塞在

"DubboServerHandler-xxx:20880-thread-1789" #28440 daemon prio=5 os_prio=0 tid=0x0000000008ffd000 nid=0x5712 runnable [0x000000004cbd7000]
java.lang.Thread.State: RUNNABLE
    at java.io.FileInputStream.readBytes(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:246)
    at sun.security.provider.NativePRNG$RandomIO.readFully(NativePRNG.java:410)
    at sun.security.provider.NativePRNG$RandomIO.implGenerateSeed(NativePRNG.java:427)
    - locked <0x00000000c03a3c90> (a java.lang.Object)
    at sun.security.provider.NativePRNG$RandomIO.access$500(NativePRNG.java:329)
    at sun.security.provider.NativePRNG$Blocking.engineGenerateSeed(NativePRNG.java:272)
    at java.security.SecureRandom.generateSeed(SecureRandom.java:522)

因為這個地方有加鎖,locked <0x00000000c03a3c90>,所以其它線程調用到這里時會等待這個lock:

"DubboServerHandler-xxx:20880-thread-1790" #28441 daemon prio=5 os_prio=0 tid=0x0000000008fff000 nid=0x5713 waiting for monitor entry [0x000000004ccd8000]
java.lang.Thread.State: BLOCKED (on object monitor)
    at sun.security.provider.NativePRNG$RandomIO.implGenerateSeed(NativePRNG.java:424)
    - waiting to lock <0x00000000c03a3c90> (a java.lang.Object)
    at sun.security.provider.NativePRNG$RandomIO.access$500(NativePRNG.java:329)
    at sun.security.provider.NativePRNG$Blocking.engineGenerateSeed(NativePRNG.java:272)
    at java.security.SecureRandom.generateSeed(SecureRandom.java:522)

去查 NativePRNG$Blocking代碼,看到它的文檔描述:

A NativePRNG-like class that uses /dev/random for both seed and random material. Note that it does not respect the egd properties, since we have no way of knowing what those qualities are.

奇怪怎么-Djava.security.egd=file:/dev/./urandom參數沒起作用,仍使用/dev/random作為隨機數的熵池,時間久或調用頻繁的話熵池很容易不夠用而導致阻塞;於是看了一下 SecureRandom.getInstanceStrong()的文檔:

Returns a SecureRandom object that was selected by using the algorithms/providers specified in the securerandom.strongAlgorithms Security property.

原來有自己的算法,在 jre/lib/security/java.security 文件里,默認定義為:

securerandom.strongAlgorithms=NativePRNGBlocking:SUN

如果修改算法值為NativePRNGNonBlocking:SUN的話,會采用NativePRNG$NonBlocking里的邏輯,用/dev/urandom作為熵池,不會遇到阻塞問題。但這個文件是jdk系統文件,修改它或重新指定一個路徑都有些麻煩,最好能通過系統環境變量來設置,可這個變量不像securerandom.source屬性可以通過系統環境變量-Djava.security.egd=xxx來配置,找半天就是沒有對應的系統環境變量。只好修改代碼,不采用SecureRandom.getInstanceStrong這個新方法,改成了SecureRandom.getInstance("NativePRNGNonBlocking")

對於SecureRandom的兩種算法實現:SHA1PRNG 和 NativePRNG 跟 securerandom.source 變量的關系,找到一篇解釋的很清楚的文章:Using the SecureRandom Class

On Linux:

1) when this value is “file:/dev/urandom” then the NativePRNG algorithm is registered by the Sun crypto provider as the default implementation; the NativePRNG algorithm then reads from /dev/urandom for nextBytes but /dev/random for generateSeed

2) when this value is “file:/dev/random” then the NativePRNG algorithm is not registered by the Sun crypto provider, but the SHA1PRNG system uses a NativeSeedGenerator which reads from /dev/random.

3) when this value is anything else then the SHA1PRNG is used with a URLSeedGenerator that reads from that source.

4) when the value is undefined, then SHA1PRNG is used with ThreadedSeedGenerator

5) when the code explicitly asks for “SHA1PRNG” and the value is either “file:/dev/urandom” or “file:/dev/random” then (2) also occurs

6) when the code explicitly asks for “SHA1PRNG” and the value is some other “file:” url, then (3) occurs

7) when the code explicitly asks for “SHA1PRNG” and the value is undefined then (4) occurs

至於SHA1PRNG算法里,為何用urandom時,不能直接設置為file:/dev/urandom而要用變通的方式設置為file:///dev/urandom或者 file:/dev/./urandom,參考這里

In SHA1PRNG, there is a SeedGenerator which does various things depending on the configuration.

  1. If java.security.egd or securerandom.source point to “file:/dev/random” or “file:/dev/urandom”, we will use NativeSeedGenerator, which calls super() which calls SeedGenerator.URLSeedGenerator(/dev/random). (A nested class within SeedGenerator.) The only things that changed in this bug was that urandom will also trigger use of this code path.

  2. If those properties point to another URL that exists, we’ll initialize SeedGenerator.URLSeedGenerator(url). This is why “file:///dev/urandom”, “file:/./dev/random”, etc. will work.

 

http://hongjiang.info/java8-nativeprng-blocking/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM