前言
最近我們的APP在線用戶越來越多,接口的響應速度也是越來越慢,經過運維排查發現是由於併發查詢太多導致的數據庫壓力比較大,架構師經過調研給出了數據庫讀寫分離的解決方案,爲了快速解決問題,我們最終採用AOP技術實現了數據庫讀寫分離方案。
目錄
- 什麼是數據庫讀寫分離以及爲什麼要讀寫分離?
- 數據庫讀寫分離實現方式及優缺點分析
- 用AOP實現的數據庫讀寫分離方案
- 總結
什麼是數據庫讀寫分離以及爲什麼要讀寫分離?
數據庫讀寫分離,顧名思義就是將數據庫的讀操作和寫操作分開。
傳統項目架構中,所有的操作都在同一個數據庫執行,在併發量很小的時候這並不會出現什麼問題,但是在併發量很大時單個數據庫就會出現性能問題。
假設單個數據庫的QPS爲1000,數據庫的併發量爲2000,狼多肉少的情況下接口響應速度變慢也就不足爲怪了。
數據庫讀寫分離方案的出現就是爲了解決這個問題的。
項目採用數據庫讀寫分離方案之後共有4個數據庫,其中1個主數據庫和3個從數據庫,主從之間採用binlog文件的方式進行數據同步。主數據庫負責處理所有的寫操作,從數據庫異步進行數據同步;所有的讀操作會平均分散到3個從數據庫上執行。
數據庫讀寫分離之後,主數據庫處理寫操作,多個從數據庫組成集羣共同提供查詢服務,原本大併發量的查詢操作將被平均分到3個數據庫上,從而降低了每個數據庫的併發,提升額查詢效率;同時還將數據做了冗餘備份,增加了系統的抗風險能力。
數據庫讀寫分離實現方式及優缺點分析
根據解決方式所在的層面不同,數據庫讀寫分離主要有3種解決方案:
1、應用層面
實現方式主要是在應用層加一個數據源路由層,將查詢操作路由到從庫,將寫入操作路由到主庫。
優點:方案實現起來比較輕便、路由策略可自由控制,擴展性強。
缺點:功能有限,個人要實現完整的方案難度較大,且該方案與代碼強耦合,跨語言不通用。
2、中間件層面
實現方式是在應用和數據庫之間加一層代理,由代理來轉發操作請求。像阿里的MyCat、360的Atlas、美團的DBProxy等都是此類實現方案。
優點:解決方案與應用層代碼解耦,通用性較好。
缺點:應用和數據庫之間增加了代理層,連接由直連改爲代理會導致性能有所下降。
3.數據庫驅動層面
實現方式是提供一個支持數據庫讀寫分離的驅動。像Mysql自帶的ReplicationDriver驅動、噹噹開源的Sharding-JDBC、淘寶開源的TDDL等解決方案都是基於數據庫驅動層面實現的。
優點:功能豐富、集成方式較簡單,應用層面改動較小。
缺點:需使用特定數據庫驅動,擴展性較低。
用AOP實現的數據庫讀寫分離方案
採用這個方案一是因爲足夠輕便,不需要額外增加什麼東西;二是因爲AOP技術門檻較低,實現起來比較快速。
先介紹一下我們項目的技術背景,項目採用SpringBoot框架開發,採用Mybatis作爲ORM層,請求的調用鏈一般就是Controller調用Service,Service調用Mapper。
方案架構圖:
主要原理:
一般來講,我們對於方法的命名會遵循一定的規範,比如插入方法我們會以insert或者save等關鍵字開頭,更新方法我們會以update或edit等關鍵字開頭,查詢方法則會以select或find等關鍵字開頭。這種命名習慣可以很好的達到代碼自解釋性,讓人一眼就能望文知意。
我們正好可以利用這個特點來自定義一個AOP切面切到所有Service實現類裏的所有方法,再根據方法開頭的關鍵字來切換不同的數據源,最終達到寫入操作路由到主數據庫,查詢操作路由到三臺從庫的目的。
實現步驟:
1、定義並配置好主數據庫和從數據庫數據源信息。
2、將Mybatis默認使用的數據源改成我們自定義的可動態切換的RoutingDataSource。
在RoutingDataSource裏,我們將關鍵字與數據源一一對應起來存入map,key-value對應關係:
write->writeDatasource
read0->read1Datasource
read1->read2Datasource
read2->read3Datasource
3、繼承Spring框架的AbstractRoutingDataSource抽象類自定義一個RoutingDataSource類,並實現determineCurrentLookupKey方法,該方法就是我們實現數據源切換的關鍵所在。
determineCurrentLookupKey方法中,我們從DataSourceContext中獲取數據源對應的key值。
4、看一下DataSourceContext裏面的邏輯:
在DataSourceContext裏我們定義了一個ThreadLocal變量用來保存該線程所使用的的數據源信息,並且對外提供了兩個切換數據源的方法switchToReadDatasource和switchToWriteDatasource。
5、最後來看一下AOP切面類:
我們使用@Before註解定義2個切入點,在方法執行前進行切換數據源的增強處理。
在攔截到以select、get、find等關鍵字開頭的讀方法時我們切換至讀數據源,攔截到以insert、update、delete等關鍵字開頭的寫方法時我們切換至寫數據源。
寫個測試類來測試一下:
我們先插入一條數據、再來讀取這條數據,看下執行結果:
可以看到,對於insert操作程序自動切換到了write數據源,對於select操作程序又隨機切換到了read2數據源。
總結
一般來講大部分項目都是讀多寫少,數據量和併發量上去之後,單個數據庫往往會面臨比較大的性能壓力,數據庫讀寫分離方案正好適用於這種讀多寫少的應用場景。
讀寫分離更多的是擴展了數據庫的讀能力,多個數據庫共同分擔讀操作可以顯著提高系統併發能力。
任何方案有優點肯定也會有缺點,讀寫分離也不例外。讀寫分離需要對數據庫進行主從架構的改造,增加了系統的複雜性,同時主從之間數據同步也會存在延時,對於需要查詢精確數據的業務來說可能並不合適。
對於輕量級的業務來講,如果需要快速實現數據庫的讀寫分離可以採用AOP自己實現讀寫分離方案;如果有足夠的人力和技術,可以從系統層面對項目進行讀寫分離的改造,個人傾向於採用驅動層的一些開源解決方案,如Sharding-JDBC。