專案開發中容易被忽視的部分

weixin_34391854發表於2008-11-22

注:文中提到的案例不是真是實施,只是為了便於描述虛構的;文中提到的公司也非真實發生事件,也只是為了便於描述和理解而使用,並無任何佔取利益的意圖,請勿對號入座。

通常當我們說要開發一個專案的時候,作為程式設計人員,比較容易想到的地方是,我要有一個比較好的框架,好的程式碼質量。這是從維護的角度講的。而稍為正式一點的公司,可能會有一個UE設計部門,專門負責使用者體驗方面的職責。這是從客戶使用的角度講的。相對來說,這方面的工作已經是比較容易遭受忽略的部分了——試問一下,你所在的企業是否有專人專門負責這一職責?據我的瞭解,很多公司在這方面是重視程度不足的。當然了,很多公司會有人去做這樣的工作,但不一定是專職人員,可能是管產品策劃的產品經理,也可能是技術負責人。不過怎麼講,如果說我們需要做這部分工作,大部人人還是較容易得到理解的,畢竟產品好賣與否,市場佔有率多大,附加值有多高,跟著客戶的感覺是有很大的關聯的。

還有一些工作,則非常容易被忽視:部署和維護。


一說到部署和維護,感覺好像就是開發完之後,與程式設計人員無關的一些工作了。比如說部署,那就是找個什麼人,往伺服器裡面一裝就好了。再比如說維護,可能就是找個人當客服,把使用者的問題收集收集,能解答的解答之,不能解答的找技術人員處理之。看起來似乎真的很簡單的事情,其實這裡面學問還不少。如果系統在設計的階段不考慮這部分的內容,那麼總有一天就會對你造成很大的壓力。這類問題比程式架構問題的潛伏週期更長,暴露出來後,其直觀嚴重性較小。這讓我想起來一個典故:“君有疾在腠理,不治將恐深。” 桓侯又不應的原因是因為看起來沒什麼大問題,如果說哪個手指頭動不了了,又或者肚子痛得無法吃飯了,恐怕不會不應的。這些問題的也有點類似,只是更為隱蔽——再怎麼積累,看起來也死不了人。因此,我們需要再系統設計的階段,至少在重構的階段,就要考慮到這些問題。我要和大家交流的,是原則層次的部分,不涉及到編碼,甚至可能不會談到具體的設計。這些原則都比較簡單,應該是比較好應用的。


今天,我只說部署。部署一個相對較大的系統,其實不是一件很簡單的工作。先說第一個部署問題:配置檔案。

比如說,你會有一堆的配置檔案需要配置,會有一堆的周邊應用或者服務要隨之部署。對於配置檔案,恐怕已開始大家都是隨便找個地方一擱就好,比如放在APP_DATA目錄下,甚至是直接就在web.config裡面新增<add key="xxx" value="zzz"/>。這種做法一開始確實覺得很順手,可是時間一長,你會發現這些配置內容越來越多。多不是問題,問題是他們是有區別的。之所以有配置檔案,是因為如果能夠通過配置而改變和擴充套件系統的功能,將會變得很方便。可是這些“改變和擴充套件”有的是發生在時間軸上,例如隨著時間推移發現使用者需求改變了。而還有的呢,是發生在空間軸上的,例如部署在不同的機器上需要有不同的配置,比如本地除錯的時候可能希望把IP限制全部放開,而真正運營的伺服器不能這麼幹。如果你沒有考慮到這一點,很可能會把這些東西都放在一塊,最嚴重的莫過於所有配置都在web.config裡面。

“有問題嗎?配置檔案都是要修改的啊!”沒錯,甚至一些超大系統的部署工作實際上需要另一個程式去扶助之才能完成。可是如果你的系統沒到那麼大的程度時,花費人力去做這麼一個部署工具實在是不值得。因為這個工具在每一次新版本升級的時候,都需要重新開發測試。對於一些線上運營的系統,很多時候甚至就是小修小補。比如說部落格園這樣的系統,你總不能說整天“安裝”整個新版本吧?這時候部署工具恐怕無能為力,除非你又弄一個“升級/補丁部署工具”來。

“反正這也就是一次性的工作,有那麼重要嗎?大不了稍微花點時間去弄一下就好了。”也沒錯,修改一下也不花多少時間——相對開發整個系統來說。可是那也是時間啊!對於一個正在執行的系統,修改配置的速度越快越好,因為它影響你係統的上線速度。而更重要的是,人很容易就會犯錯,如果配置方面的設計有問題,就會增加出錯的機率。

“容易出錯?不會吧?”確實很容易,這個可能需要舉一個很具體的例子:

