openoffice 致使应用宕机处理
程序员文章站
2022-07-12 16:13:12
...
openoffice是单线程的,如果并发操作,不管是通过java调用,还是命令调用,都会出问题,异常处理,可能会在openoffice服务器生成文件锁,导致命令一直执行不下去造成线程死锁;这种情况,tomcat线程被占满,tomcat默认连接数10000,等待执行队列数100,也就是服务器会接受10100个连接,服务器最多产生10100个文件句柄,所以,线程假死执行不下去,文件句柄会飙升;所以要防止应用假死,防止openoffice并发操作, 应用在负载均衡的时候,nginx必须对openoffice转换文件的请求指向同一台机器或者每个应用单独部署一个openoffice服务;同时程序里面应用的连接开启一个就行了,无需新建多个链接对象。
java版本针对openoffice单线程和可能线程假死的问题,我采取的方案有:
1、线程隔离转换文件,主线程异步关闭连接的;
2、转换接口采用jdk动态代理
3、openoffice连接采用单例。
代码如下:
转化器动态代理类:
外部调用类:
调用方法:
以上,完美处理openoffice宕机与单线程的问题。
java版本针对openoffice单线程和可能线程假死的问题,我采取的方案有:
1、线程隔离转换文件,主线程异步关闭连接的;
2、转换接口采用jdk动态代理
3、openoffice连接采用单例。
代码如下:
转化器动态代理类:
/** * DocumentConverter动态代理 * 线程隔离,openoffice文件转换操作,此步骤可能会导致宕机; 为了不影响主线程,转换过程采用线程隔离; * 转换文件执行超时后,通过主线程关闭连接,同时输出日志 * @author lyq * @date 2020-09-16 * */ public class DocumentConverterDynaProxy implements InvocationHandler { private static final Logger logger = LoggerFactory.getLogger(DocumentConverterDynaProxy.class); private static ThreadPoolExecutor executor = null; static{ //有限队列,缓存30个请求等待,线程池默认策略,缓存超出上限报错,此配置与tomcat max-threads(100) 值有关 BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(30); //初始化1个线程,最多1个线程 executor = new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, workQueue,new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("openoffice-convert-thread"); return thread; } } ); } private DocumentConverter documentConverter; private Integer timeoutSec = 30;//转换超时时间,单位:秒 /** * 绑定并获取代理对象 * @param documentConverter 转换器接口 * @param timeoutSec,执行转换超时时间,单位:秒 * @return DocumentConverter代理对象 * @author lyq * @date 2020-09-16 */ public Object bind(DocumentConverter documentConverter,Integer timeoutSec){ this.documentConverter = documentConverter; this.timeoutSec = timeoutSec; Class [] clzz = {DocumentConverter.class}; Object obj = Proxy.newProxyInstance( this.documentConverter.getClass().getClassLoader(),clzz , this); return obj; } /** * */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ExecCallable runner = new ExecCallable(method, args); Future future = null; try{ // synchronized (executor) { future = executor.submit(runner); return future.get(timeoutSec, TimeUnit.SECONDS); // } }catch(TimeoutException e){ synchronized (runner.isExec) { if(runner.isExec.get()){ if(OfficePatch.execExceptionCount.get()>3){ logger.error("openoffice执行"+timeoutSec+"秒转换超时了,请系统管理员检测openoffice服务器应用是否正常使用"); }else{ OfficePatch.execExceptionCount.incrementAndGet(); logger.error("openoffice执行"+timeoutSec+"秒转换超时,关闭连接。"); } asynDisconnect();//断开连接线程才会跑下去; }else{ runner.isExec.set(true); logger.error("openoffice 缓存中等待"+timeoutSec+"秒超时,未执行转换,当前等待队列数量:"+executor.getQueue().size()); } throw new OpenOfficeConvertException(e); } }catch(RejectedExecutionException e2){ //abort策略,缓存超出容量,报错。 logger.error("openoffice 超出等待执行缓存",e2); throw new OpenOfficeConvertException(e2); }finally{ // this.conn.disconnect(); if(future!=null){ future.cancel(true); } } } /** * * 异步关闭连接 * OpenOfficeConnection的disconnect跟StreamOpenOfficeDocumentConverter类的转换的 * 方法convertInternal会加同步锁,但是convertInternal转换过程会假死,造成线程永久不释放, * 所以disconnect执行不了,通过异步关闭,避免tomcat线程被永久占用,最终造成应用崩溃。 * @author lyq * @date 2020-09-16 */ private void asynDisconnect() { try{ XComponent component = (XComponent)readSuperPrivateField( OfficePatch.getConnection(), "bridgeComponent"); logger.debug("disconnecting"); writeSuperPrivateField(OfficePatch.getConnection(),"expectingDisconnection",false); component.dispose(); }catch(Exception e2){ logger.error("openoffice关闭连接失败。",e2); } } private static Object readSuperPrivateField(Object target,String filedname) { if( target == null){ throw new RuntimeException("ClassUtils.readSuperPrivateField:属性target 为空"); } if( target.getClass().getSuperclass() == Object.class){ throw new RuntimeException("readSuperPrivateField:属性target"+target.getClass().getName()+" 没有超类"); } Field field = FieldUtils.getDeclaredField(target.getClass().getSuperclass(), filedname,true); if (field == null) { throw new RuntimeException("readSuperPrivateField:属性target"+target.getClass().getName()+" 超类 没有属性:"+ filedname); } Object value; try { value = FieldUtils.readField(field, target, true); } catch (IllegalAccessException e) { throw new RuntimeException(e.getMessage()); } return value; } private static Object writeSuperPrivateField(Object target,String filedname,Object value) { if( target == null){ return new RuntimeException("readSuperPrivateField:属性target 为空"); } if( target.getClass().getSuperclass() == Object.class){ return new RuntimeException("readSuperPrivateField:属性target "+target.getClass().getName()+" 没有超类"); } Field field = FieldUtils.getDeclaredField(target.getClass().getSuperclass(), filedname,true); if (field == null) { return new RuntimeException("readSuperPrivateField:属性target "+target.getClass().getName()+" 超类 没有属性:"+ filedname); } try { FieldUtils.writeField(field, target, value,true); } catch (IllegalAccessException e) { return new RuntimeException(e.getMessage()); } return value; } class ExecCallable implements Callable{ private AtomicBoolean isExec = new AtomicBoolean(false); private Method method = null; private Object[] args = null; public ExecCallable(Method method,Object[] args) { // TODO Auto-generated constructor stub this.method = method; this.args = args; } @Override public Object call() throws Exception { // TODO Auto-generated method stub synchronized (this.isExec) { if(!isExec.get()){//未超时 isExec.set(true); Object result = method.invoke(documentConverter, args); return result; }else{ return null; } } } } }
外部调用类:
import java.net.ConnectException; import java.util.concurrent.atomic.AtomicInteger; import com.artofsolving.jodconverter.DocumentConverter; import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection; import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection; /** * 连接单例 * @author lyq * 2020.9.17 */ public class OfficePatch { static AtomicInteger execExceptionCount = new AtomicInteger(0) ; //连接异常处理次数,重连后清0 private static OpenOfficeConnection conn = null; private static String LOCK_ = ""; public static OpenOfficeConnection getConnection() throws ConnectException{ if(conn==null){ synchronized (LOCK_) { if(conn==null){ conn = new SocketOpenOfficeConnection("ip", 端口); System.out.println("openOffice正在连接。。"); conn.connect(); } } } if(!conn.isConnected()){ synchronized (LOCK_) { if(!conn.isConnected()){ System.out.println("openOffice正在重新连接。。"); execExceptionCount.set(0); conn.connect(); } } } return conn; } /** * 绑定并获取代理转换对象 * @param converter openoffice转换器 * @param connection converter所调用的连接 * @param timeoutSec 超时时间,单位:秒 * @return */ public static DocumentConverter getDocmentConverterProxy(DocumentConverter converter,Integer timeoutSec){ DocumentConverterDynaProxy proxy = new DocumentConverterDynaProxy(); return (DocumentConverter)proxy.bind(converter,timeoutSec); } }
调用方法:
OpenOfficeConnection connection = OfficePatch.getConnection(); DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection); converter = OfficePatch.getDocmentConverterProxy(converter, 100);
以上,完美处理openoffice宕机与单线程的问题。