2015年5月2日

程式設計=廉價工作?

不知多久以來,一直存在的一種狀況:程式設計是廉價的工作。
會有這樣子的心情跟感受,有三:
  • 有人說,程式設計是很無趣的 ("好像"不是很難、不是很有價值的工作)。
  • 技術門檻越來越低,從業的人越來越多;而且做出來的軟體系統,品質並不高。(原因可能不只是"人"的部份而已)
  • 經歷過,而且很長的一段時間被當做廉價勞工。
很多年前在網路上看到(而已經找不到)的一份(好像是M$做的)資料;指出,(不良的)系統調教的方向與範疇,70% 來自所做的應用系統,20% 需要檢視作業系統,而 10% 則可能看看主機硬體是否有調整或升級的必要。個人在這個行業多年的觀察,70% 之語應該指的是歐美的情形,但對照於國內來看,那算是太客氣了。

7、8 年前接觸到的 Java 應用系統,一個是自己主導的、一個是有參與的、一個是維運而沒有權力介入的,還有現在維運的系統;都看到了一個很平常的問題:系統慢、很吃記憶體...等。而這些系統,甚至與其他見過的 80% 的應用系統,都有一個(很典型的)共同點:處理(關聯式)資料庫的資料。

除了自己主導做的系統很認真的設計外,餘者多讓我覺得:難怪很多人認為程式設計是廉價的。因為,連基本的資料庫處理方法都做的不好。

在這裡想指出兩個觀點:
  • 資料連線的建立是耗時、不經濟的
  • 除非是跨資料庫,否則少有同時多個資料庫連線的需要
白話的意思是:除非有情境需要特殊設計外,否則一般應用程式當下只會用到一個資料庫連線資源 (connection object)。

話說回來,這個年代所做的已不是十多年前的 standalone program,而多半是 web application。使用的是 App Server 來做為程式的執行環境。這些 App Server 執行環境存在的主要目的,其中一個就是資源管理(/控),而且資料庫連線資源是大宗。程式基本上是向 DataSource (也就是 connection pool) 取得資料庫連線資源 (connection object),基於並受限於 pool 的管制以免資料庫 (DB server) 受到太大的網路連線需求衝擊。

然而,除非你買的是百萬元級 App Server(例如 WebLogic);否則,pool 中的資源並沒受制於 API 的規定而受特別控制;一旦,程式要求 connection 時,pool manager 就會試著產生一個 connection 給程式用:
// DataSource.getConnection()
Connection conn = someDataSource.getConnection();
// ...
return conn;

但抱歉的是,用的 App Server 不是百萬元起跳,程式也不只是一個 class 、一個 data table 來源,就可以把前端 (client side) 的作業需求處理完畢。一旦,一群 class、一堆 data table 的處理,各 class 程式中需要 connection 時,透過上面的 API 取用;那麼,一個作業 (one thread) 可能會用到很多 connection objects,而同時間不同的使用者作業(multi-thread),將勢必造成 connection 不足 (leak) 的現象。

索性,O.O. 的世界有個重要的觀念:封裝。我們可以做一個 helper class,封裝 get connection 的需求:
// ...
Connection conn = ConnectionHelper.getConnection(dataSourceName);
// ...

在其中有兩件事情要處理:
  1. 以 dataSourceName 向 context 取得 DataSource。
  2. 向 DataSource 取得 Connection。

第 1 點的做法:

  • 使用 Servlet 的 load-on-startup 定義,對整個系統會用到的 DataSource 進行登記,以便爾後繼續取用。
  • 若改用 Spring framework 的話,則 bean object 除了不為 lazy initialized 外,利用 init-method 來進行 DataSource 登記,而這個 DataSource 則用 JndiObjectFactoryBean 以 JNDI name 取得並注入 (inject) 即可。
  • 若不是這兩個方法,而想自己做 singleton object(甚至用 static class) 的話,要注意的是:不要每次都呼叫/調用 new InitialContext() 及 lookup()。因為...它不經濟而且有點慢;現況來說,太常見到這類程式臨時在這裡採用這種方式的設計,做的只是上面的事。是浪費記憶體、[笨的可以] 的方法。可以做的是改用 static holder 技巧,該 holder 則是用 static inner class 在 class loading 初始化階段產生,用來 hold 住 DataSource 物件。
而這裡要做的是,以 dataSourceName 來對應以上面各方法取得的 DataSource 而已。

第 2 點的做法:

  • return connection object 前,用 ThreadLocal 留住向 DataSource 取得的 connection object,不用每次都向 DataSource 要,逕自 ThreadLocal 物件取得即可,直到 thread 結束。
  • thread,這裡多半指的是 HTTP thread。即然使用 ThreadLocal 物件來 "hold" 住及釋放資源,那麼必須找出 transaction 作業的起點,來進行取得與釋放動作;一般來說,實作的方法多半是 Servlet filter 或 Spring framework 的 OncePerRequestFilter,要注意的是該 filter "通常"是第一個(/最後一個)。
如果有必要的話,花一點力氣去偵測取得的 connection object 是否沒做最後的 close(),幫忙做一下 close(),以免 connection leak 發生;必竟用的不見得是百萬元級的 App Server 啊。

以上出現很多名詞與技巧,若不清楚、不懂、不知道,那表示這些東西一點兒都不廉價。
別再說:我也做過軟體、這不算什麼、[(一點也不難)找個會寫 Java 的人來做]...這種 "廉價" 的想法。 <除非他真的懂 Java>

ps. 這裡尚未探討到 XA / 2PC 的議題