关于页面缓存的疑问

我用了文件缓存,觉得挺不错的。对于不经常更新的页面可以采用这种方式,效果基本等同于静态页面。

但是我的问题是,我不想设置一个固定的失效时间来控制缓存的更新,而是想当数据库的内容改变时自动去更新缓存。

yii提供一种缓存依赖的机制,但是我发现他好像只可以如下方式使用:


缓存依赖 -------- 

 除了过期设置,缓存数据还会因某些依赖条件发生改变而失效。如果我们缓存了某文件的内容,而该文件后来又被更新了,我们应该让缓存中的拷贝失效,从文件中读取最新内容(而不是从缓存)。  我们把一个依赖关系表现为一个 [CCacheDependency] 或它的子类的实例,调用 [set()|CCache::set] 的时候把依赖实例和要缓存的数据一起传入。  

 // 缓存将在 30 秒后过期 

// 也可能因依赖的文件有更新而更快失效 

Yii::app()->cache->set($id, $value, 30, new CFileCacheDependency('FileName')); 

而我用页面缓存的方式如下:

首先,在main.php的配置文件中加入:

	'cache'=>array(


		 'class'=>'system.caching.CFileCache',


		 'keyPrefix'=>$_SERVER[REQUEST_URI],


	),

然后在controller里这样:

public function filters()


{


	return array(


		array(


			'COutputCache - update,create,admin',


			'duration' => 300,


		),


	);


}

这里有两个地方我需要请教,第一,'keyPrefix’我用$_SERVER[REQUEST_URI]的方式是可以得到我希望的结果的,但是有没有更好的方式?

第二,‘duration’ => 300, 这个时间太固定了,我怎么做才能让缓存随数据库内容变了自动更新?

如果说可以通过afterSave来处理缓存的话,问题是我怎么知道处理那个缓存?总不能处理所有缓存吧?

期待您的答复。多谢。

你可以使用CDbCacheDependency。参见:http://www.yiiframework.com/doc/api/COutputCache#dependency-detail

qiang,我还有个问题,

‘keyPrefix’=>$_SERVER[REQUEST_URI],

这个key前缀是不科学的,可能会被人恶意请求,比如请求10w次,我的磁盘可以就爆了。

但是我的目的还是想让每个文章都缓存了。如:http://www.dddd.com/index.php/downCenter/show/id/4

我该如何设置这个keyPrefix呢?

期待您的答复。

keyPrefix的默认值是Yii::app()->getId()的值,这个不能满足需求吗?为什么要改成你自己的

miles ,默认的是controller的ID;

比如说吧,一个新闻功能,我希望把每个具体的新闻的页面缓存,意思就是把每个xxx/show/id/1 这样的东西缓存住。

而默认的方式,对xxx/show/id/1 ,xxx/show/id/2 ,xxx/show/id/3 … 等来说,是同一个缓存的。就是说比如你先打开了xxx/show/id/1,它被cache到文件了,你再打开xxx/show/id/2时,它直接返给你xxx/show/id/1 所缓存的内容了。不知道您理解我的意思吗?

问题是这样的,我用’keyPrefix’=>$_SERVER[REQUEST_URI],可以达到效果,但是缺陷是,如果有人恶意的访问一些无效的url,就会造成一堆垃圾缓存文件。这是我头疼的事情。

继续等待…

不要这样去修改cache.keyPrefix,否则的确会出现你描述的问题。

COutputCache有一个varyByParam属性,你应该用这个。另外,为了避免你说的问题,在使用缓存前,应该预先检查id的合法性(比如id值应该介于1到100000)之间。

不修改cache.keyPrefix的话,无法把缓存细化到一个id上啊。比如:浏览http://www.dsd.com/index.php/downCenter/show/id/4 时候,生成了缓冲文件,而再浏览http://www.dsd.com/index.php/downCenter/show/id/3 (其他id)时, 不会再独立被缓存为文件,而是返回 id=4时的那个文件。所以,我必须改变cache.keyPrefix,让他以请求的uri为前缀。

另外你说的检查id范围是不是不太现实啊,因为这个规则是不好确定的,比如你说的(比如id值应该介于1到100000)之间的话,如果我就几百个记录,而用户恶意请求其他的id,我的磁盘同样会吃不消的。过分点说,如果我的记录数超过了100000 ,是不是我还要记得去修改这个规则呢?

我和miles讨论过这个实现措施,大体方案是:

在controller的init中搞一个异常事件,一般用户恶意请求的结果是404的东西,它会抛出异常的,而在抛异常之前,缓存已经生成了。那么我要做的是,在事件句柄处理中,删掉生成的缓存即可。

大体如下:

public function handlerClearCache($event)


{


	Yii::app()->cache->delete($key);


}


public function init()


{


	parent::init();


	Yii::app()->attachEventHandler('onException', array('MyController','handlerClearCache'));

;

}

但是我的问题是,这里如何取得这个key呢?搞了一下午也没搞出来。请强帮我一下了。多谢。

