如何设计一个本地缓存,涨姿势了!

Posted Java技术栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何设计一个本地缓存,涨姿势了!相关的知识,希望对你有一定的参考价值。

最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级缓存和二级缓存;一级缓存相对来说比较简单,功能比较齐全的是二级缓存,基本上满足了一个缓存该有的功能。

考虑点

考虑点主要在数据用何种方式存储,能存储多少数据,多余的数据如何处理等几个点,下面我们来详细的介绍每个考虑点,以及该如何去实现;


1.数据结构


2.对象上限

因为是本地缓存,内存有上限,所以一般都会指定缓存对象的数量比如1024,当达到某个上限后需要有某种策略去删除多余的数据;


3.清除策略


4.过期时间


5.线程安全


6.简明的接口

提供一个傻瓜式的对外接口是很有必要的,对使用者来说使用此缓存不是一种负担而是一种享受;提供常用的get,put,remove,clear,getSize方法即可;


7.是否持久化


8.阻塞机制


如何实现

以上大致介绍了实现一个本地缓存我们都有哪些需要考虑的地方,当然可能还有其他没有考虑到的点;下面继续看看关于每个点都应该如何去实现,重点介绍一下思路;


1.数据结构

本地缓存最常见的是直接使用Map来存储,比如guava使用ConcurrentHashMap,ehcache也是用了 ,Mybatis二级缓存使用HashMap来存储:
Map<Object, Object> cache = new ConcurrentHashMap<Object, Object>()


Mybatis使用HashMap本身是非线程安全的,所以可以看到起内部使用了一个SynchronizedCache用来包装,保证线程的安全性。,这篇推荐大家看下。
当然除了使用Map来存储,可能还使用其他数据结构来存储,比如redis使用了双端链表,压缩列表,整数集合,跳跃表和字典;当然这主要是因为redis对外提供的接口很丰富除了哈希还有列表,集合,有序集合等功能;

2.对象上限

3.清除策略






4.过期时间

设置过期时间,让缓存数据在指定时间过后自动删除;常见的过期数据删除策略有两种方式:被动删除和主动删除;
被动删除:每次进行get/put操作的时候都会检查一下当前key是否已经过期,如果过期则删除,类似如下代码:
if (System.currentTimeMillis() - lastClear > clearInterval) {
  clear();
}


主动删除:专门有一个job在后台定期去检查数据是否过期,如果过期则删除,这其实可以有效的处理冷数据;
关注微信公众号:Java技术栈,在后台回复:redis,可以获取我整理的 N 篇最新Redis 教程,都是干货。

5.线程安全

尽量用线程安全的类去存储数据,比如使用ConcurrentHashMap代替HashMap;或者提供相应的同步处理类,比如Mybatis提供了SynchronizedCache:
public synchronized void putObject(Object key, Object object) {
  ...省略...
}

@Override
public synchronized Object getObject(Object key) {
  ...省略...
}


 
   
   
 


6.简明的接口

提供常用的get,put,remove,clear,getSize方法即可,比如Mybatis的Cache接口:
public interface Cache {
  String getId();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  Object removeObject(Object key);
  void clear();
  int getSize();
  ReadWriteLock getReadWriteLock();
}


再来看看guava提供的Cache接口,相对来说也是比较简洁的:
public interface Cache<K, V> {
  V getIfPresent(@CompatibleWith("K") Object key);
  V get(K key, Callable<? extends V> loader) throws ExecutionException;
  ImmutableMap<K, V> getAllPresent(Iterable<?> keys);
  void put(K key, V value);
  void putAll(Map<? extends K, ? extends V> m);
  void invalidate(@CompatibleWith("K") Object key);
  void invalidateAll(Iterable<?> keys);
  void invalidateAll();
  long size();
  CacheStats stats();
  ConcurrentMap<K, V> asMap();
  void cleanUp();
}



7.是否持久化

 
   
   
 

8.阻塞机制

除了在Mybatis中看到了BlockingCache来实现此功能,之前在看<<java并发编程实战>>的时候其中有实现一个很完美的缓存,大致代码如下:
public class Memoizerl<A, V> implements Computable<A, V> {
    private final Map<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();
    private final Computable<A, V> c;

    public Memoizerl(Computable<A, V> c) {
        this.c = c;
    }

    @Override
    public V compute(A arg) throws InterruptedException, ExecutionException {
        while (true) {
            Future<V> f = cache.get(arg);
            if (f == null) {
                Callable<V> eval = new Callable<V>() {
                    @Override
                    public V call() throws Exception {
                        return c.compute(arg);
                    }
                };
                FutureTask<V> ft = new FutureTask<V>(eval);
                f = cache.putIfAbsent(arg, ft);
                if (f == null) {
                    f = ft;
                    ft.run();
                }
                try {
                    return f.get();
                } catch (CancellationException e) {
                    cache.remove(arg, f);
                }
            }
        }
    }
}


总结

本文大致介绍了要设计一个本地缓存都需要考虑哪些点:数据结构,对象上限,清除策略,过期时间,线程安全,阻塞机制,实用的接口,是否持久化;当然肯定有其他考虑点,欢迎补充。

https://my.oschina.net/OutOfMemory/blog/3133013

- END -
推荐阅读:
1、
2、
3、!
4、!
5
关注 Java技术栈 公众号在后台回复: Java ,可获取一份栈长整理的最新 Java 技术干货。

点击「阅读原文」和栈长学更多~

以上是关于如何设计一个本地缓存,涨姿势了!的主要内容,如果未能解决你的问题,请参考以下文章

java数组的特点,涨姿势了!

pytorch 学习笔记之编写 C 扩展,又涨姿势了

涨姿势且看知乎大神是如何控制汽车嵌入式代码质量的?

涨姿势!避免3种响应式设计灾祸

2021腾讯Java面试题精选,涨姿势!

涨姿势题2_水题_两种解法