Hello World
Spiga

片段缓存的实际应用、延迟加载及Eazy类库

2009-09-22 14:54 by 老赵, 14033 visits

片段缓存)已经实现完整了,但好像还没有提到如何在项目中进行实际应用,那么现在就来谈一谈这方面。之前也有朋友提出,这个片段缓存到底省下的是什么啊?好像数据都是在Controller中获取的,视图的生成不会带来多少开销啊,难道节省的只是拼接HTML字符串的时间吗?这其实就涉及到片段缓存在实际项目中该如何使用的问题了。

上周日的幻灯片中我提到Ruby on Rails中的Fragment Caching一般是这样使用的:

class BlogController < ApplicationController
  def list
    unless read_fragment(:action => 'list' )
      @articles = Article.find_recent
    end
  end
end

以及它的视图模板:

<% cache do %>   <!- Here's the content we cache ->
  <ul>
    <% for article in @articles -%>
      <li><p><%= h(article.body) %></p>
    <% end -%>
  </li></ul>
<% end %>

以上是一个Controller和一个名为list的Action方法,再list方法中回去检查片段缓存是否存在,如果不存在才去加载@articles字段。视图模板中的逻辑也类似,如果缓存不存在才去访问@article字段。这样,读取@acticle的开销便节省下来了。在我之前公布的ASP.NET MVC片段缓存实现中,还无法在Controller中操作缓存,近期打算加上这方面的功能。

可是这样的做法可能会产生一个问题:在并发情况高的环境下,可能视图会访问到没有初始化的@articles字段。因为在Action方法中缓存还没有过期,但是就在正式生成视图内容,缓存过期了——于是就引起了错误。不过,其实我在想到“片段缓存”时,第一反应并不是这种做法,而是使用“延迟加载”。

使用延迟加载,也就是说在Action方法中虽然不会加载某个字段,但是还是会给这个字段“打一个桩(stub)”。如果视图中不妨问这个字段自然无妨,但在需要访问的话,也可以正常获取到数据。于是这种做法既可以省下开销,又不会出现问题。

不过使用延迟加载并非完全没有代价,它要求资源回收的时机不能太早。据我所知,有些朋友会使用Action Filter,在OnActionExecuted时回收所有资源(如数据库连接),这样在视图中自然无法获取数据了。因此,如果您使用延迟加载,请务必在OnResultExecuted时回收资源。

那么,我们该如何使用延迟加载呢?最容易的做法自然是稍微修改一下你的Model,保持接口不变即可。不过现在您也可以关注一下Eazy类库。

Eazy类库是我前一段时间设想中的“延迟辅助类库”,名字来源于Easy + Lazy,目前托管在CodePlex中。有了这个类库的帮助,您就无需对自己的类库“小动干戈”了。于是,您就可以在Action方法使用这样的代码来设置字段的延迟功能:

public ActionResult List()
{ 
    var model = LazyBuilder.Create<Model>()
        .Setup(m => m.Articles, () => Article.FindRecent())
        .Instance;

    return View(model);
}

LazyBuilder.Create方法会创建Model类型的实例,然后使用Setup方法可以为某个属性指定一个委托,而这个委托便会在第一次访问这个属性时执行。LazyBuilder的实现机制非常清晰,只是使用Emit在运行时动态创建目标类型的子类而已。例如Model类型:

public class Model
{
    public virtual List<Article> Articles { get; set; }
}

便会为它生成如下的子类:

public class Model$LazyProxy : Model
{
    public override List<Article> Articles
    {
        get
        {
            if (this.Articles$LazyLoader != null)
            {
                base.Articles = this.Articles$LazyLoader();
                this.Articles$LazyLoader = null;
            }

            return base.Articles;
        }
        set
        {
            base.Articles = value;
            this.Articles$LazyLoader = null;
        }
    }

    public Func<List<Article>> Articles$LazyLoader = null;
}

这段写法是我认为实现延迟加载最理想的方式了,最重要的是它保留了属性原有的逻辑,只是在加载时机上做了文章。此外,在不需要延迟加载的情况下,属性的行为也不会改变。

不过Eazy项目其实才创建了2天,目前只能通过指定泛型类型来构造对象,这意味着这个类型必须有默认的构造函数。根据我的设想,它以后还会支持其他的构造方式,例如:

var builder = LazyBuilder.Create(() => new Model(1, 2) { Time = DateTime.Now });
builder.Setup(m => m.Articles, () => Article.FindRecent());
var model = builder.Instance;

由于完全使用Emit,因此不会带来反射的开销,性能是很有保障的。例如LazyBuilder.Create<Model>的消耗和new Model()相比也就是多了些方法调用而已。目前Eazy项目还比较简陋,例如代码中不会抛出恰当的异常,单元测试也不够完整。此外,我还在考虑是否要对只读属性或接口提供延迟加载的支持。

当然,如果您有什么想法也请告诉我,这里先谢过了。

Creative Commons License

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

Add your comment

21 条回复

  1. 第一控制.NET
    *.*.*.*
    链接

    第一控制.NET 2009-09-22 14:58:00

    昨天关心的问题,今天有了全面方案了。呵呵。

  2. 在别处
    *.*.*.*
    链接

    在别处 2009-09-22 15:08:00

    以前呢,觉得您老写的太少,最近觉得您写的太多。。。

  3. rainnoless
    *.*.*.*
    链接

    rainnoless 2009-09-22 15:20:00

    Ruby on Rails,我喜欢,正在入门学习研究中,觉得其框架中的缓存方式好简洁。
    苦闷啊,只能在.net1.1下,为了局部缓存,硬是要把一些部分抽出来支离破碎的整理成用户控件,呵呵。
    羡慕那些在.net2.0/3.5下那些多样化的缓存策略啊,呵呵。
    尤其我在.net1.1下想模仿实现.net2.0下的数据库缓存依然时,参考老外给出的一个实在别扭的方案时,问题多多,到之后还没有整成,郁闷之极,呵呵。

  4. 老赵
    admin
    链接

    老赵 2009-09-22 15:26:00

    @rainnoless
    .net 1.1,要死了,你还是赶快转移到ror吧。
    倒不是我喜欢ror或者ror更好,是因为.net 1.1实在落后于时代了。

  5. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-09-22 15:30:00

    呵呵,个人也喜欢ror.

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

    css9[未注册用户] 2009-09-22 15:58:00

    @在别处

    在别处:以前呢,觉得您老写的太少,最近觉得您写的太多。。。



    这样不好么?一时吸收不了,可以慢慢消化,放到这里又不会坏掉。老赵估计是有计划或靠灵感的,老赵别受这话影响啊。。

  7. 老赵
    admin
    链接

    老赵 2009-09-22 16:04:00

    @css9
    当然不会受影响的,我纯粹靠灵感的。
    不写就忘了,现在TODO List上还有十几篇呢。
    他应该也是调侃,别当真,赫赫。

  8. JiaruiStone
    *.*.*.*
    链接

    JiaruiStone 2009-09-22 18:11:00

    先顶,回家慢慢研究~

  9. aohan
    *.*.*.*
    链接

    aohan 2009-09-22 18:35:00

    老赵真是神人!

    佩服的五体投地.

  10. rainnoless
    *.*.*.*
    链接

    rainnoless 2009-09-22 20:51:00

    老赵什么时候再拿几本好书出来“贱卖”啊,我在上海这样也好渔翁得利啊,检漏啊,呵呵。

  11. 王德水
    *.*.*.*
    链接

    王德水 2009-09-22 22:24:00

    老赵的时间是从哪来的?时间是买不到,借不到,租不到呀

  12. 王德水
    *.*.*.*
    链接

    王德水 2009-09-22 22:26:00

    在asp.net mvc之前我就把Agile web development with Rails看完了,可惜一直没项目做,还有就是Rails自身也不稳定,版本之间变化太大,好在帮我对mvc有了更多的认识

  13. 老赵
    admin
    链接

    老赵 2009-09-22 22:58:00

    @王德水
    我认为这本书的关键不是mvc,rails也不只是一个mvc框架。
    这本书的关键是各种agile时间,rails中各种agile工具的搭配。
    其实那些工具大都可以在.net平台上找到替代品,平时开发时一定要用起来。

  14. 王德水
    *.*.*.*
    链接

    王德水 2009-09-23 07:30:00

    @Jeffrey Zhao
    确实是这样的,但是这本书却对rails也做了一个详细的讲解,是目前学习rails的不二之选,但对agile这点,我到不认为是讲agile的,从某个角度看只能说是在rails下的xp实践, 因为这本书自始至终都是站在单兵作战的角度。

  15. 王德水
    *.*.*.*
    链接

    王德水 2009-09-23 07:33:00

    migration和测试这块,非常不错,但asp.net mvc要想做到很好的测试,就要多动很多大脑。

  16. 老赵
    admin
    链接

    老赵 2009-09-23 09:13:00

    @王德水
    agile又不是光指项目管理,xp就是agile实践方式之一啊。
    有本书叫做《高效程序员的45个习惯——敏捷开发修炼之道》,里面就讲的是敏捷相关的实践,包括敏捷编程,敏捷测试,敏捷调试等等。
    Rails那本书讲的就是敏捷开发,agile development,写在书名上的。
    // 不过45个习惯我也刚看,呵呵。

  17. 王德水
    *.*.*.*
    链接

    王德水 2009-09-23 09:51:00

    @Jeffrey Zhao
    对对,我忘了书名是agile development.抱歉, 实际上敏捷的难点就是如何让程序员成为一个xp的人(敏捷编程,敏捷测试,敏捷调试...)

  18. 王德水
    *.*.*.*
    链接

    王德水 2009-09-23 09:53:00

    @Jeffrey Zhao
    可以告诉我《高效程序员的45个习惯——敏捷开发修炼之道》的英文名字吗? 谢谢

  19. 老赵
    admin
    链接

    老赵 2009-09-23 10:43:00

    @王德水
    还是不要叫做xp吧,agile是指导原则,xp是实践方式之一,还有很多其他实践方式。
    45个习惯的英文名网上搜一下吧。

  20. SJYds[未注册用户]
    *.*.*.*
    链接

    SJYds[未注册用户] 2009-09-23 14:52:00

    老赵,这本Concurrent Programming on Windows有电子版的吗

  21. 老赵
    admin
    链接

    老赵 2009-09-23 15:03:00

    @SJYds
    没有啊。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我