• 基于STSdb和fastJson的磁盘/内存缓存


    更新

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

    下载

    点击这里下载

  • 相关阅读:
    FZU-SE-K 第一次累计得分排行榜
    OO第四次总结
    OO第二次总结
    面向对象构造与设计第一次总结
    软件工程实践2019第四次作业
    蹒跚的第一步
    学期导图
    一篇随笔
    【软工】提问回顾与个人总结
    【软工】结对项目博客
  • 原文地址:https://www.cnblogs.com/unruledboy/p/SuperCache.html
Copyright © 2020-2023  润新知