再谈随机数引起的阻塞问题

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/

游标引起阻塞问题

09-05

将查询发送到服务器后,所有应用程序必须立即完成提取所有结果行。如果应用程序没有提取所有结果行,锁可能会留在表上而阻塞其他用户。如果使用的应用程序将Transact-SQL 语句透明地提交给服务器,则该应用程序必须提取所有结果行。如果应用程序没这样做(如果无法配置它执行此操作),则可能无法解决阻塞问题。为避免此问题,可以将这些应用程序限制在报表或决策支持数据库上。rnrn我们来看一下因游标使用不当而引起的阻塞的例了rnrn li_dqks = long(mid(ddlb_1.text,pos(ddlb_1.text,'-') + 1))rn DECLARE my_cur CURSOR FOR rn SELECT zy_brxx.zyhm rn FROM zy_brxx where dqks = :li_dqks and dqzt is null;rn open my_cur;rn fetch my_cur into :ls_zyhm ;rn do while sqlca.sqlcode = 0 rn uf_mrqd(ls_zyhm)rn if dw_1.rowcount() <= 0 then rn fetch my_cur into :ls_zyhm ; rn continuern end ifrn dw_1.print()rn fetch my_cur into :ls_zyhm ;rn looprn close my_cur ;rnrn以下是uf_mrqd 函数内容rndatetime ldt_date01, ldt_date02rnint li_hs,li_count,li_dqks,li_dmlbrnstring ls_pmlb,ls_brxm,ls_dqks,ls_dqchrndec4de_fsfy,de_yjkrnrnselect brxm,dqks,dqch into :ls_brxm,:li_dqks,:ls_dqch from zy_brxx where zyhm=:ls_zyhm;rnselect ksmc into :ls_dqks from gy_ksdm where ksdm = :li_dqks ;rnldt_date01=datetime(date(em_1.text),time('00:00:00'))rnldt_date02=datetime(date(em_2.text),time('23:59:59'))rnrndw_1.object.sj.text=string(ldt_date01,'yyyy.mm.dd hh:mm:ss')+'-'+string(ldt_date02,'yyyy.mm.dd hh:mm:ss')rndw_1.object.zyhm.text=ls_zyhmrndw_1.object.brxm.text=ls_brxmrndw_1.object.brks.text=ls_dqksrndw_1.object.ch.text=ls_dqchrndw_1.retrieve(ldt_date01,ldt_date02,ls_zyhm)rnrnde_fsfy = gf_getbrfy(ls_zyhm)rnrnrnde_yjk = gf_getyjk(ls_zyhm,1)rnrndw_1.object.bz.text="注: 病人截止目前总费用: "+string(de_fsfy,'0.00')rnif de_fsfy < de_yjk then rn dw_1.object.bz_02.text="病人预交款: "+string(de_yjk,'0.00')+" 尚余:"+string(de_yjk - de_fsfy,'0.00')rnelsern dw_1.object.bz_02.text="病人预交款: "+string(de_yjk,'0.00')+" 欠费:"+string(de_fsfy - de_yjk,'0.00')rnend ifrnrn///////rnrn显然这个游标有问题,最主要的问题是dw_1.print()不应该放入这个游标内,打印完了这个游标才结束,否则zy_brxx这个表一直被阻塞.rn我们可以这样改一下rnrnrn li_dqks = long(mid(ddlb_1.text,pos(ddlb_1.text,'-') + 1))rn ll_row=dw_2.retrieve(li_dqks)rn for i=1 to ll_rowrn ls_zyhm=dw_2.object.zyhm[i]rn uf_mrqd(ls_zyhm)rn dw_1.print()rn nextrn这样就不会引起阻塞了 论坛

语句引起数据库阻塞

04-09

大家好,帮我优化下一面的语句,以下语句是直接写在程序里的,但耗时很长。最下面条件是取控件里的值。rnrnselect c.FDate as '日期',c.FBillNO as '订单编号',b.FNumber as '物料代码',rnb.FName as '物料名称',b.FModel as '物料规格',b.F_103 as '物料型号',rnconvert(decimal(18, 2),d.FAuxQty) as '数量',a.FName as '单位',rnconvert(decimal(18,2),d.FSecQty) as '辅助数量',a1.FName as '辅助单位',rnt.FName as '品牌',d.Fdate as '交货日期',d.FEntrySelfP0245 as '入仓点',rnd.FSourceBillNo as '申购单号',rnconvert(decimal(18,2),d.FCommitQty) as '已入库数量',rnc.FExplanation as '摘要',v1.FName as '制单人',v2.FName as '审核人',rnc.FCheckDate as '审核日期',d.FEntrySelfP0242 as '工程编号',rnd.FEntrySelfP0241 as '设备名称',d.FEntrySelfP0247 as '说明',rnd.FEntrySelfP0248 as '附录',rncase when c.FCheckerID > 0 then 'Y' when c.FCheckerID < 0 then 'Y' else '' end as '审核标志',rncase when c.FStatus = 3 then 'Y' else '' end AS '关闭标志',rncase when c.FCancellation = 1 then 'Y' else '' end as '作废标志',rncase when d.FMRPClosed = 1 then 'Y' else '' end as '行业务关闭标志',rncase when d.FMrpAutoClosed = 0 then 'Y' else '' end as '手工行业务关闭' from POOrder c rnleft outer join POOrderEntry d on c.FInterID = d.FInterID rnleft outer join t_User v1 on c.FBillerID = v1.FUserID rnleft outer join t_User v2 on c.FCheckerID = v2.FUserID rnInner join t_ICItem b on d.FItemID = b.FItemID rnInner join t_MeasureUnit a on a.FMeasureUnitID = b.FUnitID rnInner join t_MeasureUnit a1 on a1.FMeasureUnitID = b.FSecUnitID rnleft outer join t_Item_3001 t on d.FEntrySelfP0244 = t.FItemID where rnc.FDate >= '" + dtpQiShiRiQi.Text.Trim() + "' and rnc.FDate <= '" + dtpJieZhiRiQi.Text.Trim() + "' and rn('" + txtFBillNo.Text.Trim() + "' = '' OR c.FBillNo like '%' + '" + txtFBillNo.Text.Trim() + "' + '%') and rn('" + txtFSourceBillNo.Text.Trim() + "' = '' OR d.FSourceBillNo like '%' + '" + txtFSourceBillNo.Text.Trim() + "' + '%') and rn('" + txtFFullNumber.Text.Trim() + "' = '' OR b.FNumber like '%' + '" + txtFFullNumber.Text.Trim() + "' + '%') and rn('" + txtFItemName.Text.Trim() + "' = '' OR b.FName like '%' + '" + txtFItemName.Text.Trim() + "' + '%') and rn('" + txtFEntrySelfP0242.Text.Trim() + "' = '' OR d.FEntrySelfP0242 like '%' + '" + txtFEntrySelfP0242.Text.Trim() + "' + '%') and rn('" + txtFEntrySelfP0241.Text.Trim() + "' = '' OR d.FEntrySelfP0241 like '%' + '" + txtFEntrySelfP0241.Text.Trim() + "' + '%') and rn('" + txtFuserName.Text.Trim() + "' = '' OR v1.FName like '%' + '" + txtFuserName.Text.Trim() + "' + '%') 论坛

没有更多推荐了,返回首页