设计一个高效的缓存管理服务 C#
一直以来,我都发现程序的运行速度不够理想。通过查代码,发现程序对数据库的访问非常频繁,而且检索出来的数据量比较大。为了让程序运行快起来,我想对程序采用适当的缓存方法。
我在C#尝试了5种方法进行数据缓存,具体如下:
(如有遗漏,错误欢迎大家指正,欢迎提建议。)
1:Session方法:此方法是针对于每个用户来的,如果用户量比较大,那么建议不要采用此方法,否则会大量耗尽服务器资源。
2:Cache方法:
2.1:对于每个用户来说访问的数据最好是一致的,否则要用不同的key标识不同的缓存。
3:往客户端写文件
首先A页面在运行的时候,从数据库中取到了数据集,在显示出数据报表的同时,A页面将数据集转化为xml文件,写入客户端。客户如果要打印该报表,那么程 序就从客户端取存入的xml文件,如果xml文件存在,就从中读取数据并显示成打印报表显示出来。如果xml文件不存在,就提示重新检索数据。
例如:
<script type=text/javascript>
</script>
但是,每次操作客户端的文件,系统都会提示是否运行没有标识的ActiveX控件,用户点否就不能成功保存文件了。
4:用cookie来存储
--------------------写cookie----------------------------------------
HttpCookie myCookie = new HttpCookie("DateCookie_lui");//DateCookie_lui为要创建的cookie键值对的键名。
DateTime now = DateTime.Now;
myCookie.Value = now.ToString();//给此cookie赋值
myCookie.Expires = now.AddMinutes(1);//设置cookie的过期日期和时间(此为1分钟后过期),在客户再次访问服务 器同时附带cookie文件,如其中以这个名字的cookie过期,就会自动清除它。
this.Response.SetCookie(myCookie);//将此cookie写入客户端
--------------------读cookie----------------------------------------
HttpCookie myCookie = new HttpCookie("myTestCookie_lui");//创建一个cookie操作对象
myCookie = Request.Cookies["DateCookie_lui"];//得到客户端传来的cookie数据,DateCookie_lui为要取的cookie键值对的键名
if(myCookie != null)
Response.Write(" Cookie Name is:"+myCookie.Name+". Cookie Value is:"+myCookie.Value);
else
Response.Write(" Not found!");
用cookie的方法存储数据,经过我反复尝试发现cookie存储值的大小被限定,(每项仅可存汉字仅1686个,字母或数字仅5059个),且 cookie文件的大小一旦超过大小(20kb左右)就会导致找不到页面的错误,且每个cookie文件中只能存20项键值对。
5:尝试在A页面把dataset转化为xml字符串,隐藏于页面中,post提交到B页面;在B页面取到该xml字符串,然后将xml字符串转化为dataset,生成报表。
将dataset转化为xml的字符串:
------------------------------------------------------------------------
将xml字符串转化为dataset:
------------------------------------------------------------
页面中如果字体变成乱码:要设置该页面的<%@ Page responseEncoding="gb2312"%>
设计一个高效的缓存管理服务:------------
摘要:一般大家做的缓存都是实时更新,并且用LRU算法实现缓存过期策略,但当缓存越来越大的时候,对缓存做的线程同步会导致应用的响应便慢。如何更有效的使用缓存,如何提高缓存命中率,如何减少对缓存加锁操作,如何提高缓存的性能,我们来讨论一下。
1、找出活跃数据,我们用一种分离的方式来找出活跃数据,单独写一个提取活跃数据的后台程序从数据库里统计出最近一小时查阅次数最多的前1w篇文章 的ID,这些文章肯定是用户最常访问的文章,把这些文章的数据取出来用FTP上传到缓存服务器上,并发消息给缓存服务器通知它有新的缓存数据可用了,因为 提取的都是活跃数据,所以这样的数据做缓存命中率会高一些。
2、缓存服务器收到提取活跃数据程序的通知后,从本机磁盘上读取缓存信息加载到内存 里,并替换到上次使用的缓存,这样缓存就不是实时更新的,而是相对只读的,每1小时才更新一次,所以使用缓存不需要加锁,只需使用缓存的时候把缓存对象用 一个临时变量引用一下,防止在新旧缓存交替的时候被变为null。
3、用一个单独的DB来作为数据版本数据库,里面保存着每篇文章的版本信息,版 本信息是int类型的,无论谁修改了某篇文章,都要在版本数据库里让相应的文章版本号加1,写一个版本管理服务提供查询某个文章版本和更新某个文章版本的 功能。因为版本数据库的字段大多都是int型,表应该很窄,性能不会很差。
4、用户请求一篇文章的时候,先看缓存服务器有没有,如果没有,直接从 数据库里取出来;如果有,取出缓存数据版本号,并从版本服务器上获取该文章真实版本号,如果一直,就使用缓存数据,如不一直,从数据库里取出文章数据,并 更新缓存。这里虽然用户的每个请求都要访问版本数据库,但因为版本数据库结构简单,容易优化,所以出现性能瓶颈的的可能性比较小,而如果缓存命中率足够高 的话能减少大量对文章数据库的请求。
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
namespace DataCache {
//可缓存的实体类
public class Canbecached{public int Version;}
public class Article : Canbecached { }
public class CacheItem : Canbecached
{
public Article Article;
}
//每个文章都在版本数据库里保存版本号,该接口提供查询版本方法
//如果某个文章修改了,要调用这个接口的UpdateXXVersion方法
//来更新数据版本,以便让缓存服务器可以得到真实数据的最新的版本
public interface VersionManager
{
int GetArticleVersion(int articleId);
void UpdateArticleVserion(int articleId);
}
//该类用于管理缓存,以及提供缓存使用接口
//缓存和使用缓存的服务不一定是一个进程,甚至不是一台机器,通过socket
//或者Remoting来使用及更新缓存你
internal static class ArticleCacheManager
{
private static volatile bool _cacheAvailable = false;
static Dictionary<int, CacheItem> _cache = new Dictionary<int, CacheItem>();
public static bool CacheAvailable {
get { return _cacheAvailable; }
}
public static void InitCache()
{
_cache = new Dictionary<int, CacheItem>();
_cacheAvailable = true;
}
public static void LoadCache()
{
Dictionary<int, CacheItem> cache = readCacheFromDisk();
_cache = cache; //该操作是线程安全的,不需要lock(_cahce),因为使用_cache的时候都先放到临时变量里再使用的。
}
private static Dictionary<int, CacheItem> readCacheFromDisk() {
throw new NotImplementedException();
}
private static void checkCacheAndArticleId(int articleId) {
if (!_cacheAvailable) throw new InvalidOperationException("Cache not Init.");
if (articleId < 1) throw new ArgumentException("articleId");
}
internal static VersionManager GetVersionManager()
{
throw new NotImplementedException();
}
internal static Article GetArticle(int articleId) {
checkCacheAndArticleId(articleId);
Dictionary<int, CacheItem> cache = _cache;
CacheItem item;
if (cache.TryGetValue(articleId, out item))
return item.Article;
else
return null;
}
internal static void UpdateArticle(int articleId, Article article) {
checkCacheAndArticleId(articleId);
Dictionary<int, CacheItem> cache = _cache;
CacheItem item;
if (cache.TryGetValue(articleId, out item))
item.Article = article; //这个赋值操作是线程安全的,不需要lock这个Item。
}
}
//从数据库里读取文章信息
internal static class DBAdapter
{
internal static Article GetArticle(int articleId, bool IsUpdateCache) {
Article article = new Article();
if(IsUpdateCache)ArticleCacheManager.UpdateArticle(articleId, article);
throw new NotImplementedException();
}
}
//用来保存一个文章
public class ArticleItem
{
public int ArticleId;
public ArticleItem(int articleId)
{
ArticleId = articleId;
}
public Article Article;
public void Load()
{
//1、缓存正在切换到时候直接从DB取数据
if(!ArticleCacheManager.CacheAvailable)
{
Article = DBAdapter.GetArticle(ArticleId,false);
return;
}
VersionManager versionManager = ArticleCacheManager.GetVersionManager();
//2、比较缓存版本和真实数据版本确定是否使用缓存信息
DataCache.Article article = ArticleCacheManager.GetArticle(ArticleId);
if(article != null && article.Version == versionManager.GetArticleVersion(ArticleId))
Article = ArticleCacheManager.GetArticle(ArticleId); //尽管这里判断了版本,但也有可能取到旧数据,因为当你获取数据版本并决定使用缓存数据的时候,可能恰好用户修改了文章数据,这种情况只要等用户下次刷新一下页面了,用户体验并不是太差。
else
Article = DBAdapter.GetArticle(ArticleId, article != null);//如果article不是null,说明只是缓存数据版本太旧,这时候要把从数据库取出的数据更新到缓存里
}
}
class Program {
static Dictionary<int, ArticleItem> _articles = new Dictionary<int, ArticleItem>();
static void Main(string[] args)
{
//初始化缓存
ArticleCacheManager.InitCache();
//检查是否有新的缓存可用
new Thread(checkCacheProc).Start();
//用户请求一篇文章
ArticleItem article1 = new ArticleItem(1);
article1.Load();
}
public static void checkCacheProc(Object state)
{
Thread.CurrentThread.Name = "Check whether there is a new cache Thread";
while (true)
{
try {
if (newCacheAvailable())
ArticleCacheManager.LoadCache();
Thread.Sleep(TimeSpan.FromMinutes(60));
}
catch (Exception ex) {
Trace.TraceError("check cache occur an error:"+ ex.Message);
}
}
}
private static bool newCacheAvailable() {
throw new NotImplementedException();
}
}
}
不足
1、更新文章的时候需要更新版本数据库,还要用一个事务来保证一致性,需要更改现有代码,并且降低写性能。不过我觉得这比用户更新数据的时候同时通知缓存服务器还是要简单一些,那样更复杂,还是尽量保证设计简单吧,如果版本数据库撑不住了再试试这种方案。
2、因为判断数据版本号和使用缓存数据不是一个原子操作,在这中间数据版本号可能会更新,所以在高并发的情况下,可能给用户显示了比较旧的数据,只有用户再次刷新才会发现文章版本号变了而使用最新数据,这里就的牺牲用户体验换取性能了。