程序整体框架概述
所接触的是数据采集方面的任务,整个数据采集模块主框架被封装在一个a.jar包之中,程序入口common通过启动命令指定,使用nohuo与&指令将服务运行在后台:
nohup java -cp 'pwd'/a.jar main.common [args]>>nohup.out &
进入common继续往下看:
首先关注启动命令中所指定的args参数,此参数指定配置文件路径,(程序启动后,a.jar文件所在目录即为根目录,指定路径以此为基础)。通过jar包sysconfig类中定义的静态方法,读取cfg配置文件内容,依次赋值给sysconfig类中的静态变量,作后续使用。
之后利用jar包Server类中的ServerRun静态方法,通过读取上述sysconfig类中的port静态变量,使用mina框架提供的socket通信方法,监听本机端口。
public static void ServerRun(int PORT) throws Exception {
SocketAcceptor acceptor = new NioSocketAcceptor();
acceptor.setReuseAddress(true);
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
chain.addLast("threadPool", new ExecutorFilter(Executors.newFixedThreadPool(sysconfig.socket_poolsize)));
acceptor.setHandler(new Server());
acceptor.getSessionConfig().setUseReadOperation(true);
acceptor.getSessionConfig().setReadBufferSize(30000);
acceptor.bind(new InetSocketAddress(PORT));
System.out.println("Listening on port " + PORT);
for (;;) {
Thread.sleep(1000);
}
}
如此一来,通过telnet连接至指定端口,就可以做到与程序进行交互。Server类继承mina框架的IoHandAdapter类,便于管理连接,[mian Iohander事件触发机制可以参考该博文]
(https://blog.csdn.net/boonya/article/details/51583823)
当消息被接收到时,messageReceived事件被触发,这是应用程序需要处理输入信息的地方。这里将首次与外部js文件产生调用,(关于java与js的相互调用,大家可自行查阅相关资料)而后续大量的操作均需要通过外部js代码进行完成,如此重要的操作自然需要一个专门的类进行封装。js类概要功能如下:
import javax.script.*;
public class js {
public ScriptEngine engine = null;
public static ScriptEngineManager manager = new ScriptEngineManager();
public js(String args) throws Exception {
m_args = args;
engine = manager.getEngineByName("JavaScript");
engine.put("engine", engine);
engine.put("core", this);
engine.put("args", args);
...
m_files.add(sysconfig.js_run_path + sysconfig.entry_js);
}
public Object runfile(String filename) throws Exception {
java.io.FileReader reader = null;
try {
reader = new java.io.FileReader(filename);
return engine.eval(reader);
} finally {
try{ reader.close(); } catch(Exception e){}
}
}
public Object include(String filename) throws Exception{
return this.runfile(sysconfig.js_run_path + filename);
}
}
简单地说,就是通过getEngineByName初始化一个ScriptEngine,然后通过put()对变量赋值,通过eval执行js程序。
了解了js类的主要方法,回过头继续看messageReceived方法中所作的处理。
public class Server extends IoHandlerAdapter {
@Override
public void messageReceived(IoSession session, Object message) throws UnsupportedEncodingException {
String buf = new String(((IoBuffer) message).array(), sysconfig.socket_recv_encode);
buf = buf.trim();
if (buf.length() > 0) {
try {
js jengine = new js(buf);
jengine.engine.put("client", new Requ(session));
jengine.include(sysconfig.entry_js);
}
jengine.engine.put("input_0_0_0", buf);
jengine.engine.eval("main(input_0_0_0)");
} catch (Exception ex) {
ex.printStackTrace();
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
session.closeOnFlush();
}
}
}
}
这里首先生成一个js对象,之后通过put方法将一个Requ对象与client字符串绑定,Requ对象即为负责与telnet进行通信的实现对象,包括send和recv方法。(在后续程序中通过client.send(“msg”)即可往控制台输出内容,方便了解程序交互运行的过程)。
紧接着调用include方法调用执行一个外部js文件(sysconfig.entry_js在配置文件中的值为main.js,通过include方法拼凑出文件路径,进一步调用runfile方法执行main.js)。而事实上,mian.js里面仅仅定义了一个main(args)函数,因此在jengine.engine.put("input_0", buf);jengine.engine.eval("main(input_0)");
这两句代码的作用仅仅是将接收到的buf数据传输给main(args)执行,其中由telnet传输的args[0]此处一般为js文件名,args[1]为后续参数。通过main(args)函数继续调用args[0]执行各类操作。(这么做显得main.js多此一举,完全可以在Server类中做同样处理,但我想既然存在,自然是有一定的道理,毕竟架构师的经验要丰富的多)。
到此处,基本的架构逻辑捋顺,接下来就是在此基础上各种具体应用,如果有人愿意看,下一篇就选取一个常用的数据库指标采集进行详细的陈述。
所思所得
java与js文件的互相调用配合,架构师的作用
在java程序中通过js类封装了ScriptEngine,并将core与当前js对象绑定。之后在执行的各js文件中,通过core.include("*.js"),就可以继续调用执行其他js程序。js类中还有一个重要的方法newobject(String classname)
,这个方法实现在js脚本程序中初始化jar包中封装的类。
public Object newobject(String classname) throws Exception {
return Class.forName(classname).newInstance();
}
这样在js脚本程序中通过Object_classname= core.newobject(classname);
就可以得到classname的实例对象。
如此一来,将主要功能模块封装在jar包中,具体使用时则通过js程序来调用,这种设计使得程序的灵活性大大提高,程序后期的扩展性也得到了保障。(ps:在学校也曾读过一两本关于设计模式的书,浅尝辄止,只能在以后的工作中多花功夫了)
java与telnet之间的命令传输
在java程序中通过Server类启动并监听PORT端口,当有一个远程端口接入,就生成一个Requ对象负责通信,而根据约定,远程端口发送的命令格式为js文件名+" "+参数列表
,解析命令字符串,通过core.include与runfile方法就可以实现执行js程序并传参的功能预期。
js奇妙体验
以前对js没怎么研究过,只是初步知道是解释型语言,原生支持json,语法简单了解过。在这一个月的"使用"过程中,深深体会到它的便捷与易用性,最简单的如编写一个js文件,代码仅仅是一个方法,甚至只是几个变量的初始化,而当被eval()执行过后,该方法就被加载到了内存中,之后就可以在其它地方直接调用执行,产生一种C#扩展方法的错觉。
所遇到的一个问题
问题描述
在添加对postgresql数据库指标的采集功能时,发现无法采集数据,经排查发现原因在于无法连接至postgresql数据库。
解决问题过程中的所见所得
为什么无法连接postgresql数据库?明明代码什么的都没问题,自己编写的数据库连接测试的小程序也可以成功连接,不得不请教技术专家来解决。问题描述清楚后,老师键指如飞,我一个初出茅庐的小兵是眼花缭乱,再也插不上一句话。几分钟后老师排查出问题所在,原因很简单,jar包没有自动引用postgresql数据库驱动包。事后通过查看历史命令,将大致排查过程简单还原如下:
starce 命令跟踪---查到数据库连接不通
ps -ef | grep *.jar---得到程序pid
cd /proc/pid/fd ---进入程序文件描述符文件夹
ll -h | grep *.jar---查看引用的所有jar包---发现没有引用postgresql驱动jar包
解决方案
为了进一步确认驱动包是否可用,在测试环境下进行了一次破坏性试验。
cd /'home'/lib/
for name in `ls | grep *.jar$`;do `jar -xvf $name`;done
rm -rf *.jar
jar -cvf ojdbc14.jar *
由于ojdbc14.jar是一定会被引用,所以将所有的jar包解包后全部压入ojdbc14.jar中。重新运行程序,发现已经可以正常连接至postgresql数据库。技术与经验的差距,仅仅不到10分钟,不仅排查了问题,还给出一个临时的试验解决方案,当然最终的解决是重新编译打包程序。
事后尝试直接改写jar包中的META-INF/MANIFEST.MF文件,在其Class-Path后加上postgresql的包地址,但并不起作用(格式没问题),所以请读到此文的各位博友不吝赐教。
目前水平较低,难免对各种各样的问题理解不够深彻,还是希望各位博友多多留言交流。