这个key应该是哪个缓存文件名吗(md5值)?强请不要走开,我只能等到北京时间凌晨1点才能碰上您。帮我处理下这个问题吧。我在线等候这您答复呢。

你有没有试过varyByParam?关于你说的避免恶意用户的方法,我觉得是个好主意。我会看一下能否在COutputCache里提供这个支持。

我试过varyByParam 的方式的。

问题就是我上面说的。访问一个id时被缓存了,而再访问其他id时直接把上次缓存结果返回了。我发现这个key好像是这样的形式吧:

yii:dbschemamysql:host=localhost;dbname=dsd:root:DownCenter,

是这个东西吗?在controller中如何取到这个值呢?

发现这个key与CDbSchema.php的64行的key是一样的:

$key=‘yii:dbschema’.$this->_connection->connectionString.’:’.$this->_connection->username.’:’.$name;

应该不会啊。如果你设置了varyByParam=array(‘id’),那么计算出来的cache key是会根据id不同而不同的。

嗯。我犯错了。确实可以实现根据id不同而不同的缓存。这个ok了,那么现在我们还是继续怎么搞防止用户恶意请求的问题吧。

我现在就差的是如果取得key了。

大体是这样的:

public function handlerClearCache($event)


{


	$key='yii:dbschema'.Yii::app()->db->connectionString.':'.Yii::app()->db->username.':'.[color="#FF0000"]$this->id[/color];


            //key 的形式是这样的:yii:dbschemamysql:host=localhost;dbname=dsd:root:DownCenter


	Yii::app()->cache->delete($key);


}


public function init()


{


	parent::init();


	Yii::app()->attachEventHandler('onException', array('MyController',"handlerClearCache"));


}

而在回调函数handlerClearCache里面,红色的$this->id 是想取得当前controller的ID的,然而这里的$this是无意义的,请问强,在回调方法里如何取得当前controller的ID呢?

或者您有更好的方式处理用户恶意请求的方案吗?

我刚刚验证过,如果你针对非法的id throw exception的话,那么outputcache是不会作缓存的。

我不太明白你的意思,你的意思是我在每个action里明确的对$_GET[‘id’] 做验证吗?(验证规则也是个问题呢) 。那么的话很麻烦的啊。

如果非这样做应该是可以的,但是太麻烦,每个action都有处理接受的参数。如果我误解了您的意思,请您贴点代码出来好吗?

就我上面提到的问题,在回调函数里怎么取controller的id你有什么方法啊?在回调里,$this没定义的。所以不能用$this->id了。苦恼中。

以blog demo的PostController::actionShow()为例,loadPost()会根据输入id取得对应的post。如果post不存在,那么它会throw exception。如果post存在,那么第一次显示后,以后就会从cache去取了。当然,如果用户还是提交非法的id,那么cache是不会起作用的,这时loadPost()还是会被调用。

在任何地方,你都可以通过Yii::app()->controller获得当前的controller。

不过你试图据此去清空cache恐怕很难,因为cache key的计算很复杂。你可以查看一下COutputCache的代码。

在抛异常之前cache就已经生成了啊。在我这是这样的。

public function loadNews($id=null)

{


	if($this->_model===null)


	{


		if($id!==null || isset($_GET['id']))


			$this->_model=News::model()->findbyPk($id!==null ? $id : $_GET['id']);


		if($this->_model===null)


			throw new CHttpException(404,'The requested page does not exist.');


	}


	return $this->_model;


}

从weblog上也可以看出,缓存是作为filter的,顺序要先于404异常的。

257

1.jpg

你这个缓存的内容是db schema,不是页面缓存。你可以把schema缓存关掉看看。

qiang 你一语惊醒梦中人啊。那个缓存的文件果然是db schema 。我关掉后就不会缓存了。

就是正如你所说的,

以blog demo的PostController::actionShow()为例,loadPost()会根据输入id取得对应的post。如果post不存在,那么它会throw exception。如果post存在,那么第一次显示后,以后就会从cache去取了。当然,如果用户还是提交非法的id,那么cache是不会起作用的,这时loadPost()还是会被调用。

yii这样做的话,就不会存在被人恶意请求产生多个无用文件的问题了。但是话又说回来了,考虑到性能,我们一般把schemaCachingDuration 打开的,但是这样问题又来了,他会缓存db schema ,同样是一大堆的费文件。qiang你还是给个完美的方案吧。即:既可以打开schemaCachingDuration ,又不会因恶意请求造成产生费文件。可以实现这样吗?

schema cache只会cache数据库里有的table结构。恶意请求对它是没影响的(无论是否恶意请求,你都需要schema cache来提高性能)。

另外,如果你有memcache或apc,你可以考虑分开存放page cache和schema cache。通过设置COutputCache.cacheID可以实现这个目的。

这里有个前提,就是你不要用keyPrefix=$_SERVER[REQUEST_URI]。你这种做法的后果就是任意数据缓存都有可能被恶意请求影响。使用varyByParam就没这个问题了。