热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Asp.net状态管理(五)

5Cache5.1Cache概述Cache和Application一样是整个应用程序共用一份的,而且所有用户访问的都是相同的Cache。Cache从字面上说是缓存

5  Cache


5.1  Cache概述

Cache和Application一样是整个应用程序共用一份的,而且所有用户访问的都是相同的Cache。Cache从字面上说是缓存的意思,我们知道计算机系统本身就是一个多级缓存的结构。CPU的缓存中存放了部分内存中的数据,内存中又存放了部分硬盘中的数据。把最常用的数据放在读取最快速的硬件中存储能大大提高效率。对于Web系统来说也一样,从数据库(硬盘)中读取数据的速度肯定比从Cache(内存)中读取的效率低,基于这个特性,我们通常把改动不大而查询次数又比较多的数据放到Cache中。

既然缓存中的数据其实是来自数据库的,那么缓存中的数据如何和数据库进行同步呢?一般来说,缓存中应该存放改动不大或者对数据的实时性没有太多要求的数据。这样,我们只需要定期更新缓存就可以了。相反,如果缓存的更新频率过快的话,使用缓存的意义就不是很大了,因此更新缓存的时候需要一次性从数据库中读取大量的数据,过于频繁地更新缓存反而加重了数据库的负担。

那么ASP.NET中的Cache又提供了哪些缓存的过期策略呢?

·      永不过期。和Application一样,缓存永不过期。

·      绝对时间过期。缓存在某一时间过期,比如5分钟后。

·      变化时间过期(平滑过期)。缓存在某一时间内未访问则超时过期,这个和Session有点类似,比如我们可以设定缓存5分钟没有人访问则过期。

·      依赖过期。缓存依赖于数据库中的数据或者文件中的内容。一旦数据库中某些表的数据发生变动或者文件内容发生变动,则缓存自动过期。

缓存过期后我们就要更新缓存了,ASP.NET提供了两种更新策略。

·      被动更新。缓存过期以后手动进行更新。

·      主动更新。缓存过期以后在回调方法中更新。


5.2  Cache性能与过期策略

首先,在页面上添加两个按钮,并双击按钮实现Click事件处理方法。

Text="从缓存中读取数据" />

Text="从数据库中读取数据" />

第一个按钮实现从缓存读取数据。

注意:本例需要using以下命名空间。

using System.Diagnostics;   // 用于精确测定时间间隔

using System.Web.Caching;   // 用于缓存的策略

using System.IO;            // 用于文件操作

protected void btn_GetData_Click(object sender, EventArgs e)

