早已对纯JAVA版的网站不满了,不管是繁重的代码编写量,和无谓的3层代码编写都让我提不起兴趣。但是提到nodejs我就有兴趣来做了,原来的网站是放到云服务器上的,由于CPU和内存的限制进一步影响了网站速度和承载量。达到什么程度呢,就是3个人同时应用就会造成访问慢或卡死。
于是我想到nodejs将原来网站重写。将来就会加速网站和体现并发数优势。很多人反对我这样做,说nodejs不适合重逻辑的部分,但好了,89%的应用都是直接从用户获得参数直接传透到数据库,为啥要写那么多代码,什么时候运行过其他计算。偶尔也是对参数进行个加减而已。为啥不将几百行代码缩减到几行。
既然要动手就开始,准备好nodejs,在nodejs.org官网下在nodejs1.2X 安装好之后,下在javascript编辑器,或文本都行。我喜欢用文本直接编辑。
罗列了以下几个步骤 :
1. 安装nodejs mysql 包 (网上很多教程注意先安装git)
2. 修改java工程文件中的web,添加跨域反问,并将跨域限制为本机
3. 修改原工程jquery的ajax调用,使用访问本机127.0.0.1:1337端口访问
4. 提供nodejs直接调用数据库的调用方法
5. 修改调用返回的处理
最后就是写一个工具在原网站上进行500个轮询访问的效率查询,用以鉴定以前的效率和现在效率的差别。
这棵树就是我们需要改造的原因。原来的反问原理是,通过spring->访问controller->访问helper层->访问dao层->访问mysql->再依次将结果json返回页面处理。
上面就是全部步骤,其实我说错了,上面还不是整个过程。因为树是存在一张表中的。其中只有id,pried,name,leve,orderid等,所以关系都是锁在同一张表里,意味着要把所有树排列好一次拿出来是可以的。只要按默认顺序将树整个解析出来即可。但目前存在客户要求树也要按顺序列出来,也就是按order指定的顺序排列,那么一次将树拿出来解析就不可以了。
因此我采用先将根结点读数据库拿出来,在生成根结点界面的时候程序回调再去查询所有子节点,并从数据库返回结果生成整棵树。
这样本来一次调用却变成了N次调用,往返于服务器之间,登陆几个用户打开几次页面我的程序基本就慢的要死。
第一步介绍:我只介绍注意事项即可,其他的请搜索网上吧,而且都有很好的文章。安装好nodejs使用npm装载mysql模块是报错的,因为没装git,使用git后才能安装,输入以下命令
npm install felixge/node-mysql
完成安装mysql;
完成之后试写mysql功能简单调用一下:
var mysql = require('mysql');
var pool = mysql.createPool({ connectionLimit : 30, host : 'localhost', user : 'root', password : xxxx });
pool.query('SELECT * FROM zd.alga_cs;', function(err, rows, fields) { if (err) throw err; console.log('The solution is: ', rows); });
调用完成后看一下你是否能读出结果,测试成功则nodejs和mysql模块都装好了。
第二步:修改java原来的tomcat,因为在一个页面下以前用jquery的ajax调用spring对应的controller,所以现在需要改成调用nodejs本地下的一个端口。我设置为127.0.0.1:1337下来访问我定义的nodejs代码块。
第一就直接修改了,例如将如下:
$.ajax({ async:false,type:"post", url:"employee.getUnDeparment.do",data:"", dataType:"text", success:function(msg){ mydata=eval("("+msg+")"); // alert(msg); $.each(mydata,function(idx,item){ unuser = item.count; });
改成:
$.ajax({ async:false, type:"get",url:"http://127.0.0.1:1337/employee_getUnDepartment",data:"", dataType:"text", success:function(msg){ mydata=eval("("+msg+")"); // alert(msg); $.each(mydata,function(idx,item){ unuser = item.count; });
然后就不出数据了,按下IE的F12,看到提示CORS错误!nodejs写的http模块使用http:// 127.0.0.1:1337/employee_getUnDepartment是直接可以返回json串的,怎么到这里就不行了呢?!原来还需要改造一下java的web.xml配置,和加入两个jar包才行。
网上下载:cors-filter-2.4.jar和java-property-utils-1.9.1.jar;放入项目工程里的libs目录下,并引用这两个包。并将如下代码加到web.xml里:
注意我写的是127.0.0.1,也就是说我允许跨域到本机127.0.0.1位置。设置完了后再次调用,怎么回事,nodejs控制台已经返回了查询结果,但IE报一个ajax错误,查了之后发现如果是跨域访问,则需要返回的内容加上文件头。于是在返回结果的模块里加了头如下:
res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先写header 否则返回无效在跨域访问时 }) ;
加上这句,返回的串就可以显示到原来的界面上。速度嘛,当然比以前块几百毫秒,但调试变简单了,Ctrl+C终止程序,按上键显示上句命令,回车就完成了再次启动nodejs程序。
而且不受以前tomcat的影响,只要程序是nodejs里的,直接关闭nodejs再启动调试,使用者基本感觉不到你在一步步调试程序,他们其他的java应用里的程序还正常执行。
第三步修改ajax调用为nodejs远程:
$.ajax({async:false, type:"get",url:"http://127.0.0.1:1337/node_employee_getsubtree_do", data:"citylist="+session_citylist+"&did="+id,success:function(msg){
alert(msg); }
});
这里有一个坑,就是type:get;如果你不注意原来用的是post的话,那么在nodejs处理比较复杂,因为post是流发送,也就是有个开始投送,到接收完毕的过程,在nodejs里需要处理开始,和回调函数,这样整个改造过程就比较麻烦了。因为没有牵扯到需要post的表单,所以直接用get,否则参数会接收不到。当然如果你用了express 的话当然可以用里面包含的接收post包装好的方法。
第四步提供调用subtree的nodejs方法:
//调用子树 if(pathname=="/node_employee_getsubtree_do") { var str = arg.citylist; var did = arg.did; var subtree = ""; pool.query('select idcity,cityName,b.departmentid,departmentName,departmentlimit,count(c.employeeId) count from zd.city as a left outer join zd.department as b on a.idcity=b.cityId left outer join zd.employee c on b.departmentId = c.departmentId where 1 =1 and ('+str+') and b.pre='+ did +' and b.level=1 group by idcity,cityName,b.departmentid,departmentName,departmentlimit order by a.idcity asc,b.orderid asc ;', function(err, rows, fields) { if (err) throw err;
subtree = JSON.stringify(rows);
res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先写header 否则返回无效在跨域访问时 }) ; res.end(subtree); }); }
这里有两个坑,不小心你就掉里面了,第一个坑就是返回json串的方法,在nodejs里把结果集合改成json是用JSON.stringifv();方法格式化结果集。第二个坑就是Header必须写在前面,否则跨域不接受数据。我写的*是允许所有操作(GET UPDATE DELETE POST等)跨域提供数据。
第五步,修改JAVA程序适合调用返回nodejs程序:
其实这步根本不需要做,为什么还需要这步,是因为,以前java调用dao返回结果结集的时候字段名称有大写有小写,有混合写的。但用nodejs调用后直接都是数据库里怎么写的字段名返回就是怎么写的。所以departMent有可能变成department,因此要详细核对一下,这个坑我已经掉进去过了。
这就是一个简单的混合程序完成了。但只是比java的快了一点点,那么怎么优化呢?下面介绍一下优化。
优化思路:
减少数据库调用à减少ajax调用
这个大方向走,首先是否使用redis,想了半天,还是算了,只是为了优化一棵树,何必动用神器。自己搞个HashTabls算了。首先采用变量来优化基础查询,如下:
res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先写header 否则返回无效在跨域访问时 }) ; //判断主树是否需要缓寸 if(condmaintree!=str) { condmaintree = str; // console.log('citylist: ', str); pool.query('select idcity,cityName,b.departmentid,departmentName,departmentlimit,count(c.employeeId) count from zd.city as a left outer join zd.department as b on a.idcity=b.cityId left outer join zd.employee c on b.departmentId = c.departmentId where 1 =1 and ('+str+') and b.level=0 group by idcity,cityName,b.departmentid,departmentName,departmentlimit order by a.idcity asc,b.orderid asc ;', function(err, rows, fields) { if (err) throw err; console.log('读数据库! '); memmaintree = JSON.stringify(rows); res.end(memmaintree); //数组和json之间的数据转换 }); } else { console.log('直接返回!'); res.end(memmaintree); }
可以看出采用了nodejs全局变量condmaintree,因为所有人只有权限不同的才会需要重新加载树,所以可以这样做,改完之后只有第一次读取需要查数据库,否则直接http返回存在condmaintree里的json串。子树也是这样优化的,但字树的分支读取次数很多,需要很多全局变量,这不切合实际。怎么办,引用自己编写的HashTable,nodejs版如下HashTable.js:
var size = 0; var entry = new Object(); exports.add = function (key , value) { if(!this.containsKey(key)) { size ++ ; } entry[key] = value; } exports.getValue = function (key) { return this.containsKey(key) ? entry[key] : null; } exports.remove = function ( key ) { if( this.containsKey(key) && ( delete entry[key] ) ) { size --; } } exports.containsKey = function ( key ) { return (key in entry); } exports.containsValue = function ( value ) { for(var prop in entry) { if(entry[prop] == value) { return true; } } return false; } exports.getValues = function () { var values = new Array(); for(var prop in entry) { values.push(entry[prop]); } return values; } exports.getKeys = function () { var keys = new Array(); for(var prop in entry) { keys.push(prop); } return keys; } exports.getSize = function () { return size; } exports.clear = function () { size = 0; entry = new Object(); }
调用过程如下:
var MhashTable = require('./HashTable.js');
//调用子树 if(pathname=="/node_employee_getsubtree_do") { var str = ""; var citylist = arg.citylist; var did = arg.did; var subtree = ""; if(citylist=="") { str += " idcity =-1"; } else { str +=" idcity ="; citylist= citylist.replace(/,/g," or idcity ="); str+=citylist; } subtree = MhashTable.getValue(did); //获得变量如果为null则访问数据库 if(subtree == null) { pool.query('select idcity,cityName,b.departmentid,departmentName,departmentlimit,count(c.employeeId) count from zd.city as a left outer join zd.department as b on a.idcity=b.cityId left outer join zd.employee c on b.departmentId = c.departmentId where 1 =1 and ('+str+') and b.pre='+ did +' and b.level=1 group by idcity,cityName,b.departmentid,departmentName,departmentlimit order by a.idcity asc,b.orderid asc ;', function(err, rows, fields) { if (err) throw err; subtree = JSON.stringify(rows); MhashTable.add(did,subtree); //将结果存入hashtable console.log('哈希没找到!: ', subtree); res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先写header 否则返回无效在跨域访问时 }) ; res.end(subtree); }); } else { res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先写header 否则返回无效在跨域访问时 }) ; console.log('哈希找到!: ', subtree); res.end(subtree); } }
这样做以后,首次60次的ajax调用确实访问了数据库,但第二次的刷新树的时候就不会调用数据库了。但这也不是最终的方法,我还是决定要去掉远程调用,那么在index.jsp框架页面里引入HashTable.js,但这个版本和nodejs用的稍微有点不同。代码如下:
HashTable.js
functionHashTable()
{
var size = 0;
varentry = new Object();
this.add = function (key , value)
{
if(!this.containsKey(key))
{
size ++ ;
}
entry[key] = value;
}
this.getValue = function (key)
{
return this.containsKey(key) ?entry[key] : null;
}
this.remove = function ( key )
{
if( this.containsKey(key) && (delete entry[key] ) )
{
size --;
}
}
this.containsKey = function ( key )
{
return (key in entry);
}
this.containsValue = function ( value )
{
for(var prop in entry)
{
if(entry[prop] == value)
{
return true;
}
}
return false;
}
this.getValues = function ()
{
var values = new Array();
for(var prop in entry)
{
values.push(entry[prop]);
}
return values;
}
this.getKeys = function ()
{
var keys = new Array();
for(var prop in entry)
{
keys.push(prop);
}
return keys;
}
this.getSize = function ()
{
return size;
}
this.clear = function ()
{
size = 0;
entry = new Object();
}
}
改造index.jsp 加入下列代码:
//树缓存
var subtreeHashTabls = new HashTable();
在具体调用的方法里加入hashtable查询的过程,如下:
function getsubTree(id) {
var result = subtreeHashTabls.getValue(id);
var str = "";
if(result ==null) { //alert("远程取!");
$.ajax({async:false, type:"get",url:"http://127.0.0.1:1337/node_employee_getsubtree_do",data:"citylist="+session_citylist+"&did="+id,success:function(msg){
subtreeHashTabls.add(id,msg);// alert(msg);
}}); }
result =subtreeHashTabls.getValue(id);
var mydata=eval(result);
…
…}
经过这样的改造后,只需要读取一次树,其他时候读取树完全由内存里的HashTable读取,根本都不需要访问ajax跟服务器发生交互。
改造完毕后,我的页面首次加载比原来快1秒,再次加载快3秒,当然并发量我并没有测试,应该部署后会比原来强大许多,这就是nodejs优势,当然HashTable也尽了很大的力。