做投票系統的經驗

      寫這篇經驗之前,先總結下做這個系統出現的一些問題:

      忽現Unicode編碼、票數清零、服務器當機、內存溢出、多線程處理文件造成文件錯誤更新、沒有控制好用戶狂刷、最囧的是,IO流沒有close掉......


      在公司剛換了個項目組,接手了一個投票系統的任務,參於被投票的對象是一批經過多次評選的經紀人(一共60人),誰得票最高即可獲得豐厚獎品的。

      投票的規則以每個IP每天最多可以投50票,投票者抓住這個規則之後,一直瘋狂的刷點...

      我們開始把投票結果記錄到.properties裏面,也許因爲刷點的原因,出現了莫名其妙的\u0000空格Unicode錯誤編碼,造成對配置文件解析錯誤。嚴重的情況是,有某些參賽者的票數突然清零,狂飆冷汗,有不少的參賽者打電話來投訴(糟了,要被開除了...)看了下配置文件,還是一些無緣無故出現Unicode編碼。

      原來做了幾個投票頁,1個IP只能投1票,所以很少同步操作的情況。但現在不同了,於是放棄.properties,改成xml,.properties最大的問題是更新後會引起順序混亂,不好管理,而xml解決了這個問題,對於編碼問題也容易處理。最重要數據被清零的問題也解決了。

<vote>
  <members>
    <member id="1" count="12839"></member>
    <member id="2" count="4550"></member>
    <member id="3" count="245"></member>
    <member id="4" count="20"></member>
    <member id="5" count="39"></member>

    ....
  </members>
  <dates>
    <date id="20090611">
        <ip addr="192.168.16.38" count="6"></ip>

        ....
    </date>

    ....
  </dates>
</vote>

      P.S.:這次新學了JDOM,用它來解析XML真的很方便,有興趣可以學習一下。  http://www.ibm.com/developerworks/cn/java/j-jdom/index.html

      爲了收集參賽者被投票的信息,在投票中加入了一個記錄投票數據的xml,結構如下:

<vote>
  <members>
    <member id="1" name="張三" count="3">
      <ip addr="192.168.16.38" startTime="09:37:35" endTime="09:37:39" count="3"></ip>

      ....
    </member>

    ....
  </members>
  <ips>
    <ip addr="192.168.16.38" startTime="09:37:35" endTime="09:37:39" count="3">
      <info voteId="1" voteTime="09:37:35"></info>

      <info voteId="1" voteTime="09:37:37"></info>
      <info voteId="1" voteTime="09:37:39"></info>

      ....

    </ip>

    ....
  </ips>
</vote>


    這個投票詳細記錄的方法我們選擇在晚上更新,但是更新之前服務器突然掛掉了,不知道什麼原因,這個新的生成xml的方法更新以後,服務器掛掉的頻率更高了,後來查看內存,一直狂漲,這下子就囧了。。。肯定是流忘記關閉了。然後回憶xml更新的一句代碼,這是JDOM封裝的保存更新xml的方法。代碼如下:

public void updateXml(Document doc, String xmlUrl) {
   Format format = Format.getPrettyFormat();
   format.setEncoding("UTF-8");
   format.setExpandEmptyElements(true);
   XMLOutputter printDoc = new XMLOutputter();
   printDoc.setFormat(format);
   try {
    
printDoc.output(doc, new FileOutputStream(xmlUrl));
   } catch (FileNotFoundException e) {
    e.printStackTrace();
   } catch (IOException e) {
    e.printStackTrace();
   }
 }


裏面有個 new FileOutputStream(xmlUrl), 問題就出在這裏了,壓力測試時沒注意好內存的變化,於是重新初始化一個FileOutputStream對象,結束時關掉。

修改後,代碼如下:

 public void updateXml(Document doc, String xmlUrl) {
   FileOutputStream out = null;
   Format format = Format.getPrettyFormat();
   format.setEncoding("UTF-8");
   format.setExpandEmptyElements(true);
   XMLOutputter printDoc = new XMLOutputter();
   printDoc.setFormat(format);
   try {
    
out = new FileOutputStream(xmlUrl);
    printDoc.output(doc, out);

   } catch (FileNotFoundException e) {
    e.printStackTrace();
   } catch (IOException e) {
    e.printStackTrace();
   }
finally {
    try {
     out.flush();
     out.close();
    } catch (IOException e) {
     e.printStackTrace();
    }

 }

       哎,真失敗吖。。搞了兩年程序出了這樣一個失誤。。悲哀吖。。問題還是沒有完全解決,投票的人還是一直狂點狂刷,於是在頁面加了這樣段很簡單的腳本,限制了投票人的瘋狂操作。js代碼如下:

var num = 0;
var oId = "";
function toVote(id){
    oId = id;
    
if(num == 5) {
        alert("很抱歉,您投票的頻率太快,請稍後再試。");
        return;
   }
   if(num!=1){
       num = 1;
       createxmlHttpuest("update.jsp?id="+id+"&time"+new Date());
      
num = 5;
       fangshua();
   } else {
       alert("正在投票中,請稍後");
   }
}

 
function fangshua() {
    window.setTimeout(function(){num = 0;} ,5000);
}

    紅色部分就是新加進去的防刷腳本,雖然簡單,但是很實用。

    但是爲了避免刷票而控制程序問題,還是治標不治本,於是在調用更新票數的方法上加上同步鎖,避免多線程引起的錯誤。代碼如下:

Object obj = new Object();
....
synchronized (obj) {

    String cTime = xml.CurrentlyTimes("yyyyMMdd");
    try {
        content = xml.getVoteUpdate(id, cTime, ip);             
    } catch (Exception e) {
        content = null;
    }
    if (content.equals("ok")) {
        xml.statVote(ip, id);
    }

}

 

   對更新的方法經過壓力測試,50個同步線程執行,統計的xml與詳細記錄的xml文件更新也不會出現多線程寫入的錯誤。但是還有一個細節,處理一個文件,建議單獨使用一個File對象,避免出現引用錯誤。

 

File file = new File(this.statVoteXmlURL);    
Document newDoc = null;
SAXBuilder sb = new SAXBuilder();
sb.setIgnoringElementContentWhitespace(true);     
if (file.exists()) {
    sb.setIgnoringElementContentWhitespace(true);
    try {
        newDoc = sb.build(file);
    } catch(Exception e) {
        e.printStackTrace();
        return ;
    }
....

 

   總結一下之前做投票系統需要注意的問題:

   1、一定要做防刷分腳本.

   2、頻繁被調用的方法要做同步,做多線程壓力測試.

   3、處理文本的對象要單獨初始化,避免引用衝突.

   4、使用xml記錄數據比使用文本記錄要好.

   5、流對象不能單獨在參數中實例化,不然會很囧...它不會自己關掉的,必須手動關掉.

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章