设为首页 收藏本站
查看: 1039|回复: 0

[经验分享] Discuz论坛分表以及memcache缓存优化

[复制链接]

尚未签到

发表于 2017-4-15 14:58:43 | 显示全部楼层 |阅读模式
http://hi.baidu.com/nopsky/blog/item/34feffef740868e1b2fb958e.html
2011-07-14 12:24
大部分的论坛在数据量达到一定程序的时候就会出现浏览帖子,回帖能操作缓慢的情况,一般情况都是由于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%以上,论坛的浏览和回帖也很流畅
  ps:
  出现的问题:当编辑某个帖子第N页内容以后,不是跳转到当前,而是跳转到第一页的时候,会出现缓存不更新的情况
  2011/10/24
  PS:
  可以增加一个sqlkey的存储时间戳,用来和memkey比较进行更新,如果memcache和web不在一台服务器的话,可以把last_delpost的值保存在本地文件,从而减少memcache的网络请求


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-365153-1-1.html 上篇帖子: 64位机器上安装memcache 下篇帖子: SapphireCache与EhCache、MemCache功能比较
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表