[TOC]
## 微服務數庫設計,把復雜的關系簡單化
|方法/工具 | 優點 | 缺點 | 技術邊界|流程與例子|?
|:--|:--|:--|:--|:--|
|域驅動設計|聚焦業務概念,簡潔模型|初期投入較大,學習曲線陡|需要投入較長周期進行域研究|實施DDD,進行域分析與建模,設計領域模型|?
|Event Storming|發現業務隱藏元素,簡化流程|事件抽取難度大,成果依人|難以實現完全自動化|通過工作坊演練域事件,發現實體及數據結構|
|數據庫范式理論|消除數據冗余,簡化依賴|過度歸約影響查詢性能|需要在理論與實踐之間權衡|按照1NF到3NF,規范化數據模型|?
|物理模型轉換|選擇主鍵和拆分表簡化關系|容易過度優化,造成查詢難度|需要在性能與理論之間平衡|在邏輯模型的基礎上進行物理優化,如添加索引、拆分表等|
|觀測與迭代|根據實際場景不斷優化|初期投入難以全面考慮各種復雜場景|需要長期穩定的產品架構進行演進|觀察業務場景與訪問模式,對模型設計進行持續優化|
|ER圖工具|直觀展示實體關系,方便優化|手工建模難以大規模解析|可以結合其他方法部分自動生成|使用工具繪制ER圖,優化實體與關系|
|數據庫遷移工具 |簡化物理模型變更的回歸|遷移過程中的數據一致性難保證|需要清晰的物理模型映射關系|在邏輯模型變更后使用工具更新數據庫|
|代碼生成工具|簡化物理模型的設計過程|生成的代碼質量依賴工具|需要對應支持的編程語言與框架|通過建模工具生成代碼,手工補充業務邏輯|
|ORM框架|簡化SQL語句編寫,屏蔽數據庫差異|性能開銷較大,Debug難度較大|需要對ORM框架有深入理解,選擇合適的 level|在代碼中使用ORM API操作數據庫?? |
|分庫分表工具|簡化單庫表關系,應對高并發|分庫分表策略難以設計與調整|需要在擴展性與關系完整性之間權衡|根據業務規則使用工具切分數據到不同數據庫|?
|緩存中間件|簡化數據庫查詢,部分邏輯在緩存中處理|數據一致性較難保證,緩存雪崩風險|需要設計好緩存過期和更新策略|業務查詢先查緩存,緩存未命中再查詢DB|
|搜索引擎|簡化應用直接訪問數據庫|數據導入過程較為復雜,同步延遲難以避免|需要對不同搜索引擎有深入理解,選擇最適合的數據建模方式|應用通過搜索引擎API查詢數據,搜索引擎對應維護數據庫|
|消息隊列|簡化不同系統模塊的數據庫關系|增加系統復雜性,消息丟失或重復的風險|需要保證MQ的高可用性并嚴格保證消息的冪等性|使用MQ使模塊異步通訊,不直接操作數據庫|
|文檔數據庫|簡化關系模型的表連接概念|查詢性能較差,事務支持較弱|需要在關系完整性與擴展性之間權衡|直接使用文檔進行數據存儲|
## 微服務架構的關系數據庫優化
### 1)分庫分表:
根據業務功能對數據庫進行縱向拆分,形成相對獨立的數據庫。再根據業務量對單表進行橫向拆分,實現分表。這有利于降低單表訪問壓力,提高數據庫擴展性.
*****
1. 分庫:根據業務功能將數據庫進行縱向拆分,形成相對獨立的數據庫。這可以降低單個數據庫的訪問壓力,也方便后續的水平擴展。
2. 分表:單個數據庫的表數據量過大時,可以將表進行橫向拆分,實現表的擴展。一般按照主鍵范圍或時間范圍進行拆分。
3. sharding key選擇:選擇合適的分片鍵,可以使數據分布較均衡。如果數據分布不均,會導致數據傾斜,影響擴展性。
4. 擴展策略:預先制定表的擴展策略,如每多少數據進行拆分一個表等。這可以指導業務量增長時的數據庫擴展規劃。
5. ID生成策略:使用分布式ID生成方案,為新 insert 的數據選擇 correct 的表。這確保了數據路由的準確性。
6. 路由機制:將 SQL 路由至正確的數據庫或表進行查詢和更新。常用的路由方式有中間件路由、應用程序路由等。
7. 分片的數據同步:對于需要備份或主從的數據庫,要實現分片數據的同步復制。這確保了分片后的高可用性。
8. Query 重寫:對查詢進行解析和重寫,嚴格按照分片策略路由查詢至相關的數據庫或表。避免出現跨分片的查詢。
9. 監控報警:監控各數據庫和表的負載情況,當某個分片的數據或訪問量過大時,及時進行告警,并擴展對其進行擴容。
- 代碼示例:
```
python
# 應用層路由 - 選擇數據庫
db = db_map[table_prefix]
# 應用層路由 - 選擇表
table = '%s_%s' % (table_prefix, sharding_key % MAX_TABLE_NUM)
# 分布式ID - 取模
uid = user_counter.get_next() % MAX_TABLE_NUM
# Query 重寫
table_name = 'user_%s' % uid
sql = 'SELECT * FROM %s WHERE ...' % table_name
```
> 數據分片可以有效提高數據庫的擴展性,但也帶來了額外的復雜性。這需要系統架構師在設計階段就考慮到表的擴展與分片,并且選擇合理的方案與切分策略。?要確保分片后的高可用與事務支持,并實現查詢的重寫與路由。這需要架構師對各種分片數據庫方案與技術都很熟悉,可以設計出合理可行的系統架構。數據庫的拆分與擴展直接影響著系統的性能與擴展能力。這需要架構師具有很強的未來預見能力,提前規劃數據庫的擴展路線圖,按需進行系統升級,滿足業務量的增長需求。要在系統性能、擴展性和實現難度之間取得平衡,選擇最適合業務發展的技術方案。
*****
*****
*****
### 2)讀寫分離:
將數據庫分為主數據庫(寫)和從數據庫(讀),讀請求指向從數據庫,寫請求指向主數據庫。后續將主數據庫的數據同步到從數據庫。這減輕了主數據庫的負載,提高了讀查詢性能。
*****
1\. 部署主數據庫(寫)和從數據庫(讀),主數據庫用于寫入數據,從數據庫用于讀取數據。
2\. 應用層使用數據庫連接池,定義讀數據庫連接和寫數據庫連接。讀請求使用讀數據庫連接,寫請求使用寫數據庫連接。
3\. 寫入數據時,將數據寫入主數據庫。并同步將數據寫入從數據庫,保證主從數據一致。
4\. 讀取數據時,首先從從數據庫讀取,若讀取不到(如剛插入數據還未同步),再從主數據庫讀取。后續的讀取請求直接從從數據庫獲取。
5\. 增加從數據庫的數量,來擴展讀服務能力。多個從數據庫的數據通過主數據庫同步。
6\. 使用代理方式,將讀請求分發到不同從數據庫。并使用負載均衡算法選擇從數據庫。這實現了讀請求的分流,進一步擴展了系統的讀處理能力。
7\. 監控主從數據庫的數據同步狀態和延遲。如果發現主從數據不同步或同步滯后,需要及時修復,保證最終一致性。
- 具體的實現代碼示例:
```
python
# 數據庫連接池
db_pool = PooledDB(creator=pymysql, ...)
# 獲取讀數據庫連接
rd_conn = db_pool.connection()
# 獲取寫數據庫連接
wt_conn = db_pool.connection()
# 寫入數據
with wt_conn.cursor() as cursor:
cursor.execute(sql, args)
# 同步寫入從數據庫
# 讀取數據
with rd_conn.cursor() as cursor:
cursor.execute(sql, args)
result = cursor.fetchall()
# 如果從數據庫沒有讀取到
if not result:
with wt_conn.cursor() as cursor:
cursor.execute(sql, args)
result = cursor.fetchall()
```
> 該實現使用數據庫連接池獲取不同的數據庫連接,并在業務邏輯中正確使用。同時通過同步主從數據來確保最終一致性,并在從數據庫讀取不到數據的情況下,及時補充從主數據庫讀取,保證業務logic的準確性。
在高并發場景下,要特別關注最小空閑連接數的設置,保證有足夠的連接隨時可用。同時要監控連接池的狀態,在連接數達到上限時及時作出響應,避免影響業務的正常運行。
- 連接池實現完整過程:
```
python
# 初始化連接池
pool = PooledDB(creator=pymysql, # 使用pymysql數據庫
maxconnections=10, # 最大連接數
mincached=5, # 最少空閑數
maxcached=7, # 最大空閑數
blocking=True, # 達到最大數時是否阻塞等待
maxusage=None, # 單個連接最大復用次數
setsession=[], # 開始會話前執行的命令
ping=0,
)
# 獲取連接
conn = pool.connection()
# 使用連接
cur = conn.cursor()
cur.execute(sql)
# 釋放連接
conn.close()
```
*****
*****
*****
### 3)緩存數據庫查詢結果:
使用緩存(如Redis)記錄數據庫查詢結果,后續查詢先訪問緩存,若存在則直接返回,否則再訪問數據庫。這大幅減少了數據庫交互次數,提高性能。
*****
1\. 分析業務場景,找到數據庫查詢結果中存在高頻且穩定的部分。這些查詢結果適合寫入緩存。
2\. 選擇合適的緩存工具,如Redis。考慮數據持久化與容錯機制,確保緩存數據的安全性。
3\. 在應用層代碼中,優先從緩存獲取數據。只有當緩存不存在對應數據時,才訪問數據庫查詢。
4\. 將數據庫查詢結果在返回應用層前寫入緩存。并設置合理的緩存時間,避免數據失效。
5\. 對數據更新操作,需要同步更新緩存中的數據。確保緩存與數據庫保持一致。
6\. 監控緩存命中率和數據庫訪問量,若緩存命中率過低,需要調整緩存策略。
7\. 根據業務特點選擇不同的緩存更新策略:
\- 直接刪除:對于獨立數據,直接刪除緩存。
\- 先刪除后更新:先刪除緩存,然后異步更新數據庫,最后更新緩存。
\- 隊列更新:將更新操作入隊,由后臺任務異步執行數據庫更新和緩存更新。
代碼實現示例:
```
python
redis_cli = Redis(host='localhost', port=6379)
# 讀取數據,優先從緩存獲取
result = redis_cli.get(key)
if not result:
# 緩存不存在,從數據庫查詢
result = db.query(sql)
# 將結果寫入緩存,設置緩存時間為1小時
redis_cli.set(key, result, ex=3600)
# 數據更新操作
def update(sql):
db.execute(sql)
# 同步更新緩存
redis_cli.delete(key) # 直接刪除緩存
new_result = db.query(sql) # 獲取最新結果
redis_cli.set(key, new_result, ex=3600) # 更新緩存
# 使用隊列異步執行更新
q.put(update) # 將更新任務入隊
```
> 使用緩存可以有效減少數據庫訪問,提高系統性能。但也增加了系統的復雜性,需要開發人員在設計時理解不同的緩存策略,并根據業務場景選擇最優方案。同時要確保緩存與數據庫保持一致,避免由于數據不一致產生的邏輯錯誤。這需要對系統進行全面測試,在開發階段就考慮到高并發下的緩存更新問題,選擇合理可行的方案。總的來說,要在性能、一致性和復雜性之間取得平衡。
*****
*****
*****
### 4)使用索引:
在頻繁作為查詢條件和排序依據的字段上創建索引,這加速了數據庫數據的查找速度,減少查詢時間。但索引也會消耗一定存儲空間和處理能力,需要權衡使用。
*****
1. 確定索引字段:需要確定作為查詢條件和排序依據的字段,這些字段的數據頻繁變化和參與查詢,并且對查詢性能有較大影響。
2. 選擇索引類型:常見的有B樹索引和哈希索引,這里選擇B樹索引作為示例。
3. 創建索引:在數據庫中針對選擇的字段創建B樹索引,例如:
```
CREATE INDEX idx_user_name
ON user (name)
```
4. 驗證索引:創建索引后,對包含該字段的查詢語句進行驗證,確認索引生效并且提高了查詢性能。
```
SELECT * FROM user WHERE name = 'Tom'
```
5. 選擇索引策略:對不同的字段可以采用不同的索引策略:
- 單字段索引:如果僅有一個字段用于查詢過濾或排序,可以在該字段上單獨創建索引。
- 復合索引:如果多個字段組合用于查詢過濾或排序,可以在這些字段上創建復合索引,來避免建多份單字段索引。
- 唯一索引:如果字段值需要唯一且頻繁作為查詢條件,可以將索引設置為唯一索引。這既滿足唯一性要求,也可以加速查詢。
- 覆蓋索引:如果查詢返回的字段都被索引覆蓋,數據庫可以直接從索引中獲取數據,加速查詢速度。
- 索引下推:在查詢過濾條件的字段上創建索引,用于加速WHERE子句的過濾操作。
- 索引上推:在查詢排序的字段上創建索引,用于加速ORDER BY子句的排序操作。
6. 監控索引:創建索引后,需要對各索引的使用情況進行監控,主要監控:
- 索引命中率:索引被查詢使用的次數。如果太低,說明索引沒有發揮作用,可以考慮刪除。
- 索引大小:索引文件本身占用的空間。如果過大,會消耗較多存儲資源和處理能力,需要權衡索引帶來的好處。
- 表大小:索引會增加插入和更新語句的消耗,監控表大小異常增長需要檢查索引使用策略。
*****
*****
*****
### 5)連接池技術:
使用數據庫連接池在應用層管理數據庫連接,避免頻繁創建和釋放連接導致性能損失。這有效地重復使用了數據庫連接,減少系統消耗。
*****
*****
*****
### 6)SQL 編寫優化:
合理利用數據庫的并行執行能力,編寫高效的 SQL 語句。如使用批處理方式 INSERT/UPDATE 多條數據,使用 EXISTS 替代 IN 子查詢等。這可以大幅提升數據庫處理性能。
*****
1. 選擇合適的字段查詢方式:
- 使用指定字段名查詢,避免 SELECT *。
- 用字段別名簡化查詢結果。
- chose 合適的字段類型,避免轉換開銷。
2. WHERE 條件過濾:
- 使用索引字段進行過濾查詢。
- IN 替換 OR 條件。
- IN 列表長度適當,避免過長。
- EXISTS 替代 IN 子查詢。
3. 合理使用索引:
- 單列索引或組合索引。
- 索引前綴最左前綴匹配原則。
- 避免在索引列上進行運算或函數運算。
- 索引列選擇具有區分度的字段。
- 考慮索引的復合性與查詢性能之間的平衡。
4. 其他優化技巧:
- 使用連接(JOIN)替代子查詢。
- 適當增加查詢條件來過濾結果。
- 程序中拼接SQL時使用參數而非直接拼接。
- 利用數據庫內置函數進行查詢。
- 考慮使用統計信息進行查詢優化。
- 盡量減少在數據庫中進行的計算或數據轉換。
5. 存儲過程和視圖:
- 復雜而頻繁的查詢適合創建存儲過程。
- 要考慮存儲過程的可維護性。
- 視圖可以簡化查詢語句,屏蔽表結構變更。
代碼示例:
```
sql
# 選擇指定字段
SELECT id, name FROM users;
# 別名簡化
SELECT id AS uid, name AS uname FROM users;
# IN 替代 OR
SELECT * FROM users WHERE id IN (1, 3, 5);
# EXISTS 替代 IN 子查詢
SELECT * FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE user_id=users.id);
# 單列索引
CREATE INDEX idx_user_name ON users(name);
# 聯合索引
CREATE INDEX idx_user_order ON users(name, order_date);
# 存儲過程
DELIMITER $$
CREATE PROCEDURE get_user_orders(IN uid INT)
BEGIN
SELECT * FROM orders WHERE user_id=uid;
END$$
DELIMITER ;
```
*****
*****
*****
### 7)分散壓力的中間件:
使用消息隊列等中間件在 web 服務器和數據庫之間進行解耦。這避免直接將大量請求發送到數據庫,實現流量削峰,分散數據庫壓力。
*****
1\. 選擇合適的消息隊列,如Kafka、RabbitMQ等。考慮隊列的耐久性、可擴展性等因素,確保其能夠滿足系統需求。
2\. 區分同步操作和異步操作。同步操作需要直接訪問數據庫,異步操作可以入隊列,由消費者異步處理。
3\. 消費者從消息隊列中取出任務,進行真正的數據庫操作和數據處理。
4\. 監控消息隊列的積壓數量和消費速度。如果消費出現滯后,需要及時增加消費者數量。
5\. 消息隊列與數據庫操作的結果,需要返回給用戶。可以通過以下方式實現:
\- 消息隊列返回消息ID,用于查詢操作結果。
\- 觸發器機制,消息入隊后立即返回成功,觸發器異步執行數據庫操作。
\- 輪詢機制,用戶輪詢查詢操作結果。
代碼實現示例:
```
python
# 消費者
def consume():
while True:
# 消費者從消息隊列取出任務
msg = queue.get()
# 執行真正的數據庫操作
db.execute(msg['sql'], msg['args'])
# 用戶請求
# 同步操作直接訪問數據庫
result = db.query(sql, args)
# 異步操作入消息隊列
queue.put({'sql': sql, 'args': args})
# 返回消息ID用于結果查詢
msg_id = queue.put_id
# 用戶查詢操作結果
result = db.get_result(msg_id) # 使用消息ID查詢
# 或使用輪詢查詢
while True:
result = db.get_result(msg_id)
if result:
break
```
> 使用消息隊列可以有效分散數據庫壓力,避免直接把大量請求打到數據庫上。同時也增加了系統的復雜性,需要開發人員理解消息隊列與數據庫的交互機制。?要確保消息沒有遺漏或重復消費,并且結果能正確返回給用戶。這需要對系統的異步鏈路進行全面測試,確保業務的準確性。?開發人員在設計系統架構時,要根據業務場景選擇恰當的消息隊列方式與數據庫交互方案。要在性能、數據準確性與系統復雜度間取得平衡,構建高效穩定的系統。
*****
*****
*****
### 8)服務器升級:
必要時可以選擇升級數據庫服務器配置,如增加 CPU、內存、存儲以增強處理能力。但這也增加了運維成本,需要綜合判斷。
*****
1. CPU:增加CPU核數可以提高數據庫的執行能力,特別是在計算與排序等方面。但也增加了并發控制的難度,需要結合業務場景選擇合適的CPU數量。
2. 內存:增加內存可以減少磁盤IO,特別是在數據緩存、索引、臨時表等方面。但也增加了數據庫系統的成本,需要根據工作負載選擇恰當的內存容量。
3. 存儲:選擇高速固態硬盤(SSD)可以大幅提高數據庫的IO吞吐量與訪問速度。對于日志、臨時表和索引等經常讀寫的數據,使用SSD存儲可以產生更高的效果。
4. RAID配置:使用RAID5或RAID10等方式對多個磁盤進行行列式組合,既可以提高讀寫性能,也具有一定容錯能力。這能夠提高數據庫的整體IO能力與數據安全性。
5. 網絡:采用高速網卡與交換機可以加速數據庫服務器間的數據交互速度。特別是主備切換或讀寫分離等架構下,網絡性能起著關鍵的作用。需要選擇與業務量相匹配的網絡配置。
6. 操作系統:選擇專業的數據庫操作系統,如RedHat Enterprise Linux等。這類系統默認具有更好的數據庫運行優化配置,可以充分發揮硬件性能,保證數據庫的穩定運行。
7. 其他:使用SSD緩存、增加交換空間、文件系統選擇等方式可以進一步提高系統性能。但也增加了系統的復雜性,需要結合產品特性與業務需要進行評估。
- 系統設計
- 需求分析
- 概要設計
- 詳細設計
- 邏輯模型設計
- 物理模型設計
- 產品設計
- 數據驅動產品設計
- 首頁
- 邏輯理解
- 微服務架構的關系數據庫優化
- Java基礎架構
- 編程范式
- 面向對象編程【模擬現實】
- 泛型編程【參數化】
- 函數式編程
- 響應式編程【異步流】
- 并發編程【多線程】
- 面向切面編程【代碼復用解耦】
- 聲明式編程【注解和配置】
- 函數響應式編程
- 語法基礎
- 包、接口、類、對象和切面案例代碼
- Springboot按以下步驟面向切面設計程序
- 關鍵詞
- 內部類、匿名類
- 數組、字符串、I/O
- 常用API
- 并發包
- XML
- Maven 包管理
- Pom.xml
- 技術框架
- SpringBoot
- 項目文件目錄
- Vue
- Vue項目文件目錄
- 遠程組件
- 敏捷開發前端應用
- Pinia Store
- Vite
- Composition API
- uniapp
- 本地方法JNI
- 腳本機制
- 編譯器API
- 注釋
- 源碼級注釋
- Javadoc
- 安全
- Swing和圖形化編程
- 國際化
- 精實或精益
- 精實軟件數據庫設計
- 精實的原理與方法
- 項目
- 零售軟件
- 擴展
- 1001_docker 示例
- 1002_Docker 常用命令
- 1003_微服務
- 1004_微服務數據模型范式
- 1005_數據模型
- 1006_springCloud
- AI 流程圖生成
- Wordpress_6
- Woocommerce_7
- WooCommerce常用的API和幫助函數
- WooCommerce的鉤子和過濾器
- REST API
- 數據庫API
- 模板系統
- 數據模型
- 1.Woo主題開發流程
- Filter
- Hook
- 可視編輯區域的函數工具
- 渲染字段函數
- 類庫和框架
- TDD 通過測試來驅動開發
- 編程范式對WordPress開發
- WordPress和WooCommerce的核心代碼類庫組成
- 數據庫修改
- 1.WP主題開發流程與時間規劃
- moho
- Note 1
- 基礎命令