您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“yii2如何實現分庫分表”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“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分配了數據庫地址,數據庫名稱,配置如下:
'components' => [ 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=10.10.10.10;port=4005;dbname=wordpress', 'username' => 'wp', 'password' => '123', 'charset' => 'utf8', ],
這就是yii2做IOC的一個典型事例,model層默認就會取這個db做為mysql連接對象,所以model訪問都經過這個connection,可以從ActiveRecord類里看到。
class ActiveRecord extends BaseActiveRecord { /** * Returns the database connection used by this AR class. * By default, the "db" application component is used as the database connection. * You may override this method if you want to use a different database connection. * @return Connection the database connection used by this AR class. */ public static function getDb() { return Yii::$app->getDb(); }
追蹤下去,最后會走yii2的ioc去創建名字叫做”db”的這個component返回給model層使用。
abstract class Application extends Module { /** * Returns the database connection component. * @return \yii\db\Connection the database connection. */ public function getDb() { return $this->get('db'); }
yii2上述實現決定了只能連接了1臺數據庫服務器,選擇了其中1個database,那么具體訪問哪個表,是通過在Model里覆寫tableName這個static方法實現的,ActiveRecord會基于覆寫的tableName來決定表名是什么。
class OrderInfo extends \yii\db\ActiveRecord { /** * @inheritdoc * @return */ public static function tableName() { return 'order_info'; }
2)1庫N表:因為orderInfo數據量變大,各方面性能指標有所下降,而單機硬件性能還有較大冗余,于是可以考慮分多張order_info表,均攤數據量。假設我們要份8張表,那么可以依據uid(用戶ID)%8來決定訂單存儲在哪個表里。
然而1庫1表的時候,tableName()
返回是的order_info,于是理所應當的重載這個函數,提供一種動態變化的能力即可,例如:
class OrderInfo extends \yii\db\ActiveRecord { private static $partitionIndex_ = null; // 分表ID /** * 重置分區id * @param unknown $uid */ private static function resetPartitionIndex($uid = null) { $partitionCount = \Yii::$app->params['Order']['partitionCount']; self::$partitionIndex_ = $uid % $partitionCount; } /** * @inheritdoc */ public static function tableName() { return 'order_info' . self::$partitionIndex_; }
提供一個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動態變化類似。
class OrderInfo extends \yii\db\ActiveRecord { private static $databaseIndex_ = null; // 分庫ID private static $partitionIndex_ = null; // 分表ID /** * 重置分區id * @param unknown $uid */ private static function resetPartitionIndex($uid = null) { $databaseCount = \Yii::$app->params['Order']['databaseCount']; $partitionCount = \Yii::$app->params['Order']['partitionCount']; // 先決定分到哪一張表里 self::$partitionIndex_ = $uid % $partitionCount; // 再根據表的下標決定分到哪個庫里 self::$databaseIndex_ = intval(self::$partitionIndex_ / ($partitionCount / $databaseCount)); } /** * @inheritdoc */ public static function tableName() { $database = 'wordpress' . self::$databaseIndex_; $table = 'order_info' . self::$partitionIndex_; return $database . '.' . $table; }
在分表邏輯基礎上稍作改造,即可實現分庫。假設分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()
動態變化的。
'db0' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=10.10.10.10;port=6184;dbname=wordpress0', 'username' => 'wp', 'password' => '123', 'charset' => 'utf8', // 'tablePrefix' => 'ktv_', ], 'db1' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=10.10.10.11;port=6184;dbname=wordpress2', 'username' => 'wp', 'password' => '123', 'charset' => 'utf8', // 'tablePrefix' => 'ktv_', ], 'db2' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=10.10.10.12;port=6184;dbname=wordpress4', 'username' => 'wp', 'password' => '123', 'charset' => 'utf8', // 'tablePrefix' => 'ktv_', ], 'db3' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=10.10.10.13;port=6184;dbname=wordpress6', 'username' => 'wp', 'password' => '123', 'charset' => 'utf8', // 'tablePrefix' => 'ktv_', ],
覆寫getDb()
方法,根據庫下標返回不同的數據庫連接即可。
class OrderInfo extends \yii\db\ActiveRecord { private static $databaseIndex_ = null; // 分庫ID private static $partitionIndex_ = null; // 分表ID /** * 重置分區id * @param unknown $uid */ private static function resetPartitionIndex($uid = null) { $databaseCount = \Yii::$app->params['Order']['databaseCount']; $partitionCount = \Yii::$app->params['Order']['partitionCount']; // 先決定分到哪一張表里 self::$partitionIndex_ = $uid % $partitionCount; // 再根據表的下標決定分到哪個庫里 self::$databaseIndex_ = intval(self::$partitionIndex_ / ($partitionCount / $databaseCount)); } /** * 根據分庫分表,返回庫名.表名 */ public static function tableName() { $database = 'wordpress' . self::$databaseIndex_; $table = 'order_info' . self::$partitionIndex_; return $database . '.' . $table; } /** * 根據分庫結果,返回不同的數據庫連接 */ public static function getDb() { return \Yii::$app->get('db' . self::$databaseIndex_); }
這樣,無論是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如何實現分庫分表”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。