{

    InsertRecord();

    Stopwatch sw=new Stopwatch();

    sw.Start();

    if (Cache["Data"]==null)

    {

        Response.Write("缓存无效
");

    }

    else

    {

        DataSet ds = Cache["Data"] as DataSet;

        Response.Write(string.Format("查询结果:{0}
", ds.Tables[0].Rows[0][0]));

        Response.Write(string.Format("耗费时间:{0}
", sw.ElapsedTicks));

    }

}

在这里有几点需要说明。

·      一开始的InsertRecord()方法是我们自己创建的,用来向数据库插入一条记录。这样,我们就能看出来数据是否是从缓存中读取的了。

InsertRecord()方法如下:

private void InsertRecord()

{

    using (SqlConnection conn = new SqlConnection(@"server=(local)/SQLEXPRESS;

    database=Forum;Trusted_Connection=True"))

    {

        conn.Open();

        using (SqlCommand cmd = new SqlCommand("Insert into CacheTest (Test) values

       ('Test')", conn))

        {

            cmd.ExecuteNonQuery();

        }

    }

}

·      如果缓存存在则输出查询结果和查询耗费的时间,如果缓存不存在则输出“缓存无效”。

·      Stopwatch类用于精确测定逝去的时间,ElapsedTicks属性返回了间隔的计数器刻度,所谓计数器刻度就是系统的计数器走过了多少次。当然,Stopwatch还有ElapsedMilliseconds能返回间隔的总毫秒数。之所以使用ElapsedTicks,因为它是一个更小的时间单位。

第二个按钮直接从数据库读取数据。

protected void btn_GetDataFromDb_Click(object sender, EventArgs e)

{

    InsertRecord();

    Stopwatch sw = new Stopwatch();

    sw.Start();

    DataSet ds = GetData();

    Response.Write(string.Format("查询结果:{0}
", ds.Tables[0].Rows[0][0]));

    Response.Write(string.Format("耗费时间:{0}
", sw.ElapsedTicks));

}

在这里,我们把读取数据的操作使用一个GetData()方法进行了封装,方法实现如下:

private DataSet GetData()

{

    DataSet ds = new DataSet();

    using (SqlConnection conn = new SqlConnection(@"server=(local)/SQLEXPRESS;

    database=Forum;Trusted_Connection=True"))

    {

        SqlDataAdapter da = new SqlDataAdapter("select count(*) from CacheTest", conn);

        da.Fill(ds);           

    }

    return ds;

}

为了能体现出缓存的效率,我们在Forum数据库中又新建立了一个CacheTest数据表,表结构很简单,如图12-18所示。

图12-18  CacheTest表结构

我们在表中插入了10万条以上的记录,使得表的大小达到了100MB左右。

运行程序,单击“从数据库中读取数据”按钮,如图12-19所示,我们可以看到,这个操作耗费了相当多的时间。

文本框:<br>图12-19  从数据库读取数据<br>需要花费大量的时间<br>因为我们直接从数据库读取count(*)&#xff0c;所以每次单击按钮查询结果显示的数字都会&#43;1。现在你单击“从缓存中读取数据”肯定是显示“缓存无效”&#xff0c;因为我们还没有添加任何缓存。

然后&#xff0c;我们在页面上添加三个按钮并双击按钮创建事件处理方法&#xff0c;三个按钮使用不同的过期策略添加缓存。

OnClick&#61;"btn_InsertNoExpirationCache_Click" />

过期缓存" OnClick&#61;"btn_InsertAbsoluteExpirationCache_Click" />

过期缓存" OnClick&#61;"btn_InsertSlidingExpirationCache_Click" />

三个按钮的Click事件处理方法如下&#xff1a;

protected void btn_InsertNoExpirationCache_Click(object sender, EventArgs e)

{

    DataSet ds &#61; GetData();

    Cache.Insert("Data", ds);

}

protected void btn_InsertAbsoluteExpirationCache_Click(object sender, EventArgs e)

{

    DataSet ds &#61; GetData();

    Cache.Insert("Data", ds,null, DateTime.Now.AddSeconds(10), TimeSpan.Zero);

}

protected void btn_InsertSlidingExpirationCache_Click(object sender, EventArgs e)

{

    DataSet ds &#61; GetData();

    Cache.Insert("Data", ds, null, DateTime.MaxValue, TimeSpan.FromSeconds(10));

}

我们来分析一下这三种过期策略。

·      永不过期。直接赋值缓存的Key和Value即可

·      绝对时间过期。DateTime.Now.AddSeconds(10)表示缓存在10秒后过期&#xff0c;TimeSpan.Zero表示不使用平滑过期策略。

·      变化时间过期&#xff08;平滑过期&#xff09;。DateTime.MaxValue表示不使用绝对时间过期策略&#xff0c;TimeSpan.FromSeconds(10)表示缓存连续10秒没有访问就过期。

在这里&#xff0c;我们都使用了Insert()方法来添加缓存。其实&#xff0c;Cache还有一个Add()方法也能向缓存中添加项。不同之处在于Add()方法只能添加缓存中没有的项&#xff0c;如果添加缓存中已有的项将失败&#xff08;但不会抛出异常&#xff09;&#xff0c;而Insert()方法能覆盖原来的项。

n  注意&#xff1a;和Application不同&#xff0c;这里不需要使用在插入缓存的时候进行锁操作&#xff0c;Cache会自己处理     并发。

现在&#xff0c;我们就可以打开页面对这三种过期策略进行测试了。

n  1&#xff0e;单击“从缓存中读取数据”按钮&#xff0c;提示“缓存无效”。

n  2&#xff0e;单击“从数据库中读取数据”按钮&#xff0c;查询结果显示现在记录总数为100646。

n  3&#xff0e;单击“插入永不过期缓存”按钮&#xff0c;然后连续单击“从缓存中读取数据”按钮&#xff0c;可以发现&#xff0c;无论过去多久&#xff0c;缓存始终没有过期&#xff0c;而且观察记录查询结果可以发现值始终没有发生变化。不同的是&#xff0c;从缓存中读取数据的效率比从数据库中读取数据提高了几个数量级&#xff0c;如图12-20所示&#xff0c;你可以和图12-19进行比较。

文本框:<br>图12-20  从缓存中读取数据<br>所花费的时间<br>4&#xff0e;单击“插入绝对时间过期缓存”&#xff0c;然后连续单击“从缓存中读取数据”按钮&#xff0c;大约10秒过期后&#xff0c;页面提示“缓存无效”&#xff0c;说明缓存过期了。

n  5&#xff0e;单击“插入变化时间过期缓存”&#xff0c;然后连续单击“从缓存中读取数据”按钮&#xff0c;缓存始终不过期&#xff0c;如果我们等待10秒后再去单击按钮&#xff0c;页面提示“缓存无效”&#xff0c;说明缓存过期了。

我们再来看一下依赖过期策略。所谓依赖过期就是缓存的依赖项&#xff08;比如一个文件&#xff09;的内容改变之后缓存也就失效了。由于篇幅关系&#xff0c;这里只介绍文件依赖。我们在页面上再加两个按钮并双击按钮添加Click事件处理方法。

Click" />

OnClick&#61;"btn_AddFileDependencyCache_Click" />

在本例中&#xff0c;我们将使缓存依赖一个txt文本文件。因此&#xff0c;首先在项目中添加一个test.txt文本文件。单击“修改文件”按钮实现文件的修改。

protected void btn_ModifyFile_Click(object sender, EventArgs e)

{

    FileStream fs &#61; new FileStream(Server.MapPath("test.txt"), FileMode.Append,

    FileAccess.Write);

    StreamWriter sw &#61; new StreamWriter(fs);

    sw.WriteLine(DateTime.Now.ToString());

    sw.Close();

    fs.Close();

}

我们通过在文件的最后写入当前的时间来修改文件。插入文件依赖缓存按钮的事件处理方法如下&#xff1a;

protected void btn_AddFileDependencyCache_Click(object sender, EventArgs e)

{

    CacheDependency cd &#61; new CacheDependency(Server.MapPath("test.txt"));

    DataSet ds &#61; GetData();

    Cache.Insert("Data", ds, cd);

}

添加文件依赖缓存同样简单&#xff0c;通过CacheDependency关联了一个文件依赖。

现在就可以打开页面进行测试了。

n  1&#xff0e;单击“从缓存中读取数据”按钮&#xff0c;提示“缓存无效”。

n  2&#xff0e;单击“从数据库中读取数据”按钮&#xff0c;查询结果显示现在记录总数为100710。

n  3&#xff0e;单击“插入文件依赖缓存”按钮&#xff0c;然后连续单击“从缓存中读取数据”按钮&#xff0c;可以发现&#xff0c;无论过去多久&#xff0c;缓存始终没有过期&#xff0c;而且观察记录查询结果可以发现值始终没有发生变化。

n  4&#xff0e;单击“修改文件”按钮&#xff0c;然后单击“从缓存中读取数据”按钮&#xff0c;提示“缓存无效”。由于文件已经修改了&#xff0c;依赖这个文件的缓存立刻失效了。


5.3  Cache的更新策略

最后&#xff0c;我们来讨论缓存的更新策略。在Web程序中我们通常会使用被动更新。所谓被动更新&#xff0c;就是在调用数据的时候判断缓存是否为空&#xff0c;如果为空则先更新缓存然后再从缓存中读取数据&#xff0c;如果不为空则直接从缓存中读取数据。可以把“从缓存中读取数据”按钮的Click事件处理方法修改成如下&#xff0c;实现被动更新。

protected void btn_GetData_Click(object sender, EventArgs e)

{

    InsertRecord();

    DataSet ds &#61; new DataSet();

    Stopwatch sw &#61; new Stopwatch();

    sw.Start();

    if (Cache["Data"] &#61;&#61; null)

    {

        ds &#61; GetData();

        Cache.Insert("Data", ds, null, DateTime.Now.AddSeconds(10), TimeSpan.Zero);

    }

    else

    {

        ds &#61; Cache["Data"] as DataSet;

    }

    Response.Write(string.Format("查询结果&#xff1a;{0}
", ds.Tables[0].Rows[0][0]));  

    Response.Write(string.Format("耗费时间&#xff1a;{0}
", sw.ElapsedTicks));

}

我们可以看出&#xff0c;如果没有人访问数据缓存是不会更新的&#xff0c;只有缓存被访问的时候发现缓存无效才会去更新。这样很明显的一个缺点就是&#xff0c;如果缓存过期了更新操作将花费很长时间&#xff0c;这个时候的查询也需要花费很多时间。我们可以利用缓存的回调功能让缓存过期后自动续建实现自动更新的目的。

protected void btn_InsertActiveUpdateCache_Click(object sender, EventArgs e)

{

    DataSet ds &#61; GetData();

    Cache.Insert("Data", ds, null, DateTime.Now.AddSeconds(10), TimeSpan.Zero,

    CacheItemPriority.Default, CacheRemovedCallback);

}

最后一个参数表明缓存被移除以后自动调用CacheRemovedCallback()方法&#xff0c;方法实现如下。

private void CacheRemovedCallback(String key, object value, CacheItemRemovedReason

removedReason)

{

    DataSet ds &#61; GetData();

    Cache.Insert(key, ds, null, DateTime.Now.AddSeconds(10), TimeSpan.Zero, CacheItemPriority.

    Default, CacheRemovedCallback);

}

在回调方法中&#xff0c;我们再次插入一个支持回调的缓存。这样&#xff0c;缓存被移除以后又能自动更新了。说了这么多创建缓存的方法&#xff0c;读者可能会问怎么手动移除缓存呢&#xff1f;比如我们要移除Key&#61;"Data"的缓存只需要&#xff1a;

Cache.Remove("Data");

你可能会马上想到用Cache.RemoveAll()方法移除所有缓存&#xff0c;可是Cache没有提供这样的方法&#xff0c;我们只能通过遍历来实现移除所有缓存。

IDictionaryEnumerator CacheEnum &#61; HttpRuntime.Cache.GetEnumerator();

while (CacheEnum.MoveNext())

{

    Cache.Remove(CacheEnum.Key.ToString());

}


5.4  Cache总结

同样&#xff0c;我们以第一节中的几个问题结束对Cache的讨论。

·      存储的物理位置。服务器内存。

·      存储的类型限制。任意类型。

·      状态使用的范围。当前请求上下文&#xff0c;所有用户共用一份。

·      存储的大小限制。任意大小。

·      生命周期。有多种过期策略控制缓存的销毁。

·      安全与性能。数据总是存储在服务端&#xff0c;安全性比较高&#xff0c;但不易存储过多数据。

·      优缺点与注意事项。检索数据速度快&#xff0c;过期策略丰富。注意别把对实时性要求很高的数据放到Cache中&#xff0c;不断更新Cache会对数据库造成压力。


推荐阅读
  • 本文介绍了如何在 Spring Boot 项目中使用 spring-boot-starter-quartz 组件实现定时任务,并将 cron 表达式存储在数据库中,以便动态调整任务执行频率。 ... [详细]
  • 包含phppdoerrorcode的词条 ... [详细]
  • 一个建表一个执行crud操作建表代码importandroid.content.Context;importandroid.database.sqlite.SQLiteDat ... [详细]
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 使用Tkinter构建51Ape无损音乐爬虫UI
    本文介绍了如何使用Python的内置模块Tkinter来构建一个简单的用户界面,用于爬取51Ape网站上的无损音乐百度云链接。虽然Tkinter入门相对简单,但在实际开发过程中由于文档不足可能会带来一些不便。 ... [详细]
  • Spring Data JdbcTemplate 入门指南
    本文将介绍如何使用 Spring JdbcTemplate 进行数据库操作,包括查询和插入数据。我们将通过一个学生表的示例来演示具体步骤。 ... [详细]
  • 本文介绍了如何在 ASP.NET 中设置 Excel 单元格格式为文本,获取多个单元格区域并作为表头,以及进行单元格合并、赋值、格式设置等操作。 ... [详细]
  • Flutter 2.* 路由管理详解
    本文详细介绍了 Flutter 2.* 中的路由管理机制,包括路由的基本概念、MaterialPageRoute 的使用、Navigator 的操作方法、路由传值、命名路由及其注册、路由钩子等。 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • importpymysql#一、直接连接mysql数据库'''coonpymysql.connect(host'192.168.*.*',u ... [详细]
  • 本文介绍如何使用 Python 的 DOM 和 SAX 方法解析 XML 文件,并通过示例展示了如何动态创建数据库表和处理大量数据的实时插入。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有