1. 程式人生 > >基於STSdb和fastJson的磁碟/記憶體快取

基於STSdb和fastJson的磁碟/記憶體快取

更新

1. 增加了對批量處理的支援,寫操作速度提升5倍,讀操作提升100倍

2. 增加了對併發的支援

需求

業務系統用的是資料庫,資料量大,部分只讀或相對穩定業務查詢複雜,每次頁面載入都要花耗不少時間(不討論非同步),覺得可以做一下快取記憶體,譬如用nosql那種key/value快速存取結果

目的

這裡不是要做一個大家都適用的磁碟/記憶體快取庫,這個做法,部分是展示STSdb的用法,部分是提供一個簡單易用的解決方案。

磁碟/記憶體

為什麼不用memcached或者AppFabric Cache這樣的現成解決方案呢?因為業務要快取的記憶體或大或小,小的幾KB,大的幾MB,如果使用者一多,勢必對記憶體有過度的需求。所以選擇做一個基於磁碟的。

當然,這個解決方案是支援記憶體快取的。構造的時候傳遞空字串便可。

STSdb是什麼

再來說明一下STSdb是什麼:STSdb是C#寫的開源嵌入式資料庫和虛擬檔案系統,支援實時索引,效能是同類產品的幾倍到幾十倍,訪問官方網站

實現

存取

因為是基於磁碟,所以需要使用到高效的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方法便可。

下載

點選這裡下載

相關推薦

基於STSdbfastJson磁碟/記憶體快取

更新 1. 增加了對批量處理的支援,寫操作速度提升5倍,讀操作提升100倍 2. 增加了對併發的支援 需求 業務系統用的是資料庫,資料量大,部分只讀或相對穩定業務查詢複雜,每次頁面載入都要花耗不少時間(不討論非同步),覺得可以做一下快取記憶體,譬如用nosql那種key/value快速存取結果 目的

Android RxJava 實戰系列:從磁碟 / 記憶體快取中 獲取快取資料

前言 Rxjava,由於其基於事件流的鏈式呼叫、邏輯簡潔 & 使用簡單的特點,深受各大 Android開發者的歡迎。 RxJava如此受歡迎的原因,在於其提供了豐富 &

Fresco磁碟記憶體快取 工具類

1. 新增依賴,在AndroidManifest.xml清單檔案中配置: compile 'com.facebook.fresco:fresco:1.5.0' <application android:name=".util.BaseApplication"  

C#開源磁碟/記憶體快取引擎

using System; using System.Collections.Generic; using System.Linq; using System.Text; using STSdb4.Database; using fastJSON; using System.IO; na

磁碟快取記憶體快取的區別

記憶體快取 快取記憶體(英語:cache,英語發音:/kæʃ/ kash [1][2][3],簡稱快取),其原始意義是指訪問速度比一般隨機存取儲存器(RAM)快的一種RAM,通常它不像系統主存那樣使用DRAM技術,而使用昂貴但較快速的SRAM技術。 原理

Glide 快取策略 記憶體快取磁碟快取

本文主要介紹瞭如何配置和管理Glide中的快取,其中大部分內容都可以直接在官方Wiki中找到,這裡只是進行了整理和彙總。言歸正傳,Glide支援圖片的二級快取(並不是三級快取,因為從網路載入並不屬於快取),即記憶體快取和磁碟快取。 磁碟快取 一般的圖片快取指的就是磁碟快取

Android快取機制Lrucache記憶體快取DiskLruCache磁碟快取

1.1 記憶體快取——LruCache原始碼分析     1.1.1 LRU     LRU,全稱Least Rencetly Used,即最近最少使用,是一種非常常用的置換演算法,也即淘汰最長時間未使用的物件。LRU在作業系統中的頁面置換演算法中廣泛使用,我們的記憶體或快取空間是有限的,當新加入一個物

Android記憶體快取磁碟快取的實現

記憶體快取 Android自帶的LruCache實現了記憶體快取,LruCache內部主要使用LinkedHashMap的特性來實現,因為LinkedHashMap可支援FIFO和LRU訪問。 LinkedHashMap的特點 LinkedHashMap繼

Android RxJava操作符的學習---組合合併操作符---從磁碟記憶體快取中獲取快取資料

