Retrofit 风格的 RxCache及其多种缓存替换算法

简介: Retrofit 风格的 RxCache及其多种缓存替换算法

RxCache 是一个支持 Java 和 Android 的 Local Cache 。


之前的文章《给 Java 和 Android 构建一个简单的响应式Local Cache》《RxCache 整合 Android 的持久层框架 greenDAO、Room》曾详细介绍过它。


目前,对框架增加一些 Annotation 以及 Cache 替换算法。


一. 基于 Annotation 完成缓存操作



类似 Retrofit 风格的方式,支持通过标注 Annotation 来完成缓存的操作。


例如先定义一个接口,用于定义缓存的各种操作。

public interface Provider {
    @CacheKey("user")
    @CacheMethod(methodType = MethodType.GET)
    <T> Record<T> getData(@CacheClass Class<T> clazz);
    @CacheKey("user")
    @CacheMethod(methodType = MethodType.SAVE)
    @CacheLifecycle(duration = 2000)
    void putData(@CacheValue User user);
    @CacheKey("user")
    @CacheMethod(methodType = MethodType.REMOVE)
    void removeUser();
    @CacheKey("test")
    @CacheMethod(methodType = MethodType.GET, observableType = ObservableType.MAYBE)
    <T> Maybe<Record<T>> getMaybe(@CacheClass Class<T> clazz);
}


通过 CacheProvider  创建该接口,然后可以完成各种缓存操作。

public class TestCacheProvider {
    public static void main(String[] args) {
        RxCache.config(new RxCache.Builder());
        RxCache rxCache = RxCache.getRxCache();
        CacheProvider cacheProvider = new CacheProvider.Builder().rxCache(rxCache).build();
        Provider provider = cacheProvider.create(Provider.class);
        User u = new User();
        u.name = "tony";
        u.password = "123456";
        provider.putData(u); // 将u存入缓存中
        Record<User> record = provider.getData(User.class); // 从缓存中获取key="user"的数据
        if (record!=null) {
            System.out.println(record.getData().name);
        }
        provider.removeUser(); // 从缓存中删除key="user"的数据
        record = provider.getData(User.class);
        if (record==null) {
            System.out.println("record is null");
        }
        User u2 = new User();
        u2.name = "tony2";
        u2.password = "000000";
        rxCache.save("test",u2);
        Maybe<Record<User>> maybe = provider.getMaybe(User.class); // 从缓存中获取key="test"的数据,返回的类型为Maybe
        maybe.subscribe(new Consumer<Record<User>>() {
            @Override
            public void accept(Record<User> userRecord) throws Exception {
                User user = userRecord.getData();
                if (user!=null) {
                    System.out.println(user.name);
                    System.out.println(user.password);
                }
            }
        });
    }
}


CacheProvider 核心是 create(),它通过动态代理来创建Provider。

