Hello World
Spiga

输出缓存与CachePanel

2008-07-28 10:02 by 老赵, 39268 visits

缓存的级别

缓存的作用自不必说,提高系统性能最重要的手段之一。上至应用框架,下至文件系统乃至CPU,计算机中各部分设计都能见到缓存的身影。许多朋友一直在追求如何提高Web应用程序的性能,其实最容易被理解和采纳的一条估计就是“缓存”了。也正因为如此Live Journal才会开发出memcached,而微软也推出了Velocity。

有朋友说生成静态页?好吧,在老赵看来,其实这只是把页面内容缓存至硬盘罢了。不过这就涉及到了缓存的某些“级别”了。撇开硬件(如CPU)和系统(例如文件系统,数据库系统)的缓存不说,如果只谈论应用级别——也就是我们平时“代码”中会遇到的缓存来说,一般可以将缓存分为两大类:缓存“数据”和“输出”。啥叫缓存数据呢?例如在代码中,我们将一个表示用户的User实例放入内存,以避免第二次读取时访问较慢的存储设备,这就是缓存了“数据”。至于缓存输出,最典型的也就莫过于刚才提到的“静态页”了。生成了静态页之后,大量的请求将直接获得结果,而不需要进行动态处理,这性能自然就提高了。

这两大类缓存其实也就是缓存的两个级别。之所以把它们分个“高低”或“上下”(并非褒贬,仅指“位置”),是因为在一个应用中,这两类缓存在使用和处理上有所不同。

对于数据缓存,如果用典型的三层架构(有些朋友会单独提出一个“缓存层”,这我就不多作区分了,该往哪层靠拢相信大家自能分清)来举例,往往发生在业务逻辑层和数据访问层。数据访问层里的缓存自然与业务逻辑无关,因此缓存的大都以“单个对象”为主,而缓存的命中与过期策略由“对象”所表示的“数据”的特性来决定,例如Hibernate的一级和二级缓存会根据对象的ID进行确定命中或过期操作。有些数据访问框架提供了更复杂的缓存功能,例如Hibernate也能够根据Query生成Key来进行缓存。而业务逻辑层中的缓存策略则可能较为复杂,命中与过期都与业务需求密切相关。例如一个用户的好友列表(假设是一个int数组)将会根据用户修改其好友信息时过期——当然也可能不是简单的过期,而是直接在内存中进行修改,不过这时候就要考虑到并发性等等。

至于“输出”缓存则容易理解多了。任何应用最终都要有所输出,输出往往就要导致格式的改变,例如变成HTML,XML,亦或是二进制流(有人说其实一切都是二进制流——没错没错,不过我们还是在略高层次上看这个问题吧)。这种直接和表现相关的缓存自然是放在“表现层”了。缓存“输出”相对于缓存“数据”的优点便是有更高的性能。“数据”还需要通过运算才能变成“输出”(试想我们可能需要操作了10K的数据只为了生成1K的有效内容),而缓存“输出”则直接面向了输出内容,性能的提高难道不是显而易见的吗?至于缺点,那就是降低了缓存的复用几率,一个HTML形式的数据自然无法被需要SOAP格式的输出所复用。降低了复用几率则意味着降低了缓存命中率,则意味着提高了存储所需的空间——或者降低了性能。等等,这不是和之前所说“较高性能”的有所矛盾吗?没错,我们刚开始学算法就会接触到一个词:“时间换空间、空间换时间”,这就道出玩儿编程的本质:一切都是在权衡,世上没有真正完美的做法。缓存“输出”其实玩的就是“空间换时间”的游戏,我们缓存的便是同样数据的不同表现形式。

当然缓存输出还有个问题可能就是不太容易设计合适缓存策略(主要是过期策略),对于实时性要求高的内容,往往不得不放弃输出缓存而启用数据缓存。

话说回来,在很多情况下,我们的业务真需要很高的实时性吗?例如一张新建的照片真的需要被立即索引到吗?照片的访问次数真要在列表中即时更新吗?很多时候需求的一些细节膨胀只是一厢情愿,对于用户来说并没有太大意义——尤其是在Web 2.0应用中,因为用户是很容易被诱导的,而且并不会对很多数据进行追究。因此技术架构师在和领域专家进行探讨时不如多提一些建议,一个常用的“句式”是:“XX要求的目的是不是为了YY?如果ZZ的话您能否接受呢?”。不过,如果一个用户删除了自己的一篇文章,却发现它还在自己的管理列表中出现,那么就实在不能说是一个“能够令人接受”的现象了。

