早已對(duì)純JAVA版的網(wǎng)站不滿了,不管是繁重的代碼編寫量,和無(wú)謂的3層代碼編寫都讓我提不起興趣。但是提到nodejs我就有興趣來(lái)做了,原來(lái)的網(wǎng)站是放到云服務(wù)器上的,由于CPU和內(nèi)存的限制進(jìn)一步影響了網(wǎng)站速度和承載量。達(dá)到什么程度呢,就是3個(gè)人同時(shí)應(yīng)用就會(huì)造成訪問(wèn)慢或卡死。
于是我想到nodejs將原來(lái)網(wǎng)站重寫。將來(lái)就會(huì)加速網(wǎng)站和體現(xiàn)并發(fā)數(shù)優(yōu)勢(shì)。很多人反對(duì)我這樣做,說(shuō)nodejs不適合重邏輯的部分,但好了,89%的應(yīng)用都是直接從用戶獲得參數(shù)直接傳透到數(shù)據(jù)庫(kù),為啥要寫那么多代碼,什么時(shí)候運(yùn)行過(guò)其他計(jì)算。偶爾也是對(duì)參數(shù)進(jìn)行個(gè)加減而已。為啥不將幾百行代碼縮減到幾行。
既然要?jiǎng)邮志烷_(kāi)始,準(zhǔn)備好nodejs,在nodejs.org官網(wǎng)下在nodejs1.2X 安裝好之后,下在javascript編輯器,或文本都行。我喜歡用文本直接編輯。
羅列了以下幾個(gè)步驟 :
1. 安裝nodejs mysql 包 (網(wǎng)上很多教程注意先安裝git)
2. 修改java工程文件中的web,添加跨域反問(wèn),并將跨域限制為本機(jī)
3. 修改原工程jquery的ajax調(diào)用,使用訪問(wèn)本機(jī)127.0.0.1:1337端口訪問(wèn)
4. 提供nodejs直接調(diào)用數(shù)據(jù)庫(kù)的調(diào)用方法
5. 修改調(diào)用返回的處理
最后就是寫一個(gè)工具在原網(wǎng)站上進(jìn)行500個(gè)輪詢?cè)L問(wèn)的效率查詢,用以鑒定以前的效率和現(xiàn)在效率的差別。
這棵樹(shù)就是我們需要改造的原因。原來(lái)的反問(wèn)原理是,通過(guò)spring->訪問(wèn)controller->訪問(wèn)helper層->訪問(wèn)dao層->訪問(wèn)mysql->再依次將結(jié)果json返回頁(yè)面處理。
上面就是全部步驟,其實(shí)我說(shuō)錯(cuò)了,上面還不是整個(gè)過(guò)程。因?yàn)闃?shù)是存在一張表中的。其中只有id,pried,name,leve,orderid等,所以關(guān)系都是鎖在同一張表里,意味著要把所有樹(shù)排列好一次拿出來(lái)是可以的。只要按默認(rèn)順序?qū)?shù)整個(gè)解析出來(lái)即可。但目前存在客戶要求樹(shù)也要按順序列出來(lái),也就是按order指定的順序排列,那么一次將樹(shù)拿出來(lái)解析就不可以了。
因此我采用先將根結(jié)點(diǎn)讀數(shù)據(jù)庫(kù)拿出來(lái),在生成根結(jié)點(diǎn)界面的時(shí)候程序回調(diào)再去查詢所有子節(jié)點(diǎn),并從數(shù)據(jù)庫(kù)返回結(jié)果生成整棵樹(shù)。
這樣本來(lái)一次調(diào)用卻變成了N次調(diào)用,往返于服務(wù)器之間,登陸幾個(gè)用戶打開(kāi)幾次頁(yè)面我的程序基本就慢的要死。
第一步介紹:我只介紹注意事項(xiàng)即可,其他的請(qǐng)搜索網(wǎng)上吧,而且都有很好的文章。安裝好nodejs使用npm裝載mysql模塊是報(bào)錯(cuò)的,因?yàn)闆](méi)裝git,使用git后才能安裝,輸入以下命令
npm install felixge/node-mysql
完成安裝mysql;
完成之后試寫mysql功能簡(jiǎn)單調(diào)用一下:
varmysql = 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);});
調(diào)用完成后看一下你是否能讀出結(jié)果,測(cè)試成功則nodejs和mysql模塊都裝好了。
第二步:修改java原來(lái)的tomcat,因?yàn)樵谝粋€(gè)頁(yè)面下以前用jquery的ajax調(diào)用spring對(duì)應(yīng)的controller,所以現(xiàn)在需要改成調(diào)用nodejs本地下的一個(gè)端口。我設(shè)置為127.0.0.1:1337下來(lái)訪問(wèn)我定義的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; });
然后就不出數(shù)據(jù)了,按下IE的F12,看到提示CORS錯(cuò)誤!nodejs寫的http模塊使用http:// 127.0.0.1:1337/employee_getUnDepartment是直接可以返回json串的,怎么到這里就不行了呢?!原來(lái)還需要改造一下java的web.xml配置,和加入兩個(gè)jar包才行。
網(wǎng)上下載:cors-filter-2.4.jar和java-property-utils-1.9.1.jar;放入項(xiàng)目工程里的libs目錄下,并引用這兩個(gè)包。并將如下代碼加到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,也就是說(shuō)我允許跨域到本機(jī)127.0.0.1位置。設(shè)置完了后再次調(diào)用,怎么回事,nodejs控制臺(tái)已經(jīng)返回了查詢結(jié)果,但IE報(bào)一個(gè)ajax錯(cuò)誤,查了之后發(fā)現(xiàn)如果是跨域訪問(wèn),則需要返回的內(nèi)容加上文件頭。于是在返回結(jié)果的模塊里加了頭如下:
res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先寫header 否則返回?zé)o效在跨域訪問(wèn)時(shí)}) ;
加上這句,返回的串就可以顯示到原來(lái)的界面上。速度嘛,當(dāng)然比以前塊幾百毫秒,但調(diào)試變簡(jiǎn)單了,Ctrl+C終止程序,按上鍵顯示上句命令,回車就完成了再次啟動(dòng)nodejs程序。
而且不受以前tomcat的影響,只要程序是nodejs里的,直接關(guān)閉nodejs再啟動(dòng)調(diào)試,使用者基本感覺(jué)不到你在一步步調(diào)試程序,他們其他的java應(yīng)用里的程序還正常執(zhí)行。
第三步修改ajax調(diào)用為nodejs遠(yuǎn)程:
$.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); }
});
這里有一個(gè)坑,就是type:get;如果你不注意原來(lái)用的是post的話,那么在nodejs處理比較復(fù)雜,因?yàn)?/span>post是流發(fā)送,也就是有個(gè)開(kāi)始投送,到接收完畢的過(guò)程,在nodejs里需要處理開(kāi)始,和回調(diào)函數(shù),這樣整個(gè)改造過(guò)程就比較麻煩了。因?yàn)闆](méi)有牽扯到需要post的表單,所以直接用get,否則參數(shù)會(huì)接收不到。當(dāng)然如果你用了express的話當(dāng)然可以用里面包含的接收post包裝好的方法。
第四步提供調(diào)用subtree的nodejs方法:
//調(diào)用子樹(shù)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 否則返回?zé)o效在跨域訪問(wèn)時(shí) }) ; res.end(subtree); }); }
這里有兩個(gè)坑,不小心你就掉里面了,第一個(gè)坑就是返回json串的方法,在nodejs里把結(jié)果集合改成json是用JSON.stringifv();方法格式化結(jié)果集。第二個(gè)坑就是Header必須寫在前面,否則跨域不接受數(shù)據(jù)。我寫的*是允許所有操作(GET UPDATE DELETE POST等)跨域提供數(shù)據(jù)。
第五步,修改JAVA程序適合調(diào)用返回nodejs程序:
其實(shí)這步根本不需要做,為什么還需要這步,是因?yàn)椋郧?/span>java調(diào)用dao返回結(jié)果結(jié)集的時(shí)候字段名稱有大寫有小寫,有混合寫的。但用nodejs調(diào)用后直接都是數(shù)據(jù)庫(kù)里怎么寫的字段名返回就是怎么寫的。所以departMent有可能變成department,因此要詳細(xì)核對(duì)一下,這個(gè)坑我已經(jīng)掉進(jìn)去過(guò)了。
這就是一個(gè)簡(jiǎn)單的混合程序完成了。但只是比java的快了一點(diǎn)點(diǎn),那么怎么優(yōu)化呢?下面介紹一下優(yōu)化。
優(yōu)化思路:
減少數(shù)據(jù)庫(kù)調(diào)用à減少ajax調(diào)用
這個(gè)大方向走,首先是否使用redis,想了半天,還是算了,只是為了優(yōu)化一棵樹(shù),何必動(dòng)用神器。自己搞個(gè)HashTabls算了。首先采用變量來(lái)優(yōu)化基礎(chǔ)查詢,如下:
res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先寫header 否則返回?zé)o效在跨域訪問(wèn)時(shí)}) ;//判斷主樹(shù)是否需要緩寸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('讀數(shù)據(jù)庫(kù)! '); memmaintree = JSON.stringify(rows); res.end(memmaintree); //數(shù)組和json之間的數(shù)據(jù)轉(zhuǎn)換 });} else { console.log('直接返回!'); res.end(memmaintree);}
可以看出采用了nodejs全局變量condmaintree,因?yàn)樗腥酥挥袡?quán)限不同的才會(huì)需要重新加載樹(shù),所以可以這樣做,改完之后只有第一次讀取需要查數(shù)據(jù)庫(kù),否則直接http返回存在condmaintree里的json串。子樹(shù)也是這樣優(yōu)化的,但字樹(shù)的分支讀取次數(shù)很多,需要很多全局變量,這不切合實(shí)際。怎么辦,引用自己編寫的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();}
調(diào)用過(guò)程如下:
var MhashTable = require('./HashTable.js');
//調(diào)用子樹(shù)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則訪問(wèn)數(shù)據(jù)庫(kù) 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); //將結(jié)果存入hashtable console.log('哈希沒(méi)找到!: ', subtree); res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先寫header 否則返回?zé)o效在跨域訪問(wèn)時(shí) }) ; res.end(subtree); }); } else { res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先寫header 否則返回?zé)o效在跨域訪問(wèn)時(shí) }) ; console.log('哈希找到!: ', subtree); res.end(subtree); }}
這樣做以后,首次60次的ajax調(diào)用確實(shí)訪問(wèn)了數(shù)據(jù)庫(kù),但第二次的刷新樹(shù)的時(shí)候就不會(huì)調(diào)用數(shù)據(jù)庫(kù)了。但這也不是最終的方法,我還是決定要去掉遠(yuǎn)程調(diào)用,那么在index.jsp框架頁(yè)面里引入HashTable.js,但這個(gè)版本和nodejs用的稍微有點(diǎn)不同。代碼如下:
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加入下列代碼:
<scripttype="text/javascript">
//樹(shù)緩存
var subtreeHashTabls = new HashTable();
</script>
在具體調(diào)用的方法里加入hashtable查詢的過(guò)程,如下:
function getsubTree(id) {
var result = subtreeHashTabls.getValue(id);
var str = "";
if(result ==null) { //alert("遠(yuǎn)程取!");
$.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);
…
…}
經(jīng)過(guò)這樣的改造后,只需要讀取一次樹(shù),其他時(shí)候讀取樹(shù)完全由內(nèi)存里的HashTable讀取,根本都不需要訪問(wèn)ajax跟服務(wù)器發(fā)生交互。
改造完畢后,我的頁(yè)面首次加載比原來(lái)快1秒,再次加載快3秒,當(dāng)然并發(fā)量我并沒(méi)有測(cè)試,應(yīng)該部署后會(huì)比原來(lái)強(qiáng)大許多,這就是nodejs優(yōu)勢(shì),當(dāng)然HashTable也盡了很大的力。
聯(lián)系客服