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的网络请求

Discuz论坛分表以及memcache缓存优化相关推荐

  1. discuz论坛数据库表结构分析

    附件表:pre_forum_attachment 关键表字段说明: aid mediumint(8) --附件id tid mediumint(8) --所在主题id pid int(10)--所在帖 ...

  2. discuz论坛数据表中文对照表

    pre_common_addon 插件扩展中心服务商表 pre_common_admincp_cmenu 后台管理面板,自定义常用菜单表 pre_common_admincp_group 后台团队职务 ...

  3. Linux数据库管理——day10——分库分表、数据库硬件优化

    分库分表     分库分表也称作分片技术,主要作用是将存放在一个数据库中的数据按照特定的方法进行拆分,分散存放在多个数据库中,以达到分散多台设备实现负载均衡 垂直分割        纵向切分,把一个表 ...

  4. 分库分表下极致的优化

    题外话 这边说一句题外话,就是ShardingCore目前已经正式加入 NCC 开源组织了,也是希望框架和社区能发展的越来越好,希望为更多.netter提供解决方案和开源组件 介绍 依照惯例首先介绍本 ...

  5. mysql 分表后排序_MySQL优化分库分表,为什么要分表,分表以后如何进行排序查询,业务如何设计?...

    昨天面试新人的时候,遇到了这么一个问题,按照自己的想法大体聊了一些,但大多是感性的,并没有完整的了解why and how. 今天查了一些相关的资料,包括<MySQL性能调优与架构设计>. ...

  6. 数据库优化,分表分库

    简介:TDDL(Tabao Distributed Data Layer)是淘宝开源的一个用于访问数据库的中间件,集成了分库分表,主备,读写分离,权重调配,动态数据库配置等功能.本文以2007年TDD ...

  7. “ShardingCore”是如何针对分表下的分页进行优化的

    分表情况下的分页如何优化 首先还是要给自己的开原框架打个广告 sharding-core 针对efcore 2+版本的分表组件,首先我们来快速回顾下目前市面上分表下针对分页常见的集中解决方案 分表解决 ...

  8. 2天,我把MySQL索引、事务、分库分表、锁、性能优化撸完了!

    Java研发工程师必备技能非MySQL莫属,虽说易学好上手,但应对大厂面试,最容易遭遇滑铁卢.功败垂成的也是它. 上手简单,玩转难,才是这款开源数据库叱咤业界多年的真实写照. MySQL 8.0正式版 ...

  9. mysql中的分库分表

    大表怎么优化?某个表有近千万数据,CRUD比较慢,如何优化?分库分表了是怎么做的?分表分库了有什么问题?有用到中间件么?他们的原理知道么? 文章目录 大表怎么优化 分库分表 垂直分区 垂直分表 水平分 ...

最新文章

  1. 用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
  2. mysql防注入 php_PHP使用PDO实现mysql防注入功能详解
  3. VC++静态文本框/PICTURE控件的notify属性
  4. 进程中的一个线程死了所引发的后果
  5. Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用示例
  6. python出现—with the features you requested: lxml. Do you need to install a parser library?
  7. mongodb卸载_如何在Windows上安装MongoDB,启动,卸载
  8. CSS:公共样式(自用)
  9. Google最新算法 - 人肉搜索引擎
  10. macbook pro安装JDK
  11. 数字密码锁设计(利用数字逻辑电路的电子密码锁)
  12. Oracle执行计划Explain Plan 如何使用
  13. 如何禁止Chrome浏览器版本自动更新的方法
  14. 三星电脑打开虚拟服务器,三星笔记本进BIOS后怎么开启CPU虚拟化
  15. MySQL之存储过程及函数的使用
  16. CSP-JS 2022第二轮认证CSP-J2真题4上升点列(point)
  17. 飞思卡尔imx7 html5,【经验分享】飞思卡尔IMX6处理器的GPIO配置方式
  18. CSS 选择器 CSS3选择器
  19. PDF和CDF图的区别
  20. SpringBean篇 (1) 配置文件的装载方式

热门文章

  1. TypeScript学习(三):联合类型及推论
  2. PCL之区域生长分割
  3. Halcon 二维码
  4. HDU 6034 - Balala Power! | 2017 Multi-University Training Contest 1
  5. 「管理数学基础」2.4 泛函分析:有界线性算子与泛函、例题
  6. wordpress发送测试邮件
  7. 【数据结构笔记19】File Transfer的C语言实现,集合的简化表示,按秩归并,路径压缩
  8. 前端学习之touch.js与swiper学习
  9. android tabhost 生命周期,FragmentTabHost + FragmentLayout布局框架,Fragment生命周期
  10. sublime php快捷键,sublime快捷键