Cache系统设计

Posted deadend

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cache系统设计相关的知识,希望对你有一定的参考价值。

FROM: http://blog.csdn.net/hexinuaa/article/details/6630384

如何设计一个LRU Cache?

Google和百度的面试题都出现了设计一个Cache的题目,什么是Cache,如何设计简单的Cache,通过搜集资料,本文给出个总结。

 通常的问题描述可以是这样:

Question:

[1] Design a layer in front of a system which cache the last n requests and the responses to them from the system.

在一个系统之上设计一个Cache,缓存最近的n个请求以及系统的响应。
what data structure would you use to implement the cache in the later to support following operations.

用什么样的数据结构设计这个Cache才能满足下面的操作呢?
[a] When a request comes look it up in the cache and if it hits then return the response from here and do not pass the request to the system
[b] If the request is not found in the cache then pass it on to the system
[c] Since cache can only store the last n requests, Insert the n+1th request in the cache and delete one of the older requests from the cache

因为Cache只缓存最新的n个请求,向Cache插入第n+1个请求时,从Cache中删除最旧的请求。

[d]Design one cache such that all operations can be done in O(1) – lookup, delete and insert.

 Cache简介:

Cache(高速缓存), 一个在计算机中几乎随时接触的概念。CPU中Cache能极大提高存取数据和指令的时间,让整个存储器(Cache+内存)既有Cache的高速度,又能有内存的大容量;操作系统中的内存page中使用的Cache能使得频繁读取的内存磁盘文件较少的被置换出内存,从而提高访问速度;数据库中数据查询也用到Cache来提高效率;即便是Powerbuilder的DataWindow数据处理也用到了Cache的类似设计。Cache的算法设计常见的有FIFO(first in first out)和LRU(least recently used)。根据题目的要求,显然是要设计一个LRU的Cache。

 解题思路:

Cache中的存储空间往往是有限的,当Cache中的存储块被用完,而需要把新的数据Load进Cache的时候,我们就需要设计一种良好的算法来完成数据块的替换。LRU的思想是基于“最近用到的数据被重用的概率比较早用到的大的多”这个设计规则来实现的。

为了能够快速删除最久没有访问的数据项和插入最新的数据项,我们双向链表连接Cache中的数据项,并且保证链表维持数据项从最近访问到最旧访问的顺序。每次数据项被查询到时,都将此数据项移动到链表头部(O(1)的时间复杂度)。这样,在进行过多次查找操作后,最近被使用过的内容就向链表的头移动,而没有被使用的内容就向链表的后面移动。当需要替换时,链表最后的位置就是最近最少被使用的数据项,我们只需要将最新的数据项放在链表头部,当Cache满时,淘汰链表最后的位置就是了。

  注: 对于双向链表的使用,基于两个考虑。首先是Cache中块的命中可能是随机的,和Load进来的顺序无关。其次,双向链表插入、删除很快,可以灵活的调整相互间的次序,时间复杂度为O(1)。

    查找一个链表中元素的时间复杂度是O(n),每次命中的时候,我们就需要花费O(n)的时间来进行查找,如果不添加其他的数据结构,这个就是我们能实现的最高效率了。目前看来,整个算法的瓶颈就是在查找这里了,怎么样才能提高查找的效率呢?Hash表,对,就是它,数据结构中之所以有它,就是因为它的查找时间复杂度是O(1)。

梳理一下思路:对于Cache的每个数据块,我们设计一个数据结构来储存Cache块的内容,并实现一个双向链表,其中属性next和prev时双向链表的两个指针,key用于存储对象的键值,value用户存储要cache块对象本身。

 Cache的接口:

查询:

  • 根据键值查询hashmap,若命中,则返回节点,否则返回null。
  • 从双向链表中删除命中的节点,将其重新插入到表头。
  • 所有操作的复杂度均为O(1)。

插入:

  • 将新的节点关联到Hashmap
  • 如果Cache满了,删除双向链表的尾节点,同时删除Hashmap对应的记录
  • 将新的节点插入到双向链表中头部

更新:

  • 和查询相似

删除:

  • 从双向链表和Hashmap中同时删除对应的记录。