ASP.NET的Output Cache及其缺陷

ASP.NET作为一个成熟、强大的应用程序框架,缓存相关的设计自然是它不可或缺的一部份。而ASP.NET作为一个面向Web应用的框架,实现的便是表现层的方方面面,因此它所涉及到的缓存,自然就是上文所说的“输出缓存”了(HTTP协议的缓存不在本文讨论范围之内)。ASP.NET中的输出缓存即为所谓的“OutputCache”。Output Cache的使用可以在任意一本ASP.NET程序设计的书中找到,不过最详细的描述自然莫过于MSDN了。下文将对于ASP.NET的Output Cache进行简单描述,仅仅是为了形成完整的讨论内容。

OutputCache为WebForms框架的一部分,可以在Page和User Control中使用。OutputCache的命中可以让对于某个控件,甚至是整个请求内容的处理直接获得HTML内容,而不需要对页面或控件进行处理。Output Cache主要通过在aspx或ascx文件顶部添加<@ OutputCache />标记来使用,在标记中可以定义缓存的各种特性,例如缓存在多少时间后失效(Duration),缓存的存储位置(Location),缓存的版本控制(VaryByControl / Header / Param / Custom / ContentEncoding)以及缓存与哪个SqlDependency依赖等等。而User Control中的OutputCache标记相对于Page还可以指定一个Shared属性,如果该属性为true,则表示UserControl的缓存可以跨Page命中,反之则会为不同的页面生成不同的缓存内容。此外,ASP.NET还提供了<asp:Substitution />控件,该控件在User Control或Page被缓存的时候依然会被执行,使程序员可以通过编程的方式为缓存内容中的特定部分进行改变。

ASP.NET OutputCache使用起来非常容易,但是在实际运用中往往会显得不够。例如有一个Users控件,用于显示UserIDs所指定的用户。这很容易。不过我们现在有个“特别”的需求,就是在同一个页面中放置两个这样的控件:

<jeffz:Users runat="server" UserIDs="1, 2, 3, 4, 5" />
<jeffz:Users runat="server" UserIDs="6, 7, 8, 9, 10" />

两个控件“实例”的UserIDs参数截然不同,自然输出的内容也会大相径庭。不过如果我们为Users控件添加了OutputCache(并且使用了最传统的VaryByParam="*")之后,问题就出现了。猜猜看结果如何?第二个控件实例的输出和第一个完全相同了,都是参数“1, 2, 3, 4, 5”的结果。这是因为VaryByParam的Param是指QueryString,或Post时的Form数据,而我们的页面在请求时哪有这方面的变化?于是我们就需要开始寻找解决方案了,翻遍了MSDN有关OutputCache的内容,可能只有一个VaryByCustom有些接近。可是VaryByCustom将某个特定参数传给GlobalApplication的GetVaryByCustomString方法中,其余的信息就只有个HttpContext了。所以VaryByCustom也只能通过公用的信息来判断是否使用之前缓存的版本,而无法根据哪个页面的具体哪个控件实例,以及某个控件实例的状态来决定缓存的版本。因此我们可以这么认为,在这种情况下,我们的Users控件无法使用ASP.NET的OutputCache。

还有一种情况就是需要“无条件”地为控件保存多个版本的缓存。例如有个需求是写一个RandomUsers控件,根据UserIDs指定的数据中随机挑选出几个用于展示的用户数据,例如:

<jeffz:RandomUsers runat="server" UserIDs="1, 2, 3, 4, 5" Count="3" />

那么现在还能够使用ASP.NET的OutputCache吗?至少老赵不知道该如何做。因此我们需要一个额外的缓存输出的解决方案,它的要求其实只有两个:

  • 可以自由地定义缓存版本。
  • 可以为每个版本缓存不同的副本,并随机输出。

这就是老赵下面要提到的这个解决方案:CachePanel的需求来源。

CachePanel的构建与使用

其实CachePanel很简单,相信已经有一些朋友能够想象出这个组件的大致逻辑了。根据老赵的习惯,我们还是使用“用例驱动开发”的方式来进行组件开发。例如老赵期望的使用方式是这样的:

<jeffz:CachePanel runat="server"
    Duration="00:15:00"
    CopyCount="10"
    CacheKey="RandomUsers"
    ResolveCacheKey="CachePanel_ResolveCacheKey"
>
    <jeffz:RandomUsers runat="server" UserIDs="1, 2, 3, 4, 5" Count="3" />
</asp:CachePanel>

以下是CachePanel的各种成员描述与代码:

public class CachePanel : Control
{
    public TimeSpan Duration { get; set; }
 
    public int CopyCount { get; set; }
 
    public string CacheKey { get; set; }
 
    public EventHandler ResolveCacheKey { get; set; }
 
    ...
}
  • Duration属性:每份缓存副本的有效时间长度。这里使用TimeSpan的字符串表示法,老赵认为相较传统的秒数,这样能够更直接地设定和读取一段时间长度。
  • CopyCount属性:每个缓存版本的副本数量,输出时将任意选择一个副本输出。在上例中,我们通过生成10个副本让用户看起来的确是在输出随机的结果,而其实我们只是缓存了10个副本而已。
  • CacheKey属性:不同的CacheKey决定了不同的缓存版本。请注意这个CacheKey是全局的,因此不同页面中的CachePanel如果CacheKey相同,将会得到相同的缓存结果(排除CopyCount属性的影响)。
  • ResolveCacheKey事件:提供了一个动态指定缓存版本的可能。开发人员可以响应该事件,根据上下文环境的不同(例如QueryString,Form或Header的不同)对CacheKey进行改变。

可以看到,其实只是这简单的四个成员就能满足上文的要求(而且事实上,在理论上CopyCount也能够省略,因为我们有ResolveCacheKey事件,不是吗?)。

至于与缓存相关的具体逻辑,其实非常简单。首先是在OnInit事件中检查是否命中缓存:

  1. 执行ResolveCacheKey事件以确认CacheKey。
  2. 随机选取副本编号。
  3. 根据CacheKey和副本编号确认被缓存的数据所使用的key(如果CacheKey为空,则使用默认的CacheKey,它保证了同一页面中的位置相同的CachePanel实例共享缓存版本)。
  4. 如果缓存命中,则清除CachePanel内的所有子控件。

代码如下:

public class CachePanel : Control
{
    ...
    private static Random s_random = new Random(DateTime.Now.Millisecond);
 
    public bool CacheHit { get; private set; }
 
    private string m_cacheKey;
    private string m_cachedContent;
 
    protected override void OnInit(EventArgs e)
    {
        var resolveCacheKey = this.ResolveCacheKey;
        if (resolveCacheKey != null)
        {
            resolveCacheKey(this, EventArgs.Empty);
        }
 
        int copyIndex = s_random.Next(this.CopyCount);
        this.m_cacheKey = this.GetCacheKey(copyIndex);
 
        this.m_cachedContent = this.Context.Cache.Get(this.m_cacheKey) as string;
        this.CacheHit = (this.m_cachedContent != null);
        if (this.CacheHit) this.Controls.Clear();
 
        base.OnInit(e);
    }
 
    private string GetCacheKey(int copyIndex)
    {
        var cacheKeyBase = this.CacheKey ?? this.GetDefaultCacheKeyBase();
        return "$CachePanel$" + cacheKeyBase + "_" + copyIndex;
    }
 
    private string GetDefaultCacheKeyBase()
    {
        return this.Context.Request.AppRelativeCurrentExecutionFilePath + "_" + this.UniqueID;
    }
    ...
}

由于内容被清空,然后到了生成内容阶段,事情就好办了——简单的缓存子控件生成的HTML内容即可:

public partial class CachePanel : Control
{
    ...
    protected override void RenderChildren(HtmlTextWriter writer)
    {
        if (this.m_cachedContent == null)
        {
            StringBuilder sb = new StringBuilder();
            HtmlTextWriter innerWriter = new HtmlTextWriter(new StringWriter(sb));
            base.RenderChildren(innerWriter);
 
            this.m_cachedContent = sb.ToString();
            this.Context.Cache.Insert(this.m_cacheKey, this.m_cachedContent, null,
                DateTime.Now.Add(this.Duration), Cache.NoSlidingExpiration);
        }
 
        writer.Write(this.m_cachedContent);
    }
}

至此,CachePanel就制作完成了,其实只是短短的几十行代码而已。到这里老赵不禁又要发一句感慨:只要了解了框架的运行规则,开发出各种扩展又有多少难度呢?一切都只是看您有多少想象力而已。

不过大家在使用CachePane时可能还需要注意以下几点:

  • CacheKey的作用域是整个ASP.NET应用程序,因此如果您要指定CacheKey的话,请给出清晰而明确的值。
  • CachePanel能够缓存页面中任意部分的内容,不过在使用时可能就需要您根据CacheHit属性的值来判断是否需要为控件填充数据,否则可能就会无法达到缓存的目的。
  • CachePanel将会在缓存命中时清空所有子控件,因此在操作时也请注意这一点,以免出现不可预知(Unpredictable)的结果。
Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

64 条回复

  1. Shiny Zhu
    *.*.*.*
    链接

    Shiny Zhu 2008-07-28 02:42:00

    asp.net mvc preview 4也加了缓存控制,星期4晚上的课老赵要讲吧?期待中。

  2. Bēniaǒ
    *.*.*.*
    链接

    Bēniaǒ 2008-07-28 03:28:00

    呵呵,还有个板凳可以坐..

  3. 怪怪
    *.*.*.*
    链接

    怪怪 2008-07-28 04:26:00

    其实, 把ASP.NET(或任何一个其它框架)的框架打散自己重新组装, 还是比较一劳永逸的事情, 也能部分使用框架的成果。

    当然鉴于ASP.NET封装的太死, 脏手是免不了了...

  4. 缓存疑问[未注册用户]
    *.*.*.*
    链接

    缓存疑问[未注册用户] 2008-07-28 07:26:00

    您好,对于缓存我有些疑问,望指教:
    1、拿数据缓存来说,我们缓存了一个User用户表。页面显示最新注册的5个用户。我的理解是新增一个用户(或update,delete操作),是不是我们要在操作数据库成功后,应当人为的对缓存做相应的操作。如果是,该如何操作缓存中的单条数据呢?如果否,是不是在用户表被更新后,即认为缓存失效,然后重新读库,再重写缓存。
    2、SqlCacheDependency能够判断被缓存的数据表是否有更新,他是不是只能做到检测到数据更新后使缓存实效,接下来我们要做的是人为去更新缓存(做相应的增、删、改操作)呢?还是重新读库然后重写缓存?
    谢谢!

  5. 金色海洋(jyk)
    *.*.*.*
    链接

    金色海洋(jyk) 2008-07-28 07:28:00

    弱弱的问一下,OutputCache 缓存的是数据源还是page绑定后生成的html?

    如果 OutputCache 只能缓存数据源的话,那么好像效率不是太好。
    如果 OutputCache 可以把页面最终生成的html缓存下来的话,能不能说一下如何设置呢?谢谢。

    不好意思,快上班了,没有太仔细看您的文章。

  6. tonyc[未注册用户]
    *.*.*.*
    链接

    tonyc[未注册用户] 2008-07-28 07:38:00

    @缓存疑问
    SqlCacheDependency当然不用人为操作啦,SQL2005是查询通知,Sql2000为定时轮询,都是有变更自动更新缓存的

  7. 缓存疑问[未注册用户]
    *.*.*.*
    链接

    缓存疑问[未注册用户] 2008-07-28 08:07:00

    @tonyc
    "有变更自动更新缓存"?
    不是吧!

  8. 蛙蛙池塘
    *.*.*.*
    链接

    蛙蛙池塘 2008-07-28 08:24:00

    Velocity是微软的吗?不是一个模板框架吗?

  9. 雅阁布
    *.*.*.*
    链接

    雅阁布 2008-07-28 08:32:00

    up!!

  10. PerfectDesign
    *.*.*.*
    链接

    PerfectDesign 2008-07-28 08:36:00

    很强啊,佩服

  11. Wuya
    *.*.*.*
    链接

    Wuya 2008-07-28 08:37:00

    总的来说老赵的CachePanel就是对Cache类的简单封装,其实OutputCache也是对Cache的封装,形式不同而已。

    关键词:编程高速缓存、Cache对象。

  12. Indigo Dai
    *.*.*.*
    链接

    Indigo Dai 2008-07-28 08:43:00

    老赵深感UpdatePanel的强大,也想成为一XXXPanel之父呀,呵呵

  13. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 08:55:00

    @LZ
    能不能给个demo下载呢?

  14. 心悦
    *.*.*.*
    链接

    心悦 2008-07-28 09:21:00

    性能得与失之间寻求平衡!

  15. 菜菜灰
    *.*.*.*
    链接

    菜菜灰 2008-07-28 09:28:00

    大站都是直接生成html,就是老赵所说的缓存“输出”

  16. 老赵
    admin
    链接

    老赵 2008-07-28 09:43:00

    --引用--------------------------------------------------
    缓存疑问: 您好,对于缓存我有些疑问,望指教:
    1、拿数据缓存来说,我们缓存了一个User用户表。页面显示最新注册的5个用户。我的理解是新增一个用户(或update,delete操作),是不是我们要在操作数据库成功后,应当人为的对缓存做相应的操作。如果是,该如何操作缓存中的单条数据呢?如果否,是不是在用户表被更新后,即认为缓存失效,然后重新读库,再重写缓存。
    2、SqlCacheDependency能够判断被缓存的数据表是否有更新,他是不是只能做到检测到数据更新后使缓存实效,接下来我们要做的是人为去更新缓存(做相应的增、删、改操作)呢?还是重新读库然后重写缓存?
    谢谢!
    --------------------------------------------------------
    1、最新的5个用户,就5分钟过期一次的策略好了,这就是我说的“不必即时”的数据。
    2、缓存实效了就读不到了,需要重新去数据源读。

  17. Dflying Chen
    *.*.*.*
    链接

    Dflying Chen 2008-07-28 09:48:00

    赵老师好

  18. 老赵
    admin
    链接

    老赵 2008-07-28 09:50:00

    --引用--------------------------------------------------
    缓存疑问: @tonyc
    &quot;有变更自动更新缓存&quot;?
    不是吧!
    --------------------------------------------------------
    有变更自动让缓存失效。

  19. 老赵
    admin
    链接

    老赵 2008-07-28 09:50:00

    --引用--------------------------------------------------
    金色海洋(jyk): 弱弱的问一下,OutputCache 缓存的是数据源还是page绑定后生成的html?

    如果 OutputCache 只能缓存数据源的话,那么好像效率不是太好。
    如果 OutputCache 可以把页面最终生成的html缓存下来的话,能不能说一下如何设置呢?谢谢。

    不好意思,快上班了,没有太仔细看您的文章。
    --------------------------------------------------------
    OutputCache缓存的自然是HTML。
    具体使用方式可以看MSDN。

  20. hi,jef[未注册用户]
    *.*.*.*
    链接

    hi,jef[未注册用户] 2008-07-28 09:51:00

    hi,赵老师,

    请教一个问题,就是页面有个“保存” 按钮,当用户提交后,数据保存,但用户这时点击浏览器刷新按钮,又重复了刚才的保存操作,又保存了条数据,想问下赵老师有什么好点的方法避免这种情况?谢谢。

  21. 老赵
    admin
    链接

    老赵 2008-07-28 09:51:00

    --引用--------------------------------------------------
    蛙蛙池塘: Velocity是微软的吗?不是一个模板框架吗?
    --------------------------------------------------------
    同名吧

  22. 老赵
    admin
    链接

    老赵 2008-07-28 09:51:00

    --引用--------------------------------------------------
    Dflying Chen: 赵老师好
    --------------------------------------------------------
    去死吧你个死胖子,哈哈

  23. 老赵
    admin
    链接

    老赵 2008-07-28 09:52:00

    --引用--------------------------------------------------
    Wuya: 总的来说老赵的CachePanel就是对Cache类的简单封装,其实OutputCache也是对Cache的封装,形式不同而已。

    关键词:编程高速缓存、Cache对象。
    --------------------------------------------------------
    虽然是简单封装,但是怎么会是“对Cache的简单封装”呢?呵呵。

  24. 老赵
    admin
    链接

    老赵 2008-07-28 09:53:00

    --引用--------------------------------------------------
    菜菜灰: 大站都是直接生成html,就是老赵所说的缓存“输出”
    --------------------------------------------------------
    还是要看站点(业务)的性质。

  25. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 09:55:00

    老赵:
    我自己试了下,页面代码是这样的:
    WebUserControl1是一个用户控件,它有一个属性sTitle,控件呈现的内容就是这引属性的值
    <cc1:CachePanel runat="server"
    Duration="00:15:00"
    CopyCount="10"
    CacheKey="WebUserControl1">

    <cc1:WebUserControl1 sTitle="2" runat="server" ></cc1:WebUserControl1>
    </cc1:CachePanel>
    运行后说:
    Parser Error Message: Type 'ServerControl_CachePanel.CachePanel' does not have a public property named 'WebUserControl1'.

    Source Error:


    Line 30: CacheKey="WebUserControl1">
    Line 31:
    Line 32: <cc1:WebUserControl1 sTitle="2" runat="server" ></cc1:WebUserControl1>
    老赵是否帮忙解释下,最好把您的DEMO发我一份.

  26. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 09:56:00


    <cc1:WebUserControl1 sTitle="2" runat="server" ></cc1:WebUserControl1>
    改成:
    <uc1:WebUserControl1 sTitle="2" runat="server" ></uc1:WebUserControl1> 都不行.


  27. 老赵
    admin
    链接

    老赵 2008-07-28 09:58:00

    @hi,jef
    redirect回本页

  28. 老赵
    admin
    链接

    老赵 2008-07-28 10:00:00

    @姜敏
    明显是aspx页面标记的问题,相当于编译不通过,这个问题还是自己调试吧。
    我不给demo下载其实也是为了让读者可以自行研究,而不是简单的拿来主义。

  29. 制造web的下一个10年(中国制造)[未注册用户…
    *.*.*.*
    链接

    制造web的下一个10年(中国制造)[未注册用户] 2008-07-28 10:11:00

    看了您的wodeyichu.
    好像是做了两个图片服务器,个人觉得css,js最好也放在资源服务器上。
    好像没做服务器分流。
    我主要想请教数据库方面的东东。
    您有没有采取读写分离?若是用了不妨给讲讲^_^

    这几个上,你会选择什么?
    表分区,日表,月表(想想还是表分区吧^_^)
    听人说sqlserver不支持集群,有这事吗?
    另外也想通过您对Squid,memcached有个彻底理解。
    ^_^要求太多了^_^也请路过的看着眼熟的说说^_^

  30. 老赵
    admin
    链接

    老赵 2008-07-28 10:17:00

    @制造web的下一个10年(中国制造)
    css/js之类的好说,主要是开发上用同一个域名的方便,以后在开发上支持分离就容易了——主要还是并行下载的优势。
    我的衣橱使用了数据库物理切分,但是暂时没有使用表分区,现在还在快速发展阶段,没有作太多优化。
    sql server不支持天然的负载均衡,只支持failover集群,不过镜像,log shipping,data replication一应俱全,也就是说做负载均衡的基础都有了。
    Squid,memcached的资料到处都是,主要作用都是缓存,前者缓存http请求结果,后者缓存数据。

  31. 侯垒
    *.*.*.*
    链接

    侯垒 2008-07-28 10:26:00

    --引用--------------------------------------------------
    Jeffrey Zhao: @hi,jef
    redirect回本页
    --------------------------------------------------------
    @Jeffrey Zhao
    如果这个页面上其它的数据显示,例如:我保存成功后要提示一下保存成功,如果redirect回本页,提示信息不就没有了.另外如果当前页面上有GridView而且存在分页,一旦redirect到本页不就只显示第一页了,这样的话,原来页面的状态将不保.对于这样的情况有没有更好的解决方案?

  32. 老赵
    admin
    链接

    老赵 2008-07-28 10:27:00

    @侯垒
    客户端保存cookie,提交前检查cookie情况。

  33. 侯垒
    *.*.*.*
    链接

    侯垒 2008-07-28 10:28:00

    thanks

  34. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 10:37:00

    老赵,谢谢您的教学方式,我的解决方案是这样的:
    [ParseChildren(false )]
    public class CachePanel : WebControl
    {
    加了[ParseChildren(false )]
    ParseChildren(false) 当其内部定义为flase时,那么放在此控件内的元素将被解析成控件
    不知道是不是这样呢,哈哈.

  35. 制造web的下一个10年(中国制造)[未注册用户…
    *.*.*.*
    链接

    制造web的下一个10年(中国制造)[未注册用户] 2008-07-28 10:40:00

    非常感谢您的回复。
    ^_^介绍一下自己^_^asp.net程序员,热衷于互联网开发。
    (实际上刚刚开始)
    ^_^闲扯两句^_^相信中国人能够设计出让世界接受的互利网模式。
    而不是yahoo后的sohu,netease。
    也不是facebook后的xiaonei,51。
    更不是twitter后的饭否,唧歪。
    撤远了
    实际上是觉的您写的文章特别易懂。所以没有太多的去google就直接上来问了。
    您若是对这些开讲,我想很多人会激动不易,至少我是这样想的^_^

  36. ppchen(陈荣林)
    *.*.*.*
    链接

    ppchen(陈荣林) 2008-07-28 11:07:00

    this.Context.Cache,使用了Cache
    如果IIS中设置了Web园最大工作进程大于1的话,可能会有问题的吧?

  37. 老赵
    admin
    链接

    老赵 2008-07-28 12:17:00

    @ppchen(陈荣林)
    不排除这种可能,当然最多是每个进程各一个缓存副本吧。

  38. 老赵
    admin
    链接

    老赵 2008-07-28 12:17:00

    @姜敏
    试验下来我的代码没有问题啊

  39. andy.wu
    *.*.*.*
    链接

    andy.wu 2008-07-28 13:25:00

    深有同感,asp.net的页面缓存用起来虽然方便,但不够灵活。

    正想也做个控件,就发现老赵的了,思路基本一致。

  40. 老赵
    admin
    链接

    老赵 2008-07-28 14:49:00

    @andy.wu
    还好我早写了,赫赫。

  41. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 15:04:00

    CopyCount="10"
    如果这个数量太大的话,cache的命中率是不是会降低呢?为何不把它设为"1"呢?
    这样就可以保证命中率为100%了,不知道LZ的目的是什么,我的想法又是否正确呢?

  42. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 15:05:00

    或者根本就不用这个参数,直接写上key不就行了,这样就不会cache的时候命中不了了.

  43. alex hu
    *.*.*.*
    链接

    alex hu 2008-07-28 15:10:00

    先顶一下,呆会就实验一下

  44. 老赵
    admin
    链接

    老赵 2008-07-28 15:12:00

    @姜敏
    关于CopyCount的作用我的文章里不是写的很清楚吗?

  45. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 15:25:00

    文章中的定义:
    CopyCount属性:每个缓存版本的副本数量,输出时将任意选择一个副本输出。在上例中,我们通过生成10个副本让用户看起来的确是在输出随机的结果,而其实我们只是缓存了10个副本而已。
    可是我觉的好像没有这个必要啊.能不能解释下生成副本的好处呢?

  46. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 15:30:00

    我的demo如下:
    <cc1:CachePanel runat="server"
    Duration="00:15:00"
    CopyCount="1"
    CacheKey="WebUserControl1">

    <cc1:WebUserControl1 ID="WebUserControl1" sTitle="2" runat="server" ></cc1:WebUserControl1>
    <cc1:WebUserControl1 ID="WebUserControl2" sTitle="3" runat="server" ></cc1:WebUserControl1>
    <cc1:WebUserControl1 ID="WebUserControl3" sTitle="4" runat="server" ></cc1:WebUserControl1>
    </cc1:CachePanel>
    每次都能保证cache能够命中,其实我觉的没有生成副本的必要.
    原文中的取得cache的key的方法
    int copyIndex = s_random.Next(this.CopyCount);
    this.m_cacheKey = this.GetCacheKey(copyIndex);
    如果是copycount的数量大的话,往往会出现命中不了的现象
    不太明白LZ生成副本的目的是什么?

  47. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 15:32:00

    我觉的其实缓存的目的不就是把控件的输出HTML代码保存于内存中吗?既然这样,在控件参数cacheKey的设置时给一个不重复的值不就行了.有必要搞一个副本个数的参数吗?

  48. Juzz Pig(橘子&猪)
    *.*.*.*
    链接

    Juzz Pig(橘子&猪) 2008-07-28 15:47:00

    @ 姜敏
    CopyCount是为了让客户看起来在随机选取数据。
    业务需要。并无其他用意。

  49. 姜敏
    *.*.*.*
    链接

    姜敏 2008-07-28 15:49:00

    啊,原来是这样啊,看来是我没有看明白啊,谢谢指教.

  50. 老赵
    admin
    链接

    老赵 2008-07-28 16:44:00

    @姜敏
    恩,其实在大多数情况下CopyCount总是为1。

  51. CindyChen
    *.*.*.*
    链接

    CindyChen 2008-07-30 15:18:00

    --引用--------------------------------------------------
    Jeffrey Zhao: --引用--------------------------------------------------
    Dflying Chen: 赵老师好
    --------------------------------------------------------
    去死吧你个死胖子,哈哈
    --------------------------------------------------------

    别以为人家没看出来,两个人在眉来眼去打情骂俏!!!

  52. 老赵
    admin
    链接

    老赵 2008-07-30 21:23:00

    @CindyChen
    不会吧……这你也吃醋……

  53. CindyChen
    *.*.*.*
    链接

    CindyChen 2008-07-30 22:07:00

    介个⋯⋯平时生活太乏味,工作太辛苦,闲来打情骂俏可使神清气爽,有利于身心健康、工作顺利、爱情甜蜜、家庭幸福、社会稳定、国家富强、世界和平⋯⋯
    两位gg自便自便,继续继续⋯⋯

    我,继续吃醋⋯⋯⋯⋯

  54. 王孟军!
    *.*.*.*
    链接

    王孟军! 2008-07-31 11:10:00

    A。老赵请教个问题
    我设置页面输出缓存的时候
    我点击Query Button的时候,没有回传到服务器去
    我既想缓存,又要可以使用Query Button
    难道使用局部局部缓存?

    B。情景是这样的
    1。我登陆OK后,默认显示当天的执法记录报表
    2。也可以根据条件 查询,然后显示执法记录报表

    C。我要的结果
    1。登陆成功后,默认显示当天的执法记录报表,
    并且,当我离开报表显示页面,再次回来的时候,显示当天的缓存数据。
    而且 当我Click Query Button的时候,还要可以回传
    谢谢

  55. 老赵
    admin
    链接

    老赵 2008-08-01 01:34:00

    @王孟军!
    Cache Panel会清除控件的,所以要小心使用。

  56. 蛙蛙池塘
    *.*.*.*
    链接

    蛙蛙池塘 2008-08-03 08:56:00

    天呐,老赵的帖子的阅读量动不动就上W,上2W,平均阅读量也五六K了,太受欢迎了,好强悍呀,呵呵。

  57. swing
    *.*.*.*
    链接

    swing 2008-08-03 12:43:00

    不知道这样理解对不对,请老赵指教:

    OutputCache 使服务器直接从缓存中返回HTML,不作其它处理。
    Cache Panel则照常执行页面生命周期,最后从缓存中获取HTML。

    Cache Panel性能上劣于OutputCache,灵活控制上优于OutputCache。

    是这样吗

  58. 老赵
    admin
    链接

    老赵 2008-08-03 20:47:00

    @swing
    如果非要在OutputCache和CachePanel在性能上比个高低的话那么的确没有错,赫赫。

  59. 老赵
    admin
    链接

    老赵 2008-08-04 01:26:00

    @蛙蛙池塘
    这片只有4K多,唉……

  60. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-08-05 11:46:00

    不光是打情骂俏,好像还互换帐号玩。。。


    --引用--------------------------------------------------
    CindyChen: --引用--------------------------------------------------
    Jeffrey Zhao: --引用--------------------------------------------------
    Dflying Chen: 赵老师好
    --------------------------------------------------------
    去死吧你个死胖子,哈哈
    --------------------------------------------------------

    别以为人家没看出来,两个人在眉来眼去打情骂俏!!!
    --------------------------------------------------------

  61. runMan[未注册用户]
    *.*.*.*
    链接

    runMan[未注册用户] 2008-08-05 14:20:00

    赵老师您好,我对“网站应用程序”和“缓存”这两个概念有些地方不是很清楚,想请教一下您:
    建立一个web站点,其中只有一个页面Default.aspx,Page_Load代码如下:
    if (Page.Cache["myTime"] != null)
    {
    Response.Write(Page.Cache["myTime"].ToString());
    }
    else
    {
    string strNow = DateTime.Now.ToString();
    Page.Cache.Insert("myTime", strNow, null, DateTime.Now.AddDays(1), TimeSpan.Zero);
    Response.Write(strNow);
    }
    现在把web放到服务器A上:
    1. 当用户C1在地址栏敲入服务器A的IP地址打开WEB站点时,这时应该说打开了一个应用程序;如果这时另一个用户C2在另一台机器也打开这个页面,这时两个用户属于一个应用程序还是两个啊?如果是一个,那可以所我只要打开网站的某个页面放在那儿不关闭,这个应用程序都是一直存在着?
    2:当C1用户打开页面时,生成一个缓存字符串(记录当前时间并打印出来),这时如果所有访问站点的C1、C2都关闭浏览器,应用程序生命期算结束了吧,那这个缓存还在A服务器的内存中吗?

    非常感谢~~

  62. LuChaoShuai
    *.*.*.*
    链接

    LuChaoShuai 2008-08-16 18:33:00

    public bool CacheHit { get; private set; }

    又发现一个新特性。

  63. jinquan[未注册用户]
    *.*.*.*
    链接

    jinquan[未注册用户] 2008-10-13 17:36:00

    我顶!

  64. jinquan[未注册用户]
    *.*.*.*
    链接

    jinquan[未注册用户] 2008-10-13 17:45:00

    好呀!

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我