相信不少同學的開發過程是有原始碼工具做管理的,那麼我們也會發現,這些配置檔案通常也受原始碼管理工具管理的。這很正常也很有必要,配置檔案如何配置,以及其變化,如果丟失了,則很有可能需要花很多時間才能搞清楚如何部署這個系統。比如說這個工具就是TFS,開發的工具是VS。好了,為了便於開發,我們通常也會在開發人員所在的機器中部署一個完整系統,或者子系統、微系統。為了簡便,我稱為local系統。

“沒有!”不會吧?你們的系統大到了這種程度?或者是因為安全方面的原因?好吧,那就沒有吧。但是,總會有一個安裝在內網的,類似alpha版本的這麼一個系統吧?那麼,無論是前面說到的local系統,還是這個alpha系統的配置很可能和真實系統之間還是有差別的。比如說,我們的系統可能需要和第三方的某個系統做聯動,開發的時候呼叫的應該是對方提供的一個測試介面——人家肯定不會讓你直接一上來就用正式運營的介面的,除非他瘋了。相應的,正式版肯定是要呼叫人家正式的介面。容易想到,這個差異肯定是在配置檔案當中的。不容易想到的是,簽入到原始碼管理工具中的配置,很可能是local或者alpha的配置。那麼,當我們部署完正式版——我稱之為release版, 我們肯定得要修改這個配置。

到這裡,問題還沒出現。假設,某天我們發現系統中有一個較大的Bug需要修改,然後呢,需要增加或者修改十幾項無論部署在那一臺機器都需要的配置。改之,簽入。嗯,接下來該打補丁了。我們對比history,發現wwwroot中改了幾個aspx/cs檔案,有一個dll專案中的cs改了(也就是說這個dll也要重新編譯),還有某一個配置檔案改了。於是,我們編譯好那個dll,連同其他aspx/cs和配置檔案一打包,上傳到伺服器覆蓋對應的舊檔案,然後重啟一下IIS的AppPool(或者儲存一下根下面的web.config),好了!真的好了嗎?開啟IE訪問之,真的好了,原來的那幾個Bug不見了,新的功能也有了!

過了幾天,你可能會驚訝的發現,這幾天使用者的線上付費都通過測試介面“付錢了”,你也給使用者“發貨了”。使用者付給你的是“測試幣”,可你發的可是真貨啊!檢查後發現,原來上次覆蓋的那個配置檔案的問題——雖然大部分配置在測試環境和真實環境中沒有區別,可是那個介面……

“這個根本就是機制的問題,應該有其他的機制保證!”太對了,不過我是為了便於描述,才省略了中間很多步驟和機制。比如說,你應該有beta/rc等中間過渡測試版本,再比如應該有完整的測試案例,至少是關鍵測試案例,最後應該有專人檢查。但是,沒有任何一種機制能夠百分之百的打包票的,因此我們通常會設定多個機制來提高可靠性。每增加一個機制,自然就會增加一份成本。而且,如果你打算最後還有人能夠檢查的話,更是需要這個檢查過程能夠簡單清晰。

“我們這種升級,都不會複製配置檔案的!”哎呀,你這麼說,我感到太高興了,因為我也是這麼認為的。不過,如果配置檔案有修改,是不是變成需要手工干預了?這個過程可能會比較慘痛,而且,通常只有部署的同學才會感同身受,別人通常會站著說話不腰疼——覺得沒什麼大不了的。

“我們不會都不復制,我們把那些有差別的檔案都剔除了。”嘿嘿,這已經比較接近我想要說的方案了。不過還有一個問題:哪些檔案是要剔除的?看花眼了吧?

說了半天,也該說出我自認為成功的實踐原則了:

1、配置檔案的位置要相對集中在一個地方,比如說~/App_Data。與之相反的是到處都有,比如每個目錄裡面的web.config或者App_Data裡面都有點配置,除非真的有必要(通常都是沒有必要的);

2、配置檔案要按照部署差異歸類,比如說~/App_Data/MachineDependent是機器相關的,~/App_Data/MachineIndependent則存放每一臺機器都應該相同的配置;

3、暫時沒有第三了。

這個很簡單的原則,可以讓你的部署過程變得更為簡單,比如說,部署的時候,只要記得把MachineDependent目錄咔嚓掉,通常就不會出什麼大亂子了。當然了,這個原則不是銀彈,不解決所有的問題,該有rc版還得有,該做檢查還得檢查,甚至需要手動修改的地方還得手動修改。但是其它因素完全相同的情況下,確實大大簡化了一些工作:

1、該複製什麼不該複製什麼只有一條規則,不容易搞錯;

2、不是所有配置都需要手動修改了,有一部分只要複製就好了;

3、即使是要修改的,你也知道在哪裡找到他們,而且由於不需要修改的部分已經摘出來了,配置內容肯定相對少了不少,因此看花眼的可能性降低了不少。