1. 需求場景     2. 功能說明 對於從磁碟 / 記憶體快取中 獲取快取資料 的功能邏輯如下: 3. 具體實現 詳細請看程式碼註釋 // 該2變數用於模擬記憶體快取 & 磁碟快取中的資料 String me

Python基於單例模式實現具有時效性的記憶體快取

Python基於單例模式實現具有時效性的記憶體快取 版本說明:Python 2.7 Python有不少第三方的快取庫,如cacheout、memcached等。因為專案需求,這裡不使用第三方庫,自己實現具有時效性的記憶體快取,用來快取重複利用的資料。 1 設計實現

在子函式中malloc分配記憶體free釋放記憶體的方法(基於C)

1. 子函式malloc分配記憶體        為了增強程式可讀性,有時會在子函式中malloc分配記憶體。測試瞭如下三種方法,容易想到的是第一種。事實證明這種也是錯誤的! #include <stdio.h> #include &l

Glide原始碼閱讀(四)記憶體快取磁碟快取、跳過快取

一、記憶體快取實現com.bumptech.glide.util.LruCache<T, Y>中,通過LinkedHashMap做記憶體快取Engine中,MemoryCache.put(EngineKey, EngineResource);新增到LinkedHa

Glide 快取工具例子,快取大小獲取,磁碟快取清除(2 種方法),記憶體快取清除

Glide 快取 Simple快取路徑的指定快取大小的獲取磁碟快取清除(兩種方法)記憶體快取清除可 clone 之後檢視使用 SimpleGlide cache Simple.The cache path specifiedThe cache sizeThe disk cache (two ways)Memo

java中程序間通訊,基於檔案共享記憶體

附上編譯執行的原始碼: 1,請分別建立兩個工程來進行讀寫操作; 2,請在相應的目錄建立一個檔案例如:/home/tory/workspace/sharedMemory.txt 3,往檔案中輸入超過12個位元組資料:echo asldkjasldkddas > /hom

CPU記憶體磁碟快取關係?(加深理解)

1. 暫存器是中央處理器內的組成部份。暫存器是有限存貯容量的高速存貯部件,它們可用來暫存指令、資料和位址。在中央處理器的控制部件中,包含的暫存器有指令暫存器(IR)和程式計數器(PC)。在中央處理器的算術及邏輯部件中,包含的暫存器有累加器(ACC)。 2. 記

iOS開發之記憶體快取 磁碟快取 沙盒

        最近一直看到“快取”兩字,索性自己總結一下,希望大神看到多多指點。         說到快取,快取分為記憶體快取和磁碟快取兩種,記憶體是指當前程式的執行空間,磁碟是程式的儲存空間; 記憶體快取速度快容量小,磁碟快取容量大速度慢可持久化;記憶體是臨時儲存檔案用

Android內部儲存外部儲存以及快取清理記憶體清理!

android內部的儲存結構和路徑獲取方法:清除本地資料和快取:計算快取和記憶體資料的大小:計算快取大小以及刪除的方法:這文章裡面的檔案刪除方法有一些問題:沒有迴圈是資料夾的刪除方法;另外:File.list(); 獲取的是該資料夾下的所有子檔案的相對路徑;File.list

程序與記憶體6-快取記憶體1(每CPU頁框快取記憶體記憶體快取記憶體

首先我提幾個主題: 磁碟快取記憶體、記憶體快取記憶體、硬體快取記憶體、每cpu頁框快取記憶體、頁快取記憶體、目錄項快取記憶體、索引節點快取記憶體、轉換後援緩衝器(TLB)、哈佛結構的快取記憶體、寫緩衝器、快取記憶體一致性、L1和L2等快取記憶體在驅動的使用。 上面這些就我

檢視基於Android 系統單個程序記憶體CPU使用情況的幾種方法

Total PSS by OOM adjustment:     16839 kB: System                16839 kB: system (pid 791)      9279 kB: Persistent                 9279 kB: com.android.s

記憶體快取硬碟快取

由於工作中經常要處理圖片的載入快取的問題,所以需要理清系統快取的問題,否則經常會出現OOM問題。防止多圖OOM的解決技術是使用LruCache和DiskLruCache兩種技術。 一 LruCache