[分享] 对于使用CActiveDataProvider和CMemCache的看法

想接触一下memcached和memcache,就搭了个环境来玩玩,

不过在结合CActiveDataProvider和CMemCache使用的时候,遇到了一些问题,当然是因为我对于缓存,页面缓存,片段缓存的概念不是很清晰造成的,这些在我上一个帖子就体现出来了,

http://www.yiiframework.com/forum/index.php?/topic/14909-使用cmemcache的错误/

真的很烂 :P

感谢Q群:Yii技术互助平台(80196965)里面的 数字,Leric,他们热心解答了我遇到的一些概念上的问题。

OK,回到正题,因为是使用CActiveDataProvider和CMemCache,所以中间还是遇到了不少问题,主要体现在:

我是model里面做缓存的,所以用CActiveDataProvider的时候,就会出现,翻页了,但是页面里面的数据没有发生改变,于是,我进行了一系列的修改:

Model层:




class Product extends CActiveRecord

{

    public $cacheKey = 'product'; //这个用途就是用来区分不同的cache的,后面会体会到。

    //.......其它代码

    

    //To newbie: 为什么要覆盖这个方法呢?是因为使用CActiveDataProvider的时候,它是调用这个方法来获取数据的。

    public function findAll($condition='',$params=array())

    {

        $cache = Yii::app()->cache;

        if ($cache->get($this->cacheKey) === false)

        {

            $rs =  parent::findAll($condition, $params);

            $cache->set($this->cacheKey, $rs, 30);

        }

        else

        {

            $rs = $cache->get($this->cacheKey);

        }

	return $rs;

    }

}



新建了一个MyActiveDataProvider,继承于CActiveDataProvider,目的是覆盖fetchData()这个方法,赋值给model的cacheKey(见上面定义)




class MyActiveDataProvider extends CActiveDataProvider

{

    public $modelCacheKey; //这个属性就是用来赋值给model的cacheKey

    

    protected function fetchData()

	{

		$criteria=clone $this->getCriteria();

		$baseCriteria=$this->model->getDbCriteria(false);


		if(($pagination=$this->getPagination())!==false)

		{

			if($baseCriteria!==null)

				$this->model->setDbCriteria(clone $baseCriteria);

			$pagination->setItemCount($this->getTotalItemCount());

			$pagination->applyLimit($criteria);

		}


		if(($sort=$this->getSort())!==false)

		{

			if($baseCriteria!==null)

			{

				$c=clone $baseCriteria;

				$c->mergeWith($criteria);

				$this->model->setDbCriteria($c);

			}

			else

				$this->model->setDbCriteria($criteria);

			$sort->applyOrder($criteria);

		}

		

		//这里赋值到model的cacheKey

        if (!empty($this->modelCacheKey))

        {

            $this->model->cacheKey = $this->modelCacheKey;

        }


		$this->model->setDbCriteria($baseCriteria);

		return $this->model->findAll($criteria);

	}

}



Controller层




public function actionIndex()

{

    //在这个方法里面,我使用了两次MyActiveDataProvider,都是从Product里面取数据的

    $modelCacheKey = empty($_GET['hot']) ? 'site_index_hot_0' : 'site_index_hot_'.$_GET['hot'];


    $ads = new MyActiveDataProvider(

        'Product', 

        array(

            'criteria'=>array(

                'condition'=>'display=1 AND ad=1',

            ),

        )

    );


    $models = new MyActiveDataProvider(

        'Product',

        array(

            'criteria'=>array(

                'condition'=>'display=1 ad<>1',

            ),

            'pagination'=>array(

                'pageSize'=>2,

                'pageVar'=>'hot',

            ),

            'modelCacheKey'=>$modelCacheKey, //这里是亮点,通过MyActiveDataProvider对model的cacheKey赋值!

        )

    );


    $this->render(

        'index',

        array(

            "models"=>$models,

            "ads"=>$ads,

        )

    );

}



View层




