系統設計之初應該考慮到的問題

一個起碼合格的系統,在設計之初就應該考慮到各個方面,不求完備,起碼也要深思熟慮。當然,你也可以在接到任務的下一刻,就敲起了鍵盤,然後在不斷地迭代開發中發現問題,解決問題。但是,一些問題,真的是在設計之初就可以預見的。這裏只是以自己的經歷,提出自己的拙見,並且不斷提醒自己,這個問題下一次一定要考慮到。

    1.日誌

日誌的存在絕對不是某些人嘴中的所謂軟件工程的條條框框的面子工程,而是一個系統中最重要的、不可或缺的部分。日誌的存在除了記錄系統運行時的一些數據,更多也起到一種證據和數據追溯的作用。當系統複雜到一定程度後,往往會出現一些莫名其妙的錯誤,但是又十分難於復現或在短時間內通過閱讀代碼找到原因,這個時候,日誌就起到了重要的作用。通過日誌,記錄下每一步關鍵操作,業務流程是在哪 裏中斷的,這對於快速排查問題是十分重要的。另外,在一些與外部交互的API中,日誌也記錄了Request和Response,這爲定位原因提供 了依據,不至於在出現問題時相互扯皮。短信沒發送成功,誰的錯?用戶還是程序員,或者接口提供商,或者某團體又在開會了?郵件沒收到,又是誰的錯?當然,記錄日誌並不是爲了真的去找誰的麻煩,而是爲了定位問題,解決問題。

記錄日誌時,一定要做好歸類,分門別類記錄不同類型的日誌,當然,記錄的載體隨意,可以是文本文件,也可以說數據庫。次重要的日誌記文本文件,重要的記數據庫。

一般我是這麼對要記錄在文本的日誌分類的:

/**
 * 日誌類型
 * @desc:
 * @author [email protected]
 * @date:2013-09-12
 */
public enum LogType {
    PAY("PAY"),//支付模塊
    BORROW("BORROW"),//資金模塊
    USER("USER"),//用戶模塊
    API("API"),//API模塊
    ANY("ANY");//其它任意類型
     
     
    private String type;
    private LogType(String type){
        this.type=type;
    }
    public String getType() {
        return type;
    }
     
}

對於系統中的一些模塊,我採用的是request-response模式來設計。在父類中記錄日誌,然後子類繼承父類,調用父類的方法提交參數並獲得響應。這樣,所有子模塊的參數都會被記錄下來,而在子模塊中,這個日誌記錄的過程就像不存在一樣。這種方式記錄的都是重要的日誌,和業務結合比較緊密。可以簡化爲下面的代碼:

public class Yreq {
    public String service;
    public int orderNo;
    public final String KEY="ADX8-KOL4-88CD";
    protected HashMap<String, Object> map=new HashMap<>();
     
    public Yreq(){
        orderNo=(int) System.currentTimeMillis();//生成唯一遞增請求序列號,此處僅作模擬
        map=data();
    }
     
     
    public HashMap<String, Object> data(){
        map.put("orderNo", orderNo);
        return map;
    }
     
    public void submit(){
        map.put("key", KEY);
        System.out.println("do sth here...");
        System.out.println("log here");
    }
}
 
--
public class MobileApply extends Yreq{
    private String serviceName="MobileApply";
    private String mobile;
    private int userId;
     
    public MobileApply(){
        super();
    }
    /**
     * 構造request參數
     */
    public void Param(){
        map.put("mobile", mobile);
        map.put("userId", userId);
        map.put("servieName",serviceName);
    }
}
 
MobileApply mobileApply=new MobileApply();
        mobileApply.setUserId(5);
        mobileApply.setMobile("15934881123");
        mobileApply.Param();
        mobileApply.submit();


2.測試
測試已經是一個爛了又爛的話題,單元測試、性能壓測都屬於測試的範圍。我覺得比較重要,也需要特意強調的就是單元測試,尤其是單元測試的意義和存在的價值。有這麼一個場景,某客戶反映我們的系統還款手續費有問題,而我只是一個新來的程序員,並且我們的客戶很多 。這個時候一般的做法是這樣的:
(1)在客戶網站註冊一個賬號路人甲;
(2)發佈一筆借款,發佈借款時才發現需要實名認證;
(3)提交實名認證;
(4)後臺審批實名認證;
(5)繼續發佈借款,發現還需要申請信用額度;
(6)申請信用額度;
(7)後臺審批信用額度申請
(8)終於好了,現在可以發佈借款了。
(9)後臺審批借款;
(10)註冊賬號路人乙;
(11)前臺充值金額;
(12)後臺審批充值請求;
(13)路人乙借款給路人甲
(14)後臺管理員審批這筆借款;
(15)路人甲還款給路人乙;
(16)路人乙查看收到的還款;
    一個小時後,老闆揹着手踱過來,站我背後問道“小X呀,客戶提到的BUG改好了沒呀?”,我只能一邊諾諾地回答“我還在測試借款那一步。。額。。貌似有點小問題無法借款”,看着老闆搖着腦袋遠去的背影,只能暗罵“催!催!催!腦殼有包啊,啷個有額個快,bug我都還沒復現呢”。
    問題就出在當流程複雜後,限制條件越來越多,每步流程都有很多先決條件,並且都是繁瑣而耗時的手工操作。由於沒有單元測試,出現問題時,我必須一步一步來,即使我在之前已經建好了測試賬號,也只能省略有限的幾步,還是不得不按部就班,花費大量的時間去復現BUG 。有了單元測試後呢?簡單多了,我只要按照模塊像搭積木一樣的拼裝各個環節,最後來個斷言,直接告訴我過了還是沒過。過了,我就可以
