一个基于STSdb和fastJson的磁盘/内存缓存
需求
业务系统用的是数据库,数据量大,部分只读或相对稳定业务查询复杂,每次页面加载都要花耗不少时间(不讨论异步),觉得可以做一下高速缓存,譬如用nosql那种key/value快速存取结果
目的
这里不是要做一个大家都适用的磁盘/内存缓存库,这个做法,部分是展示STSdb的用法,部分是提供一个简单易用的解决方案。
磁盘/内存
为什么不用memcached或者AppFabric Cache这样的现成解决方案呢?因为业务要缓存的内存或大或小,小的几KB,大的几MB,如果用户一多,势必对内存有过度的需求。所以选择做一个基于磁盘的。
当然,这个解决方案是支持内存缓存的。构造的时候传递空字符串便可。
STSdb是什么
再来说明一下STSdb是什么:STSdb是C#写的开源嵌入式数据库和虚拟文件系统,支持实时索引,性能是同类产品的几倍到几十倍,访问官方网站。
我之前介绍过:STSdb,最强纯C#开源NoSQL和虚拟文件系统 和 STSdb,最强纯C#开源NoSQL和虚拟文件系统 4.0 RC2 支持C/S架构 ,大家可以先看看。
实现
存取
因为是基于磁盘,所以需要使用到高效的Key/Value存取方案,碰巧我们有STSdb :)
序列化
因为要求简便快速,用的是fastJson。
代码
代码比较简单,花了2个小时写的,很多情况没考虑,譬如磁盘空间不足、过期空间回收等,这些留给大家做家庭作业吧。另外,为了发布方便,STSdb和fastJson的代码都合并到一个项目里。
CahceEngine.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using STSdb4.Database;
using fastJSON;
using System.IO;
namespace Com.SuperCache.Engine
{ public class CacheEngine
{
private const string KeyExpiration = "Expiration" ;
private string dataPath;
private static IStorageEngine memoryInstance = null ;
private bool isMemory = false ;
static CacheEngine()
{
memoryInstance = STSdb.FromMemory();
}
public CacheEngine( string DataPath)
{
dataPath = DataPath;
if (!dataPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
dataPath += Path.DirectorySeparatorChar;
isMemory = string .IsNullOrEmpty(DataPath);
}
public void Add<K>( string Category, K Key, object Data)
{
Add(Category, Key, Data, null );
}
private IStorageEngine Engine
{
get
{
if (isMemory)
return memoryInstance;
else
return STSdb.FromFile(GetFile( false ), GetFile( true ));
}
}
public void Add<K>( string Category, K Key, object Data, DateTime? ExpirationDate)
{
var engine = Engine;
var table = engine.OpenXIndex<K, string >(Category);
var result = JSON.Instance.ToJSON(Data);
table[Key] = result;
table.Flush();
//add cache expiration time
var expiration = engine.OpenXIndex<K, DateTime>(KeyExpiration);
//default 30 mins before expiration
var expirationDate = ExpirationDate == null || ExpirationDate <= DateTime.Now ? DateTime.Now.AddMinutes(30) : (DateTime)ExpirationDate;
expiration[Key] = expirationDate;
expiration.Flush();
engine.Commit();
if (!isMemory)
engine.Dispose();
}
private string GetFile( bool IsData)
{
if (!Directory.Exists(dataPath))
Directory.CreateDirectory(dataPath);
return dataPath + "SuperCache." + (IsData ? "dat" : "sys" );
}
public V Get<K, V>( string Category, K Key)
{
var engine = Engine;
var table = engine.OpenXIndex<K, string >(Category);
string json;
V result;
if (table.TryGet(Key, out json))
{
result = JSON.Instance.ToObject<V>(json);
var expiration = engine.OpenXIndex<K, DateTime>(KeyExpiration);
DateTime expirationDate;
//verify expiration date
if (expiration.TryGet(Key, out expirationDate))
{
//expired
if (expirationDate < DateTime.Now)
{
result = default (V);
table.Delete(Key);
table.Flush();
expiration.Delete(Key);
expiration.Flush();
engine.Commit();
}
}
}
else
result = default (V);
if (!isMemory)
engine.Dispose();
return result;
}
}
} |
新建
构造CacheEngine需要传递缓存保存到哪个文件夹。
基于内存
如果你不喜欢基于磁盘的缓存,可以使用基于内存,构造函数传递空字符串便可。
增加/更新
同一个方法:Add。用户可以指定类型(Category),譬如User,Employee等。键(Key)支持泛型,值(Data)是object。有一个overload是过期日期(ExpirationDate),默认当前时间30分钟后
获取
Get方法需要指定类型(Category)和键(Key)。
例子
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Com.SuperCache.Engine;
namespace Com.SuperCache.Test
{ public class Foo
{
public string Name { get ; set ; }
public int Age { get ; set ; }
public double ? Some { get ; set ; }
public DateTime? Birthday { get ; set ; }
}
class Program
{
static void Main( string [] args)
{
TestAddGet();
//TestExpiration();
Console.Read();
}
private static void TestExpiration()
{
var engine = new CacheEngine( @"..\..\data" );
var o = engine.Get< string , Foo>( "User" , "wchen" );
Console.WriteLine(o != null ? o.Name : "wchen does not exist or expired" );
}
private static void TestAddGet()
{
var engine = new CacheEngine( @"..\..\data" );
var f = new Foo { Name = "Wilson Chen" , Age = 30, Birthday = DateTime.Now, Some = 123.456 };
engine.Add( "User" , "wchen" , f, DateTime.Now.AddSeconds(10));
var t = @"Bla Bla Bla......" ;
engine.Add( "PlainText" , "Bla" , t);
var o = engine.Get< string , Foo>( "User" , "wchen" );
Console.WriteLine(o.Name);
var o4 = engine.Get< string , Foo>( "User" , "foo" );
Console.WriteLine(o4 != null ? o4.Name : "foo does not exist" );
var o2 = engine.Get< string , string >( "PlainText" , "Bla" );
Console.WriteLine(o2);
var o3 = engine.Get< string , string >( "PlainText" , "A" );
Console.WriteLine(o3 ?? "A does not exist" );
}
}
} |
说明
项目中引用了System.Management是因为STSdb支持内存数据库,需要判断最大物理内存。如果不喜欢,大家可以移除引用,并且去掉STSdb4.Database.STSdb.FromMemory方法便可。
下载
点击这里下载