手写数据库轮子项目 MYDB 之二 | DataManager (DM) 数据之缓存

一、模块介绍

DM 管理数据库的 DB 文件和日志文件。它是上层模块和下层文件系统的中间层,向上提供数据的包装,向下将数据写入磁盘。DM 的主要职责有:1) 抽象 DB 文件为 DataItem 供上层模块使用,并提供缓存;2) 分页管理 DB 文件,并进行缓存;3)管理日志文件,保证在发生错误时可以根据日志进行恢复。

二、引用计数缓存淘汰策略

无论是向上传递数据还是向下写入数据,都需要缓存以提升操作效率。那么缓存淘汰策略使用 Least Recently Used (LRU) 还是引用计数呢?

在 LRU 策略中,资源驱逐不可控,上层模块无法感知。而在引用计数策略中,只有上层模块主动释放引用,缓存在确保没有模块在使用这个资源了,才会去驱逐资源。

如果使用LRU,设想这样一个场景:某个时刻缓存满了,缓存驱逐了一个资源,这时上层模块想要将某个资源强制刷回数据源,这个资源恰好是刚刚被驱逐的资源。那么上层模块就发现,这个数据在缓存里消失了,这时候就陷入了一种尴尬的境地:是否有必要做回源操作?

  1. 不回源。由于没法确定缓存被驱逐的时间,更没法确定被驱逐之后数据项是否被修改,这样是极其不安全的;
  2. 回源。如果数据项被驱逐时的数据和现在又是相同的,那就是一次无效回源(因为被驱逐时脏页会自动刷回磁盘);
  3. 放回缓存里,等下次被驱逐时回源。看起来解决了问题,但是此时缓存已经满了,这意味着你还需要驱逐一个资源才能放进去。这有可能会导致缓存抖动问题。

三、抽象类

1. 属性

用一个Map存储缓存中的资源号和其相应的数据,用一个Map存储缓存中的资源号和其被引用的次数,用一个Map存储缓存中的资源号和其是否正在被访问的标识符(用于多线程场景)。

    private HashMap<Long, T> cache;                     // 资源及其相应的数据
    private HashMap<Long, Integer> references;          // 资源及其被引用的个数
    private HashMap<Long, Boolean> getting;             // 资源是否正在被获取

    private int maxResource;                            // 缓存的最大缓存资源数
    private int count = 0;                              // 缓存中元素的个数
    private Lock lock;

2. 抽象方法

我们在程序中设置了一个抽象类,有两个抽象方法分别用于当资源在缓存不存在时获取资源,和当资源要缓存淘汰时将资源写回。这两个抽象方法留给子类去做具体实现。

protected abstract T getForCache(long key) throws Exception;

protected abstract void releaseForCache(T obj);

3. 从缓存中获取资源

抽象类中的普通方法用于描述子类中共有的方法,因为我们并不知道资源封装的具体类型,所以使用一个通配符 T。

当想要获取一个资源时通过死循环无限尝试,这里是需要加锁的。

如果发现资源正在被其他线程所访问(暗含资源就在缓存当中,把这个 if 放在下一个 if 前面可能是为了避免 if 嵌套,并与 continue 搭配使用),那么就 sleep 一段时间,再次尝试获取。

如果发现资源就在缓存当中(暗含资源没有被其他线程访问),首先给资源的引用标识加一,然后再返回资源。

如果上述两个 if 判断都为假,说明资源没有在缓存中。

如果发现缓存中的资源个数已经等于最大资源个数了,直接抛出异常终止程序。

如果缓存还没有满,那接下来就要从磁盘中获取资源并放入缓存中,首先 count++,然在 getting 中注册一下,表明这个资源正在被我使用。接下来调用抽象方法从磁盘中获取资源。如果出现异常,操作需要回滚。如果资源成功被获取,那么首先从getting中删除key,然后把资源放入缓存,然后把资源的引用数量设为1(为什么不设置为0呢?0表示不可能再被使用)。

    protected T get(long key) throws Exception {
        while(true) {
            lock.lock();
            if(getting.containsKey(key)) {
                // 请求的资源正在被其他线程获取
                lock.unlock();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    continue;
                }
                continue;
            }

            if(cache.containsKey(key)) {
                // 资源在缓存中,直接返回
                T obj = cache.get(key);
                references.put(key, references.get(key) + 1);
                lock.unlock();
                return obj;
            }

            // 尝试获取该资源
            if(maxResource > 0 && count == maxResource) {
                lock.unlock();
                throw Error.CacheFullException;
            }
            count ++;
            getting.put(key, true);
            lock.unlock();
            break;
        }

        T obj = null;
        try {
            obj = getForCache(key);
        } catch(Exception e) {
            lock.lock();
            count --;
            getting.remove(key);
            lock.unlock();
            throw e;
        }

        lock.lock();
        getting.remove(key);
        cache.put(key, obj);
        references.put(key, 1);
        lock.unlock();
        
        return obj;
    }

4. 从缓存中释放资源

首先将资源的引用数减一,如果变成0了,就可以写回数据库,并从缓存中删除。如果不为0,就更新引用为减一后的值。

    protected void release(long key) {
        lock.lock();
        try {
            int ref = references.get(key)-1;
            if(ref == 0) {
                T obj = cache.get(key);
                releaseForCache(obj);
                references.remove(key);
                cache.remove(key);
                count --;
            } else {
                references.put(key, ref);
            }
        } finally {
            lock.unlock();
        }
    }

5. 关闭缓存

关闭缓存并写回所有资源。

    protected void close() {
        lock.lock();
        try {
            Set<Long> keys = cache.keySet();
            for (long key : keys) {
                T obj = cache.get(key);
                releaseForCache(obj);
                references.remove(key);
                cache.remove(key);
            }
        } finally {
            lock.unlock();
        }
    }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值