非阻塞式🌌 Non Blocking Jdbc
Part 1
阻塞式程式碼是現代軟體工程的禍根,它需要比較多的CPU cycles以及memory使用資源,提高了執行程式的成本
I. Blocking/Asynchronous/Asynchronous-Non-Blocking
這個章節(I)會學到:
- 了解 non-blocking calls 在做什麼
- 了解 event based system 的重要性以及他們如何協助調用 non-blocking calls
圖表的呈現面向:
- 程式語言: Java
- 會使用到 Java 執行緒
- 非同步的方法為綠底,同步的方法是紅底
- 藍底是不會產生阻塞的Java方法
Blocking Call
save
方法呼叫write
方法write
方法是一個blocking call: 寫入磁碟- 直到 write to disk 完成之後,這個 thread 才會停止 blocking 狀態
- 這個thread在blocking call return之後重新開始
- 接著這個thread呼叫notify方法
Asynchronous Call 非同步呼叫
save
方法呼叫write
方法,這個write
方法包裝了一個asynchronous call- 此
write
包裝方法使用Executor(線程池或其他併發機制)產出一個執行緒來寫入磁碟 - 這個新產生的執行緒進入阻塞狀態,直到write to disk工作完成
- 執行
save
方法的主要執行緒不受到組塞,可以執行其他程序 - 新產生的執行緒在完成 blocking call 之後回傳,並呼叫
notify
方法
Asynchronous Non Blocking Call
save
方法呼叫write
方法 (write方法使用 Java NIO)- Non Blocking Call 不會阻塞主要執行緒
- non-blocking write 方法進入阻塞狀態,直到write to disk的工作完成
- 執行
save
方法的主要執行緒並不會受到阻塞,可以執行其他程序 - 在non-blocking call完成之後,這個non-blocking方法呼叫
notify
方法
結論
-
Blocking Call
- 此時此刻當今應該沒有人在用 blocking call 了
- 但如果真的 真的 真的需要 blocking call,要列出正當理由
- 例如說,向JDBC資料庫做的JDBC call,就算是一個blocking call
- JDC SQL 2 package: 這跟
java.sql
主要不同之處在於,它是異步、為了高吞吐量的程式開發
-
Asynchronous Call
- 使用新產生的執行緒,異步處理blocking call,是避免讓主要執行緒阻塞的好方法
- 如果這個由新產生的執行緒包裝的blocking call執行期間短會很有幫助
- 但如果blocking call長時間使用資源,可能會造成JVM中 thread starvation 的問題
- 在單體式架構 (有充足CPU,RAM資源),可能會在app執行一段期間之後遇到執行緒耗盡的問題
- 在微服務架構下,這問題可能會視deployment model情況而變更嚴重
- 主要原因在於有很多微服務,每個微服務有清楚的權責且部署到的地方(例如K8s pod)
- 通常每個pod的RAM以及CPU的資源有限 Deloy Java microservices to K8s
- 因此這個做法不是最佳解,應該盡可能避免blocking call
-
Non Blocking Call
- 不會阻塞系統內的任何執行緒
- Jva NIO 是從 JDK1.4 開始就有的功能
- 寫 non-blocking code 的方法眾多參考Non-blocking Algorithms
- Asynchronous code可能需要花點時間習慣,因為它可能不是嚴格的sequential
II. Remote Procedure Calls v.s. Messaging/Events Service calls
這個章節(II)圖表的呈現面向:
- 一個服務負責一個job
- 不僅限於Java,也適用於其他程式語言
Direct Remote Procedure call
RPC: Two services
- 如上圖,Service One 對 ServiceTwo 做出Remote Procedure Call 的請求
- ServiceTwo 執行程序,並將 response 回傳給 ServiceOne
- 這個行為本身是個 synchronous call
- 所以 ServiceOne 會等待 response
- 使用的通訊協議可能是以下其中一個:
- low level sockets with a custom protocol
- HTTP/REST protocol
- other protocol
- ServiceOne 跟 ServiceTwo 耦合度很高,邏輯須一致
RPC: Two services timeout
- ServiceTwo 如果 no response 的話,傳送請求的 ServiceOne 需要有處理機制
- 可能是ServiceTwo 停機、意外錯誤或者網路問題
Many services
- 以上是有三個Services同時需要一個Service,會發送請求與等待回覆的情況
- 若多個 Services 都對 ServiceOne 提 RPC calls,此 ServiceOne 則需要處理 Synchronous requests 以及每個 Service inferface 各自的 timeouts
Conclusion for RPC
- 這裡遇到的問題是Services之間緊密的耦合度
- 使用方式是同步的調用,基本上是blocking call
- 很容易被理解為 blocking call 而寫成 synchronous 方法
Queue based events
- ServiceOne 在 QueueX 上
publish
一則message - 對此message有接收興趣的ServiceTwo 就從 QueueX 訂閱這個隊列的訊息
- ServiceFour根據從 QueueX 接收到的訊息,再發布message至QueueY
- ServiceOne 對#3發布的訊息感興趣,所以訂閱 QueueY
Conclusion for Messaging
- 使用 Messaging queue 解決了 services 之間的高耦度
- 每個向 queue 發送訊息 的 service 可以繼續做其它要做的工作
- 使用異步調用方法,本質上是 non-blocking call
- 優點除了解耦,另外各個services也可以獨立開發,獨立測試,建構響應式系統更容易 (參考What do you mean by Event Driven?)
Erlang Events
Erlang 是高併發語言,由Ericsson發明
Akka Actors and Events
- 受 Erlang 影響很多,Akka Actors and Event 在 JVM 運行,支援 Scala 以及 Java
Part 2
這個部分主要討論到 Java NIO, Java JDBC 以及 non-blocking JDBC 的需求
Java IO NIO 以及 NIO2
主要層面
- Java IO 在每個 connection model 的每次連線都有個 thread,可擴充性並不高
- Java NIO
- Java NIO2
小結論
Database Cursors/Connections
JDBC 連線池
當 Java 的 JDBC 標準出現時,我們發現開啟/關閉JDBC連線的每個操作都會耗費高昂的資源而且很耗時間
因此出現了JDBC連線池,目前大部分的Java Server標準安奘版都配有 JDBC 連線池
每次連線到Database Server端的資源
每一筆連線都代表某些程序、記憶體或其他資料庫端的資源
JDBC連線在 server 端都會耗費一些資源或費用,在Oracle資料庫有個術語稱之 Cursors,不是無限的資源,可能會在某些情況用完
Database Administrator 資料庫管理難題
假設情境
開發寫了個 JDBC 應用程式,在開發環境都沒問題,所以進到效能測試。DBA就發現隨著這個app 的負荷增加,所要求的資料庫資源就變得更昂貴。
開發因為資源不足怪DBA,DBA則怪開發的程式設計不佳,不清楚資料庫的資源有限
NIO 跟 NIO2 就是以 non-blocking thread 減少使用資源量的設計
Non Blocking JDBC
Blocking a thread is a resource issue as each blocked thread holds onto ~0.5MB of stack and may incur context switch and memory-access delays (adds latency to thread processing) when being switched to. For Example, 100 blocked threads hold one to ~50MB of memory (outside of Java heap). – Davaid Morten 每個阻塞的執行緒會占用至多 0.5MB 的棧區,可能導致上下文切換以及記憶體存取的延遲性增加
Part 3
這部分主角是 R2DBC,也會探究 Oracle 提供 non-blocking JDBC 的觀點
R2DBC.IO
重要層面
- Reactive Relational Database Connectivity (R2DBC) 響應式關聯式資料庫連線給關聯式資料庫提供了響應式編程API
- 支援多類型的資料庫,像是 GCP Cloud Spanner, MySQL, PostgreSQL, H2, MariaDB, MSSQL, MySQL, PostgreSQL
- 有某些以 KOTLIN 為基礎的 drivers
小結
JDBC是過去好幾年以來的API標準,但我們需要提供JDBC一個非同步,非阻塞式的標準,而R2DBC就能補足這個需求空缺
參考 R2DBC 延伸資料
R2DBC plus Spring framework
Spring framework 有 Flux
,Mono
, WebFlux
承擔了建置 reactive streams 的功能,再加上 R2DBC,讓 reactive JDBC 實作起來更加容易。
許多效能測試都有一致的結果顯示WebFlux + R2DBC 在延遲、吞股量、cpu使用量等數據上表現都顯示這兩者是很優秀的組合,參考官方文件Spring support for R2DBC
小結
R2DBC 提供跨資料庫的響應式、非阻塞式資料庫存取的API,不是Oracle支援的計畫,更像是開源社群的成果,但因為跟Spring一起使用效果很好,值得考慮使用
Oracle ADBA and R2DBC
Oracle 在 2018 開始了個計畫要將 Asynchronous Database Access API 導入 Oracle,但目前終止了(discontinused)(🙄)
參考 Oracle ADBA for Asynchronous Database Access API
Oracle JDBC Reactive Extensions
在停掉ADBA計畫之後,Oracle想出了JDBC Reactive Extensions,用來作為存取Oracle 資料庫–以非阻塞式存取的解決方案。目前這個Java標準不是所有RDBMS access都能使用…其他的RDBMS廠商沒有被包含在這個計畫中…
小結
Oracle 提供了一個以non-blocking方式存取 Oracle 資料庫方式,但只是 Oracle DB 的 JDBC DRIVER 的 extension。Oracle Java 沒有像標準化JDBC一樣,將 non-blocking API 對 RDBMS 的存取做標準化。(所以就是買進來就撒手不管的實際例子🤔)
延伸
- Oracle Reactive Extension