Java使用SecureRandom 在Linux生成随机数踩坑实录

背景

公司服务陆续接入sonar代码质量检测扫描,并且集成了p3c规则,在一个红包服务中使用了Random来产生随机数,扫描提示如下:

Creating a new Random object each time a random value is needed is inefficient and may produce numbers which are not random depending on the JDK. For better efficiency and randomness, create a single Random, then store, and reuse it.

建议使用SecureRandom,于是根据建议本地(Windows)修改测试没问题后,代码跟着正常迭代发生产。

踩坑:

这个看似正常简单的用法替换,却隐藏着一个大隐患。修改的代码发布后,陆续收到一些请求处理报错:

(这里也说一下,因为用了自研的日志告警系统,所以可以在第一时间收到报错的内容)
之前逻辑设计为了模块之间解耦,使用了CMQ异步处理,报错可以看出是在删除CMQ消息超时了,看到源码从CMQ拉取消息处理会有30秒的超时。正常的一个请求不会用到30秒的时间。刚开始还以为是链路调用中调用下游rpc服务超时导致,后面排除了(因为每个grpc请求都设置了3秒的超时时间)。
最后定位到是卡在了替换代码后获取随机数这一行:

int probability = this.rand.nextInt(configCenter.getPrizeProbilityPeriod());

定位:

这个时候问题来了:为什么本地可以正常运行,发布到生产服务器就会有问题?
第一想到的就是环境不一样(本地是Windows,服务器是Linux),SecureRandom实现也不一样,通过看SecureRandom的源码发现doc注释有一个注意事项:

翻译过来就是:取决于具体的实现,generateSeed和nextBytes方法可能会由于熵被收集而出现阻塞,例如它们在类Unix操作系统中需要从/dev/random读取。

/dev/random是Linux特殊的设备文件,可以用作随机数发生器。
在读取时,/dev/random设备会返回小于熵池噪声总数的随机字节。/dev/random可生成高随机性的公钥或一次性密码本。若熵池空了,对/dev/random的读操作将会被阻塞,直到从别的设备中收集到了足够的环境噪声为止。

SecureRandom在Linux默认使用NativePRNG算法(很慢),在Windows默认使用SHA1PRNG算法。

三种解决方案:

1.获取SecureRandom 实例时指定SHA1PRNG算法:

SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");

2.启动命令加上参数:

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

3.使用org.apache.commons.lang3包的RandomStringUtils

经验教训:

1.只要有代码修改,都尽量让测试同学在测试环境回归通过后再上生产。

2.对于新的用法或工具,先去了解它的实现。

参考:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6521844
https://stackoverflow.com/questions/137212/how-to-deal-with-a-slow-securerandom-generator
https://www.iteye.com/blog/394498036-2357358

发表评论