「風語者客服+」是針對中小型企業推出的客服SaaS,節約了企業自建客服系統所需的巨大成本。為了給企業提供穩定可靠且優質的服務,我們在整體架構上費盡心思。雖然不盡完美,希望藉此拋磚引玉,互相切磋。
前言
」Look deep into nature,and then you will understand everything better.「 -- Albert Einstein
我國傳統文化上,要做成一件事,講究三個方面:明道,優術,取勢。在軟體架構設計方面而言,也是類似的道理:遵循自然規律以明確大的方向,使用優秀的實操戰術,再根據實際情況落地。
這是個快餐年代,幾乎所有人都只做一件事 -「取勢」。幾乎沒有多少人會去理解一個Servlet的工作原理,去理解一次HTTP請求的完整流程,因為有超多框架幫你屏蔽了這裡的細節。詢問一個人會什麼技術,回答也往往是我會Hibernate、Spring、Ibatis、會PullToRefresh組件、會使用SDWebimage。不過這些框架(Framework)其實並不是軟體架構。軟體架構是一所有生命力的房子,而這些框架只是大一點的板磚。
因為筆者水平有限,這裡只提一些普遍準則,也就是」正確的廢話「,以饗視聽。不會深入到實操戰術上,比如怎麼用Spring實施MVC架構,怎麼使用Maven管理依賴,Redis的常用操作,怎麼搭建一個負載均衡的集群,如何使用阿里巴巴的Dubbo框架進行服務化等等。如果大家有興趣,可以自行搜索,有很多優秀的文章可供參考。
不幸的「程序猿」和「程序媛」各有各的痛苦,幸福的程式設計師都是相似的。其實說幸福有點言過其實,下面就說說怎麼讓他們不那麼痛苦。
一. 很好的模塊化支持「At the bottom of every person's dependency, thereis always pain, Discovering the pain and healing it is an essential step inending dependency.」 --Chris Prentiss
他們都在一個相對穩定的軟體架構裡編碼,自己的代碼不會依賴很多模塊,不會因為自己微小的改動造成全局的失敗。正如"1984"中的老大哥說的,Ignorance isstrength(」對外界的「無知就是一種力量). 任何一個模塊都不能有太強的存在感。
曾經在一個大型網際網路公司裡面,任何人只要用到一個核心模塊的功能,就必須依賴一個部署在某遠程伺服器的庫,而且還有IP限制,只能把代碼部署到指定網段才能運行起來。導致基本上沒法在本地進行單元測試或者簡單調試。這個核心庫的存在感太強,就成了開發的瓶頸,嚴重的降低了生產力和碼農的幸福程度。
在「風語者客服+」的架構中,每個碼農都可以很方便的在本地把服務啟動起來,一分鐘up and running,隨便做一些改動就可以立竿見影的看到效果。這裡要歸功於幾個東西:
1. Git代碼管理
在團隊作戰中,每個程式設計師可以取下來完整的最新代碼庫,也可以在本地分支上盡情揮毫潑墨,而不擔心影響別人的工作。也可以把本地修改先stash起來,review一下別人的代碼,再unstash恢復回來。要想提高團隊效率,代碼倉庫管理建議儘快遷移到Git上。
2. Maven、Gradle、Cocopods等依賴管理
Maven是一個管理依賴(Dependency)的工具,現在在Java社區應該是比較普及的,無法想像現在還有團隊直接拷貝jar包來管理依賴。雖然早期沒有Maven的時候,都是拷貝jar包這麼過來的,碰到的問題也是顯而易見的,依賴的jar包作者改了某個bug,沒能及時傳導到調用方。多個調用方使用不一致的jar包,導致各種奇異bug。對應的在安卓社區,使用gradle的比較多,iOS的Objective-C開發中,多採用CocoaPods。
二. 高內聚,低耦合He should focus on his knitting, nottrying to do everything. Do one thing well.-- Steve Jobs
"Do one thing well"其實不算是老喬的專利,UNIX哲學和Google哲學都提倡這一點。這句話本身不完全對,比如對於一個商人,如果只會Do onething well,那他無法在市場中存活,但是在工程師中卻是萬般推崇的哲學。
我們可以期望一個人具備一百種技能,然而對一個工具只期望它把一個需求解決好解決徹底,對於實現工具的一個類,一個方法,更是如此。但是,實際經驗中,我們經常看到一個5000行以上代碼的類,活像一個巨人版的瑞士軍刀,什麼都能做,但是什麼都做不好。這就是」Separation of Duty"沒有做好的典範。
在風語者」客服+「對外提供的SDK和API中,我們也提倡同樣的思想,力爭把App使用」客服+「SDK的門檻降到最低,每個API都能自言其一,而且API直接沒有時序上的依賴關係。內部各個模塊的開發,也秉承同樣的責任分割原則。責任分割原則的落實,沒有什麼好的框架或者工具來支持。只能通過老鳥經常去做Code Review,找出存在的問題,提出重構方案,並督促菜鳥改進。
個人一般採用的重構思路,僅作為參考,照搬後被老闆批評乃至造成工傷概不負責:
把一個大的工具類,根據主題不同,拆分成若干個互不幹擾的高內聚工具類;舉個例子,一個萬能的NetworkUtils可能可以拆成HttpUtils, FTPUtils,TelnetUtils等;對於一個被頻繁調用的類,仔細觀察調用情況,如果有一些方法的被調用頻率遠遠低於其他方法,那麼需要考慮這個方法是不是應該放在這個類中;存在A,B兩個類之間的相互依賴,或者更多類的混亂依賴,那麼就更要抽絲剝繭,通過合理安排類的功能來去除環形依賴;嘗試一句話說清楚一個類的功能,不要使用「和」,「以及」,「或者」等連接詞;如果出現了這些連接詞,就需要引起重視;三. 用進化擁抱變化「It is not the strongest or the most intelligentwho will survive but those who can best manage change.」 ― Leon C. Megginson
前段時間,朋友圈瘋傳一篇文章 -——「架構腐化之謎」,大家都深表同感,紛紛表示對自己架構的未來的擔憂。然而,說句不合時宜的話, 90%的擔憂是杞人憂天,因為以現在產品更新換代的速度,90%的項目面市即意味著死亡,沒等到架構腐朽,產品已經入土了。
剩下10%裡面,也許有9%會一直堅持活下去,但是不會蓬勃發展,也就是說,只要保證不出現內存洩露之類的問題,代碼就會一直在幾臺小伺服器上運行下去,哪怕後面沒有人維護也沒關係。只有1%的產品,會日新月異的更新迭代,最終成長為巨無霸,或者巨無霸的生態下的一個環節。這個言論看似悲觀,卻是對現實最好的妥協。
謬用一下泰戈爾的名言:「不是槌的打擊,而是水的載歌載舞,使鵝卵石臻於完美」, 不是閉門造車的架構,而是不斷擁抱變化的需求,才使得架構臻於完美。
假如在早期就糾結於架構的完美性,而延遲產品的交付,是非常得不償失的。只有生存下來,才有機會。再根據市場變化,不斷優化架構,從而延長軟體的生命周期。那麼,假如撞大運,真的成了這1%,怎樣做才能算是擁抱變化?
首先,請參考本文第一點和第二點。如果這兩點基本功沒有練好,那麼談架構的進化就和還沒有通關十八羅漢的新手就想練成九陰真經是一個道理。
在設計之初,初步考慮系統的Scalability(可伸縮性)
下面在第四點會詳細闡述。
內部的各個模塊儘量做到可插拔
一方面是接口和實現的分離,可以隨著需求的變化更換實現;另一方面,儘量把功能服務化,成為微服務,並且可以監控到服務的互相調用情況,當某個服務老化,可以逐步廢棄或使用新的服務取代之。這一點上,阿里巴巴的Dubbo框架是一個不錯的選擇。
儘量採用優秀的框架,站在巨人的肩膀上
例如在Web層面,我們使用Twitter的Bootstrap前端框架來實現響應式Web編程,提高生產效率的同時減少了為解決各種設備適配問題的投入。當然,這就需要設計師配合,按照Bootstrap規範來設計頁面,減少一些個性化設計。
最後,考慮系統的Resilience(彈性,也叫耐受性)
俗一點說,就是變成一隻打不死的小強,代碼中儘量提前預判可能遇到的各種情形。經常看到代碼裡面有一堆的if(){}判斷語句,我就問作者,「你考慮過else{}嗎?」一般回答都是,「這絕對只有if,不會有else的」,可如果真的遇到else怎麼辦?千年蟲問題就是這麼誕生的。可能很多新同學還不知道什麼是千年蟲問題,簡單地說,就是當年的碼農,為了省一點內存空間,只用了2位數來表達年份,比如int year = 98; 表達1998年。我猜碼農當時的心態也是,「就我這代碼,還能活到2000年,搞笑吧?」
程式設計師們平時可以多擴大自己的腦洞,想想有哪些else情況自己沒有處理,而且可以輕易處理的。比如伺服器掛了,那麼App端是不是也要跟著crash,還是給出友好一點的提示,或者更友好一點,使用本地緩存。
四. 設計可擴展,但不要過度設計it's better to have infinite scalability and not need it, than to needinfinite scalability and not have it--@littleidea 網友
無限的擴展能力是一種奢望,但是起碼不能讓擴展能力成為0。試想一下,你辛辛苦苦為老闆開發了一個網站,過了一個月,網站超負荷了,老闆說,「小A啊,之前2臺伺服器花了我5萬塊,預計流量馬上要翻倍了,再給你5萬塊,幫我扛過去啊。」結果你發現,問題不是線性增加伺服器就能解決的,原來的程序沒有做分層(Web,Business Logic, Data Access等),導致加伺服器也只能把所有層的代碼全搬到新的伺服器,雖然只是Business Logic的計算有壓力,卻要浪費老闆很多伺服器。更糟糕的是,因為程序裡面用到了文件系統和作業系統命令,不好做負載均衡。
這裡有一些準則供參考:
代碼分層是必須的,層次明朗以後,當哪個層次的負載較重,想辦法對該層次進行優化或者擴容即可;保持核心服務是無狀態的,所謂無狀態就是沒有和請求相關的數據依賴;儘可能的選用已被驗證的廣泛採用的成熟基礎架構;充分利用Zookeeper等集群管理工具,來對服務進行管理;風語者「客服+」中,把業務相關的代碼內部組裝為風語者ServiceBox,使用阿里巴巴的Dubbo服務進行註冊管理。當負載增加時,可以迅速在運維層面增加服務節點,以提供更高的服務能力,從而保證客戶的優質體驗。
作者簡介:
黃耀華
具有11年的計算機軟體研發及大型網際網路產品研發經驗,曾在IBM,百度,人人網等業內頂級企業任職,特別對企業級軟體,大規模數據處理,搜尋引擎,移動App開發等領域的原理和實現有著全面且深入的認識。(編輯/魏偉)
更多SaaS技術文章,請掃描下方二維碼關注我們