• 读discuzx3.1 数据库层笔记


    最近开始在看discuzx3.1的代码,看到数据库层的实现,discuzx的数据库层能够支撑数据库分库,分布式部署,主要水平分表,也可以很方便的支持其他数据库。性能上,可以做读写分离,支持数据缓存。可以说,是一个很完善的数据库层的解决方案了。

    数据库层分为三层,业务逻辑层封装,抽象层,和驱动层。如图:


    其中,数据抽象层封装定义数据库操作,负责解析sql语句,连接底层驱动执行sql,并数据安全过滤。

    数据库抽象层由discuzx_database类实现,该类所有的成员变量和方法都是静态的,可以直接调用。本类中,init 方法用来初始化底层驱动,设置数据库配置,并且建立默认的数据库连接。table方法,设置当前操作的数据表,这个方法很关键,数据库的分布式部署,读写 分离,都是由表名来确定的。这个在驱动层具体实现。其他如insert,delete,update,fetch_all等等方法是封装了数据表的基本操 作,checkquery方法负责数据的安全检查,防注入。query方法负责解析sql语句,调用驱动层,执行sql语 句,quote,field_quote,field,format等方法负责sql语句的格式化。

    数据库驱动层选择数据库,直接连接数据库,跟数据库交互。目前 discuzx默认提供两种底层驱动,mysql和mysqli。以mysql为例,整个驱动层其实由db_dirver_mysql和 db_driver_mysql_slave两个类外加数据库配置文件实现,db_driver_mysql_salve继承于 db_driver_mysql。

    配置文件中(./config/config_global.php )可配置数据表的读写分离,包括多台从服务器,分布式部署。

     $_config['db']['1']['slave']['1']['dbhost'] = 'localhost';
     $_config['db']['1']['slave']['1']['dbuser'] = 'root';
     $_config['db']['1']['slave']['1']['dbpw'] = 'root';
     $_config['db']['1']['slave']['1']['dbcharset'] = 'gbk';

    可配置 禁用从数据库的数据表, 表名字之间使用逗号分割

     * @example common_session, common_member 这两个表仅从主服务器读写, 不使用从服务器
     * $_config['db']['common']['slave_except_table'] = 'common_session, common_member';

    可根据数据表进行分布式部署

     @example 将 common_member 部署到第二服务器, common_session 部署在第三服务器, 则设置为
     $_config['db']['map']['common_member'] = 2;
     $_config['db']['map']['common_session'] = 3;

    先看在抽象层(DB 类)初始化。在discuz_application的init时,调用_init_db()方法

    private function _init_db() {
    if($this->init_db) {
    $driver = function_exists('mysql_connect') ? 'db_driver_mysql' : 'db_driver_mysqli';
    if(getglobal('config/db/slave')) {
    $driver = function_exists('mysql_connect') ? 'db_driver_mysql_slave' : 'db_driver_mysqli_slave';
    }
    DB::init($driver, $this->config['db']);
    }
    }

    抽象层DB的init

    public static function init($driver, $config) {
    self::$driver = $driver;
    self::$db = new $driver;
    self::$db->set_config($config);
    self::$db->connect();
    }


    根据配置文件中的读写分离来确定底层dirver,如果支持从库,则初始化db_dirver_mysql_salve。

    db_driver_mysql中的table_name表是实现分库的钥匙,根据表名来确定当前需要连接的数据库服务器编号,并连接数据库,存入连接池中,同时设置为当前连接。

    function table_name($tablename) {
    if(!empty($this->map) && !empty($this->map[$tablename])) {
    $id = $this->map[$tablename];
    if(!$this->link[$id]) {
    $this->connect($id);
    }
    $this->curlink = $this->link[$id];
    } else {
    $this->curlink = $this->link[1];
    }
    return $this->tablepre.$tablename;
    }

    这里提一下,$this->link可以实现数据库只需要连接一次,不需要重复连接。

    在写入数据时,调用主库,而读取数据时调用从库,见db_driver_mysql_salve, 该类覆盖了table_name表,判断该表是否需要读写分离,并且确定该表的数据库服务器组编号

    public function table_name($tablename) {
    $this->tablename = $tablename;
    if(!$this->slaveexcept && $this->excepttables) {
    $this->slaveexcept = in_array($tablename, $this->excepttables, true);
    }
    $this->serverid = isset($this->map[$this->tablename]) ? $this->map[$this->tablename] : 1;
    return $this->tablepre.$tablename;
    }

    重写了query方法,判断如果当前需要读从库则选择并连接从库。

    public function query($sql, $silent = false, $unbuffered = false) {
    if(!(!$this->slaveexcept && strtoupper(substr($sql, 0 , 6)) === 'SELECT' && $this->_slave_connect())) {
    $this->_master_connect();
    }
    $this->tablename = '';
    $this->slaveexcept = false;
    return parent::query($sql, $silent, $unbuffered);
    }

    db_dirver_mysql中其他方法的用途。set_config方法,用于加载配置信息。content方法,根据服务器号连接相应数据 库,存入连接池,并设置为当前连接。query使用当前连接执行sql语句。fetch_all,fetch_first,result_first定义 常用操作。

    db_dirver_mysql_slave中的其他方法的用途。set_config执行父类方法,设置不需要读写分离的数据表。_choose_slave方法,在多台从服务器的情况下,根据权重选择一台从库。

    至此,数据库的抽象层和底层基本清楚了。

    业务逻辑层其实是对抽象层的封装。逻辑层其实是所有的业务逻辑中涉及 数据库操作的部分。都能直接通过抽象层的DB::query或者DB::insert等方法来读写数据。不过通常数据表的操作都再次进行了封装。数据表业 务类在source/class/table目录中。数据表操作类都继承于discuz_table类,该在在source/calss/discuz目 录下。数据库的数据缓存也在本层中实现。

    __coustruct方法,设置表名和主键,设置是否缓存和缓存时间。该类主要封装了一些常用数据库操 作,count,insert,delete,fetch,fetch_all等。数据缓存相关的方法:获取指定键值的数据缓存fetch_cache, 存储单条记录缓存store_cache,清除指定记录缓存clear_cache,update_cache 更新指定单条记录缓存,update_batch_cache 批量更新指定记录缓存,reset_cache 重置指定数据的缓存,increase_cache 在原有缓存上追加更新缓存。缓存操作的方法由实际数据库操作的方法调用,如fetch方法:

    public function fetch($id, $force_from_db = false){
    $data = array();
    if(!empty($id)) {
    if($force_from_db || ($data = $this->fetch_cache($id)) === false) {
    $data = DB::fetch_first('SELECT * FROM '.DB::table($this->_table).' WHERE '.DB::field($this->_pk, $id));
    if(!empty($data)) $this->store_cache($id, $data);
    }
    }
    return $data;
    }

    public function store_cache($id, $data, $cache_ttl = null, $pre_cache_key = null) {
    $ret = false;
    if($this->_allowmem) {
    if($pre_cache_key === null)$pre_cache_key = $this->_pre_cache_key;
    if($cache_ttl === null)$cache_ttl = $this->_cache_ttl;
    $ret = memory('set', $id, $data, $cache_ttl, $pre_cache_key);
    }
    return $ret;
    }

    判断是否缓存和缓存中是否存,如果是则直接返回缓存,而不读数据库。从数据库中读出数据后,存入缓存中。而是否从缓存中读取时由外部参数来定义的, 默认是可缓存。本方法中的缓存读写用通过memory来实现的,memory方法定义在function_core文件中,至于缓存的具体实现,需要在另 外一篇文章中说明了。


    至此,discuzx3.1的数据库层都有了,分析的有点凌乱。

  • 相关阅读:
    备忘
    跨域问题思考
    个人理解的Lambda表达式的演化过程
    MySql 事务与锁
    Python连接MySQL
    将python虚拟环境移植到无法联网的电脑
    PyQt5+QtDesigner+fbs+python创建桌面程序
    Pycharm+Anaconda安装及配置
    一个极好的JavaScript学习网址
    Python GUI工具Tkinter以及拖拉工具Page安装
  • 原文地址:https://www.cnblogs.com/jidan/p/3595218.html
Copyright © 2020-2023  润新知