閒下來看看小電影,沒通過測試?那也不怕,分解開來,一步一步來。
    又比如,我開發了一個新模塊,這個模塊需要生成十多個新用戶進行多輪測試,怎麼辦?沒有單元測試之前,只能傻乎乎地手工註冊,1個 ,2個,3個...,後來發現有點傻啊,預先準備了一堆SQL語句,做點修改,放到數據庫裏執行一下,獲取到了ID主鍵,然後又去資金錶裏,以這個主鍵爲userId,再插入一條數據。這個節奏貌似有點不對勁啊,傻不傻呀,有單元測試爲啥不用?寫個循環不就好了。

3.數據指標與監控

    服務器的運行情況如何?內存佔用和CPU消耗如何?優化的效果如何?這些都需要數據來說話。話說有一天項目經理安排新來的程序員甲優化 一臺服務器,過了半個小時,甲興沖沖地地跑去對經理說“我優化好了!”,

經理問“你都改了啥?”
甲:“啪啪啪啪。。。”
經理:“性能提升了多少?CPU佔用降低了麼?相比之前的QPS增加到多少了?”,
甲:“啊?啥?”,
經理:“那我們現在的用戶數是多少?”,
甲:“什麼??”
經理:“出去!”。

    好了,不言而喻,沒有數據的優化都是耍流氓。數據是評判優化的標準,也是優化的方向。轉眼三天過去了,項目經理急匆匆地跑來問甲:"XXX的網站突然打不開了,咋搞的!?",甲愣住了,趕緊回憶最近做了啥改動,想想最近也 沒幹啥啊,好在甲也是受過專業訓練的,一點也不慌張,立馬想到第二步,看日誌。打開Apache日誌一看,有個MySQL的錯誤,說是什麼什 麼不能寫入數據。立馬明白了,磁盤空間滿了!都怪自己,平時不注意監控數據。磁盤用了多少也沒注意,日誌生成了一堆又一堆也不管,三個月前的備份都還在。又回到了第一個問題,日誌寫入是否可控?是否可能寫滿磁盤?是否有定期清理機制?

4.重發機制
回到第一個問題,短信發送不出去咋辦?難道就放着嗎?顯然不行,有時候發送不了可能是網絡的問題,也有可能是數據的問題,而這些問題都是可以解決的。比如我遇到的問題,一個短信發送前需要登陸。但是由於這個接口被多個客戶使用,某個客戶B手賤,代碼中調用了之前遺留的退出操作。而新的流程中並不需要這個退出,而且退出了就不能發短信了。而我們又改不了B的代碼,A很鬱悶,B還不知道。怎麼 辦?很簡單,在A的代碼中send函數加入login函數,一旦A發送失敗,根據返回碼判斷,如果是登錄狀態不對,就給他重新登錄,重發一次試試。

public function sendSMS($arr) {
        $ret = $this->client->sendSMS($arr['mobile'], $arr['msg']);
        //如果被註銷了,重新登錄,重發,只試一次
        if($ret=='-110'){
            $this->client->login(); 
            $retNew=$this->client->sendSMS($arr['mobile'], $arr['msg']);
        return $retNew;
        }
        return $ret;
    }

當然,這個重發也可以是一直重試直到成功,但是不要犯下和這裏類似的錯誤。

5.多和少的問題
    系統是否設計過度?還是估計不足?按照500QPS標準設計的網站,在第三個月後,隨着業務的擴大,還能滿足需求不?性能優化和擴展容易不?是否留有緩存接口?有的話,有沒有考慮緩存節點故障的可能性?有沒有考慮到雪崩的可能性?還是緩存只是個裝飾?問題的起因是這樣的,我曾經面對這樣的一個網站,他的設計是典型的MV架構,對,是MV,不是MVC。少了controll層。他的做法是直接在模板中使用list和module,並且帶入參數,從M層獲取數據,繞開了C層。這樣一來,系統變得極其簡單,一點不懂編程的人也能通過在V層裏大量用List和循環來顯示數據,根本不需要寫C層代碼,只需要懂HTML和簡單的模板語法。問題就在這,幾個月後,系統規模擴大了,服務器已經扛不住了,我需要給這個系統加入緩存,遇到問題了,由於沒有C層,我很難對緩存進行細粒度控制,V層顯然不合適而且也麻煩,M 層也是類似問題,因爲我無法保證和統一其它地方的V調用這個M也是這個緩存機制。結果就是M層變得很凌亂,系統體驗變得很呆滯。
    類似的問題還出現在很多地方,系統設計之初太短見,導致後期擴展困哪。好的系統設計,5年後還能輕鬆應對新需求,爛的設計,半年後就很吃力了。那設計過度呢?額,設計過度的系統,我還是願意設計過度一點也不短視。

    一個合格的系統,還有很多很多需要去考慮,這裏就先說這麼多吧。

轉自:白菜


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