Contents

非阻塞式🌌 Non Blocking Jdbc

Part 1

阻塞式程式碼是現代軟體工程的禍根,它需要比較多的CPU cycles以及memory使用資源,提高了執行程式的成本

I. Blocking/Asynchronous/Asynchronous-Non-Blocking

這個章節(I)會學到:

  1. 了解 non-blocking calls 在做什麼
  2. 了解 event based system 的重要性以及他們如何協助調用 non-blocking calls

圖表的呈現面向:

  1. 程式語言: Java
  2. 會使用到 Java 執行緒
  3. 非同步的方法為綠底,同步的方法是紅底
  4. 藍底是不會產生阻塞的Java方法

Blocking Call

//TODO

  1. save方法呼叫write方法
  2. write方法是一個blocking call: 寫入磁碟
  3. 直到 write to disk 完成之後,這個 thread 才會停止 blocking 狀態
  4. 這個thread在blocking call return之後重新開始
  5. 接著這個thread呼叫notify方法

Asynchronous Call 非同步呼叫

//TODO

  1. save方法呼叫write方法,這個write方法包裝了一個asynchronous call
  2. write包裝方法使用Executor(線程池或其他併發機制)產出一個執行緒來寫入磁碟
  3. 這個新產生的執行緒進入阻塞狀態,直到write to disk工作完成
  4. 執行save方法的主要執行緒不受到組塞,可以執行其他程序
  5. 新產生的執行緒在完成 blocking call 之後回傳,並呼叫notify方法

Asynchronous Non Blocking Call

//TODO

  1. save方法呼叫write方法 (write方法使用 Java NIO)
  2. Non Blocking Call 不會阻塞主要執行緒
  3. non-blocking write 方法進入阻塞狀態,直到write to disk的工作完成
  4. 執行save方法的主要執行緒並不會受到阻塞,可以執行其他程序
  5. 在non-blocking call完成之後,這個non-blocking方法呼叫notify方法

結論

  1. Blocking Call

    • 此時此刻當今應該沒有人在用 blocking call 了
    • 但如果真的 真的 真的需要 blocking call,要列出正當理由
    • 例如說,向JDBC資料庫做的JDBC call,就算是一個blocking call
    • JDC SQL 2 package: 這跟java.sql主要不同之處在於,它是異步、為了高吞吐量的程式開發
  2. 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
  3. 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)圖表的呈現面向:

  1. 一個服務負責一個job
  2. 不僅限於Java,也適用於其他程式語言

Direct Remote Procedure call

RPC: Two services

//TODO

  1. 如上圖,Service One 對 ServiceTwo 做出Remote Procedure Call 的請求
  2. ServiceTwo 執行程序,並將 response 回傳給 ServiceOne
  3. 這個行為本身是個 synchronous call
  4. 所以 ServiceOne 會等待 response
  5. 使用的通訊協議可能是以下其中一個:
    • low level sockets with a custom protocol
    • HTTP/REST protocol
    • other protocol
  6. ServiceOne 跟 ServiceTwo 耦合度很高,邏輯須一致

RPC: Two services timeout

//TODO

  1. ServiceTwo 如果 no response 的話,傳送請求的 ServiceOne 需要有處理機制
  2. 可能是ServiceTwo 停機、意外錯誤或者網路問題

Many services

//TODO

  1. 以上是有三個Services同時需要一個Service,會發送請求與等待回覆的情況
  2. 若多個 Services 都對 ServiceOne 提 RPC calls,此 ServiceOne 則需要處理 Synchronous requests 以及每個 Service inferface 各自的 timeouts

Conclusion for RPC

  1. 這裡遇到的問題是Services之間緊密的耦合度
  2. 使用方式是同步的調用,基本上是blocking call
  3. 很容易被理解為 blocking call 而寫成 synchronous 方法

Queue based events

//TODO

  1. ServiceOne 在 QueueX 上publish一則message
  2. 對此message有接收興趣的ServiceTwo 就從 QueueX 訂閱這個隊列的訊息
  3. ServiceFour根據從 QueueX 接收到的訊息,再發布message至QueueY
  4. ServiceOne 對#3發布的訊息感興趣,所以訂閱 QueueY

Conclusion for Messaging

  1. 使用 Messaging queue 解決了 services 之間的高耦度
  2. 每個向 queue 發送訊息 的 service 可以繼續做其它要做的工作
  3. 使用異步調用方法,本質上是 non-blocking call
  4. 優點除了解耦,另外各個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

主要層面

  1. Java IO 在每個 connection model 的每次連線都有個 thread,可擴充性並不高
  2. Java NIO
  3. 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

重要層面

  1. Reactive Relational Database Connectivity (R2DBC) 響應式關聯式資料庫連線給關聯式資料庫提供了響應式編程API
  2. 支援多類型的資料庫,像是 GCP Cloud Spanner, MySQL, PostgreSQL, H2, MariaDB, MSSQL, MySQL, PostgreSQL
  3. 有某些以 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 的存取做標準化。(所以就是買進來就撒手不管的實際例子🤔)

延伸