|
早已對(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)邮志烷_始,準(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)在效率的差別。 這棵樹就是我們需要改造的原因。原來(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)闃涫谴嬖谝粡埍碇械?。其中只?/span>id,pried,name,leve,orderid等,所以關(guān)系都是鎖在同一張表里,意味著要把所有樹排列好一次拿出來(lái)是可以的。只要按默認(rèn)順序?qū)湔麄€(gè)解析出來(lái)即可。但目前存在客戶要求樹也要按順序列出來(lái),也就是按order指定的順序排列,那么一次將樹拿出來(lái)解析就不可以了。 因此我采用先將根結(jié)點(diǎn)讀數(shù)據(jù)庫(kù)拿出來(lái),在生成根結(jié)點(diǎn)界面的時(shí)候程序回調(diào)再去查詢所有子節(jié)點(diǎn),并從數(shù)據(jù)庫(kù)返回結(jié)果生成整棵樹。 這樣本來(lái)一次調(diào)用卻變成了N次調(diào)用,往返于服務(wù)器之間,登陸幾個(gè)用戶打開幾次頁(yè)面我的程序基本就慢的要死。 第一步介紹:我只介紹注意事項(xiàng)即可,其他的請(qǐng)搜索網(wǎng)上吧,而且都有很好的文章。安裝好nodejs使用npm裝載mysql模塊是報(bào)錯(cuò)的,因?yàn)闆]裝git,使用git后才能安裝,輸入以下命令 npm install felixge/node-mysql 完成安裝mysql; 完成之后試寫mysql功能簡(jiǎn)單調(diào)用一下: 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); }); 調(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)試,使用者基本感覺不到你在一步步調(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è)開始投送,到接收完畢的過(guò)程,在nodejs里需要處理開始,和回調(diào)函數(shù),這樣整個(gè)改造過(guò)程就比較麻煩了。因?yàn)闆]有牽扯到需要post的表單,所以直接用get,否則參數(shù)會(huì)接收不到。當(dāng)然如果你用了express 的話當(dāng)然可以用里面包含的接收post包裝好的方法。
第四步提供調(diào)用subtree的nodejs方法: //調(diào)用子樹 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)化一棵樹,何必動(dòng)用神器。自己搞個(gè)HashTabls算了。首先采用變量來(lái)優(yōu)化基礎(chǔ)查詢,如下: res.writeHeader(200,{ 'Access-Control-Allow-Origin' : '*' // 先寫header 否則返回?zé)o效在跨域訪問(wèn)時(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ù)據(jù)庫(kù),否則直接http返回存在condmaintree里的json串。子樹也是這樣優(yōu)化的,但字樹的分支讀取次數(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)用子樹 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('哈希沒找到!: ', 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í)候就不會(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"> //樹緩存 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í)候讀取樹完全由內(nèi)存里的HashTable讀取,根本都不需要訪問(wèn)ajax跟服務(wù)器發(fā)生交互。 改造完畢后,我的頁(yè)面首次加載比原來(lái)快1秒,再次加載快3秒,當(dāng)然并發(fā)量我并沒有測(cè)試,應(yīng)該部署后會(huì)比原來(lái)強(qiáng)大許多,這就是nodejs優(yōu)勢(shì),當(dāng)然HashTable也盡了很大的力。 |
|
|