先说问题:就是我在升级了 Aspose 版本到 24 后,在本地 Windows 环境下测试正常,然后测试服务器上正常,上到生产环境下载 PDF 文件的时候就卡主了,后台日志也没有报错,排查了各种服务器环境、JDK版本、Nginx配置、服务器字体最终才找到真正的问题
下面开始复现
最近有个老项目在用 Word 模板转 PDF 的时候格式有些问题,我查了一下发现是 Aspose 版本太低的缘故,所以我先升级了 Aspose 版本
bash
<!-- 原版本 -->
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>16.8.0</version>
</dependency>
<!-- 新版本 注意虽然要求 jdk17 但是 8 也可以正常运行 -->
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>24.12</version>
<classifier>jdk17</classifier>
</dependency>
下面是实现方式,也是参照的别人的代码,通过反射绕过了 Aspose 的 License ,此方法不可商用且不合法,建议大家不要学我,放这个上来主要为了证明不是代码的问题以及我的排查思路
java
import com.aspose.words.*;
import com.itextpdf.text.pdf.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Map;
public class WordBookMark {
private static final Logger log = LoggerFactory.getLogger(WordBookMark.class);
private static volatile boolean licenseApplied = false;
private static void applyLicense() {
try {
// 直接调用已有的反射绕过方法
registerWord2412();
log.info("Aspose License加载成功");
} catch (Exception e) {
log.error("Aspose License加载失败, 使用评估模式继续: {}", e.getMessage());
e.printStackTrace();
}
// if (licenseApplied) {
// return;
// }
// synchronized (WordBookMark.class) {
// if (licenseApplied) {
// return;
// }
// try {
// String licenseStr =
// "<License>\n" +
// " <Data>\n" +
// " <Products>\n" +
// " <Product>Aspose.Total for Java</Product>\n" +
// " <Product>Aspose.Words for Java</Product>\n" +
// " </Products>\n" +
// " <EditionType>Enterprise</EditionType>\n" +
// " <SubscriptionExpiry>20991231</SubscriptionExpiry>\n" +
// " <LicenseExpiry>20991231</LicenseExpiry>\n" +
// " <SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber>\n" +
// " </Data>\n" +
// " <Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature>\n" +
// "</License>";
// License asposeLic = new License();
// asposeLic.setLicense(new ByteArrayInputStream(licenseStr.getBytes("UTF-8")));
// log.info("Aspose License加载成功");
// } catch (Exception e) {
// log.warn("Aspose License加载失败, 使用评估模式继续: {}", e.getMessage());
// }
// licenseApplied = true;
// }
}
public static void createFile2(String docPath,
OutputStream out,
Map<String, Object> dataMap,
String password) throws Exception {
long totalStart = System.currentTimeMillis();
log.info("Word转PDF开始, 模板={}", docPath);
applyLicense();
File templateFile = new File(docPath);
if (!templateFile.exists()) {
throw new FileNotFoundException("Word模板不存在: " + docPath);
}
log.info("Word模板大小={}KB", templateFile.length() / 1024);
try (InputStream is = new FileInputStream(templateFile)) {
long t1 = System.currentTimeMillis();
Document document = new Document(is);
log.info("Word文档加载完成, 耗时={}ms", System.currentTimeMillis() - t1);
DocumentBuilder builder = new DocumentBuilder(document);
if (dataMap != null) {
for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (builder.moveToBookmark(key, true, true)) {
builder.write(value == null ? "" : String.valueOf(value));
}
}
}
log.info("Word书签填充完成, 耗时={}ms", System.currentTimeMillis() - totalStart);
log.info("开始配置字体");
configureFonts(document);
log.info("字体配置完成");
PdfSaveOptions options = new PdfSaveOptions();
options.setEmbedFullFonts(false);
options.setUseHighQualityRendering(false);
DownsampleOptions downsampleOptions = options.getDownsampleOptions();
if (downsampleOptions != null) {
downsampleOptions.setDownsampleImages(true);
downsampleOptions.setResolution(220);
}
String ownerPassword = password == null ? "" : password;
PdfEncryptionDetails encryptionDetails =
new PdfEncryptionDetails("", ownerPassword, PdfPermissions.PRINTING);
options.setEncryptionDetails(encryptionDetails);
long t2 = System.currentTimeMillis();
log.info("开始save PDF");
FontInfoCollection infos = document.getFontInfos();
for (FontInfo info : infos) {
log.info("使用字体: {}", info.getName());
}
document.save(out, options);
log.info("save PDF完成");
log.info("Word转PDF完成, 模板={}, save耗时={}ms, 总耗时={}ms",
docPath, System.currentTimeMillis() - t2, System.currentTimeMillis() - totalStart);
} catch (Exception e) {
log.error("Word转PDF失败, 模板={}, 已耗时={}ms", docPath, System.currentTimeMillis() - totalStart, e);
throw new IOException("Word转PDF失败: " + docPath + ", " + e.getMessage(), e);
}
}
private static void configureFonts(Document document) {
FontSettings fontSettings = new FontSettings();
if (SystemUtils.isWindows()) {
fontSettings.setFontsFolder("C:\\Windows\\Fonts", true);
}
document.setFontSettings(fontSettings);
}
public static void fillTemplate(String templatePath,
OutputStream out,
Map<String, Object> map,
int pageCount,
String password) throws Exception {
long totalStart = System.currentTimeMillis();
log.info("PDF模板填充开始, 模板={}, 页数={}", templatePath, pageCount);
File templateFile = new File(templatePath);
if (!templateFile.exists()) {
throw new FileNotFoundException("PDF模板不存在: " + templatePath);
}
PdfReader reader = null;
PdfStamper stamper = null;
ByteArrayOutputStream filledBos = null;
PdfReader finalReader = null;
com.itextpdf.text.Document doc = null;
PdfCopy copy = null;
ByteArrayOutputStream pagedBos = null;
PdfReader encryptReader = null;
PdfStamper encryptStamper = null;
ByteArrayOutputStream encryptedBos = null;
try {
reader = new PdfReader(templatePath);
filledBos = new ByteArrayOutputStream();
stamper = new PdfStamper(reader, filledBos);
AcroFields form = stamper.getAcroFields();
BaseFont baseFont;
if (SystemUtils.isWindows()) {
baseFont = BaseFont.createFont(
"C:\\WINDOWS\\Fonts\\simsun.ttc,1",
BaseFont.IDENTITY_H,
BaseFont.EMBEDDED
);
} else {
baseFont = BaseFont.createFont(
"STSong-Light",
"UniGB-UCS2-H",
BaseFont.EMBEDDED
);
}
form.addSubstitutionFont(baseFont);
if (map != null) {
for (String fieldName : form.getFields().keySet()) {
Object value = map.get(fieldName);
if (value != null) {
form.setField(fieldName, String.valueOf(value));
}
}
}
stamper.setFormFlattening(true);
stamper.close();
stamper = null;
reader.close();
reader = null;
log.info("PDF表单填充完成, 耗时={}ms", System.currentTimeMillis() - totalStart);
finalReader = new PdfReader(filledBos.toByteArray());
pagedBos = new ByteArrayOutputStream();
doc = new com.itextpdf.text.Document();
copy = new PdfCopy(doc, pagedBos);
doc.open();
int totalPages = finalReader.getNumberOfPages();
int maxPage = Math.min(pageCount, totalPages);
for (int i = 1; i <= maxPage; i++) {
PdfImportedPage page = copy.getImportedPage(finalReader, i);
copy.addPage(page);
}
doc.close();
doc = null;
finalReader.close();
finalReader = null;
encryptReader = new PdfReader(pagedBos.toByteArray());
encryptedBos = new ByteArrayOutputStream();
encryptStamper = new PdfStamper(encryptReader, encryptedBos);
encryptStamper.setEncryption(
null,
password == null ? new byte[0] : password.getBytes(java.nio.charset.StandardCharsets.UTF_8),
PdfWriter.ALLOW_PRINTING,
PdfWriter.ENCRYPTION_AES_128
);
encryptStamper.close();
encryptStamper = null;
encryptReader.close();
encryptReader = null;
out.write(encryptedBos.toByteArray());
out.flush();
log.info("PDF模板填充完成, 模板={}, 总耗时={}ms", templatePath, System.currentTimeMillis() - totalStart);
} catch (Exception e) {
log.error("PDF模板填充失败, 模板={}, 已耗时={}ms", templatePath, System.currentTimeMillis() - totalStart, e);
throw new IOException("PDF模板填充失败: " + templatePath + ", " + e.getMessage(), e);
} finally {
try {
if (stamper != null) stamper.close();
} catch (Exception ignored) {
}
try {
if (reader != null) reader.close();
} catch (Exception ignored) {
}
try {
if (finalReader != null) finalReader.close();
} catch (Exception ignored) {
}
try {
if (doc != null && doc.isOpen()) doc.close();
} catch (Exception ignored) {
}
try {
if (encryptStamper != null) encryptStamper.close();
} catch (Exception ignored) {
}
try {
if (encryptReader != null) encryptReader.close();
} catch (Exception ignored) {
}
}
}
/**
* aspose-words:jdk17:24.12 版本
*/
public static void registerWord2412() {
try {
Class<?> zzodClass = Class.forName("com.aspose.words.zzod");
Constructor<?> constructors = zzodClass.getDeclaredConstructors()[0];
constructors.setAccessible(true);
Object instance = constructors.newInstance(null, null);
Field zzWws = zzodClass.getDeclaredField("zzWws");
zzWws.setAccessible(true);
zzWws.set(instance, 1);
Field zzVZC = zzodClass.getDeclaredField("zzVZC");
zzVZC.setAccessible(true);
zzVZC.set(instance, 1);
Class<?> zz83Class = Class.forName("com.aspose.words.zz83");
constructors.setAccessible(true);
constructors.newInstance(null, null);
Field zzZY4 = zz83Class.getDeclaredField("zzZY4");
zzZY4.setAccessible(true);
ArrayList<Object> zzwPValue = new ArrayList<>();
zzwPValue.add(instance);
zzZY4.set(null, zzwPValue);
Class<?> zzXuRClass = Class.forName("com.aspose.words.zzXuR");
Field zzWE8 = zzXuRClass.getDeclaredField("zzWE8");
zzWE8.setAccessible(true);
zzWE8.set(null, 128);
Field zzZKj = zzXuRClass.getDeclaredField("zzZKj");
zzZKj.setAccessible(true);
zzZKj.set(null, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
一发现功能卡主异常我就在里面加上了日志,然后因为本地测试环境都正常我就在生产环境单独起了一个服务测试,发现卡在了 log.info("开始save PDF"); 这一行,我就开始检查服务器环境这些,根据下面这些可以发现虽然 jdk 版本不太一样,但是差别不大不太可能是 jdk 的问题
bash
# 生产环境数据
[root@ecm-d765 ceshi]# ps -ef | grep insurance-SNAPSHOT.jar
root 9120 485 0 15:05 pts/0 00:00:00 grep --color=auto insurance-SNAPSHOT.jar
root 16872 1 4 14:56 pts/0 00:00:28 java -Xms2048m -Xmx2048m -Xmn512m -Xss521k -server -XX:+HeapDumpOnOutOfMemoryError -jar insurance-SNAPSHOT.jar
[root@ecm-d765 ceshi]# cat startup.sh
#!/bin/bash
BASEDIR=pwd
PID=$(ps aux|grep -w 'insurance-SNAPSHOT.jar'|grep -v grep |awk '{print $2}')
JAVAPACK=insurance-SNAPSHOT.jar
cd $BASEDIR
if [ -z $PID ];then
nohup java -Xms2048m -Xmx2048m -Xmn512m -Xss521k -server -XX:+HeapDumpOnOutOfMemoryError -jar $JAVAPACK &
fi
[root@ecm-d765 ceshi]# java -version
java version "1.8.0_441" Java(TM) SE Runtime Environment (build 1.8.0_441-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.441-b07, mixed mode)
[root@ecm-d765 ceshi]# jar tf /data/back_service/ceshi/insurance-SNAPSHOT.jar | grep aspose
BOOT-INF/lib/aspose-cells-18.11.jar
BOOT-INF/lib/aspose-words-24.12-jdk17.jar
BOOT-INF/lib/aspose-words-24.12-shaping-harfbuzz-plugin.jar
# 测试环境数据
root@quanyi-devops:/etc# ps -ef|grep insurance-server-0.0.1-SNAPSHOT.jar
root 3157039 1 2 14:29 pts/0 00:00:55 java -Xms1024m -Xmx1024m -Xmn512m -Xss521k -server -XX:+HeapDumpOnOutOfMemoryError -jar insurance-server-0.0.1-SNAPSHOT.jar
root 3220938 2798766 0 15:06 pts/2 00:00:00 grep --color=auto insurance-server-0.0.1-SNAPSHOT.jar
root@quanyi-devops:/etc# java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
root@quanyi-devops:/etc# cd /data/application/insurance/
root@quanyi-devops:/data/application/insurance# cat startup.sh
#!/bin/bash
BASEDIR=pwd
PID=$(ps aux|grep -w 'insurance-server-0.0.1-SNAPSHOT.jar'|grep -v grep |awk '{print $2}')
JAVAPACK=insurance-server-0.0.1-SNAPSHOT.jar
cd $BASEDIR
if [ -z $PID ];then
nohup java -Xms1024m -Xmx1024m -Xmn512m -Xss521k -server -XX:+HeapDumpOnOutOfMemoryError -jar $JAVAPACK &
fi
root@quanyi-devops:/data/application/insurance# jar tf /data/application/insurance/insurance-server-0.0.1-SNAPSHOT.jar | grep aspose
BOOT-INF/lib/aspose-cells-18.11.jar
BOOT-INF/lib/aspose-words-24.12-jdk17.jar
BOOT-INF/lib/aspose-words-24.12-shaping-harfbuzz-plugin.jar
所以怀疑是字体的问题,就加了下面这行打印字体的,然后我把测试环境的字体全部同步到生产环境了,还是不行
bash
FontInfoCollection infos = document.getFontInfos();
for (FontInfo info : infos) {
log.info("使用字体: {}", info.getName());
}
# 在服务器上执行
# 查看字体数量
fc-list | wc -l
# 检查是否存在对应字体
fc-list | grep -i simsun
此时发现了一个很重要的问题,就是测试环境和生产环境服务器不是一样,测试环境是 ubuntu 20.04 ,生产环境是 CentOS 7,所以怀疑是服务器版本不一样导致的
bash
# 测试环境
root@quanyi-devops:/data/application/insurance# cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.6 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.6 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
# 生产环境
[root@ecm-d765 ceshi]# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"
既然是服务器版本不一样,然后也知道是卡在了 document.save()方法,那剩下的就是看这个方法为什么卡主了,直接去抓线程栈
bash
# 注意9771是我服务的Id,不知道的自己 ps -ef|grep 服务名 看下
jstack 9771 > /tmp/aspose.txt
grep -A 50 -B 10 "http-nio-8579-exec-1" /tmp/aspose.txt
下面是我的线程栈数据
bash
[root@ecm-d765 ceshi]# jstack 9771 > /tmp/aspose.txt
[root@ecm-d765 ceshi]# grep -A 50 -B 10 "http-nio-8579-exec-1" /tmp/aspose.txt at
sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269) at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93) at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86) - locked <0x00000000fe7fb660> (a sun.nio.ch.Util$3) - locked <0x00000000fe7fb670> (a java.util.Collections$UnmodifiableSet) - locked <0x00000000fe7fb618> (a sun.nio.ch.EPollSelectorImpl) at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97) at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:816) at java.lang.Thread.run(Thread.java:750) "http-nio-8579-exec-10" #33 daemon prio=5 os_prio=0 tid=0x00007f8745d51800 nid=0x2726 waiting on condition [0x00007f86f8803000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000fe8157c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:108) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:750) "http-nio-8579-exec-9" #32 daemon prio=5 os_prio=0 tid=0x00007f8745d4f800 nid=0x2724 waiting on condition [0x00007f86f8886000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000fe8157c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:108) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:750) "http-nio-8579-exec-8" #31 daemon prio=5 os_prio=0 tid=0x00007f8745d4d800 nid=0x2722 waiting on condition [0x00007f86f8909000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000fe8157c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:108) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:750) "http-nio-8579-exec-7" #30 daemon prio=5 os_prio=0 tid=0x00007f8745d4b800 nid=0x271f waiting on condition [0x00007f86f898c000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000fe8157c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) -- at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:108) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:750) "http-nio-8579-exec-1" #24 daemon prio=5 os_prio=0 tid=0x00007f8745d26800 nid=0x2718 runnable [0x00007f86f8c9a000] java.lang.Thread.State: RUNNABLE at java.io.FileInputStream.readBytes(Native Method) at java.io.FileInputStream.read(FileInputStream.java:255) at sun.security.provider.NativePRNG$RandomIO.readFully(NativePRNG.java:424) at sun.security.provider.NativePRNG$RandomIO.implGenerateSeed(NativePRNG.java:441) - locked <0x0000000080646e38> (a java.lang.Object) at sun.security.provider.NativePRNG$RandomIO.access$500(NativePRNG.java:331) at sun.security.provider.NativePRNG.engineGenerateSeed(NativePRNG.java:226) at java.security.SecureRandom.generateSeed(SecureRandom.java:550) at com.aspose.words.internal.zzXdE$1.zzWnP(Unknown Source) at com.aspose.words.internal.zzWU9.zzWnP(Unknown Source) - locked <0x00000000eaf62ba0> (a com.aspose.words.internal.zzWU9) at com.aspose.words.internal.zzq1.zzWnP(Unknown Source) at com.aspose.words.internal.zzq1.zzY21(Unknown Source) at com.aspose.words.internal.zzq1.zzXfC(Unknown Source) at com.aspose.words.internal.zzWH6.zzXfC(Unknown Source) - locked <0x00000000eb6f04b8> (a com.aspose.words.internal.zzWH6) at com.aspose.words.internal.zzXEQ.zzXfC(Unknown Source) - locked <0x00000000eaf62b80> (a com.aspose.words.internal.zzXEQ) at com.aspose.words.internal.zzWto$zzXfC.engineNextBytes(Unknown Source) - locked <0x00000000eaf62b80> (a com.aspose.words.internal.zzXEQ) at java.security.SecureRandom.nextBytes(SecureRandom.java:482) at com.aspose.words.internal.zzVUV.nextBytes(Unknown Source) at com.aspose.words.internal.zzXMm.zzVWJ(Unknown Source) at com.aspose.words.internal.zzWPz.<init>(Unknown Source) at com.aspose.words.internal.zzZX3.zzZP1(Unknown Source) at com.aspose.words.internal.zzXWE.zzZgj(Unknown Source) at com.aspose.words.internal.zzXWE.zzXfC(Unknown Source) at com.aspose.words.internal.zzYsD.zzZEf(Unknown Source) at com.aspose.words.internal.zzXcT.zzXfC(Unknown Source) at com.aspose.words.internal.zzTW.zzjx(Unknown Source) at com.aspose.words.internal.zzXcT.zzWHX(Unknown Source) at com.aspose.words.zzQp.zzYNw(Unknown Source) at com.aspose.words.zzWae.zzjx(Unknown Source) at com.aspose.words.zzWae.zzXfC(Unknown Source) at com.aspose.words.zzWae.zzjx(Unknown Source) at com.aspose.words.zzX6Q.zzjx(Unknown Source) at com.aspose.words.Document.zzXfC(Unknown Source) at com.aspose.words.Document.zzjx(Unknown Source) at com.aspose.words.Document.zzjx(Unknown Source) at com.aspose.words.Document.save(Unknown Source) at com.mece.insurance.core.utils.WordBookMark.createFile2(WordBookMark.java:122) at com.mece.insurance.controller.insurance.InsuranceHistoryController.CreatePDF(InsuranceHistoryController.java:1684) at com.mece.insurance.controller.insurance.InsuranceHistoryController.createPdf(InsuranceHistoryController.java:346) at com.mece.insurance.controller.insurance.InsuranceHistoryController.insuranceHistoryToPdf(InsuranceHistoryController.java:286) at com.mece.insurance.controller.insurance.InsuranceHistoryController$$FastClassBySpringCGLIB$$48bc3861.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
重点看这里
bash
"http-nio-8579-exec-1"
at java.io.FileInputStream.readBytes(Native Method)
at sun.security.provider.NativePRNG$RandomIO.readFully
at sun.security.provider.NativePRNG$RandomIO.implGenerateSeed
at java.security.SecureRandom.generateSeed
at java.security.SecureRandom.nextBytes
at com.aspose.words.internal.zzVUV.nextBytes
...
at com.aspose.words.Document.save
发现卡在 SecureRandom.generateSeed() 这里,这个是 Aspose 在 save PDF 的过程中需要生成随机数的,知道问题出在哪里解决起来就简单了
bash
# 查看系统熵池
cat /proc/sys/kernel/random/entropy_avail
# 然后测试一下
head -c 32 /dev/random | xxd
可以看到这个熵池只有 53,正常的应该在 2000 多,然后下个命令也卡主了,所以问题就确定了
bash
[root@ecm-d765 ceshi]# cat /proc/sys/kernel/random/entropy_avail
53
[root@ecm-d765 ceshi]# head -c 32 /dev/random | xxd
我们安装熵服务
bash
yum install -y epel-release
yum install -y haveged
systemctl enable haveged
systemctl start haveged
# 重新检查熵池大小
cat /proc/sys/kernel/random/entropy_avail
# 测试,如果很快就返回了说明正常
time head -c 32 /dev/random >/dev/null
然后重启服务问题修复
最后结论:CentOS7 随机熵池耗尽导致 Aspose 在生成 PDF 时阻塞