溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

yii2如何實(shí)現(xiàn)分庫(kù)分表

發(fā)布時(shí)間:2021-07-07 10:43:22 來(lái)源:億速云 閱讀:114 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要為大家展示了“yii2如何實(shí)現(xiàn)分庫(kù)分表”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“yii2如何實(shí)現(xiàn)分庫(kù)分表”這篇文章吧。

大家可以從任何一個(gè)gii生成model類(lèi)開(kāi)始代碼上溯,會(huì)發(fā)現(xiàn):yii2的model層基于ActiveRecord實(shí)現(xiàn)DAO訪問(wèn)數(shù)據(jù)庫(kù)的能力。

而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è)庫(kù)1個(gè)表,1個(gè)庫(kù)N個(gè)表,M個(gè)庫(kù)N個(gè)表這樣走過(guò)來(lái)的,下面拿訂單表為例,分別說(shuō)說(shuō)。

1)1庫(kù)1表:yii2默認(rèn)采用PDO連接mysql,框架默認(rèn)會(huì)配置一個(gè)叫做db的component作為唯一的mysql連接對(duì)象,其中dsn分配了數(shù)據(jù)庫(kù)地址,數(shù)據(jù)庫(kù)名稱(chēng),配置如下:

'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連接對(duì)象,所以model訪問(wèn)都經(jīng)過(guò)這個(gè)connection,可以從ActiveRecord類(lèi)里看到。

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臺(tái)數(shù)據(jù)庫(kù)服務(wù)器,選擇了其中1個(gè)database,那么具體訪問(wèn)哪個(gè)表,是通過(guò)在Model里覆寫(xiě)tableName這個(gè)static方法實(shí)現(xiàn)的,ActiveRecord會(huì)基于覆寫(xiě)的tableName來(lái)決定表名是什么。

class OrderInfo extends \yii\db\ActiveRecord
{
 /**
 * @inheritdoc
 * @return
 */
 public static function tableName()
 {
 return 'order_info';
 }

 2)1庫(kù)N表:因?yàn)閛rderInfo數(shù)據(jù)量變大,各方面性能指標(biāo)有所下降,而單機(jī)硬件性能還有較大冗余,于是可以考慮分多張order_info表,均攤數(shù)據(jù)量。假設(shè)我們要份8張表,那么可以依據(jù)uid(用戶ID)%8來(lái)決定訂單存儲(chǔ)在哪個(gè)表里。

然而1庫(kù)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)用來(lái)標(biāo)記分表的下標(biāo),并且重載tableName來(lái)為model層拼接生成本次操作的表名。

3)M庫(kù)N表:1庫(kù)N表逐漸發(fā)展,單機(jī)存儲(chǔ)和性能達(dá)到瓶頸,只能將數(shù)據(jù)分散到多個(gè)服務(wù)器存儲(chǔ),于是提出了分庫(kù)的需求。但是從”1庫(kù)1表”的框架實(shí)現(xiàn)邏輯來(lái)看,model層默認(rèn)取db配置作為mysql連接的話,是沒(méi)有辦法訪問(wèn)多個(gè)mysql實(shí)例的,所以必須解決這個(gè)問(wèn)題。

一般產(chǎn)生這個(gè)需求,產(chǎn)品已經(jīng)進(jìn)入中期穩(wěn)步發(fā)展階段。有2個(gè)思路解決M庫(kù)問(wèn)題,1種是yii2通過(guò)改造直連多個(gè)地址進(jìn)行訪問(wèn)多庫(kù),1種是yii2仍舊只連1個(gè)地址,而這個(gè)地址部署了dbproxy,由dbproxy根據(jù)你訪問(wèn)的庫(kù)名代理連接多個(gè)庫(kù)。

如果此前沒(méi)有熟練的運(yùn)維過(guò)dbproxy,并且php集群規(guī)模沒(méi)有大到單個(gè)mysql實(shí)例客戶端連接數(shù)過(guò)多拒絕服務(wù)的境地,那么第1種方案就可以解決了。否則,應(yīng)該選擇第2種方案。

無(wú)論選擇哪種方案,我們都應(yīng)該進(jìn)一步改造tableName()函數(shù),為database名稱(chēng)提供動(dòng)態(tài)變化的能力,和table動(dòng)態(tài)變化類(lèi)似。

