javascript
简单好用!利用Spring AOP技术10分钟实现一个数据库读写分离方案
前言
最近我們的APP在線用戶越來越多,接口的響應速度也是越來越慢,經過運維排查發現是由于并發查詢太多導致的數據庫壓力比較大,架構師經過調研給出了數據庫讀寫分離的解決方案,為了快速解決問題,我們最終采用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。
總結
以上是生活随笔為你收集整理的简单好用!利用Spring AOP技术10分钟实现一个数据库读写分离方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 4 次版本迭代,我们将项目性能提升了 3
- 下一篇: 如何优雅的处理业务逻辑中的定时和延时问题