早在2008年,我自學PHP後開發了COGS,併成功用於學校內部的OJ,ruvtex。也曾經對外開放過,可是因爲學校網絡不穩定,後來一直連不上了。我還把COGS推薦給了OOJ,只是直到如今都過於冷清。隨着COGS功能不斷完善,體系愈來愈龐大,Bug也很是多。限於當時水平,架構很是混亂,以致於到沒法繼續維護的地步,因而我遺憾的宣佈了COGS的死亡。隨後我又萌生了一個從新設計的念頭,只是高二時期忙於NOI和文化課的學習,這一計劃就一直被擱置到NOI2009結束。web
NOI2009結束之後,我終於有了時間,能夠進行我新的OJ的開發了。以前的COGS不敢開源,由於寫的太爛,開源的話確定會黑得萬劫不復。不過新的OJ不一樣,爲了提升對本身要求,我決定對其開源。算法
我在暑假時期閱讀了有關PHP高級開發、MVC架構、Javascript、CSS、Linux系統編程等大量書籍。受到經典的架構Zend Framework的啓發,起初決定用它開發,可是學習不久就發現Zend Framework過於龐大,學習難度太大,而且難以找到比較好的資料。在Zend Framework的官方網站上閱讀純英文的文檔實在太費力了,並且效果很差,始終沒有弄明白其本質。開源界有一個箴言,叫作「不要從新發明輪子」。什麼意思呢?就是說不少開發人員老是在作一些前人已經作過,並且作得很好的工做。好比libxml2已是一個很是優秀的C語言XML庫了,你再本身寫一個XML解析庫,就是在浪費時間,儘管也許你作的不錯,但也最多算的上是「從新發明了輪子」。起初我很是篤信這句「箴言」,拼死也要學Zend Framework,作了大量的無用功。但後來細細一想,咱們最初學算法、數據結構的時候,不是都是要本身實現每個細節嗎?儘管庫函數可能已經作得很好了,如qsort,平衡樹。而我如今也是MVC架構的初學者,在對其理解不是很深入的狀況下,直接學習某個庫、某個架構,是很是不明智的行爲。所以我決定要「發明輪子」——本身開發一個合適的架構,就叫——BYVoid Framework Library(BFL)。數據庫
剛好當時父親的公司準備建一個產品介紹性網站,爲節約成本就把開發的任務交給了我。我想這是一個可貴的練手的機會,決定用個人BFL開發。果真在開發的時候遇到了史無前例的問題,可是都一個個迎刃而解了,並且得到了很多經驗,其中包括Apache2 .htaccess的設置。兩個星期之後,公司的網站製做完成,個人"MVC架構輕量級內容管理系統"也發佈了。編程
2009年9月,我正式開始開發新的OJ。在開發先我想先起個名字——就像「沒有名字的船不會帶來好運」。起初準備叫vacuum,英語意思是「真空」,算是Beyond the Void的衍生。可是我不幸地發現這個名字已經在sourceforge上被佔用了,後來決定更名爲vakuum,即vacuum的德語拼寫方法。瀏覽器
Vakuum做爲一個OJ,我把它剖分爲了三個部分,vakuum-web,vakuum-judge和judger。vakuum-web是一個網站,用於和用戶交互,處理各類請求,它是一個PHP網站,應該可以跨平臺。vakuum-judge則是評測機終端,用於接收來自vakuum-web的評測任務,評測之後返回結果。而judger是評測的核心,其中包含編譯器compiler、執行器executor和檢查器checker三部分,分別用於編譯用戶程序、執行用戶程序和比對用戶輸出與測試數據。簡而言之,vakuum-web是一個用戶與核心的中介,而vakuum-judge則是judger的通訊接口。個人目標是網站和評測分離,並容許多評測機協同工做,所以vakuum-web和vakuum-judge之間少不了通訊。安全
參考不少成熟OJ的結構,發現幾乎都是把vakuum-judge模塊實現爲一個常駐進程daemon,不斷檢查數據庫是否有新的任務出現,對其評測,但後寫回數據庫。多數設計都沒有分離vakuum-web和judger,即限定了只能有一個評測機,少數能夠實現分離,可是其實是多寫了一個通訊程序。個人想法是vakuum-judge也用PHP實現,這樣就避免了本身寫一個socket通訊程序,並且不須要額外得到底層權限以常駐進程。這樣只須要接收來自vakuum-web的HTTP請求,對其處理,而後將結果寫回。但是這樣的一個同步傳輸方案有很大的問題,即vakuum-web發送請求之後須要長時間被阻塞在通訊上,等待vakuum-judge評測的結束。如此一來加大了傳輸的風險,二來加劇vakuum-web的服務器負載,三來還沒法讓用戶看到評測的進度。所以我想出了一個異步傳輸方案,即vakuum-web只發送請求,完畢後就斷開鏈接,以後等待vakuum-judge的回傳便可。然而PHP有一個特性,就是用戶瀏覽器斷開鏈接之後,PHP腳本也會中止執行,vakuum-web是在模擬用戶瀏覽器發送請求,斷開連接之後就至關於瀏覽器按下了「中止」鍵,vakuum-judge正在執行的腳本無論到了哪裏都會中止。查資料之後才知道PHP有這樣的一個函數,ignore_user_abort(),忽略用戶停止,就是用來對付這種狀況的。服務器
通訊方式設計很成功了,還有一個問題就是如何處理評測隊列。這是一個棘手的問題,要麼爲何有那麼多大型OJ常常卡在評測隊列上,出現Waiting長龍呢?幾乎全部的OJ都是寫了一個常駐進程的daemon處理隊列。我想爲了實現多評測機調度,這個daemon必須是如今vakuum-web端,可是這樣就違背了我當初vakuum-web可以「跨平臺」的設想,並且vakuum-web必須得到系統底層權限——不只維護不便,還有安全隱患。網絡
絞盡腦汁之後,我想出了「鏈式反應」的想法,其實也是受了通訊設計方式的啓發。這種方法不須要得到底層權限,不用額外寫一個daemon,可以跨平臺,不用爲安全性額外操心,還能夠充分利用先前寫好的代碼,到底是什麼方法呢?其實就是用一個PHP進程來充當隊列處理器,這個隊列處理器不須要常駐內存,僅僅在用時纔會出現。就是當用戶提交一個評測任務之後,若是有空閒評測機的話,當即對任務進行處理,而後當vakuum-judge返回評測完畢的信號時,vakuum-web端再對評測任務隊列進行檢查,看看有沒有新的任務出現,若是有的話,馬上執行便可。這種方法很簡潔,並且支持多評測機協同工做,由於每次結果返回時都要檢查隊列,就好像鏈式反應,或者多米諾骨牌同樣,只要處理了第一個,後面的就都會接着被處理。數據結構
這種隊列處理方法的惟一缺陷在於依賴評測機的返回信號,若是評測機那邊出了什麼問題,或者由於通訊緣由vakuum-web沒有正常接收到信號,隊列就會被卡住。所以首先須要保證的是vakuum-judge須要絕對的安全和穩定,除非無可抗拒理由(如網線被拔),不會由於任何緣由而不正常返回信號。此外還要增長人工干預手段(如強制繼續處理隊列),避免真的不可抗拒緣由的到來。架構
——————————————————————————————————
先到此爲止,歡迎繼續關注Vakuum開發筆記,下次將要寫的是評測機核心(judger)的設計。目前的開發進度停滯在後臺管理各類繁雜的細枝末節的處理上