想进BAT?负载均衡必须懂!从入门到实操,linux老鸟带你走上高并发架构之路,详情点击>>>
0

我的帖子

个人中心

设置

  发新话题

当网站性能遭遇瓶颈的时候


一、问题描述
1.起源。我在做一个在线考试系统的项目中,希望用户回答的每一道题都有相应的记录:一是记录每道题的正确、错误的次数;二是记录用户每个错题,用来形成用户的错题集。

2.实现。由于考试的试卷中有不定数量的试题,所以我使用循环在判断用户回答是否正确,并在循环中记录数据,并写入数据库。

3.瓶颈。由于每提交一个答卷,就会产生数十次或更多的数据库写入或更新的操作,因此会耗费大量的时间。
二、问题分析

1.由于每道题都要被记录两次:一次是更新试题对错的次数,二次是记录到错题集中,如果每个试卷有20道试题,那么每个答卷就要进行40次数据库操作,导致数据库压力很大。
2.由于每个试题都是不同的数据库记录,因此难以批量更新(有的是新增记录,有的是更新记录)。
3.如果大量用户并发提交,那么服务器就可能崩溃,速度缓慢。虽然我的小网站平时没有那么多人来光顾,但我自己开发的系统总不能最终成为不可用的废品吧,所以必须优化下!
三、解决思路和方案

1.因为有大量数据库操作,所以我首先考虑到的是使用redis来提升性能。
2.由于更新数据的操作既有新增记录,又有更新记录,所以必须把更新操作和操作从逻辑上分离出来。
3.我使用的是thinkphp框架开发,模型层自带批量新增的函数addAll(),因此,新增数据的操作就用它解决了;然后通过网上搜索或自己编写一个函数,拼装批量更新sql语句,形如如下的语句结构:
UPDATEcategories SET
    display_order = CASE id

        WHEN 1 THEN 3
        WHEN 2 THEN 4
        WHEN 3 THEN 5
    END,
    title = CASE id
        WHEN 1 THEN 'New Title 1'
        WHEN 2 THEN 'New Title 2'
        WHEN 3 THEN 'New Title 3'
    END
WHERE id IN(1,2,3)

4.通过redishash表来记录要更新或新增的数据,在适当的时机,触发数据库操作,通过php操作redis的数据,执行成功后就删除那些redis数据,从而解决瓶颈问题。

附:以下是对试题对错记录的优化,对于用于错题集的优化代码类似,因此只展示前者的代码了。
5.redis记录过程:
$check ? $field = 'r' : $field = 'w';//检查对错
    $redis = new \Redis();
    $redis->connect('127.0.0.1',6379);
    $redis->hIncrBy(‘qid_check_log’,'qid.'.$qid.'.'.$field,1); //键值累加1

6.把redis数据转存到mysql中:
    $redis = new \Redis();
    $redis->connect('127.0.0.1',6379);
    $data_cache = $redis -> hGetAll(‘qid_check_log’);
    $temp = array();    //待更新的数据
    $i = 0;

    //要增加的数据
    foreach($data_cache as $key=>$num){
        $arr = explode('.',$key);
        $qid = $arr[1];
        $field = $arr[2];
        $temp[$i]['qid'] = $qid;
        $ids[] = $qid;//需要更新的试题id
        $temp[$i][$field] = $num;
        $redis -> hDel($qid_check_log,$key);
        $i++;
    }

    if(empty($ids)) return true;//如果没有更新,则直接返回

    //获取原来的数据
    $map['qid'] = array('in',$ids);
    $old_data = M('questionlog') -> where($map) -> select();
    $old_data2 = array();
    foreach($old_data as $one){
        $old_data2[$one['qid']] = $one;
    }
    unset($old_data);

    //合并数据
    foreach($temp as &$one){
        if(isset($one['r'])){
            $one['r'] = $old_data2[$one['qid']]['r'] + $one['r'];
        }else{
            $one['r'] = $old_data2[$one['qid']]['r'];
        }

        if(isset($one['w'])){
            $one['w'] = $old_data2[$one['qid']]['w'] + $one['w'];
        }else{
            $one['w'] = $old_data2[$one['qid']]['w'];
        }
    }

    $re = batch_update('questionlog',$temp,'qid');//执行批量更新

    后记:其实当初我在编写程序的时候没有设计好,如果设计得合理的化,也许不需要redis也能完成这个优化,不过,也正好因为这个机会,让我在项目中真正用到了redis,感受到了它的速度优势,哈哈!





本帖最后由 七彩极 于 2018-5-8 21:26 编辑
谢谢楼主分享




‹‹ 上一贴:【已解决】如何添加注册登录留言   |   下一贴:【经验教程】批量检查单选和多选按钮是否选中 ... ››
  发新话题
快速回复主题
关于我们 | 诚聘英才 | 联系我们 | 网站大事 | 友情链接 |意见反馈 | 网站地图
Copyright©2005-2018 51CTO.COM
本论坛言论纯属发布者个人意见,不代表51CTO网站立场!如有疑义,请与管理员联系:bbs@51cto.com