標籤:記錄 specified 演算法實現 怎麼 最好 inpu als waiting path
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 generateSeed2) 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 ThreadedSeedGenerator5) when the code explicitly asks for “SHA1PRNG” and the value is either “file:/dev/urandom” or “file:/dev/random” then (2) also occurs6) when the code explicitly asks for “SHA1PRNG” and the value is some other “file:” url, then (3) occurs7) 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.
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.
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/
再談隨機數引起的阻塞問題