Discuz论坛分表以及memcache缓存优化[转载]

大部分的论坛在数据量达到一定程序的时候就会出现浏览帖子,回帖能操作缓慢的情况,一般情况都是由于posts表过大导致的,本文也是从对Disucz增加和memcache和posts分表的方式出发.环境有限只有1台WEB和DB,都是非独享,所以posts到800W多的时候,压力就比较大了

1.memcache

Discuz以前的版本都是面向过程的方式(DiscuzX还没了解),例如对于posts,members基本是在论坛各个文件当中,这对加入memcache建立和更新机制增加了复杂度,所以我还是打算稍微改造一下db_mysql.class.php文件,增加一个query_memcache的函数来处理memcache

代码如下

    function query_memcache($sql,$memkey=”,$type=”) {
        include_once DISCUZ_ROOT.’/include/memcache.inc.php’;
        $mem = MemCacheForBbs::getInstance();
        $diffsql = strtolower(substr($sql, 0, 6));
        if( $diffsql == ‘select’) {
            //批量删除的帖子的时候,记录删除时间,用于与缓存时间对比
            if(!$last_delpost = $mem->load(‘last_delpost’)) {
                $last_delpost = time();
                $mem->set(‘last_delpost’, $last_delpost);
            }
            $cache_time = $mem->load(md5($memkey));
            $cache_time = empty($cache_time) ? 0 : $cache_time;
            if(!($results = $mem->load(md5($sql))) || $last_delpost >= $cache_time){
                $results = array();
                $query = $this->query($sql,$type);
                while($item = $this->fetch_array($query)){
                    $results[] = $item;
                }
                $res = $mem->set(md5($sql),$results);
                $mem->set(md5($memkey), time());
            }
            return $results;
        } elseif($diffsql == ‘delete’ && $memkey == ‘delpost’){
            $this->query($sql,$type);
            $mem->set(‘last_delpost’, time());
        } else {
            $this->query($sql,$type);
            if(is_array($memkey)) {
                foreach($memkey as $v) {
                    $mem->del(md5($v));
                }
            } else {
                $mem->del(md5($memkey));
            }
        }
    }

通过函数可以看到我在这里增加了两个额外的参数,一个是memkey,一个是last_delpost

memkey主要用来保存这个SQL语句的关键更新字是什么

例如在viewthread.php中读取帖子列表

原来的操作是

    $query = $db->query(“SELECT p.*, m.uid, m.username, m.groupid, m.adminid, m.regdate, m.lastactivity, m.posts, m.digestposts, m.oltime,
        m.pageviews, m.credits, m.extcredits1, m.extcredits2, m.extcredits3, m.extcredits4, m.extcredits5, m.extcredits6,
        m.extcredits7, m.extcredits8, m.email, m.gender, m.showemail, m.invisible, mf.nickname, mf.site,
        mf.icq, mf.qq, mf.yahoo, mf.msn, mf.taobao, mf.alipay, mf.location, mf.medals,
        mf.sightml AS signature, mf.customstatus, mf.spacename, mcp.isshow, mcp.showmedals $fieldsadd
        FROM {$tablepre}posts p
        LEFT JOIN {$tablepre}members m ON m.uid=p.authorid
        LEFT JOIN {$tablepre}memberfields mf ON mf.uid=m.uid
        LEFT JOIN {$tablepre}medalcp mcp ON m.uid=mcp.uid
        WHERE p.tid=’$tid’ AND p.invisible=’0′ $onlyauthoradd  $pageadd”);

    while($post = $db->fetch_array($query)) {
        $postlist[$post[‘pid’]] = viewthread_procpost($post);
    }

