更新

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方法便可。

下载

点击这里下载

基于STSdb和fastJson的磁盘/内存缓存的更多相关文章

  1. 一个基于STSdb和fastJson的磁盘/内存缓存

    一个基于STSdb和fastJson的磁盘/内存缓存 需求 业务系统用的是数据库,数据量大,部分只读或相对稳定业务查询复杂,每次页面加载都要花耗不少时间(不讨论异步),觉得可以做一下高速缓存,譬如用n ...

  2. C#开源磁盘/内存缓存引擎

    前言 昨天写了个 <基于STSdb和fastJson的磁盘/内存缓存>,大家可以先看看.下午用到业务系统时候,觉得可以改进一下,昨晚想了一个晚上,刚才重新实现一下. 更新 1. 增加了对批 ...

  3. 基于.net的通用内存缓存模型组件

    谈到缓存,我们自然而然就会想到缓存的好处,比如: 降低高并发数据读取的系统压力:静态数据访问.动态数据访问 存储预处理数据,提升系统响应速度和TPS 降低高并发数据写入的系统压力 提升系统可用性,后台 ...

  4. Cache【硬盘缓存工具类(包含内存缓存LruCache和磁盘缓存DiskLruCache)】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 内存缓存LruCache和磁盘缓存DiskLruCache的封装类,主要用于图片缓存. 效果图 代码分析 内存缓存LruCache和 ...

  5. 分享基于MemoryCache(内存缓存)的缓存工具类,C# B/S 、C/S项目均可以使用!

    using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Caching; usi ...

  6. 基于.net的分布式系统限流组件 C# DataGridView绑定List对象时,利用BindingList来实现增删查改 .net中ThreadPool与Task的认识总结 C# 排序技术研究与对比 基于.net的通用内存缓存模型组件 Scala学习笔记:重要语法特性

    基于.net的分布式系统限流组件   在互联网应用中,流量洪峰是常有的事情.在应对流量洪峰时,通用的处理模式一般有排队.限流,这样可以非常直接有效的保护系统,防止系统被打爆.另外,通过限流技术手段,可 ...

  7. Go/Python/Erlang编程语言对比分析及示例 基于RabbitMQ.Client组件实现RabbitMQ可复用的 ConnectionPool(连接池) 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil 分享基于MemoryCache(内存缓存)的缓存工具类,C# B/S 、C/S项目均可以使用!

    Go/Python/Erlang编程语言对比分析及示例   本文主要是介绍Go,从语言对比分析的角度切入.之所以选择与Python.Erlang对比,是因为做为高级语言,它们语言特性上有较大的相似性, ...

  8. Bitmap之内存缓存和磁盘缓存详解

    原文首发于微信公众号:躬行之(jzman-blog) Android 中缓存的使用比较普遍,使用相应的缓存策略可以减少流量的消耗,也可以在一定程度上提高应用的性能,如加载网络图片的情况,不应该每次都从 ...

  9. Memory Cache(内存缓存)

    当Google测试了Google Search服务的可用性后,发现速度是最影响Web应用的可用性的因素之一.相对于作用相同但是速度慢的应用,用户更喜欢速度快的应用.多来年,Google已经掌握了如何使 ...

随机推荐

  1. 用servlet和jsp做探索数据库

    1.建一个web文件,在里面分三层,分别是实体层:DAO层,DAO层里面包含BaseDAO(数据访问层)和DAO层:还有一个servlet层,处理数据逻辑层! 一.实体层,建立两个实体,一个membe ...

  2. MVC 3 IIS7.5 网站发布及IIS配置文件问题处理

    1.环境配置 1) IIS7.5 2)VS2010 完整版 2.配置internet信息服务功能,直接上图,简洁明了. 3.打开VS2010 ,网站发布, 4.IIS网站设置 添加网站, 5-在浏览器 ...

  3. 封装ios静态库碰到的一些问题(二)

    在静态库建立好了之后呢,于是应用程序就引用它,加上拷贝的h文件,但是引用之后Build之后提示很多sybmbol 重复 于是进行检查,确实由于是从其他工程修改过来的,很多基础库都引用了,删除之,最后就 ...

  4. Linux三剑客之sed

    sed sed对文本的处理很强大,并且sed非常小,参数少,容易掌握,他的操作方式根awk有点像.sed按顺序逐行读取文件.然后,它执行为该行指定的所有操作,并在完成请求的修改之后的内容显示出来,也可 ...

  5. nginx负载均衡最新

    配置conf文件 #user  nobody;worker_processes  1;#error_log  logs/error.log;#error_log  logs/error.log  no ...

  6. java程序操作Geometry对象

    Geometry 空间地理对象,Oracle中存储Geometry对象的字段类型是 MDSYS.SDO_GEOMETRY,在数据库中构建Geometry对象的方法: v_pointarray MDSY ...

  7. 简单的css js控制table隔行变色

    (1)用expression 鼠标滑过变色: <style type="text/css"><!-- table { background-color:#0000 ...

  8. Mysql 分区处理NULL的得方式

    MySQL分区处理NULL值得方式 一般情况下,MySQL的分区把NULL当做零值,或者一个最小值进行处理 对于range分区 create table test_null( id int ) par ...

  9. angular.element的常用方法

    addClass()-为每个匹配的元素添加指定的样式类名after()-在匹配元素集合中的每个元素后面插入参数所指定的内容,作为其兄弟节点append()-在每个匹配元素里面的末尾处插入参数内容att ...

  10. uva-327

    题意:给出一个C语言加减法表达式,求出这个表达式的最终结构,以及各个变量的值,每个变量保证至出现一次,保证输入的字符串合法: 输入:一串包含+.-和小写的26个英文字母: 输出:表达式的结果,以及表达 ...