軟件系統的穩定性


stability

 

軟件系統的穩定性,主要決定於整體的系統架構設計,然而也不可忽略編程的細節,正所謂“千里之堤,潰於蟻穴”,一旦考慮不周,看似無關緊要的代碼片段可能會帶來整體軟件系統的崩潰。這正是我閱讀Release It!的直接感受。究其原因,一方面是程序員對代碼質量的追求不夠,在項目進度的壓力下,只考慮了功能實現,而不用過多的追求質量屬性;第二則是對編程語言的正確編碼方式不夠了解,不知如何有效而正確的編碼;第三則是知識量的不足,在編程時沒有意識到實現會對哪些因素造成影響。

例如在Release It!一書中,給出了如下的Java代碼片段:

package com.example.cf.flightsearch; 
//... 
public class FlightSearch implements SessionBean {
	private MonitoredDataSource connectionPool;
	public List lookupByCity(. . .) throws SQLException, RemoteException { 
		Connection conn = null; 
		Statement stmt = null;
		try { 
			conn = connectionPool.getConnection(); 
			stmt = conn.createStatement();
			
			// Do the lookup logic
			// return a list of results
		} finally { 
			if (stmt != null) {
				stmt.close();
			}
			if (conn != null) { 
				conn.close();
			}
		}
	}
}

正是這一小段代碼,是造成Airline系統崩潰的罪魁禍首。程序員充分地考慮了資源的釋放,但在這段代碼中他卻沒有對多個資源的釋放給予足夠的重視,而是以釋放單資源的做法去處理多資源。在finally語句塊中,如果釋放Statement資源的操作失敗了,就可能拋出異常,因為在finally中並沒有捕獲這種異常,就會導致后面的conn.close()語句沒有執行,從而導致Connection資源未能及時釋放。最終導致連接池中存放了大量未能及時釋放的Connection資源,卻不能得到使用,直到連接池滿。當后續請求lookupByCity()時,就會在調用connectionPool.getConnection()方法時被阻塞。這些被阻塞的請求會越來越多,最后導致資源耗盡,整個系統崩潰。

releaseit

Release It!的作者對Java中同步方法的使用也提出了警告。同步方法雖然可以較好地解決並發問題,在一定程度上可以避免出現資源搶占、竟態條件和死鎖的情況。但它的一個副作用同步鎖可能導致線程阻塞。這就要求同步方法的執行時間不能太長。此外,Java的接口方法是不能標記synchronized關鍵字。當我們在調用封裝好的第三方API時,基於“面向接口設計”的原理,可能調用者只知道公開的接口方法,卻不知道實現類事實上將其實現為同步方法,這種未知性就可能存在隱患。

假設有這樣的一個接口:

public interface GlobalObjectCache {
	public Object get(String id);
}

如果接口方法get()的實現如下:

public synchronized Object get(String id){
	Object obj = items.get(id); 
	if(obj == null) {
		obj = create(id); 
		items.put(id, obj);
	} 
	return obj;
}

protected Object create(String id) {
	//...
}

這段代碼很簡單,當調用者試圖根據id獲得目標對象時,首先會在Cache中尋找,如果有就直接返回;否則通過create()方法獲得目標對象,然后再將它存儲到Cache中。create()方法是該類定義的一個非final方法,它執行了DB的查詢功能。現在,假設使用該類的用戶對它進行了擴展,例如定義RemoteAvailabilityCache類派生該類,並重寫create()方法,將原來的本地調用改為遠程調用。問題出現了。由於采用create()方法是遠程調用,當服務端比較繁忙時,發出的遠程調用請求可能會被阻塞。由於get()方法是同步方法,在方法體內,每次只能有一個線程訪問它,直到方法執行完畢釋放鎖。現在create()方法被阻塞,就會導致其他試圖調用RemoteAvailabilityCache對象的get()方法的線程隨之而被阻塞。進而可能導致系統崩潰。

當然,我們可以認為這種擴展本身是不合理的。但從設計的角度來看,它並沒有違背Liskove替換原則。從接口的角度看,它的行為也沒有發生任何改變,僅僅是實現發生了變化。如果不是同步方法,則一個調用線程的阻塞並不會影響到其他調用線程,問題就可以避免了。當然,這里的同步方法本身是合理的,因為只有采取同步的方式才能保證對Cache的讀取是支持並發的。書中給出這個例子,無非是要說明同步方法潛在的危險,提示我們在編寫代碼時,需要考慮周全。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM