基于STSdb和fastJson的磁盤/內存緩存
2013-07-03 10:50 靈感之源 閱讀(4008) 評論(7) 收藏 舉報更新
1. 增加了對批量處理的支持,寫操作速度提升5倍,讀操作提升100倍
2. 增加了對并發的支持
需求
業務系統用的是數據庫,數據量大,部分只讀或相對穩定業務查詢復雜,每次頁面加載都要花耗不少時間(不討論異步),覺得可以做一下高速緩存,譬如用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 static object syncRoot = new object();
private bool isMemory = false;
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)
{
lock (syncRoot)
{
if (memoryInstance == null)
memoryInstance = STSdb.FromMemory();
}
return memoryInstance;
}
else
return STSdb.FromFile(GetFile(false), GetFile(true));
}
}
private string GetExpirationTable(string Category)
{
return KeyExpiration + "_" + Category;
}
public void Add<K, V>(string Category, IEnumerable<KeyValuePair<K, V>> Items, DateTime? ExpirationDate)
{
lock (syncRoot)
{
var engine = Engine;
var table = engine.OpenXIndex<K, string>(Category);
Items.ForEach(i =>
{
var key = i.Key;
var data = i.Value;
//will only serialize object other than string
var result = typeof(V) == typeof(string) ? data as string : JSON.Instance.ToJSON(data);
table[key] = result;
table.Flush();
//specify expiration
var expiration = engine.OpenXIndex<K, DateTime>(GetExpirationTable(Category));
//default 30 mins to expire from now
var expirationDate = ExpirationDate == null || ExpirationDate <= DateTime.Now ? DateTime.Now.AddMinutes(30) : (DateTime)ExpirationDate;
expiration[key] = expirationDate;
expiration.Flush();
});
engine.Commit();
//only dispose disk-based engine
if (!isMemory)
engine.Dispose();
}
}
public void Add<K>(string Category, K Key, object Data, DateTime? ExpirationDate)
{
Add<K, object>(Category, new List<KeyValuePair<K, object>> { new KeyValuePair<K, object>(Key, Data) }, ExpirationDate);
}
private string GetFile(bool IsData)
{
if (!Directory.Exists(dataPath))
Directory.CreateDirectory(dataPath);
return dataPath + "SuperCache." + (IsData ? "dat" : "sys");
}
public List<KeyValuePair<K, V>> Get<K, V>(string Category, IEnumerable<K> Keys)
{
var result = new List<KeyValuePair<K, V>>();
lock (syncRoot)
{
var engine = Engine;
var table = engine.OpenXIndex<K, string>(Category);
var expiration = engine.OpenXIndex<K, DateTime>(GetExpirationTable(Category));
var isCommitRequired = false;
Keys.ForEach(key =>
{
string buffer;
V value;
if (table.TryGet(key, out buffer))
{
//will only deserialize object other than string
value = typeof(V) == typeof(string) ? (V)(object)buffer : JSON.Instance.ToObject<V>(buffer);
DateTime expirationDate;
//get expiration date
if (expiration.TryGet(key, out expirationDate))
{
//expired
if (expirationDate < DateTime.Now)
{
value = default(V);
table.Delete(key);
table.Flush();
expiration.Delete(key);
expiration.Flush();
isCommitRequired = true;
}
}
}
else
value = default(V);
result.Add(new KeyValuePair<K, V>(key, value));
});
//only need to commit write actions
if (isCommitRequired)
engine.Commit();
//only dispose disk-based engine
if (!isMemory)
engine.Dispose();
}
return result;
}
public V Get<K, V>(string Category, K Key)
{
var buffer = Get<K, V>(Category, new K[] { Key });
var result = buffer.FirstOrDefault();
return result.Value;
}
}
}
新建
構造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 System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
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();
//Thread.Sleep(4000);
//TestExpiration();
TestPerformance();
//TestConcurrent();
Console.Read();
}
private static void TestConcurrent()
{
var w = new Stopwatch();
w.Start();
Parallel.For(1, 3, (a) =>
{
var employees = Enumerable.Range((a - 1) * 1000, a * 1000).Select(i => new KeyValuePair<int, string>(i, "Wilson " + i + " Chen"));
var engine = new CacheEngine(@"..\..\data");
engine.Add<int, string>("Employee", employees, DateTime.Now.AddMinutes(1));
});
w.Stop();
Console.WriteLine("add:" + w.Elapsed);
var engine2 = new CacheEngine(@"..\..\data");
var o = engine2.Get<int, string>("Employee", 1005);
Console.WriteLine(o);
}
private static void TestPerformance()
{
var engine = new CacheEngine(@"..\..\data");
var w = new Stopwatch();
w.Start();
var employees = Enumerable.Range(0, 1000).Select(i => new KeyValuePair<int, string>(i, "Wilson " + i + " Chen"));
engine.Add<int, string>("Employee", employees, DateTime.Now.AddMinutes(1));
w.Stop();
Console.WriteLine("add:" + w.Elapsed);
/*w.Restart();
employees.ForEach(key =>
{
var o1 = engine.Get<int, string>("Employee", key.Key);
});
w.Stop();
Console.WriteLine("individual get:" + w.Elapsed);*/
w.Restart();
var keys = employees.Select(i => i.Key);
var o = engine.Get<int, string>("Employee", keys);
w.Stop();
Debug.Assert(o.Count == keys.Count());
Console.WriteLine("get:" + w.Elapsed);
}
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(5));
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 o3 = engine.Get<string, string>("PlainText", "A");
Console.WriteLine(o3 ?? "A does not exist");
}
}
}
說明
項目中引用了System.Management是因為STSdb支持內存數據庫,需要判斷最大物理內存。如果不喜歡,大家可以移除引用,并且去掉STSdb4.Database.STSdb.FromMemory方法便可。
下載
點擊這里下載
浙公網安備 33010602011771號