LFQ
(Liufuqiang 0123)
August 4, 2009, 4:28pm
1
我用了文件缓存,觉得挺不错的。对于不经常更新的页面可以采用这种方式,效果基本等同于静态页面。
但是我的问题是,我不想设置一个固定的失效时间来控制缓存的更新,而是想当数据库的内容改变时自动去更新缓存。
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来处理缓存的话,问题是我怎么知道处理那个缓存?总不能处理所有缓存吧?
期待您的答复。多谢。
qiang
(Qiang Xue)
August 4, 2009, 4:57pm
2
LFQ
(Liufuqiang 0123)
August 5, 2009, 3:35am
3
qiang,我还有个问题,
‘keyPrefix’=>$_SERVER[REQUEST_URI],
这个key前缀是不科学的,可能会被人恶意请求,比如请求10w次,我的磁盘可以就爆了。
但是我的目的还是想让每个文章都缓存了。如:http://www.dddd.com/index.php/downCenter/show/id/4
我该如何设置这个keyPrefix呢?
期待您的答复。
miles
(Cuiming2355 Cn)
August 5, 2009, 5:37am
4
keyPrefix的默认值是Yii::app()->getId()的值,这个不能满足需求吗?为什么要改成你自己的
LFQ
(Liufuqiang 0123)
August 5, 2009, 6:15am
5
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,就会造成一堆垃圾缓存文件。这是我头疼的事情。
继续等待…
qiang
(Qiang Xue)
August 5, 2009, 4:27pm
6
不要这样去修改cache.keyPrefix,否则的确会出现你描述的问题。
COutputCache有一个varyByParam属性,你应该用这个。另外,为了避免你说的问题,在使用缓存前,应该预先检查id的合法性(比如id值应该介于1到100000)之间。
LFQ
(Liufuqiang 0123)
August 5, 2009, 4:49pm
7
不修改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呢?搞了一下午也没搞出来。请强帮我一下了。多谢。
LFQ
(Liufuqiang 0123)
August 5, 2009, 5:00pm
8
这个key应该是哪个缓存文件名吗(md5值)?强请不要走开,我只能等到北京时间凌晨1点才能碰上您。帮我处理下这个问题吧。我在线等候这您答复呢。
qiang
(Qiang Xue)
August 5, 2009, 5:04pm
9
你有没有试过varyByParam?关于你说的避免恶意用户的方法,我觉得是个好主意。我会看一下能否在COutputCache里提供这个支持。
LFQ
(Liufuqiang 0123)
August 5, 2009, 5:08pm
10
我试过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;
qiang
(Qiang Xue)
August 5, 2009, 5:44pm
11
应该不会啊。如果你设置了varyByParam=array(‘id’),那么计算出来的cache key是会根据id不同而不同的。
LFQ
(Liufuqiang 0123)
August 5, 2009, 6:22pm
12
嗯。我犯错了。确实可以实现根据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呢?
或者您有更好的方式处理用户恶意请求的方案吗?
qiang
(Qiang Xue)
August 5, 2009, 6:36pm
13
我刚刚验证过,如果你针对非法的id throw exception的话,那么outputcache是不会作缓存的。
LFQ
(Liufuqiang 0123)
August 5, 2009, 6:45pm
14
我不太明白你的意思,你的意思是我在每个action里明确的对$_GET[‘id’] 做验证吗?(验证规则也是个问题呢) 。那么的话很麻烦的啊。
如果非这样做应该是可以的,但是太麻烦,每个action都有处理接受的参数。如果我误解了您的意思,请您贴点代码出来好吗?
就我上面提到的问题,在回调函数里怎么取controller的id你有什么方法啊?在回调里,$this没定义的。所以不能用$this->id了。苦恼中。
qiang
(Qiang Xue)
August 5, 2009, 6:58pm
15
以blog demo的PostController::actionShow()为例,loadPost()会根据输入id取得对应的post。如果post不存在,那么它会throw exception。如果post存在,那么第一次显示后,以后就会从cache去取了。当然,如果用户还是提交非法的id,那么cache是不会起作用的,这时loadPost()还是会被调用。
在任何地方,你都可以通过Yii::app()->controller获得当前的controller。
不过你试图据此去清空cache恐怕很难,因为cache key的计算很复杂。你可以查看一下COutputCache的代码。
LFQ
(Liufuqiang 0123)
August 5, 2009, 7:11pm
16
在抛异常之前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
qiang
(Qiang Xue)
August 5, 2009, 7:14pm
17
你这个缓存的内容是db schema,不是页面缓存。你可以把schema缓存关掉看看。
LFQ
(Liufuqiang 0123)
August 5, 2009, 7:29pm
18
qiang 你一语惊醒梦中人啊。那个缓存的文件果然是db schema 。我关掉后就不会缓存了。
就是正如你所说的,
以blog demo的PostController::actionShow()为例,loadPost()会根据输入id取得对应的post。如果post不存在,那么它会throw exception。如果post存在,那么第一次显示后,以后就会从cache去取了。当然,如果用户还是提交非法的id,那么cache是不会起作用的,这时loadPost()还是会被调用。
yii这样做的话,就不会存在被人恶意请求产生多个无用文件的问题了。但是话又说回来了,考虑到性能,我们一般把schemaCachingDuration 打开的,但是这样问题又来了,他会缓存db schema ,同样是一大堆的费文件。qiang你还是给个完美的方案吧。即:既可以打开schemaCachingDuration ,又不会因恶意请求造成产生费文件。可以实现这样吗?
qiang
(Qiang Xue)
August 5, 2009, 7:44pm
19
schema cache只会cache数据库里有的table结构。恶意请求对它是没影响的(无论是否恶意请求,你都需要schema cache来提高性能)。
另外,如果你有memcache或apc,你可以考虑分开存放page cache和schema cache。通过设置COutputCache.cacheID可以实现这个目的。
qiang
(Qiang Xue)
August 5, 2009, 7:46pm
20
这里有个前提,就是你不要用keyPrefix=$_SERVER[REQUEST_URI]。你这种做法的后果就是任意数据缓存都有可能被恶意请求影响。使用varyByParam就没这个问题了。