public <T> T create(Class<T> clazz) {
        CacheProxy cacheProxy = new CacheProxy(rxCache);
        try {
            return (T) Proxy.newProxyInstance(CacheProvider.class.getClassLoader(), new Class[]{clazz}, cacheProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


其中,CacheProxy 实现了 InvocationHandler 接口,是创建代理类的调用处理器。

package com.safframework.rxcache.proxy;
import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.proxy.annotation.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * Created by tony on 2018/10/30.
 */
public class CacheProxy implements InvocationHandler {
    RxCache rxCache;
    public CacheProxy(RxCache rxCache) {
        this.rxCache = rxCache;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        CacheMethod cacheMethod = method.getAnnotation(CacheMethod.class);
        CacheKey cacheKey = method.getAnnotation(CacheKey.class);
        CacheLifecycle cacheLifecycle = method.getAnnotation(CacheLifecycle.class);
        Annotation[][] allParamsAnnotations = method.getParameterAnnotations();
        Class cacheClazz = null;
        Object cacheValue = null;
        if (allParamsAnnotations != null) {
            for (int i = 0; i < allParamsAnnotations.length; i++) {
                Annotation[] paramAnnotations = allParamsAnnotations[i];
                if (paramAnnotations != null) {
                    for (Annotation annotation : paramAnnotations) {
                        if (annotation instanceof CacheClass) {
                            cacheClazz = (Class) args[i];
                        }
                        if (annotation instanceof CacheValue) {
                            cacheValue = args[i];
                        }
                    }
                }
            }
        }
        if (cacheMethod!=null) {
            MethodType methodType = cacheMethod.methodType();
            long duration = -1;
            if (cacheLifecycle != null) {
                duration = cacheLifecycle.duration();
            }
            if (methodType == MethodType.GET) {
                ObservableType observableType = cacheMethod.observableType();
                if (observableType==ObservableType.NOUSE) {
                    return  rxCache.get(cacheKey.value(),cacheClazz);
                } else if (observableType == ObservableType.OBSERVABLE){
                    return  rxCache.load2Observable(cacheKey.value(),cacheClazz);
                } else if (observableType==ObservableType.FLOWABLE) {
                    return  rxCache.load2Flowable(cacheKey.value(),cacheClazz);
                } else if (observableType==ObservableType.SINGLE) {
                    return  rxCache.load2Single(cacheKey.value(),cacheClazz);
                } else if (observableType==ObservableType.MAYBE) {
                    return  rxCache.load2Maybe(cacheKey.value(),cacheClazz);
                }
            } else if (methodType == MethodType.SAVE) {
                rxCache.save(cacheKey.value(),cacheValue,duration);
            } else if (methodType == MethodType.REMOVE) {
                rxCache.remove(cacheKey.value());
            }
        }
        return null;
    }
}


CacheProxy 的 invoke() 方法先获取 Method 所使用的 Annotation,包括CacheMethod、CacheKey、CacheLifecycle。


其中,CacheMethod 是最核心的 Annotation,它取决于 rxCache 使用哪个方法。CacheMethod 支持的方法类型包括:获取、保存、删除缓存。当 CacheMethod 的 methodType 是 GET 类型,则可能会返回 RxJava 的各种 Observable 类型,或者还是返回所存储的对象类型。


CacheKey 是任何方法都需要使用的 Annotation。CacheLifecycle 只有保存缓存时才会使用。


二. 支持多种缓存替换算法



RxCache 包含了两级缓存: Memory 和 Persistence 。


Memory 的默认实现 FIFOMemoryImpl、LRUMemoryImpl、LFUMemoryImpl 分别使用 FIFO、LRU、LFU 算法来缓存数据。


2.1 FIFO


通过使用 LinkedList 存放缓存的 keys,ConcurrentHashMap 存放缓存的数据,就可以实现 FIFO。


2.2 LRU


LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的。


使用 ConcurrentHashMap 和 ConcurrentLinkedQueue 实现该算法。如果某个数据已经存放在缓存中,则从 queue 中删除并添加到 queue 的第一个位置。如果缓存已满,则从 queue 中删除最后面的数据。并把新的数据添加到缓存。

public class LRUCache<K,V> {
    private Map<K,V> cache = null;
    private AbstractQueue<K> queue = null;
    private int size = 0;
    public LRUCache() {
        this(Constant.DEFAULT_CACHE_SIZE);
    }
    public LRUCache(int size) {
        this.size = size;
        cache = new ConcurrentHashMap<K,V>(size);
        queue = new ConcurrentLinkedQueue<K>();
    }
    public boolean containsKey(K key) {
        return cache.containsKey(key);
    }
    public V get(K key) {
        //Recently accessed, hence move it to the tail
        queue.remove(key);
        queue.add(key);
        return cache.get(key);
    }
    public V getSilent(K key) {
        return cache.get(key);
    }
    public void put(K key, V value) {
        //ConcurrentHashMap doesn't allow null key or values
        if(key == null || value == null) throw new RxCacheException("key is null or value is null");
        if(cache.containsKey(key)) {
            queue.remove(key);
        }
        if(queue.size() >= size) {
            K lruKey = queue.poll();
            if(lruKey != null) {
                cache.remove(lruKey);
            }
        }
        queue.add(key);
        cache.put(key,value);
    }
    /**
     * 获取最近最少使用的值
     * @return
     */
    public V getLeastRecentlyUsed() {
        K remove = queue.remove();
        queue.add(remove);
        return cache.get(remove);
    }
    public void remove(K key) {
        cache.remove(key);
        queue.remove(key);
    }
    public void clear() {
        cache.clear();
        queue.clear();
    }
    ......
}


2.3 LFU


LFU是Least Frequently Used的缩写,即最近最不常用使用。


看上去跟 LRU 类似,其实它们并不相同。LRU 是淘汰最长时间未被使用的数据,而 LFU 是淘汰一定时期内被访问次数最少的数据。


LFU 会记录数据在一定时间内的使用次数。稍显复杂感兴趣的可以阅读 RxCache  中相关的源码。


三. 总结



RxCache 大体已经完成,初步可以使用。


RxCache github 地址:https://github.com/fengzhizi715/RxCache

Android 版本的 RxCache github 地址:https://github.com/fengzhizi715/RxCache4a

对于 Android ,除了支持常见的持久层框架之外,还支持 RxCache 转换成 LiveData。


如果想要跟 Retrofit 结合,可以通过 RxCache 的 transform 策略。


对于Java 后端,RxCache 只是一个本地缓存,不适合存放大型的数据。但是其内置的 Memory 层包含了多种缓存替换算法,不用内置的 Memory 还可以使用  Guava Cache、Caffeine 。

相关文章
|
7月前
|
缓存 算法 NoSQL
【分布式详解】一致性算法、全局唯一ID、分布式锁、分布式事务、 分布式缓存、分布式任务、分布式会话
分布式系统通过副本控制协议,使得从系统外部读取系统内部各个副本的数据在一定的约束条件下相同,称之为副本一致性(consistency)。副本一致性是针对分布式系统而言的,不是针对某一个副本而言。强一致性(strong consistency):任何时刻任何用户或节点都可以读到最近一次成功更新的副本数据。强一致性是程度最高的一致性要求,也是实践中最难以实现的一致性。单调一致性(monotonic consistency):任何时刻,任何用户一旦读到某个数据在某次更新后的值,这个用户不会再读到比这个值更旧的值。
667 0
|
1月前
|
存储 缓存 算法
分布式缓存有哪些常用的数据分片算法?
【10月更文挑战第25天】在实际应用中,需要根据具体的业务需求、数据特征以及系统的可扩展性要求等因素综合考虑,选择合适的数据分片算法,以实现分布式缓存的高效运行和数据的合理分布。
|
6月前
|
存储 缓存 算法
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
56 0
|
4月前
|
缓存 算法 前端开发
深入理解缓存淘汰策略:LRU和LFU算法的解析与应用
【8月更文挑战第25天】在计算机科学领域,高效管理资源对于提升系统性能至关重要。内存缓存作为一种加速数据读取的有效方法,其管理策略直接影响整体性能。本文重点介绍两种常用的缓存淘汰算法:LRU(最近最少使用)和LFU(最不经常使用)。LRU算法依据数据最近是否被访问来进行淘汰决策;而LFU算法则根据数据的访问频率做出判断。这两种算法各有特点,适用于不同的应用场景。通过深入分析这两种算法的原理、实现方式及适用场景,本文旨在帮助开发者更好地理解缓存管理机制,从而在实际应用中作出更合理的选择,有效提升系统性能和用户体验。
222 1
|
5月前
|
缓存 算法 NoSQL
Java中的分布式缓存与一致性哈希算法
Java中的分布式缓存与一致性哈希算法
|
5月前
|
存储 算法 Java
高并发架构设计三大利器:缓存、限流和降级问题之滑动日志算法问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之滑动日志算法问题如何解决
|
5月前
|
算法 Java 调度
高并发架构设计三大利器:缓存、限流和降级问题之使用Java代码实现令牌桶算法问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之使用Java代码实现令牌桶算法问题如何解决
|
5月前
|
缓存 算法 Java
高并发架构设计三大利器:缓存、限流和降级问题之使用代码实现漏桶算法问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之使用代码实现漏桶算法问题如何解决
|
5月前
|
算法 UED 缓存
高并发架构设计三大利器:缓存、限流和降级问题之滑动窗口算法适用于哪些场景
高并发架构设计三大利器:缓存、限流和降级问题之滑动窗口算法适用于哪些场景
|
5月前
|
存储 算法 缓存
高并发架构设计三大利器:缓存、限流和降级问题之滑动窗口算法的原理是什么
高并发架构设计三大利器:缓存、限流和降级问题之滑动窗口算法的原理是什么