詳解yii2實(shí)現(xiàn)分庫分表的方案與思路
前言
大家可以從任何一個(gè)gii生成model類開始代碼上溯,會(huì)發(fā)現(xiàn):yii2的model層基于ActiveRecord實(shí)現(xiàn)DAO訪問數(shù)據(jù)庫的能力。
而ActiveRecord的繼承鏈可以繼續(xù)上溯,最終會(huì)發(fā)現(xiàn)model其實(shí)是一個(gè)component,而component是yii2做IOC的重要組成部分,提供了behaviors,event的能力供繼承者擴(kuò)展。
(IOC,component,behaviors,event等概念可以參考http://www.digpage.com/學(xué)習(xí))
先不考慮上面的一堆概念,一個(gè)站點(diǎn)發(fā)展歷程一般是1個(gè)庫1個(gè)表,1個(gè)庫N個(gè)表,M個(gè)庫N個(gè)表這樣走過來的,下面拿訂單表為例,分別說說。
1)1庫1表:yii2默認(rèn)采用PDO連接mysql,框架默認(rèn)會(huì)配置一個(gè)叫做db的component作為唯一的mysql連接對象,其中dsn分配了數(shù)據(jù)庫地址,數(shù)據(jù)庫名稱,配置如下:
'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的一個(gè)典型事例,model層默認(rèn)就會(huì)取這個(gè)db做為mysql連接對象,所以model訪問都經(jīng)過這個(gè)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(); }
追蹤下去,最后會(huì)走yii2的ioc去創(chuàng)建名字叫做”db”的這個(gè)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上述實(shí)現(xiàn)決定了只能連接了1臺數(shù)據(jù)庫服務(wù)器,選擇了其中1個(gè)database,那么具體訪問哪個(gè)表,是通過在Model里覆寫tableName這個(gè)static方法實(shí)現(xiàn)的,ActiveRecord會(huì)基于覆寫的tableName來決定表名是什么。
class OrderInfo extends \yii\db\ActiveRecord { /** * @inheritdoc * @return */ public static function tableName() { return 'order_info'; }
2)1庫N表:因?yàn)閛rderInfo數(shù)據(jù)量變大,各方面性能指標(biāo)有所下降,而單機(jī)硬件性能還有較大冗余,于是可以考慮分多張order_info表,均攤數(shù)據(jù)量。假設(shè)我們要份8張表,那么可以依據(jù)uid(用戶ID)%8來決定訂單存儲在哪個(gè)表里。
然而1庫1表的時(shí)候,tableName()
返回是的order_info,于是理所應(yīng)當(dāng)?shù)闹剌d這個(gè)函數(shù),提供一種動(dòng)態(tài)變化的能力即可,例如:
class OrderInfo extends \yii\db\ActiveRecord { private static $partitionIndex_ = null; // 分表ID /** * 重置分區(qū)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_; }
提供一個(gè)resetParitionIndex($uid)
函數(shù),在每次操作model之前主動(dòng)調(diào)用來標(biāo)記分表的下標(biāo),并且重載tableName來為model層拼接生成本次操作的表名。
3)M庫N表:1庫N表逐漸發(fā)展,單機(jī)存儲和性能達(dá)到瓶頸,只能將數(shù)據(jù)分散到多個(gè)服務(wù)器存儲,于是提出了分庫的需求。但是從”1庫1表”的框架實(shí)現(xiàn)邏輯來看,model層默認(rèn)取db配置作為mysql連接的話,是沒有辦法訪問多個(gè)mysql實(shí)例的,所以必須解決這個(gè)問題。
一般產(chǎn)生這個(gè)需求,產(chǎn)品已經(jīng)進(jìn)入中期穩(wěn)步發(fā)展階段。有2個(gè)思路解決M庫問題,1種是yii2通過改造直連多個(gè)地址進(jìn)行訪問多庫,1種是yii2仍舊只連1個(gè)地址,而這個(gè)地址部署了dbproxy,由dbproxy根據(jù)你訪問的庫名代理連接多個(gè)庫。
如果此前沒有熟練的運(yùn)維過dbproxy,并且php集群規(guī)模沒有大到單個(gè)mysql實(shí)例客戶端連接數(shù)過多拒絕服務(wù)的境地,那么第1種方案就可以解決了。否則,應(yīng)該選擇第2種方案。
無論選擇哪種方案,我們都應(yīng)該進(jìn)一步改造tableName()
函數(shù),為database名稱提供動(dòng)態(tài)變化的能力,和table動(dòng)態(tài)變化類似。
class OrderInfo extends \yii\db\ActiveRecord { private static $databaseIndex_ = null; // 分庫ID private static $partitionIndex_ = null; // 分表ID /** * 重置分區(qū)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; // 再根據(jù)表的下標(biāo)決定分到哪個(gè)庫里 self::$databaseIndex_ = intval(self::$partitionIndex_ / ($partitionCount / $databaseCount)); } /** * @inheritdoc */ public static function tableName() { $database = 'wordpress' . self::$databaseIndex_; $table = 'order_info' . self::$partitionIndex_; return $database . '.' . $table; }
在分表邏輯基礎(chǔ)上稍作改造,即可實(shí)現(xiàn)分庫。假設(shè)分8張表,那么分別是00,01,02,03…07,然后決定分4個(gè)庫,那么00,01表在00庫,02,03表在01庫,04,05表在02庫,06,07表在03庫,根據(jù)這個(gè)規(guī)律對應(yīng)的計(jì)算代碼如上。最終ActiveRecord生效的代碼都會(huì)類似于”select * from wordpress0.order_info1
″,這樣就可以解決連接dbproxy訪問多庫的需求了。
那么yii直接訪問多Mysql實(shí)例怎么做呢,其實(shí)類似tableName()
,我們只需要覆蓋getDb()
方法即可,同時(shí)要求我們首先配置好4個(gè)mysql實(shí)例,從而可以通過yii的application通過IOC設(shè)計(jì)來生成多個(gè)db連接,所有改動(dòng)如下:
先配置好4個(gè)數(shù)據(jù)庫,給予不同的component id以便區(qū)分,它們連接了不同的mysql實(shí)例,其中dsn里的dbname只要存在即可(防止PDO執(zhí)行use database
時(shí)候不存在報(bào)錯(cuò)),真實(shí)的庫名是通過tableName()
動(dòng)態(tài)變化的。
'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()
方法,根據(jù)庫下標(biāo)返回不同的數(shù)據(jù)庫連接即可。
class OrderInfo extends \yii\db\ActiveRecord { private static $databaseIndex_ = null; // 分庫ID private static $partitionIndex_ = null; // 分表ID /** * 重置分區(qū)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; // 再根據(jù)表的下標(biāo)決定分到哪個(gè)庫里 self::$databaseIndex_ = intval(self::$partitionIndex_ / ($partitionCount / $databaseCount)); } /** * 根據(jù)分庫分表,返回庫名.表名 */ public static function tableName() { $database = 'wordpress' . self::$databaseIndex_; $table = 'order_info' . self::$partitionIndex_; return $database . '.' . $table; } /** * 根據(jù)分庫結(jié)果,返回不同的數(shù)據(jù)庫連接 */ public static function getDb() { return \Yii::$app->get('db' . self::$databaseIndex_); }
這樣,無論是yii連接多個(gè)mysql實(shí)例,還是yii連接1個(gè)dbproxy,都可以實(shí)現(xiàn)了。
網(wǎng)上有一些例子,試圖通過component的event機(jī)制,通過在component的配置中指定onUpdate,onBeforeSave等自定義event去hook不同的DAO操作來隱式(自動(dòng))的變更database或者connection或者tablename的做法,都是基于model object才能實(shí)現(xiàn)的,如果直接使用model class的類似updateAll()
方法的話,是繞過DAO直接走了PDO的,不會(huì)觸發(fā)這些event,所以并不是完備的解決方案。
這樣的方案原理簡單,方案對框架無侵入,只是每次DB操作前都要顯式的resetPartitionIndex($uid)
調(diào)用。如果要做到用戶無感知,那必須對ActiveRecord類進(jìn)行繼承,進(jìn)一步覆蓋所有class method的實(shí)現(xiàn)以便插入選庫選表邏輯,代價(jià)過高。
補(bǔ)充:關(guān)于分庫分表的一些實(shí)踐細(xì)節(jié),分表數(shù)量建議2^n,例如n=3的情況下分8張表,然后確定一下幾個(gè)庫,庫數(shù)量是2^m,但要<=表數(shù)量,例如這里1個(gè)庫,2個(gè)庫,4個(gè)庫,8個(gè)庫都是可以的,表順序坐落在這些庫里即可。
為什么數(shù)量都是2指數(shù),是因?yàn)槿绻媾R擴(kuò)容需求,數(shù)據(jù)的遷移將方便一些。假設(shè)分了2張表,數(shù)據(jù)按uid%2打散,要擴(kuò)容成4張表,那么只需要把表0的部分?jǐn)?shù)據(jù)遷移到表2,表1的部分?jǐn)?shù)據(jù)遷移到表3,即可完成擴(kuò)容,也就是uid%2和uid%4造成的遷移量是很小的,這個(gè)可以自己算一下。
總結(jié)
以上就是關(guān)于yii2實(shí)現(xiàn)分庫分表的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。
- php 分庫分表hash算法
- 1億條數(shù)據(jù)如何分表100張到Mysql數(shù)據(jù)庫中(PHP)
- PHP操作mysql數(shù)據(jù)庫分表的方法
- php實(shí)現(xiàn)mysql數(shù)據(jù)庫分表分段備份
- Yii2.0高級框架數(shù)據(jù)庫增刪改查的一些操作
- YII2數(shù)據(jù)庫查詢實(shí)踐
- Yii2框架數(shù)據(jù)庫簡單的增刪改查語法小結(jié)
- yii2.0數(shù)據(jù)庫遷移教程【多個(gè)數(shù)據(jù)庫同時(shí)同步數(shù)據(jù)】
- Yii2——使用數(shù)據(jù)庫操作匯總(增刪查改、事務(wù))
- 使用Yii2實(shí)現(xiàn)主從數(shù)據(jù)庫設(shè)置
相關(guān)文章
php使用flock阻塞寫入文件和非阻塞寫入文件的實(shí)例講解
下面小編就為大家?guī)硪黄猵hp使用flock阻塞寫入文件和非阻塞寫入文件的實(shí)例講解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07thinkPHP5框架實(shí)現(xiàn)分頁查詢功能的方法示例
這篇文章主要介紹了thinkPHP5框架實(shí)現(xiàn)分頁查詢功能的方法,結(jié)合實(shí)例形式分析了thinkPHP5實(shí)現(xiàn)分頁查詢功能的相關(guān)控制器、模板等操作技巧,需要的朋友可以參考下2018-03-03php 404錯(cuò)誤頁面實(shí)現(xiàn)代碼
如果訪問您站點(diǎn)的用戶由于各種原因暫時(shí)出現(xiàn)無法訪問頁面的錯(cuò)誤,如何給他一個(gè)友好的答復(fù),并且你也知道發(fā)生了這個(gè)錯(cuò)誤,看看下面這段程序,是用來定制404錯(cuò)誤頁面和發(fā)通知給網(wǎng)管。2009-06-06Yii2下點(diǎn)擊驗(yàn)證碼的切換實(shí)例代碼
本篇文章主要介紹了Yii2下驗(yàn)證碼的切換實(shí)例代碼,實(shí)例分析了Yii2下驗(yàn)證碼的切換的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-03-03PHP 以POST方式提交XML、獲取XML,解析XML詳解及實(shí)例
這篇文章主要介紹了PHP 以POST方式提交XML、獲取XML,解析XML詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2016-10-10CentOS 上搭建 PHP7 開發(fā)測試環(huán)境
本文給大家分享的是作者在centos上搭建部署php7的開發(fā)測試環(huán)境的全部過程,非常的細(xì)致,有需要的小伙伴可以參考下2017-02-02