LRU Cache的Java 实现:

 

  1 public interface Cache<K extends Comparable, V> {
  2 
  3    V get(K obj);  //查询
  4 
  5    void put(K key, V obj); //插入和更新
  6 
  7    void put(K key, V obj, long validTime);
  8 
  9    void remove(K key); //删除
 10 
 11    Pair[] getAll();
 12 
 13    int size();
 14 
 15 }
 16 
 17   public class Pair<K extends Comparable, V> implements Comparable<Pair> {
 18 
 19    public Pair(K key1, V value1) {
 20 
 21       this.key = key1;
 22 
 23       this.value = value1;
 24 
 25    }
 26 
 27    public K key;
 28 
 29    public V value;
 30 
 31    public boolean equals(Object obj) {
 32 
 33       if(obj instanceof Pair) {
 34 
 35          Pair p = (Pair)obj;
 36 
 37          return key.equals(p.key)&&value.equals(p.value);
 38 
 39       }
 40 
 41       return false;
 42 
 43    }
 44 
 45    @SuppressWarnings("unchecked")
 46 
 47    public int compareTo(Pair p) {
 48 
 49       int v = key.compareTo(p.key);
 50 
 51       if(v==0) {
 52 
 53          if(p.value instanceof Comparable) {
 54 
 55             return ((Comparable)value).compareTo(p.value);
 56 
 57          }
 58 
 59       }
 60 
 61       return v;
 62 
 63    }
 64 
 65    @Override
 66 
 67    public int hashCode() {
 68 
 69       return key.hashCode()^value.hashCode();
 70 
 71    }
 72 
 73    @Override
 74 
 75    public String toString() {
 76 
 77       return key+": "+value;
 78 
 79    }
 80 
 81 }
 82 
 83  public class LRUCache<K extends Comparable, V> implements Cache<K, V>,
 84 
 85       Serializable {
 86 
 87    private static final long serialVersionUID = 3674312987828041877L;
 88 
 89    Map<K, Item> m_map = Collections.synchronizedMap(new HashMap<K, Item>());
 90 
 91    Item m_start = new Item();      //表头
 92 
 93    Item m_end = new Item();        //表尾
 94 
 95    int m_maxSize;
 96 
 97    Object m_listLock = new Object();        //用于并发的锁
 98 
 99    static class Item {
100 
101       public Item(Comparable k, Object v, long e) {
102 
103          key = k;
104 
105          value = v;
106 
107          expires = e;
108 
109       }
110 
111       public Item() {}
112 
113       public Comparable key;        //键值
114 
115       public Object value;          //对象
116 
117        public long expires;          //有效期
118 
119       public Item previous;
120 
121       public Item next;
122 
123    }
124 
125    void removeItem(Item item) {
126 
127       synchronized(m_listLock) {
128 
129          item.previous.next = item.next;
130 
131          item.next.previous = item.previous;
132 
133       }
134 
135    }
136 
137    void insertHead(Item item) {
138 
139       synchronized(m_listLock) {
140 
141          item.previous = m_start;
142 
143          item.next = m_start.next;
144 
145          m_start.next.previous = item;
146 
147          m_start.next = item;
148 
149       }
150 
151    }
152 
153    void moveToHead(Item item) {
154 
155       synchronized(m_listLock) {
156 
157          item.previous.next = item.next;
158 
159          item.next.previous = item.previous;
160 
161          item.previous = m_start;
162 
163          item.next = m_start.next;
164 
165          m_start.next.previous = item;
166 
167          m_start.next = item;
168 
169       }
170 
171    }
172 
173    public LRUCache(int maxObjects) {
174 
175       m_maxSize = maxObjects;
176 
177       m_start.next = m_end;
178 
179       m_end.previous = m_start;
180 
181    }
182 
183    @SuppressWarnings("unchecked")
184 
185    public Pair[] getAll() {
186 
187       Pair p[] = new Pair[m_maxSize];
188 
189       int count = 0;
190 
191       synchronized(m_listLock) {
192 
193          Item cur = m_start.next;
194 
195          while(cur!=m_end) {
196 
197             p[count] = new Pair(cur.key, cur.value);
198 
199             ++count;
200 
201             cur = cur.next;
202 
203          }
204 
205       }
206 
207       Pair np[] = new Pair[count];
208 
209       System.arraycopy(p, 0, np, 0, count);
210 
211       return np;
212 
213    }
214 
215    @SuppressWarnings("unchecked")
216 
217    public V get(K key) {
218 
219       Item cur = m_map.get(key);
220 
221       if(cur==null) {
222 
223          return null;
224 
225       }
226 
227      //过期则删除对象
228 
229       if(System.currentTimeMillis()>cur.expires) {
230 
231          m_map.remove(cur.key);
232 
233          removeItem(cur);
234 
235          return null;
236 
237       }
238 
239       if(cur!=m_start.next) {
240 
241          moveToHead(cur);
242 
243       }
244 
245       return (V)cur.value;
246 
247    }
248 
249    public void put(K key, V obj) {
250 
251       put(key, obj, -1);
252 
253    }
254 
255    public void put(K key, V value, long validTime) {
256 
257       Item cur = m_map.get(key);
258 
259       if(cur!=null) {
260 
261          cur.value = value;
262 
263          if(validTime>0) {
264 
265             cur.expires = System.currentTimeMillis()+validTime;
266 
267          }
268 
269          else {
270 
271             cur.expires = Long.MAX_VALUE;
272 
273          }
274 
275          moveToHead(cur);  //成为最新的对象,移动到头部
276 
277          return;
278 
279       }
280 
281       if(m_map.size()>=m_maxSize) {
282 
283          cur = m_end.previous;
284 
285          m_map.remove(cur.key);
286 
287          removeItem(cur);
288 
289       }
290 
291       long expires=0;
292 
293       if(validTime>0) {
294 
295          expires = System.currentTimeMillis()+validTime;
296 
297       }
298 
299       else {
300 
301          expires = Long.MAX_VALUE;
302 
303       }
304 
305       Item item = new Item(key, value, expires);
306 
307       insertHead(item);
308 
309       m_map.put(key, item);
310 
311    }
312 
313    public void remove(K key) {
314 
315       Item cur = m_map.get(key);
316 
317       if(cur==null) {
318 
319          return;
320 
321       }
322 
323       m_map.remove(key);
324 
325       removeItem(cur);
326 
327    }
328 
329    public int size() {
330 
331       return m_map.size();
332 
333    }
334 
335 }

 

以上是关于Cache系统设计的主要内容,如果未能解决你的问题,请参考以下文章

C# Cache的一些总结

计算机组成原理存储系统综合实验

Yii2片段缓存详解

存储系统-------cache

如何利用redis来进行分布式集群系统的限流设计

Cache!聊聊缓存