最近做項目時需要實現數據冷熱分離功能,現在的NOSQL框架(redis,memcached,mongodb)均已實現了這個功能,直接拿過來用就Ok了,(知其然還要知其所以然吧,呵呵)
分析如下:
這個功能核心詞:“最近(遠)最少使用的緩存項”移除緩存就OK了。
A.最近(遠):第一感覺不就是時間排序(正序,倒序)么。
B.最少使用:就是緩存項的get頻率了 。
C.這個功能的理論支撐就是大名鼎鼎的LRU算法了,核心思想:“在前面幾條指令中使用頻繁的頁面很可能在后面的幾條指令中頻繁使用。反過來說,已經很久沒有使用的 頁面很可能在未來較長的一段時間內不會被用到。這個,就是著名的局部性原理——比內存速度還要快的 cache,也是基於同樣的原理運行的。因此,我們只需要在每次 調換時,找到最近最少使用的那個頁面調出內存 ” ,這個算法的詳細介紹網上都有我也不貼了。
實現:
A. 最近(遠),實現方式有這么幾種:
1),排序最快的當然是快速排序了,快速排序的時間復雜度:平均O(nlogn),最壞O(n^2),在一個大訪問量的站點實時運用這個排序可行性不高;
2),順序添加緩存項,自然而然老的數據就在前面新的數據就在后面了,可用的數據結構:數組,鏈表(單練,雙鏈)我用鏈表,理由:鏈表,添加,刪除都 為O(1),添加Dictionary字典作輔助緩存管理器,彌補鏈表查找缺點(鏈表查找速度比較慢)
B.最少使用:get緩存項給個計數器(這個計數器不是必須?原因:我們只要每次把get的緩存項添加到鏈表的最后面就可了,我添加計數器的理由是:使用頻率超過既定值時再移動該緩存項,否則不移動。),當集合大小超過閥值要做的就是刪除鏈表頭部的緩存項就可以了。
C.結構圖如下:
D.代碼
2 {
3 public CacheItem(K k, V v)
4 {
5 UseCount = 0;
6 Key = k;
7 Value = v;
8 }
9
10 public int UseCount = 0;
11 public K Key;
12 public V Value;
13 }
14
15 // Hot and cold
16 public class HCCache<K, V>
17 {
18 private int maxSize;
19 private int hot;
20 private Dictionary<K, LinkedListNode<CacheItem<K, V>>> cacheDic;
21 private LinkedList<CacheItem<K, V>> chacheList;
22 private volatile bool init = false;
23
24 public HCCache( int maxSize, int hot = 10)
25 {
26 if (!init)
27 {
28 this.init = true;
29 this.hot = hot;
30 this.maxSize = maxSize;
31 this.cacheDic = new Dictionary<K, LinkedListNode<CacheItem<K, V>>>(maxSize);
32 this.chacheList = new LinkedList<CacheItem<K, V>>();
33 }
34 }
35
36 public V Get(K key)
37 {
38 LinkedListNode<CacheItem<K, V>> node;
39 if (cacheDic.TryGetValue(key, out node)) // 0(1)
40 {
41 V value = node.Value.Value;
42 Interlocked.Add( ref node.Value.UseCount, 1);
43 // node.Value.UseCount++; // 這里按需要可改為原子遞增操作
44 if (node.Value.UseCount >= hot && node.Next != null)
45 {
46 lock (chacheList)
47 {
48 chacheList.Remove(node); // O(1)
49 chacheList.AddLast(node); // O(1)
50 }
51 Console.WriteLine( " 移動:{0} ", node.Value.Value);
52 }
53 Console.WriteLine( " {0}:,使用:{1},線程:{2} ", value, node.Value.UseCount,System.Threading.Thread.CurrentThread.Name);
54 return value;
55 }
56 return default(V);
57 }
58
59 public void Add(K key, V val)
60 {
61 if (cacheDic.Count >= maxSize)
62 {
63 RemoveOldItem();
64 }
65 CacheItem<K, V> cacheItem = new CacheItem<K, V>(key, val);
66 LinkedListNode<CacheItem<K, V>> node = new LinkedListNode<CacheItem<K, V>>(cacheItem);
67 chacheList.AddLast(node); // O(1)
68 cacheDic.Add(key, node); // 0(1) -->O(n);
69 }
70
71 public void ClearAll()
72 {
73 chacheList.Clear();
74 cacheDic.Clear();
75 init = false;
76 }
77
78 private void RemoveOldItem()
79 {
80 lock (chacheList)
81 {
82 // 移除老的數據 鏈表頭部都為老的數據
83 LinkedListNode<CacheItem<K, V>> node = chacheList.First;
84 if (node == null) return;
85 Console.WriteLine( " 移除:{0}使用:{1} ", node.Value.Value, node.Value.UseCount);
86 // chacheList.Remove(node);
87 chacheList.RemoveFirst();
88 cacheDic.Remove(node.Value.Key);
89 }
90
91 }
92 }
E.測試
2 HCCache< int, int> cache = new HCCache< int, int>( 100);
3
4 var th1 = new System.Threading.Thread(() =>
5 {
6 for ( var i = 0; i < 200; i++)
7 {
8 cache.Add(i, i);
9 }
10 }) { IsBackground = true, Name = " add_th1 " };
11
12 var th2 = new System.Threading.Thread(() =>
13 {
14 for ( var i = 0; i < 5; i++)
15 {
16 var countLRU = 10; // r.Next(0, 200);
17 for ( var k = 0; k < countLRU; k++)
18 {
19 var item = cache.Get( 100);
20 if (k == countLRU - 1)
21 {
22 Console.WriteLine( " 獲取:{0}, ", item);
23 }
24 }
25 }
26 }) { IsBackground = true, Name = " get_th2 " };
27
28 var th3 = new System.Threading.Thread(() =>
29 {
30 for ( var i = 0; i < 5; i++)
31 {
32 var countLRU = 10; // r.Next(0, 200);
33 for ( var k = 0; k < countLRU; k++)
34 {
35 var item = cache.Get( 101);
36 if (k == countLRU - 1)
37 {
38 Console.WriteLine( " 獲取:{0}, ", item);
39 }
40 }
41 }
42 }) { IsBackground = true, Name = " get_th3 " };
43
44 // 啟動
45 th1.Start();
46 th2.Start();
47 th3.Start();
48
49 // 等待完成
50 th1.Join();
51 th2.Join();
52 th3.Join();
53
54 Console.ReadLine();
F:這是個模擬緩存冷熱分離實驗,可以擴展的地方為:
1),冷數據的處理策略,刪除亦或是移到其他介質中去而當前緩存只保留其引用 。
2),冷數據的判定標准。(我使用get平率,因為簡單啊 )
3) ,過期數據移除出發點,a.惰性移除,b.即時監控並移除。