绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
基于STSdb和fastJson的磁盘/内存缓存
2022-04-14 15:45:54

更新

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using STSdb4.Database;
  6. using fastJSON;
  7. using System.IO;
  8. namespace Com.SuperCache.Engine
  9. {
  10. public class CacheEngine
  11. {
  12. private const string KeyExpiration = "Expiration";
  13. private string dataPath;
  14. private static IStorageEngine memoryInstance = null;
  15. private static object syncRoot = new object();
  16. private bool isMemory = false;
  17. public CacheEngine(string DataPath)
  18. {
  19. dataPath = DataPath;
  20. if (!dataPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
  21. dataPath += Path.DirectorySeparatorChar;
  22. isMemory = string.IsNullOrEmpty(DataPath);
  23. }
  24. public void Add<K>(string Category, K Key, object Data)
  25. {
  26. Add(Category, Key, Data, null);
  27. }
  28. private IStorageEngine Engine
  29. {
  30. get
  31. {
  32. if (isMemory)
  33. {
  34. lock (syncRoot)
  35. {
  36. if (memoryInstance == null)
  37. memoryInstance = STSdb.FromMemory();
  38. }
  39. return memoryInstance;
  40. }
  41. else
  42. return STSdb.FromFile(GetFile(false), GetFile(true));
  43. }
  44. }
  45. private string GetExpirationTable(string Category)
  46. {
  47. return KeyExpiration + "_" + Category;
  48. }
  49. public void Add<K, V>(string Category, IEnumerable<KeyValuePair<K, V>> Items, DateTime? ExpirationDate)
  50. {
  51. lock (syncRoot)
  52. {
  53. var engine = Engine;
  54. var table = engine.OpenXIndex<K, string>(Category);
  55. Items.ForEach(i =>
  56. {
  57. var key = i.Key;
  58. var data = i.Value;
  59. //will only serialize object other than string
  60. var result = typeof(V) == typeof(string) ? data as string : JSON.Instance.ToJSON(data);
  61. table[key] = result;
  62. table.Flush();
  63. //specify expiration
  64. var expiration = engine.OpenXIndex<K, DateTime>(GetExpirationTable(Category));
  65. //default 30 mins to expire from now
  66. var expirationDate = ExpirationDate == null || ExpirationDate <= DateTime.Now ? DateTime.Now.AddMinutes(30) : (DateTime)ExpirationDate;
  67. expiration[key] = expirationDate;
  68. expiration.Flush();
  69. });
  70. engine.Commit();
  71. //only dispose disk-based engine
  72. if (!isMemory)
  73. engine.Dispose();
  74. }
  75. }
  76. public void Add<K>(string Category, K Key, object Data, DateTime? ExpirationDate)
  77. {
  78. Add<K, object>(Category, new List<KeyValuePair<K, object>> { new KeyValuePair<K, object>(Key, Data) }, ExpirationDate);
  79. }
  80. private string GetFile(bool IsData)
  81. {
  82. if (!Directory.Exists(dataPath))
  83. Directory.CreateDirectory(dataPath);
  84. return dataPath + "SuperCache." + (IsData ? "dat" : "sys");
  85. }
  86. public List<KeyValuePair<K, V>> Get<K, V>(string Category, IEnumerable<K> Keys)
  87. {
  88. var result = new List<KeyValuePair<K, V>>();
  89. lock (syncRoot)
  90. {
  91. var engine = Engine;
  92. var table = engine.OpenXIndex<K, string>(Category);
  93. var expiration = engine.OpenXIndex<K, DateTime>(GetExpirationTable(Category));
  94. var isCommitRequired = false;
  95. Keys.ForEach(key =>
  96. {
  97. string buffer;
  98. V value;
  99. if (table.TryGet(key, out buffer))
  100. {
  101. //will only deserialize object other than string
  102. value = typeof(V) == typeof(string) ? (V)(object)buffer : JSON.Instance.ToObject<V>(buffer);
  103. DateTime expirationDate;
  104. //get expiration date
  105. if (expiration.TryGet(key, out expirationDate))
  106. {
  107. //expired
  108. if (expirationDate < DateTime.Now)
  109. {
  110. value = default(V);
  111. table.Delete(key);
  112. table.Flush();
  113. expiration.Delete(key);
  114. expiration.Flush();
  115. isCommitRequired = true;
  116. }
  117. }
  118. }
  119. else
  120. value = default(V);
  121. result.Add(new KeyValuePair<K, V>(key, value));
  122. });
  123. //only need to commit write actions
  124. if (isCommitRequired)
  125. engine.Commit();
  126. //only dispose disk-based engine
  127. if (!isMemory)
  128. engine.Dispose();
  129. }
  130. return result;
  131. }
  132. public V Get<K, V>(string Category, K Key)
  133. {
  134. var buffer = Get<K, V>(Category, new K[] { Key });
  135. var result = buffer.FirstOrDefault();
  136. return result.Value;
  137. }
  138. }
  139. }

新建

构造CacheEngine需要传递缓存保存到哪个文件夹。

基于内存

如果你不喜欢基于磁盘的缓存,可以使用基于内存,构造函数传递空字符串便可。

增加/更新

同一个方法:Add。用户可以指定类型(Category),譬如User,Employee等。键(Key)支持泛型,值(Data)是object。有一个overload是过期日期(ExpirationDate),默认当前时间30分钟后

获取

Get方法需要指定类型(Category)和键(Key)。

例子

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using System.Diagnostics;
  8. using Com.SuperCache.Engine;
  9. namespace Com.SuperCache.Test
  10. {
  11. public class Foo
  12. {
  13. public string Name { get; set; }
  14. public int Age { get; set; }
  15. public double? Some { get; set; }
  16. public DateTime? Birthday { get; set; }
  17. }
  18. class Program
  19. {
  20. static void Main(string[] args)
  21. {
  22. //TestAddGet();
  23. //Thread.Sleep(4000);
  24. //TestExpiration();
  25. TestPerformance();
  26. //TestConcurrent();
  27. Console.Read();
  28. }
  29. private static void TestConcurrent()
  30. {
  31. var w = new Stopwatch();
  32. w.Start();
  33. Parallel.For(1, 3, (a) =>
  34. {
  35. var employees = Enumerable.Range((a - 1) * 1000, a * 1000).Select(i => new KeyValuePair<int, string>(i, "Wilson " + i + " Chen"));
  36. var engine = new CacheEngine(@"..\..\data");
  37. engine.Add<int, string>("Employee", employees, DateTime.Now.AddMinutes(1));
  38. });
  39. w.Stop();
  40. Console.WriteLine("add:" + w.Elapsed);
  41. var engine2 = new CacheEngine(@"..\..\data");
  42. var o = engine2.Get<int, string>("Employee", 1005);
  43. Console.WriteLine(o);
  44. }
  45. private static void TestPerformance()
  46. {
  47. var engine = new CacheEngine(@"..\..\data");
  48. var w = new Stopwatch();
  49. w.Start();
  50. var employees = Enumerable.Range(, 1000).Select(i => new KeyValuePair<int, string>(i, "Wilson " + i + " Chen"));
  51. engine.Add<int, string>("Employee", employees, DateTime.Now.AddMinutes(1));
  52. w.Stop();
  53. Console.WriteLine("add:" + w.Elapsed);
  54. /*w.Restart();
  55. employees.ForEach(key =>
  56. {
  57. var o1 = engine.Get<int, string>("Employee", key.Key);
  58. });
  59. w.Stop();
  60. Console.WriteLine("individual get:" + w.Elapsed);*/
  61. w.Restart();
  62. var keys = employees.Select(i => i.Key);
  63. var o = engine.Get<int, string>("Employee", keys);
  64. w.Stop();
  65. Debug.Assert(o.Count == keys.Count());
  66. Console.WriteLine("get:" + w.Elapsed);
  67. }
  68. private static void TestExpiration()
  69. {
  70. var engine = new CacheEngine(@"..\..\data");
  71. var o = engine.Get<string, Foo>("User", "wchen");
  72. Console.WriteLine(o != null ? o.Name : "wchen does not exist or expired");
  73. }
  74. private static void TestAddGet()
  75. {
  76. var engine = new CacheEngine(@"..\..\data");
  77. var f = new Foo { Name = "Wilson Chen", Age = 30, Birthday = DateTime.Now, Some = 123.456 };
  78. engine.Add("User", "wchen", f, DateTime.Now.AddSeconds(5));
  79. var o = engine.Get<string, Foo>("User", "wchen");
  80. Console.WriteLine(o.Name);
  81. var o4 = engine.Get<string, Foo>("User", "foo");
  82. Console.WriteLine(o4 != null ? o4.Name : "foo does not exist");
  83. var o3 = engine.Get<string, string>("PlainText", "A");
  84. Console.WriteLine(o3 ?? "A does not exist");
  85. }
  86. }
  87. }

说明

项目中引用了System.Management是因为STSdb支持内存数据库,需要判断大物理内存。如果不喜欢,大家可以移除引用,并且去掉STSdb4.Database.STSdb.FromMemory方法便可。

分享好友

分享这个小栈给你的朋友们,一起进步吧。

STSdb
创建时间:2022-04-14 10:53:57
STSdb
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • LCR_
    专家
戳我,来吐槽~