<?php $this->widget('zii.widgets.CListView', array(

    'ajaxUpdate'=>false,

    'dataProvider'=>$models,

    'itemView'=>'_view_models',

    'itemsTagName'=>'ul',

    'itemsCssClass'=>'lists',

    'template'=>"{items}\n{pager}",

    'viewData'=>array('modelsCount'=>count($models->getData())),

    'pager'=>array(

                'class'=>'CLinkPager',

                'prevPageLabel'=>'上一页',

                'nextPageLabel'=>'下一页',

                'header'=>'',

                'cssFile'=>$this->themeCss('pager.css'),

            )

)); ?>



Maybe FAQ:

  1. 为什么要在model里面定义一个$cacheKey?

因为在缓存里面,是根据不同的key来保存的,就像是array里面的$key=>$value,而且你会发现,我在Controller层,用了两次MyActiveDataProvider,而且都是调用Product这个model的,如果它们都要缓存的话,那就会冲突了,都会取出同样的数据。还有分页,上面的代码里面,我会根据拿到的分页的数值($_GET[‘hot’])来写/取model里面的值

  1. 为什么要自己定义一个类来继承CActiveDataProvider?

上面也提到了,是因为要对model的$cacheKey赋值

  1. 这些代码有什么实际用途吗?

不好意思,还没有实际的应用。

  1. 关于性能?

同问。。。 :P

无任欢迎拍砖和交流, :)

文章放了两天,有人看,没有回,都不知道是写得不清不楚,还是方法太笨。。

我含泪把它翻译成英文,发到英文版区了。。。 T_T

http://www.yiiframework.com/forum/index.php?/topic/14967-share-mixed-with-cmemcache-and-cactivedataprovider/

我其实看了,但是可能被你的标题给误导了,不明白你是问一个问题还是分享你的经验。:P

你在model里的cacheKey为什么不根据查询的条件变动呢?比如:

$cacheKey=get_class($this) . serialize($criteria->toArray());

这样你就不用改其它代码了。

不过我有点担心的是这种方法对eager loading有没有影响。

这是分享经验来的,不过没有把标题弄好,sorry。 :P

我之前还真没有想到原来可以这样设置$cacheKey的,谢谢qiang哥提醒。

其实我也有想过会不会对那些lazy load或者eager load有影响的,不过既然我是override了model的fetchAll方法,设置了cahce的时间,应该就是没事的~

如果qiang哥你说会有影响的话,那应该怎么做model的cache呢?我本意是,在大流量同时请求情况下,不需要每次处理都要去查询数据库。

感谢qiang哥的回答~ :)

我的担心主要在于serialize对象。你可以做一些验证,确定一个AR(lazy或eager loaded)被serialize后,只保存了该保存的数据。

目前对于AR我们在__sleep里把_md设成了null,以防止它被保存,因为_md指向很多很复杂的对象。

还有一种caching的办法是在更底层的CDbCommand::queryInternal里做。

qiang哥,不好意思,晚上才有时间弄一下这个。

我试过了,用serialize对结果没有影响,我把model的findAll方法覆盖成这样,




    public function findAll($condition='',$params=array())

    {

        $cache = Yii::app()->cache;

        $criteria=$this->getCommandBuilder()->createCriteria($condition,$params);//这句用来生成$criteria的

        $cacheKey = get_class($this).serialize($criteria->toArray());//这就是qiang哥说的方法。

        if ($cache->get($cacheKey) === false)

        {

            $rs =  parent::findAll($condition, $params);

            $cache->set($cacheKey, $rs, 30);

        }

        else

        {

            $rs = $cache->get($cacheKey);

        }

        return $rs;

    }



上面的方法在使用CAcviteDataProvider的时候调用

至于qiang哥最后那里说到的,

还有一种caching的办法是在更底层的CDbCommand::queryInternal里做。

不是很明白怎么做,在CDbCommand里面没有找到queryInternal这个方法?手册没有提到有这个方法。

qiang哥,给点提示?

2010-01-11 20:42问题补充:

看CDbCommand里面的代码发现,queryInternal是私有方法,如果这样做的话,就要修改Yii的core代码?

今天处理性能问题也撞到这儿了,Yii-1.1.7里实现了Active Record的Query Cache,这个工作就简单多了

还是继承一下CActiveDataProvider,加上两个成员$cache_ttl, $cache_dependency,

在fetchData里面把model()->findAll改成model()->cache($cache_ttl, $cache_dependency)->findAll 就行啦

谢谢提醒!

学习了 ,非常感谢!