用户
 找回密码
 入住 CI 中国社区
搜索
查看: 27527|回复: 48
收起左侧

[优化] 使用XML缓存减低数据库的读取次数和服务器压力

  [复制链接]
发表于 2011-6-15 09:14:00 | 显示全部楼层 |阅读模式
最近总是被问到降低数据库访问次数的问题,恰好看到论坛里也有这种的争论和需求。
一个web程序不访问数据库是不可能的,但如何控制数据库的访问次数应该“按需”设计,不能因噎废食。本文所述的使用XML缓存数据库是一个方式,当然这种方式不是唯一的方式。
目的:将变动不大或基本不变的全部数据库表缓存在XML文件中,在程序需要调用这些表中数据时,首先查看该表是否已缓存XML文件,如果没有则创建。如果该表更新,则自动删除已缓存的数据库文件,在数据库update/delete操作完成后,立即生成一份新的XML缓存。
引申:可以简化查找后,没有XML缓存则自动创建的过程。所有的update/delete后,直接生成新的XML。
需要的条件:
[1] hepler中的file_helper。
[2] library中的dbutil。
[3] 硬盘的0755或干脆0777属性。
[4] 稍显复杂的XML对象转换为关联数组操作。
[5] 一个类文件,集成各个缓存的XML文件,在引用的controller中调用并在view中显示。

未完待续。

评分

参与人数 3威望 +10 收起 理由
yanhuaguo + 1 很给力!
Hex + 5 很给力!
saturn + 4

查看全部评分

发表于 2011-6-15 09:17:09 | 显示全部楼层
很好,期待你的代码{:soso_e113:}
 楼主| 发表于 2011-6-15 09:30:19 | 显示全部楼层
本帖最后由 spt119 于 2011-6-15 09:39 编辑

以下,将以一个类别设定的数据库表为例说明XML缓存的生成、调用、显示及删除各种操作。(注意,本文是在开启AR的前提下完成的。如果没有开启AR,可以使用$this->db->query()的方式完成)。说明:本例将遵循严格的MVC方式。各处代码请见所在的MVC位置。

第一步,数据库操作
[1] 生成结果集/MODEL中。(model名为:cate_model)
你需要把数据库中类别表保存在一个结果集中,代码示例:
PHP复制代码
 
function get()
{
  return $this->db->get('category');
}
 
复制代码

注意:假定的前提是表category中有数据。如果不知是否有数据,最好用num_rows()判断一下。再有,这里返回的是一个PHP stdclass基类数据样式,不要使用result()或result_array()。
[2] XML转存/CONTROLLER中
PHP复制代码
 
function xml()
{
  $this->load->dbutil();
  $this->load->helper('file');
  $query = $this->cate_model->get();
  /*手册中代码粘贴到此处*/
  $config = array (
    'xml' => '?xml version=\'1.0\' encoding=\'UTF-8\'?',//此句手册中没有,需自己加上
    'root'    => 'root',
    'element' => 'element',
    'newline' => "\n",
    'tab'    => "\t"
  );
  $xml = $this->dbutil->xml_from_result($query,$config);
  if (! $xml) show_error('');//你需要自己定义一个错误指针,方便错误排查
  $xml_path = '';//你需要自己定义一个xml文件存放的目录
  if (! write_file($xml_path.'category.xml',$xml))) show_error('');
}
 
复制代码

未完待续。。。
注意:因论坛的自动转移,部分代码使用了全角字符。请对应手册。
 楼主| 发表于 2011-6-15 10:01:44 | 显示全部楼层
第二步:数组操作
注意:为方便叙述,数组的操作放在了控制器中的一个function中。

[1] XML对象转换为关联数组/CONTROLLER中
PHP复制代码
 
function get_category()
{
  $xml_path = '';//你的xml文件存放目录
  $xml_category = simplexml_load_file($xml_path.'category.xml');
  //假设数据库catetory表中只有id和title两个字段
  foreach($xml_category as $c){
    $cate_info[] = array(
      'id' => $c->id,
      'title' => $c->title
    );
  }
  if (isset($cate_info)) return $cate_info;
}
 
复制代码


[2] 控制器中引入关联数组并转入view中/CONTROLLER
PHP复制代码
 
function index()
{
  //假设在控制器中的index方法中加载catetgory表中的数据
  $data = array(
    'category' => $this->get_category()
  );
  $this->load->view('catetory',$data);
  //已经减少了一次数据库查询,因为category的内容从XML缓存中导入。
}
 
复制代码

注意,以上两个function,都在同一个控制器中。

[3] 在VIEW中显示数组内容/VIEW中

PHP复制代码
 
foreach($category as $cate){
  //id,$id = $cate['id'];
  //title,$title = $cate['title'];
}
<!--html标记-->
 
复制代码

这只是最简单的显示表category中的数据,当然,你也可以在一些判断中引入关联数组,如
PHP复制代码
 
foreach($category as $cate){
  if ($cate_id == intval($cate['id'])){
    //你的代码
  }
}
<!--html标记-->
 
复制代码

需要说明的是,所有转入XML缓存的数字类型数据,在XML对象与关联数组的反复操作过程中,都被“默认”更换为string类型。因此在和其他参数比较判断是,你需要使用intval()或strval()函数控制比较类型。

未完待续。。。。

 楼主| 发表于 2011-6-15 10:11:39 | 显示全部楼层
第三步:XML的更新

当你更新数据库category表中的数据时,你需要首先删除已有的xml缓存,然后在更新完成后,根据数据库表category再重新生成一个xml文件。
具体的代码就没什么好说的了,unlink()如果不熟,可以查看一下PHP的手册。
需要注意的是。如果使用CI的file_helper操作,可以免除unlink动作,因为CI的file_helper中的write方法,默认是自动覆盖同名文件的。
最后,就是别忘了在update/delete操作,需要重新调用“第一步”中的相关步骤。

未完待续。。。。
 楼主| 发表于 2011-6-15 10:33:49 | 显示全部楼层
第四步:CI核心代码的更新

CI的数据库操作类,system/database/DB_utility.php文件中,xml_from_result方法设计有些失误,具体表现为生成的XML文件没有文件头,如“<?xml version='1.0' encoding='UTF-8'?>”。
在第一步中的$config数组中已经定义了“'?xml version=\'1.0\' encoding=\'UTF-8\'?'”,你还需要在DB_utility.php文件中继续完善。
 楼主| 发表于 2011-6-15 10:34:04 | 显示全部楼层
第五步:XSD文件的设置

XSD是规范XML格式的一类规范,建议应该首先设置一套XSD规则,然后用这个XSD规则去检查程序生成的XML文件是否符合设计需求。本步不是必须,因为生成的XML文件由CI完成,但如果采用其他方式生成XML文件,强烈建议不要减免这个步骤。
 楼主| 发表于 2011-6-15 10:34:17 | 显示全部楼层
本帖最后由 spt119 于 2011-6-15 10:42 编辑

第六步:XML文件的安全

理论上不存在于数据库中的数据,都有被“偷”的可能,(即便是存在于数据库,也不能说100%安全)。
为最大限度保证这些XML文件的安全,建议以下几个方面需要注意:
[1] 函数的private属性。出于叙述需要,上面代码中所有的函数都是默认的public,但出于安全考虑,有些function 应该设置成private属性。
[2] 0755和0777属性的适当设置。
[3] 在存放XML文件的文件中,引入CI防止非经index.php进入的index.html文件。
[4] 文件的逻辑锁。这个操作就比较复杂了,有时间我会专门再开一个文章说下这个逻辑锁的设置和原理以及用法。
[5] 全部XML文件缓存在内存中。PHP是弱类型语言,没有操作内存的能力,因此虚拟主机用户这个办法是无法实现的。如果是自有主机的话,可以考虑在centOS或freeBSD中做些设置,把XML结果全部缓存到内存后,然后立即上锁或删除XML文件的文件夹。
[6] APACHE/nginx的deny属性设置。

以上6点,只是说在极端的情况下的安全防护,事实上CI自身的安全机制还是可以放心的,尽管这种放心只是“相对”的。当你熟悉一种技术以及这种技术的应用,那么在这种技术本身做出的任何安全措施,都可以通过这种技术本身予以破解。上升到哲学的高度,这个世界上只有如果,而没有绝对。

以上,是利用XML缓存减少数据库读取次数的基本实现方式。我建议看过这篇文章的朋友们,多多利用PHP 5的面向对象,尽量用对象的方式去思考,而不是把上面这些代码复制复制再复制到各个控制器中。上面这些代码只是讲解实现的原理,如果在实际的项目中这样做,那我宁愿你没看过这篇文章,还是传统的读数据库吧。
发表于 2011-6-21 20:40:21 CI中国手机版 | 显示全部楼层
感觉用xml缓存,还不得用ci自带的缓存功能,我上一个系统一直在用,效果很好
 楼主| 发表于 2011-6-23 09:00:28 | 显示全部楼层
CI缓存是页面缓存。缓存机制启动后,输出的是静态的html。如果有读取数据库或变动的内容,只能借助AJAX,或其他方法。
上述的XML缓存,是数据库缓存,是在不缓存页面的前提下,尽量降低数据库的读取次数。
两种缓存的设计目的不一样。

本版积分规则