PHP Db类强制读主库(master)的设计

这段时间Db不给力,经常出现主从同步延迟或者挂掉的情况,导致很多业务出现异常,大家就讨论怎么样让程序强制读master,关于这个方面的讨论比较激烈,主要为两种。

  • 底层DB类不应该关注主从的抉择,应该交于业务侧的用户抉择,这样业务层使用起来比较灵活。
  • 业务层的用户不应该关注主从的抉择,应该交给DB层解决,因为如果业务层人为不小心把强制读master的代码上到了压力大的线上,会对Db造成很大的压力,会出现很多不可控的因素。

我本人持第二种观点,程序中的bug,大部分都是人的失误造成的,在编程的世界里,人才是最大的bug,不应该把业务的稳定性依赖于人。

但是现在业务上主从的问题要解决,并且再快的主从同步也会出现延迟,在不依赖其他的存储工具的情况下,使用强制读master是必须要实现的功能,那么怎么才能设计出来一个安全性高一些的操作方式呢?

最初的想法,通过在调用查询函数时加入强制读master参数。

$daoCity = new \dao\City();

$ret = $daoCity->findAllBySql("select * from city limit 1",$useMaster=true);

这个想法很快被淘汰了,原因如下。

  • 所有要修改的读取函数太多,太麻烦。
  • 使用方法不优美,忍不了。
  • 如果出现不了解的程序员直接拷贝代码,将其上线到线上环境,可能出现事故。

接着出现了第二版的设计,通过一个函数来开启master读取,再主动关闭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();

第二版的想法比第一版好了一些,但是还不是我想要的,否定的原因如下。

  • 需要额外的函数。
  • 大概率忘记关掉master查询。
  • 解决方法还是不够优雅,不能忍。

仔细思考,衍生出了第三版的设计。通过链式调用实现强制读master。

$daoCity = new \dao\City(false);
/**
 * 自动主从
 */
$ret = $daoCity->findAllBySql("select * from city limit 1");

/**
 * 强制master
 */
$ret = $daoCity->forceMaster()->findAllBySql("select * from city limit 1");

  • 解决方式对老代码几乎没有入侵。
  • 解决方法优雅。
  • 通过直接的函数名可以有效的提示使用者,这是master操作。
  • 无需主动关闭,程序实现隔离。

那么接下来我们看下怎么实现这个链式调用呢?并且无需关注主从的切换。

这是未修改过的代码。

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);
      
    }


    /**
     * 根据sql查询多条数据
     *
     * @param $sql
     * @return mixed
     */
    public function findAllBySql($sql)
    {
        return $this->tqtMysqli->queryRows($sql);
    }

代码逻辑不用细讲了,就是一个简单的Db操作父类。子类集成后实现对个表的操作。

下面加入对这个类的第一次修改。

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,这样就可以实现链式调用了,是不是感觉大功告成了。
慢着,这里有个安全问题,或者说是漏洞,将原有的
protected $tqtMysqli; 改成了 public $tqtMysqli;
带来了几个问题

  • 将不应该暴露给使用者的属性暴露出去了
  • 用户可以绕过我的 forceMaster()方法直接使用 $daoCity->tqtMysqli->forceMaster();操作master,这样我们的设计很可能被偷懒的程序员绕过。
  • 这样不优雅,不能忍。

接下来思考了各种方法,从对象复制clone中找到了灵感。
先看下php的文档的描述。

对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。

通过这个方法的帮助,做出以下设计

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);
    }

    /**
     * 发生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 在对象被复制的时候可以修改复制的对象属性,符合我们new一个类,做master操作。
  • __clone 函数不能被直接调用,保证了强制读master的操作权限收敛,避免人为的绕过
  • 解决方式优雅。

以上就是我对Db加入强制读master的一个设计。任何一个核心功能的添加都不能随意,都要深思熟虑,尽量的收敛权限,对使用者一定要报以不信任。代码设计尽量的优雅简洁。
如果有问题可以留言沟通。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容