即使部署工作全程自動化,哪怕是補丁部署也是自動化的,應用這個原則也會使得開發部署工具會變得更簡單清晰一些,最後人工檢查也更輕鬆一點。當然了,這裡說的只是原則,實際上要應用這個原則,簡單點可以“告知”開發人員即可,複雜點的,我建議把配置相關的東西封裝到底層,不允許開發人員直接接觸配置檔案,這樣會更好一些。


還有一種部署問題,是周邊的小服務、小應用。請注意,我要說的不是如何部署周邊服務,而是說,如何設計才能減少因為部署周邊小服務小應用而產生的問題。

不可避免的,當系統增長到一定程度的時候,會發現這個系統太龐大了,改減減肥了。比如說,我們會考慮類似“微核心”的思路,把周邊的一些東西儘可能剔除出去。這樣做有幾個巨大的好處:第一,系統複雜度隨著解耦而降低;第二,能夠更容易分解開發組,降低管理的難度;第三,部署也會解耦而相應的簡化。關於第三點,我稍微多說一點點:以前某個功能的升級可能要整個系統測一遍才能上線,甚至可能這個功能本身測過已經沒有問題的,但是系統中的其他部分還有Bug,導致使用者迫切需要的這個功能遲遲無法上線。如果我們把他們拆出來,這個問題就會得到巨大的簡化。

那麼,當我們拆出來很多小服務之後,就涉及到這些小服務和核心的連線,通常還是離不開配置。除了配置本身,還有另外一個細節需要考慮到,而這個通常是我們比較容易忽略的部分——版本。我這裡所說到的版本,可以是指前面說到的alpha/beta/rc/release等版本,還可以是指面向不同客戶的的版本。後者可能需要給一個具體的例子,大家才好理解:

比如說,我是一個Blog引擎服務提供商,通過定製就可以提供Blog的Hosting服務。比如說,新浪說要這樣這樣的樣式和功能,我這邊一配置就出來了;貓撲說要那樣那樣的風格和服務,我一配置也出來了。假設這個Blog引擎可能是跑在我的伺服器上,而不是安裝在新浪或者貓撲的伺服器上,也可能安裝在對方機器上,這個假設很重要,這麼做的理由可能是:可以控制我的無形資產;或者是節約對方的成本;或者是部署方便,等等。然後呢,也為了提供“快照”功能,我們需要些一個小服務,這個小服務會定時的直接抓取系統中的資料,然後以檔案或者資料庫的形式快取下來。很顯然,新浪和貓撲不太可能用同一個資料庫來存放Blog資料,因此這個小服務抓取出來的內容可能key值相同,但資料不一樣,因此至少這些快取資訊也應該獨立存放。通常,我們的設計都是隻有一個資料庫連線配置——簡單啊,不容易出錯;要訪問不同的資料庫通常也是互相獨立的,因此分別部署就好了,那麼快照服務很可能是分別部署的。好了,考慮後來又有“慢照”、“連續照”、“不照”、“什麼照”等一系列周邊服務,這些服務也要獨立部署,那麼問題就來了:

你的部署過程將會使一個n*m的關係,n個小服務對應m個核心。這是十分痛苦的,只要有一個地方配置錯了,很可能就會出現安全洩露,或者資料互相串擾等嚴重的問題。 如果我們稍微考慮一下,通常這些小服務除了版本差異的地方之外,其它的差異都會比較小,比如對於同一個服務來說,程式程式碼通常只要有一套就可以了。因此,我們應用以下原則可以減少部署的困難:

提供一個底層的框架,能夠識別不同的(核心)版本,根據不同的版本能夠讀取不同的配置;

如果能夠把這個過程變成對服務本身是透明的,則更為理想——這意味著你不需要給開發服務的人員講解過多的細節,以及便於把舊的程式碼遷移過來。

除此之外,我們還會發現一個問題,就是隨著服務的增多,部署仍然是一個困難的事情,尤其是涉及的遷移的時候。比如說,我們發現貓撲訪問量很大,需要把周邊服務遷移到一個獨立的機器去執行。於是乎我們要找到每一個服務所在的目錄,複製到新的伺服器上,同時分別修改這些服務的配置,甚至是檔案系統的許可權等。此外核心的配置可能也發生了變化——需要修改有關服務所在位置的配置資訊。考慮到這個問題,我們還可以應用以下原則來減少這部分的困難:

儘量將周邊的一些小服務集中起來,同時提供一個底層框架,能夠根據正在使用的服務讀取不同的配置;

如果這個過程是透明的則更理想——理由同上。


當我們應用了這兩個原則之後,我們就可以把周邊服務的部署過程,從n*m轉變為n,甚至是1的程度。當然,開發不會因此簡化,甚至可能會變得複雜一些——要額外開發一個底層框架出來。不過我想先這個額外的開發,其複雜度還是相對較低的,帶來的好處確不少。


不知道我的這些想法,大家是否能夠理解和認同?

 

相關文章