现在可以改成

    $query = $db->query_memcache(“SELECT * FROM {$tablepre}posts p WHERE p.tid=’$tid’ AND p.invisible=’0′ $onlyauthoradd  $pageadd”, “tid=’$tid'”);
    foreach($query as $post) {
        //自定义会员信息
        $post_member = $db->query_memcache(“SELECT m.uid, m.username, m.groupid, m.adminid, m.regdate, m.lastactivity, m.posts, m.digestposts, m.oltime,
        m.pageviews, m.credits, m.extcredits1, m.extcredits2, m.extcredits3, m.extcredits4, m.extcredits5, m.extcredits6,
        m.extcredits7, m.extcredits8, m.email, m.gender, m.showemail, m.invisible, mf.nickname, mf.site,
        mf.icq, mf.qq, mf.yahoo, mf.msn, mf.taobao, mf.alipay, mf.location, mf.medals,
        mf.sightml AS signature, mf.customstatus, mf.spacename  FROM {$tablepre}members m LEFT JOIN {$tablepre}memberfields mf ON m.uid=mf.uid WHERE m.uid=’$post[authorid]'”, “uid=’$post[authorid]'”);
        if(!empty($post_member[0])) {
            $post = array_merge($post,$post_member[0]);
        }
        $postlist[$post[‘pid’]] = viewthread_procpost($post);
    }

这样我就通过tid=’$tid’这个memkey来判断这个SQL的缓存是否有效,例如增加回帖的时候,指定这个memkey,那么在读取这个帖子列表的时候,就会发现memkey失效需要重新读取数据了.

last_delpost主要用来批量删除帖子的时候怎么做更新操作

帖子在进行批量删除以后,我们无法方便的知道那个SQL缓存需要更新,我这里就采用memkey的时间与,最后一次last_delpost的时间进行对比,来进行帖子列表缓存更新,这样既不用大批量删除缓存,也不需要去主动建立缓存,当用户访问的时候,来被动生成缓存就可以了.

然后就是搜索一下posts,members中的update和insert的一些操作了

2.posts分表

现在网上介绍分表的一般有,按hash分表,数据库分区,通过merge建立联合分表,根据pid到一定量进行分表.

a.如果更据pid进行hash分表的话,到一定时间还是会出现表的数据量过大的问题

b.数据库分区,由于要动数据库文件,目前条件不允许

c.用merge建立联合分表这个没有具体的测试过不知道效果

d.据根pid的量分割的话就会出现一个主题可能跨很多表的问题.

结合论坛的一些实际情况,采用根据主题的第一个帖子的pid来进行分表,然后对于同一个主题的帖子保存在通过一个分表里的方式来分表处理.

建立一个tid,pid,first(我建立的表名threadindex)这三个字段的索引对应表来记录pid的自动增加id和是否是主题贴

a.在增加主题的时候,我可以根据pid的大小来建立分表

b.在回帖的时候,我可以根据主题的第一个帖子的pid来获取分表的表名

c.对单个帖子操作的时候,可以通过帖子pid获取主题tid,再根据tid获取第一个帖子的值,从而计算出分表名

d.在批量操作帖子的时候需要对所有的分表进行操作.

优点:不需要跨表获取数据,扩展性好.程序修改相对简单

缺点:论坛的统计功能可能需要根据需求重新编写了,数量分布不均匀

这是我分表的一些操作函数

/**
 * 创建分表名
 *
 * @return return_type
 */
function getTableByPid($pid){
    global $db, $tablepre, $mem;
    //获取当前的分表前缀
    $fix = ceil($pid/2000000);
    $table = $tablepre.’posts_’.$fix;
    $query = $db->query(“SHOW TABLES LIKE ‘$table'”);
    if(!$res = $db->fetch_array($query)){
        $sql = “CREATE TABLE $table (
          pid int(10) unsigned NOT NULL AUTO_INCREMENT,
          fid smallint(6) unsigned NOT NULL DEFAULT ‘0’,
          tid mediumint(8) unsigned NOT NULL DEFAULT ‘0’,
          first tinyint(1) NOT NULL DEFAULT ‘0’,
          author varchar(64) NOT NULL,
          authorid mediumint(8) unsigned NOT NULL DEFAULT ‘0’,
          subject varchar(80) NOT NULL DEFAULT ”,
          dateline int(10) unsigned NOT NULL DEFAULT ‘0’,
          message mediumtext NOT NULL,
          useip varchar(15) NOT NULL DEFAULT ”,
          invisible tinyint(1) NOT NULL DEFAULT ‘0’,
          anonymous tinyint(1) NOT NULL DEFAULT ‘0’,
          usesig tinyint(1) NOT NULL DEFAULT ‘0’,
          htmlon tinyint(1) NOT NULL DEFAULT ‘0’,
          bbcodeoff tinyint(1) NOT NULL DEFAULT ‘0’,
          smileyoff tinyint(1) NOT NULL DEFAULT ‘0’,
          parseurloff tinyint(1) NOT NULL DEFAULT ‘0’,
          attachment tinyint(1) NOT NULL DEFAULT ‘0’,
          rate smallint(6) NOT NULL DEFAULT ‘0’,
          ratetimes tinyint(3) unsigned NOT NULL DEFAULT ‘0’,
          status tinyint(1) NOT NULL DEFAULT ‘0’,
          PRIMARY KEY (pid),
          KEY fid (fid),
          KEY authorid (authorid),
          KEY dateline (dateline),
          KEY invisible (invisible),
          KEY displayorder (tid,invisible,dateline),
          KEY first (tid,first)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        “;
        $db->query($sql);
        $mem->del(md5(‘AllPostTable’));
    }
    return $table;   
}

/**
 * 得到最新帖子ID
 *
 * @return return_type
 */
function getNewPid($tid,$first=0) {
    global $db, $tablepre, $mem;
    $db->query(“INSERT INTO {$tablepre}threadindex (tid,first) VALUES (‘$tid’, ‘$first’)”);
    $mem->del(md5(‘threadindex-tid-‘.$tid));
    $newPid = $db->insert_id();
    return $newPid;
}

function getTidbyPid($pid) {
    global $db,$tablepre, $mem;
    if(!$res_tid = $mem->load(md5(‘threadindex-pid-‘.$pid))) {
        $res_tid =  $db->result_first(“SELECT tid FROM {$tablepre}threadindex WHERE pid=’$pid'”);
        $mem->set(md5(‘threadindex-pid-‘.$pid), $res_tid);
    }
    return $res_tid;
}

/**
 * 获取某个主题帖子对应的分表名
 *
 * @return return_type
 */
function getTableByTid($tid) {
    global $db, $tablepre, $mem;
    if(empty($tid)) {
        return ‘cdb_posts’;
    }
    if(!$item = $mem->load(md5(‘threadindex-tid-‘.$tid))) {
        $item = $db->result_first(“SELECT pid FROM {$tablepre}threadindex WHERE tid=’$tid’ AND first=1”);
        if(empty($item)) {
            return ‘cdb_posts’;
        }
        $mem->set(md5(‘threadindex-tid-‘.$tid), $item);
    }
    $fix = ceil($item/2000000);
    return $tablepre.’posts_’.$fix;;
}

/**
 * 返回所有的分表
 *
 * @return return_type
 */
function getAllTables() {
    global $db, $tablepre, $mem;
    if(!$tablearr = $mem->load(md5(‘AllPostTable’))) {
        $query = $db->query(“SHOW TABLES LIKE ‘{$tablepre}posts_%'”);
        $tablearr = array();
        while($table = $db->fetch_array($query, MYSQL_BOTH)) {
            $tablearr[] = $table[‘0’];
        }
        $mem->set(md5(‘AllPostTable’),$tablearr);
    }
    return $tablearr;
}

这里还可以对threadindex再建立一个分表threadindex_1,例如pid>10000000 || tid > 1000000,就把数据的获取和记录去threadindex_1里获取(需要确定最新的帖子的都要插入到threadindex_1里)
目前公司的论坛现在memcache在97%以上,论坛的浏览和回帖也很流畅

您可能还喜欢...