詳解yii2實現分庫分表的方案與思路
前言
大家可以從任何一個gii生成model類開始程式碼上溯,會發現:yii2的model層基於ActiveRecord實現DAO訪問資料庫的能力。
而ActiveRecord的繼承鏈可以繼續上溯,最終會發現model其實是一個component,而component是yii2做IOC的重要組成部分,提供了behaviors,event的能力供繼承者擴充套件。
(IOC,component,behaviors,event等概念可以參考http://www.digpage.com/學習)
先不考慮上面的一堆概念,一個站點發展歷程一般是1個庫1個表,1個庫N個表,M個庫N個表這樣走過來的,下面拿訂單表為例,分別說說。
1)1庫1表:yii2預設採用PDO連線mysql,框架預設會配置一個叫做db的component作為唯一的mysql連線物件,其中dsn分配了資料庫地址,資料庫名稱,配置如下:
1 2 3 4 5 6 7 8 |
'wp' ,
|
這就是yii2做IOC的一個典型事例,model層預設就會取這個db做為mysql連線物件,所以model訪問都經過這個connection,可以從ActiveRecord類裡看到。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
追蹤下去,最後會走yii2的ioc去建立名字叫做”db”的這個component返回給model層使用。
1 2 3 4 5 6 7 8 9 |
|
yii2上述實現決定了只能連線了1臺數據庫伺服器,選擇了其中1個database,那麼具體訪問哪個表,是通過在Model裡覆寫tableName這個static方法實現的,ActiveRecord會基於覆寫的tableName來決定表名是什麼。
1 2 3 4 5 6 7 8 9 10 |
|
2)1庫N表:因為orderInfo資料量變大,各方面效能指標有所下降,而單機硬體效能還有較大冗餘,於是可以考慮分多張order_info表,均攤資料量。假設我們要份8張表,那麼可以依據uid(使用者ID)%8來決定訂單儲存在哪個表裡。
然而1庫1表的時候,tableName()
返回是的order_info,於是理所應當的過載這個函式,提供一種動態變化的能力即可,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
提供一個resetParitionIndex($uid)
函式,在每次操作model之前主動呼叫來標記分表的下標,並且過載tableName來為model層拼接生成本次操作的表名。
3)M庫N表:1庫N表逐漸發展,單機儲存和效能達到瓶頸,只能將資料分散到多個伺服器儲存,於是提出了分庫的需求。但是從”1庫1表”的框架實現邏輯來看,model層預設取db配置作為mysql連線的話,是沒有辦法訪問多個mysql例項的,所以必須解決這個問題。
一般產生這個需求,產品已經進入中期穩步發展階段。有2個思路解決M庫問題,1種是yii2通過改造直連多個地址進行訪問多庫,1種是yii2仍舊只連1個地址,而這個地址部署了dbproxy,由dbproxy根據你訪問的庫名代理連線多個庫。
如果此前沒有熟練的運維過dbproxy,並且php叢集規模沒有大到單個mysql例項客戶端連線數過多拒絕服務的境地,那麼第1種方案就可以解決了。否則,應該選擇第2種方案。
無論選擇哪種方案,我們都應該進一步改造tableName()
函式,為database名稱提供動態變化的能力,和table動態變化類似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
在分表邏輯基礎上稍作改造,即可實現分庫。假設分8張表,那麼分別是00,01,02,03…07,然後決定分4個庫,那麼00,01表在00庫,02,03表在01庫,04,05表在02庫,06,07表在03庫,根據這個規律對應的計算程式碼如上。最終ActiveRecord生效的程式碼都會類似於”select * from wordpress0.order_info1
″,這樣就可以解決連線dbproxy訪問多庫的需求了。
那麼yii直接訪問多Mysql例項怎麼做呢,其實類似tableName()
,我們只需要覆蓋getDb()
方法即可,同時要求我們首先配置好4個mysql例項,從而可以通過yii的application通過IOC設計來生成多個db連線,所有改動如下:
先配置好4個數據庫,給予不同的component id以便區分,它們連線了不同的mysql例項,其中dsn裡的dbname只要存在即可(防止PDO執行use database
時候不存在報錯),真實的庫名是通過tableName()
動態變化的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
覆寫getDb()
方法,根據庫下標返回不同的資料庫連線即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
這樣,無論是yii連線多個mysql例項,還是yii連線1個dbproxy,都可以實現了。
網上有一些例子,試圖通過component的event機制,通過在component的配置中指定onUpdate,onBeforeSave等自定義event去hook不同的DAO操作來隱式(自動)的變更database或者connection或者tablename的做法,都是基於model object才能實現的,如果直接使用model class的類似updateAll()
方法的話,是繞過DAO直接走了PDO的,不會觸發這些event,所以並不是完備的解決方案。
這樣的方案原理簡單,方案對框架無侵入,只是每次DB操作前都要顯式的resetPartitionIndex($uid)
呼叫。如果要做到使用者無感知,那必須對ActiveRecord類進行繼承,進一步覆蓋所有class method的實現以便插入選庫選表邏輯,代價過高。
補充:關於分庫分表的一些實踐細節,分表數量建議2^n,例如n=3的情況下分8張表,然後確定一下幾個庫,庫數量是2^m,但要<=表數量,例如這裡1個庫,2個庫,4個庫,8個庫都是可以的,表順序坐落在這些庫裡即可。
為什麼數量都是2指數,是因為如果面臨擴容需求,資料的遷移將方便一些。假設分了2張表,資料按uid%2打散,要擴容成4張表,那麼只需要把表0的部分資料遷移到表2,表1的部分資料遷移到表3,即可完成擴容,也就是uid%2和uid%4造成的遷移量是很小的,這個可以自己算一下。
總結
以上就是關於yii2實現分庫分表的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家