欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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连接采用单例。

代码如下:
转化器动态代理类:
/**
 * 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宕机与单线程的问题。
相关标签: openoffice宕机