這段時間Db不給力常柄,經(jīng)常出現(xiàn)主從同步延遲或者掛掉的情況,導(dǎo)致很多業(yè)務(wù)出現(xiàn)異常飘痛,大家就討論怎么樣讓程序強制讀master,關(guān)于這個方面的討論比較激烈容握,主要為兩種宣脉。
- 底層DB類不應(yīng)該關(guān)注主從的抉擇,應(yīng)該交于業(yè)務(wù)側(cè)的用戶抉擇剔氏,這樣業(yè)務(wù)層使用起來比較靈活塑猖。
- 業(yè)務(wù)層的用戶不應(yīng)該關(guān)注主從的抉擇,應(yīng)該交給DB層解決谈跛,因為如果業(yè)務(wù)層人為不小心把強制讀master的代碼上到了壓力大的線上羊苟,會對Db造成很大的壓力,會出現(xiàn)很多不可控的因素感憾。
我本人持第二種觀點蜡励,程序中的bug,大部分都是人的失誤造成的阻桅,在編程的世界里凉倚,人才是最大的bug,不應(yīng)該把業(yè)務(wù)的穩(wěn)定性依賴于人嫂沉。
但是現(xiàn)在業(yè)務(wù)上主從的問題要解決稽寒,并且再快的主從同步也會出現(xiàn)延遲,在不依賴其他的存儲工具的情況下趟章,使用強制讀master是必須要實現(xiàn)的功能杏糙,那么怎么才能設(shè)計出來一個安全性高一些的操作方式呢慎王?
最初的想法,通過在調(diào)用查詢函數(shù)時加入強制讀master參數(shù)。
$daoCity = new \dao\City();
$ret = $daoCity->findAllBySql("select * from city limit 1",$useMaster=true);
這個想法很快被淘汰了搔啊,原因如下柬祠。
- 所有要修改的讀取函數(shù)太多,太麻煩负芋。
- 使用方法不優(yōu)美漫蛔,忍不了。
- 如果出現(xiàn)不了解的程序員直接拷貝代碼旧蛾,將其上線到線上環(huán)境莽龟,可能出現(xiàn)事故。
接著出現(xiàn)了第二版的設(shè)計,通過一個函數(shù)來開啟master讀取锨天,再主動關(guān)閉master讀取毯盈。
$daoCity = new \dao\City();
$daoCity->forceMaster();
$ret = $daoCity->findAllBySql("select * from city limit 1");
$ret = $daoCity->findAllBySql("select * from city limit 1");
$ret = $daoCity->findAllBySql("select * from city limit 1";
$ret = $daoCity->findAllBySql("select * from city limit 1");
$daoCity->forceMasterOver();
第二版的想法比第一版好了一些,但是還不是我想要的病袄,否定的原因如下搂赋。
- 需要額外的函數(shù)。
- 大概率忘記關(guān)掉master查詢益缠。
- 解決方法還是不夠優(yōu)雅脑奠,不能忍。
仔細思考幅慌,衍生出了第三版的設(shè)計宋欺。通過鏈?zhǔn)秸{(diào)用實現(xiàn)強制讀master。
$daoCity = new \dao\City(false);
/**
* 自動主從
*/
$ret = $daoCity->findAllBySql("select * from city limit 1");
/**
* 強制master
*/
$ret = $daoCity->forceMaster()->findAllBySql("select * from city limit 1");
- 解決方式對老代碼幾乎沒有入侵胰伍。
- 解決方法優(yōu)雅齿诞。
- 通過直接的函數(shù)名可以有效的提示使用者,這是master操作骂租。
- 無需主動關(guān)閉祷杈,程序?qū)崿F(xiàn)隔離。
那么接下來我們看下怎么實現(xiàn)這個鏈?zhǔn)秸{(diào)用呢渗饮?并且無需關(guān)注主從的切換但汞。
這是未修改過的代碼。
class TqtDaoBase
{
protected $dbConf;
protected $table;
protected $pk = "id";
protected $tqtMysqli;
/**
* TqtDaoBase constructor.
*
* @param bool $needReconnect
* @throws \Exception
*/
public function __construct()
{
if (empty($this->dbConf)) {
throw new \Exception("db config is null", 1);
}
$this->tqtMysqli = TqtMysqli::getInstance($this->dbConf);
}
/**
* 根據(jù)sql查詢多條數(shù)據(jù)
*
* @param $sql
* @return mixed
*/
public function findAllBySql($sql)
{
return $this->tqtMysqli->queryRows($sql);
}
代碼邏輯不用細講了抽米,就是一個簡單的Db操作父類。子類集成后實現(xiàn)對個表的操作糙置。
下面加入對這個類的第一次修改云茸。
class TqtDaoBase
{
protected $dbConf;
protected $table;
protected $pk = "id";
public $tqtMysqli; //將這個屬性改成 公開的
protected static $MASTER_INSTANCE;
/**
* TqtDaoBase constructor.
*
* @param bool $needReconnect
* @throws \Exception
*/
public function __construct()
{
if (empty($this->dbConf)) {
throw new \Exception("db config is null", 1);
}
$this->tqtMysqli = TqtMysqli::getInstance($this->dbConf);
}
/**
* @return TqtDaoBase
*/
public function forceMaster()
{
if (empty(self::$MASTER_INSTANCE)) {
$className = get_called_class();
$dbLink = new $className;
$dbLink->tqtMysqli->forceMaster();
self::$MASTER_INSTANCE = $dbLink;
}
return self::$MASTER_INSTANCE;
}
代碼的邏輯主要就是new一個新的自己出來,將其中的Db 連接指向master谤饭,這樣就可以實現(xiàn)鏈?zhǔn)秸{(diào)用了标捺,是不是感覺大功告成了懊纳。
慢著,這里有個安全問題亡容,或者說是漏洞嗤疯,將原有的
protected $tqtMysqli; 改成了 public $tqtMysqli;
帶來了幾個問題
- 將不應(yīng)該暴露給使用者的屬性暴露出去了
- 用戶可以繞過我的
forceMaster()
方法直接使用$daoCity->tqtMysqli->forceMaster();
操作master,這樣我們的設(shè)計很可能被偷懶的程序員繞過闺兢。 - 這樣不優(yōu)雅茂缚,不能忍。
接下來思考了各種方法屋谭,從對象復(fù)制clone中找到了靈感脚囊。
先看下php的文檔的描述。
對象復(fù)制可以通過 clone 關(guān)鍵字來完成(如果可能桐磁,這將調(diào)用對象的 __clone() 方法)悔耘。對象中的 __clone() 方法不能被直接調(diào)用。
通過這個方法的幫助我擂,做出以下設(shè)計
class TqtDaoBase
{
protected $dbConf;
protected $table;
protected $pk = "id";
protected $tqtMysqli;
protected static $MASTER_INSTANCE;
/**
* TqtDaoBase constructor.
*
* @throws \Exception
*/
public function __construct()
{
if (empty($this->dbConf)) {
throw new \Exception("db config is null", 1);
}
$this->tqtMysqli = TqtMysqli::getInstance($this->dbConf);
}
/**
* 發(fā)生clone 新建一個Db連接衬以,并指向master
*/
public function __clone()
{
$this->tqtMysqli = TqtMysqli::getInstance($this->dbConf);
$this->tqtMysqli->forceMaster();
}
/**
* @return TqtDaoBase
*/
public function forceMaster()
{
if (empty(self::$MASTER_INSTANCE)) {
self::$MASTER_INSTANCE = clone $this;
}
return self::$MASTER_INSTANCE;
}
- __clone 在對象被復(fù)制的時候可以修改復(fù)制的對象屬性,符合我們new一個類校摩,做master操作看峻。
- __clone 函數(shù)不能被直接調(diào)用,保證了強制讀master的操作權(quán)限收斂秧耗,避免人為的繞過
- 解決方式優(yōu)雅备籽。
以上就是我對Db加入強制讀master的一個設(shè)計。任何一個核心功能的添加都不能隨意分井,都要深思熟慮车猬,盡量的收斂權(quán)限,對使用者一定要報以不信任尺锚。代碼設(shè)計盡量的優(yōu)雅簡潔珠闰。
如果有問題可以留言溝通。