您的位置:首页 > 运维架构 > 网站架构

纯Java网站开发改造为nodejs混合编程

2015-05-17 19:24 609 查看


早已对纯JAVA版的网站不满了,不管是繁重的代码编写量,和无谓的3层代码编写都让我提不起兴趣。但是提到nodejs我就有兴趣来做了,原来的网站是放到云服务器上的,由于CPU和内存的限制进一步影响了网站速度和承载量。达到什么程度呢,就是3个人同时应用就会造成访问慢或卡死。

于是我想到nodejs将原来网站重写。将来就会加速网站和体现并发数优势。很多人反对我这样做,说nodejs不适合重逻辑的部分,但好了,89%的应用都是直接从用户获得参数直接传透到数据库,为啥要写那么多代码,什么时候运行过其他计算。偶尔也是对参数进行个加减而已。为啥不将几百行代码缩减到几行。

既然要动手就开始,准备好nodejs,在nodejs.org官网下在nodejs1.2X安装好之后,下在javascript编辑器,或文本都行。我喜欢用文本直接编辑。

罗列了以下几个步骤
:

1.
安装nodejsmysql包
(网上很多教程注意先安装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后才能安装,输入以下命令

npminstallfelixge/node-mysql

完成安装mysql;

完成之后试写mysql功能简单调用一下:

var
mysql
=require('mysql');

varpool=mysql.createPool({

connectionLimit:30,

host:'localhost',

user:'root',

password:xxxx

});


pool.query('SELECT*FROMzd.alga_cs;',function(err,rows,fields){

if(err)throwerr;

console.log('Thesolutionis:',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里:

<filter>

<filter-name>CORS</filter-name>

<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>

<init-param>

<param-name>cors.allowOrigin</param-name>

<param-value>http://127.0.0.1</param-value>

</init-param>

<init-param>

<param-name>cors.supportedMethods</param-name>

<param-value>GET,POST,HEAD,PUT,DELETE</param-value>

</init-param>

<init-param>

<param-name>cors.supportedHeaders</param-name>

<param-value>Accept,Origin,X-Requested-With,Content-Type,Last-Modified</param-value>

</init-param>

<init-param>

<param-name>cors.exposedHeaders</param-name>

<param-value>Set-Cookie</param-value>

</init-param>

<init-param>

<param-name>cors.supportsCredentials</param-name>

<param-value>true</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>CORS</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

注意我写的是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"){

varstr=arg.citylist;

vardid=arg.did;

varsubtree="";

pool.query('selectidcity,cityName,b.departmentid,departmentName,departmentlimit,count(c.employeeId)countfromzd.cityasaleftouterjoinzd.departmentasbona.idcity=b.cityIdleftouterjoinzd.employeeconb.departmentId=c.departmentIdwhere1=1and('+str+')andb.pre='+did+'andb.level=1groupbyidcity,cityName,b.departmentid,departmentName,departmentlimitorderbya.idcityasc,b.orderidasc;',function(err,rows,fields){

if(err)throwerr;

subtree=JSON.stringify(rows);

res.writeHeader(200,{

'Access-Control-Allow-Origin':'*'//先写header否则返回无效在跨域访问时

});

res.end(subtree);

});

}


这里有两个坑,不小心你就掉里面了,第一个坑就是返回json串的方法,在nodejs里把结果集合改成json是用JSON.stringifv();方法格式化结果集。第二个坑就是Header必须写在前面,否则跨域不接受数据。我写的*是允许所有操作(GET
UPDATEDELETEPOST等)跨域提供数据。

第五步,修改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('selectidcity,cityName,b.departmentid,departmentName,departmentlimit,count(c.employeeId)countfromzd.cityasaleftouterjoinzd.departmentasbona.idcity=b.cityIdleftouterjoinzd.employeeconb.departmentId=c.departmentIdwhere1=1and('+str+')andb.level=0groupbyidcity,cityName,b.departmentid,departmentName,departmentlimitorderbya.idcityasc,b.orderidasc;',function(err,rows,fields){

if(err)throwerr;

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:

varsize=0;

varentry=newObject();

exports.add=function(key,value)

{

if(!this.containsKey(key))

{

size++;

}

entry[key]=value;

}

exports.getValue=function(key)

{

returnthis.containsKey(key)?entry[key]:null;

}

exports.remove=function(key)

{

if(this.containsKey(key)&&(deleteentry[key]))

{

size--;

}

}

exports.containsKey=function(key)

{

return(keyinentry);

}

exports.containsValue=function(value)

{

for(varpropinentry)

{

if(entry[prop]==value)

{

returntrue;

}

}

returnfalse;

}

exports.getValues=function()

{

varvalues=newArray();

for(varpropinentry)

{

values.push(entry[prop]);

}

returnvalues;

}

exports.getKeys=function()

{

varkeys=newArray();

for(varpropinentry)

{

keys.push(prop);

}

returnkeys;

}

exports.getSize=function()

{

returnsize;

}

exports.clear=function()

{

size=0;

entry=newObject();

}


调用过程如下:

varMhashTable=require('./HashTable.js');

//调用子树

if(pathname=="/node_employee_getsubtree_do"){

varstr="";

varcitylist=arg.citylist;

vardid=arg.did;

varsubtree="";

if(citylist==""){

str+="idcity=-1";

}else{

str+="idcity=";

citylist=citylist.replace(/,/g,"oridcity=");

str+=citylist;

}

subtree=MhashTable.getValue(did);//获得变量如果为null则访问数据库

if(subtree==null){

pool.query('selectidcity,cityName,b.departmentid,departmentName,departmentlimit,count(c.employeeId)countfromzd.cityasaleftouterjoinzd.departmentasbona.idcity=b.cityIdleftouterjoinzd.employeeconb.departmentId=c.departmentIdwhere1=1and('+str+')andb.pre='+did+'andb.level=1groupbyidcity,cityName,b.departmentid,departmentName,departmentlimitorderbya.idcityasc,b.orderidasc;',function(err,rows,fields){

if(err)throwerr;

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()

{

varsize=0;

varentry=newObject();

this.add=function(key,value)

{

if(!this.containsKey(key))

{

size++;

}

entry[key]=value;

}

this.getValue=function(key)

{

returnthis.containsKey(key)?entry[key]:null;

}

this.remove=function(key)

{

if(this.containsKey(key)&&(deleteentry[key]))

{

size--;

}

}

this.containsKey=function(key)

{

return(keyinentry);

}

this.containsValue=function(value)

{

for(varpropinentry)

{

if(entry[prop]==value)

{

returntrue;

}

}

returnfalse;

}

this.getValues=function()

{

varvalues=newArray();

for(varpropinentry)

{

values.push(entry[prop]);

}

returnvalues;

}

this.getKeys=function()

{

varkeys=newArray();

for(varpropinentry)

{

keys.push(prop);

}

returnkeys;

}

this.getSize=function()

{

returnsize;

}

this.clear=function()

{

size=0;

entry=newObject();

}

}

改造index.jsp
加入下列代码:

<scripttype="text/javascript">

//树缓存

varsubtreeHashTabls=newHashTable();

</script>

在具体调用的方法里加入hashtable查询的过程,如下:

functiongetsubTree(id){

varresult=subtreeHashTabls.getValue(id);

varstr="";

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);

varmydata=eval(result);



…}

经过这样的改造后,只需要读取一次树,其他时候读取树完全由内存里的HashTable读取,根本都不需要访问ajax跟服务器发生交互。
改造完毕后,我的页面首次加载比原来快1秒,再次加载快3秒,当然并发量我并没有测试,应该部署后会比原来强大许多,这就是nodejs优势,当然HashTable也尽了很大的力。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: