Skip to content

Tutorial.Others IV: Cache.zh_cn

liuchuangww edited this page Jul 5, 2013 · 3 revisions

English

  1. 数据缓存

这一节所介绍的缓存并不是Pi Engine的缓存机制,而是如果在模块开发的时候使用缓存来达到减少数据库访问和提高访问速度的目的。Pi Engine已经为开发者提供好了基类,开发者只需要在模块里对这个基类进行扩展就可以定制模块的缓存,并且模块开发者不需要去关心所使用的缓存是何种机制。

这一节要介绍的缓存读取方式可用下图来简要说明,由于是主动请求和生成缓存,在此我也将这种方式称作为主动式:

fig9-11

图9-11 模块内缓存请求示意图

首先模块会发出一个请求,去缓冲区读取相应的内容,缓冲区会判断模块请求的数据是否存在,若有则返回,若没有,就调用方法访问数据区(数据库)将数据读取出来返回给模块,同时将数据写入缓存。

4.1 创建缓存类

模块的缓存是作为Pi Engine有一种服务来实现的,它的基类就是Pi\Application\Registry\AbstractRegistry,因此扩展的缓存类要继承自这个类。模块的缓存类需要放在src目录下的Registry文件夹里,我们创建一个Account类来处理缓存。

member
|- src
   |- Registry
      |- Account.php

打开Account.php并添加如下代码:

路径:usr/module/member/src/Registry/Account.php

Code 9.4.1

<?php
namespace Module\Member\Registry;

use Pi\Application\Registry\AbstractRegistry;
use Pi;

class Account extends AbstractRegistry
{
    protected $module = 'member';

    protected function loadDynamic($options = array())
    {
        $module = $options['module'];
        $model  = Pi::model('account', $module);
        $rows   = $model->select(array())->toArray();

        return $rows;
    }

    public function read()
    {
        $module  = $this->module;
        $options = compact('module');
        return $this->loadData($options);
    }

    public function clear($namespace = '')
    {
        parent::clear($namespace);
        return $this;
    }

    public function flush()
    {
        $this->clear($this->module);
        return $this;
    }
}

在这段代码里,我们创建了Account类继承自AbstractRegistry类用于处理缓存,类里创建了$module私有变量,保存当前的模块名。

类里共创建了三个私有方法:loadDynamic(), clear()和flush()。这个三个私有方法都覆盖了父类的同名方法,不能更改。loadDynamic()方法用于读取数据库,也就是在整个流程中,判断缓冲区没有缓存时执行的流程,这个方法的参数由read()方法里的loadData()传递过来的,我们在loadDynamic()方法里,将account表里所有的数据都读取出来。

read()方法就是外部访问的入口,这个方法的方法名和参数可以自定义,这里我们采用最简单的方式,不带任何参数。在read()方法里必须调用loadData()方法,这个方法是父类的方法,不需要覆盖,它会根据参数生成唯一标识,再根据这个唯一标识读取缓存,如果缓存不存在就调用loadDynamic()方法从数据区读取数据,最后再写入缓存区。loadData()的参数可以是字符串或数组,如果是数组,必须要有module字段。这里的compact()函数就是将$module变量的值赋给$options变量的module字段。

clear()方法用于清除指定namespace的数据。它将调用父类的clear()方法来完成缓存区的数据清除。它的参数需要与read()方法中loadData()的参数保持一致,即,若loadData()的参数是字符串,clear()的参数就是这个字符串,若loadData()的参数是数组,clear()的参数就是模块名。 而flush()方法就是清空通过这个缓存类生成的所有缓存数据。

这里我们介绍下数据在缓存区的保存形式,先说下命名空间,也就是上面提到的唯一标识。目前对于一般的缓存(非用户或语言环境),它的命名空间的形式为{Tag}{registryKey}{module name},其中Tag为AbstractRegistry指定的前缀,一般为registry,registryKey由缓存类名和模块名组成,形式为{module name}_{class name},这两部分在实例化这个类的时候都已经生成,而module name就是通过loadData()方法传递过来的参数(字符串或数组里的module字段值)。对于上面代码生成的命名空间就是registry_member_account_member,clear('member')方法将清除这个命名空间下的所有缓存数据。另外,read()方法还可以传递其他参数给loadData(),这些参数也会决定缓存数据保存在不同的缓存文件里(以文件系统为例)。比如这里没带参数,那么缓存文件就是{prefix}_registry_member_account_member-registry.dat,若带一个参数值为hello,那么缓存文件就是{prefix}_registry_member_account_member-_hello.dat。这样就能保证不同情况的数据保存在不同文件,若用同一个缓存文件,会导致读取的数据与设想的不匹配。

4.2 调用缓存接口

接下我们需要调用read()方法来读取缓存,我们把区块里的maleUser()方法作些改动,将数据读取的方式设为缓存读取,并且除了性别外,不考虑其他筛选条件。代码修改如下:

路径:usr/module/member/src/Block.php

Code 9.4.2

<?php
namespace Module\Member;

use Pi;

class Block
{
    ...

    public static function maleUsers($options, $module = null)
    {
        $rows  = Pi::service('registry')->handler('account', $module)->read();
        $items = array();
        foreach ($rows as $row) {
            if ($row['gender'] != 'm') {
                continue;
            }
            $items[] = $row;
        }

        $block   = array(
            'items'   => $items,
        );
        return $block;
    }
}

在代码中,我们通过缓存将所有用户数据读取出来,然后筛选出性别为男的用户。现在我们先去首页查看到两个区块的用户(首页需要在后台将这两个区块组装过来,具体操作方法参考主题和区块一章),这一步是为了先把数据写入缓存;然后到注册页添加一个男性用户carter,再回到首页:

fig9-12

图9-12 在区块里应用缓存

可以看到,最近注册用户区块里,carter这个用户已经出现了,但男性用户的区块里却没有,这是因为第一次去首页的时候已经把从数据库读取的男性用户写入了缓存区,第二次查看时其实看到的是缓冲区的数据。

因此我们需要在用户每次插入或更新数据库后清空缓冲区,这样最新的用户就会在用户第一次查看的时候写入缓存区。我们需要在RegisterController/indexAction里调用clear()方法。

路径:usr/module/member/src/Controller/Front/RegisterController.php

Code 9.4.3

<?php
...
use Pi;

class RegisterController extends ActionController
{
    ...

    public function indexAction()
    {
        $form = $this->renderForm();

        if ($this->request->isPost()) {
            ...
            if ($data['password'] === $data['repeat-password']) {
                ...
                if (!$row->id) {
                    $this->view()->assign('form', $form);
                    $this->view()->assign('error', __('Failed insert user data!'));
                    return ;
                }
                $module = $this->getModule();
                Pi::service('registry')->handler('account', $module)->clear($module);
                $this->view()->setTemplate('register-success');
                $this->view()->assign('info', $data);
                return ;
            } else {
                ...
            }
        }
        ...
    }
}

在代码里,用到Pi的接口,因此要引用Pi类,当用户添加成功后,就调用clear()方法清空缓存。最后我们再添加一个男性用户zhangsan,然后刷新首页,将会看到张三已经出现在男性用户列表里了。

fig9-13

图9-13 更改数据库后清空缓存的效果

Clone this wiki locally