class OrderInfo extends \yii\db\ActiveRecord {
 
private static $databaseIndex_ = null; // 分庫(kù)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è)庫(kù)里
 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)分庫(kù)。假設(shè)分8張表,那么分別是00,01,02,03…07,然后決定分4個(gè)庫(kù),那么00,01表在00庫(kù),02,03表在01庫(kù),04,05表在02庫(kù),06,07表在03庫(kù),根據(jù)這個(gè)規(guī)律對(duì)應(yīng)的計(jì)算代碼如上。最終ActiveRecord生效的代碼都會(huì)類(lèi)似于”select * from wordpress0.order_info1″,這樣就可以解決連接dbproxy訪問(wèn)多庫(kù)的需求了。

那么yii直接訪問(wèn)多Mysql實(shí)例怎么做呢,其實(shí)類(lèi)似tableName() ,我們只需要覆蓋getDb()方法即可,同時(shí)要求我們首先配置好4個(gè)mysql實(shí)例,從而可以通過(guò)yii的application通過(guò)IOC設(shè)計(jì)來(lái)生成多個(gè)db連接,所有改動(dòng)如下:

先配置好4個(gè)數(shù)據(jù)庫(kù),給予不同的component id以便區(qū)分,它們連接了不同的mysql實(shí)例,其中dsn里的dbname只要存在即可(防止PDO執(zhí)行use database時(shí)候不存在報(bào)錯(cuò)),真實(shí)的庫(kù)名是通過(guò)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_',
],

覆寫(xiě)getDb()方法,根據(jù)庫(kù)下標(biāo)返回不同的數(shù)據(jù)庫(kù)連接即可。

class OrderInfo extends \yii\db\ActiveRecord
{
 private static $databaseIndex_ = null; // 分庫(kù)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è)庫(kù)里
 self::$databaseIndex_ = intval(self::$partitionIndex_ / ($partitionCount / $databaseCount));
 }
 
 /**
 * 根據(jù)分庫(kù)分表,返回庫(kù)名.表名
 */
 public static function tableName()
 {
 $database = 'wordpress' . self::$databaseIndex_;
 $table = 'order_info' . self::$partitionIndex_;
 return $database . '.' . $table;
 }
 
 /**
 * 根據(jù)分庫(kù)結(jié)果,返回不同的數(shù)據(jù)庫(kù)連接
 */
 public static function getDb()
 {
 return \Yii::$app->get('db' . self::$databaseIndex_);
 }

這樣,無(wú)論是yii連接多個(gè)mysql實(shí)例,還是yii連接1個(gè)dbproxy,都可以實(shí)現(xiàn)了。

網(wǎng)上有一些例子,試圖通過(guò)component的event機(jī)制,通過(guò)在component的配置中指定onUpdate,onBeforeSave等自定義event去hook不同的DAO操作來(lái)隱式(自動(dòng))的變更database或者connection或者tablename的做法,都是基于model object才能實(shí)現(xiàn)的,如果直接使用model class的類(lèi)似updateAll()方法的話,是繞過(guò)DAO直接走了PDO的,不會(huì)觸發(fā)這些event,所以并不是完備的解決方案。

這樣的方案原理簡(jiǎn)單,方案對(duì)框架無(wú)侵入,只是每次DB操作前都要顯式的resetPartitionIndex($uid)調(diào)用。如果要做到用戶無(wú)感知,那必須對(duì)ActiveRecord類(lèi)進(jìn)行繼承,進(jìn)一步覆蓋所有class method的實(shí)現(xiàn)以便插入選庫(kù)選表邏輯,代價(jià)過(guò)高。

補(bǔ)充:關(guān)于分庫(kù)分表的一些實(shí)踐細(xì)節(jié),分表數(shù)量建議2^n,例如n=3的情況下分8張表,然后確定一下幾個(gè)庫(kù),庫(kù)數(shù)量是2^m,但要<=表數(shù)量,例如這里1個(gè)庫(kù),2個(gè)庫(kù),4個(gè)庫(kù),8個(gè)庫(kù)都是可以的,表順序坐落在這些庫(kù)里即可。
為什么數(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è)可以自己算一下。

以上是“yii2如何實(shí)現(xiàn)分庫(kù)分表”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI