搭建基於springboot輕量級讀寫分離開發框架

何為讀寫分離

讀寫分離是指對資源的修改和讀取進行分離,能解決很多數據庫瓶頸,以及代碼混亂難以維護等相關的問題,使系統有更好的擴展性,維護性和可用性。

一般會分三個步驟來實現:
一. 主從數據庫搭建
信息管理系統的絕大部分瓶頸在數據庫,通過搭建主從數據庫,寫到主數據庫,讀取從數據庫,提高數據庫的吞吐量,根據業務需求可以搭建一主一從、一主多從的數據庫同步架構。如果報表多的系統,可以搭個一主多從架構,一個從數據庫供普通查詢,另一個從數據庫供報表查詢,這樣能夠避免報表的複雜查詢影響客戶正常操作。

二. 讀寫代碼分離
代碼上對讀寫進行分離。讀的邏輯相對簡單,幾乎不需要做過多的分層封裝。大部分業務邏輯在寫操作,所以我們需要專註於對寫代碼的分層、抽象封裝。注意: 在寫模塊涉及到業務數據讀取,幾乎要實時的,而且基於高內聚的原則,應該封裝進寫代碼類中,讀取主數據庫。

三. 進程分離
將讀和寫的代碼封裝到不同的進程,從進程級別避免相互影響,其實就是分佈式。實現從進程上解耦,程序運行期間的性能、異常錯誤不會相互影響,所以系統有相對高的可用性。

這裡多說一句, 如果對寫業務按領域拆分到不同的進程,會涉及到分佈式事務,在未涉及到高並發、大數據的系統,其實沒必要從進程上拆分,分佈式對事務不友好,為了處理分佈式事務,你需要付出更多的時間和金錢成本。考慮進程拆分,一定要基於實際業務需求再三權衡利弊。很多時候,也許你只需要多一個從數據庫、一個緩存、多一台服務器、多幾G內存、多幾核cpu、優化一下sql 即可解決很多性能上的問題。

如何搭建

現在我們搭建一主一從數據庫架構, 並且實現從代碼上進行讀寫分離的開發框架,但不涉及進程分離。

1. 搭建主從數據庫

mariadb 可參考搭建 mariadb 數據庫主從同步 或者 //mariadb.com/kb/en/setting-up-replication/

2. 基於springboot 搭建開發框架

2.1 項目結構

畫一下框架的模塊結構

  1. api 模塊相當於 gateway, 接收和響應請求,還包括鑒權, 參數的校驗和組裝,調用 command, query 的接口。
  2. common 模塊封裝一些和業務無關的通用功能類。
  3. query 是讀模塊,封裝非實時的查詢接口,查詢”從數據庫”。
  4. command 是寫模塊,封裝領域的業務邏輯,操作”主數據庫”。

按照上圖,用 idea 創建項目結構如下

對 command 和 query 模塊再進行細化

因為 command 模塊我們使用領域驅動開發,所以拆分成服務(Service), 倉儲(Repository), ORM, 聚合根(Aggregate)。

Query 只是簡單的查詢,我們直接用 Dao 訪問數據庫,然後把數據轉成 DTO 返回。

根據上圖,再細化項目的文檔結構

2.2 配置文件

假設我們有三個環境, 分別是開發(dev), 測試(uat), 生產(prod)。每個模塊都有單獨的配置文件。
api:
application-api-dev.yml
application-api-uat.yml
application-api-prod.yml

command:
application-command-dev.yml
application-command-uat.yml
application-command-prod.yml

query:
application-query-dev.yml
application-query-uat.yml
application-query-prod.yml

在系統啟動時,指定使用的環境, api 作為啟動項目,添加 bootstrap.yml, 因為 bootstrap.yml 優先於 application.yml 生效,所以可以在 bootstrap.yml 配置啟動環境。
我們啟用 dev 環境, bootstrap.yml 內容如下:

spring:
  profiles:
    active: common-dev,command-dev,query-dev,api-dev

那麼,對應的 application-common-dev.yml, application-command-dev.yml, application-query-dev.yml, application-api-dev.yml 配置文件將起效。

2.3 運行

在 query 項目添加一個接口

public interface UserQueryService {
    String getName(Long id);
}

並實現它

@Service
public class UserQueryServiceImpl implements UserQueryService {
    @Override
    public String getName(Long id) {
        return "my name is grissom" + id;
    }
}

在 api 中調用該接口


@RestController
@RequestMapping("user")
public class UserController {
    private final UserQueryService userQueryService;

    public UserController(UserQueryService userQueryService) {
        this.userQueryService = userQueryService;
    }

    @GetMapping("/name/{id}")
    public String name(@PathVariable("id") Long id) {
        return this.userQueryService.getName(id);
    }
}

將 api 作為啟動項,配置 application-api-dev.yml, 開放 8003 端口

server:
  port: 8003

用 postman 請求

至此,咱們的項目結構已經搭建好。
下一篇再寫如何訪問數據庫。

源碼

//github.com/grissomlau/cqrs-springboot