【项目日记(六)】第二层: 中心缓存的具体实现(下)

简介: 【项目日记(六)】第二层: 中心缓存的具体实现(下)

1. 前言

本篇文章在上一篇文章的基础

上,对中心缓存的释放内存做补充

上一篇: 中心缓存具体实现(上)

本章重点:

本篇文章着重讲解中心缓存这一层
释放内存的全部过程,包括如何在线程
缓存中回收小块儿内存,以及如何将
自己的大块span内存还给页缓存


2. 中心缓存回收/还回内存的细节

  • 第一步: 当线程缓存中的哈希桶中小块儿内存的个数大于了该线程缓存一次性向中心缓存中申请的小块儿内存的个数,此时小块儿内存会从线程缓存中还回到中心缓存的span中

细节问题一:

从线程缓存中还回来的小块儿内存是多个,然而中心缓存的桶中可能不止一个span,我们怎么知道哪个小块儿内存对应到哪个span?很明显随意将在小块儿内存还回到任意span肯定是不对的!在上一篇文章中我们提到过如何通过指针得到这块儿空间在程序地址空间上的页号,所以我们可以使用一个unordered_map来存储页号和span的映射关系,当线程缓存还回小块儿内存时,可以通过计算小块儿内存在地址空间的页号来从这个哈希表中找到对应的span,这就是解决了我们的问题!

  • 第二步:当中心缓存中的span结构体中的成员变量:use_count等于0时,代表这个span被分配出去的小块儿内存都已经还回来了,所以此时将这个span整体还给上一级,也就是页缓存

3. 中心缓存回收内存的代码实现

根据上面的讲解,每还回来一个小块儿

内存都要检查一下它对应的span结构

的use_count是否为0,如果为0就要将

整个span结构还给页缓存!

centralcache.h文件:

void CentralCache::ReleaseListToSpans(void* start, size_t size)//一个桶中有多个span,这些内存块儿属于哪个span是不确定的
{
  size_t index = AlignmentRule::Index(size);//计算在哪个桶
  _spanlist[index]._mtx.lock();
  //要想知道这些内存块分别在哪个span,将内存块的地址除8*1024,得到这个内存块属于第几页
  while (start != nullptr)
  {
    void* next = *(void**)start;
    SpanData* span = PageCache::GetInstance()->MapObjectToSpan(start);//利用地址与span的映射函数,找到这个内存块对应的span
    *(void**)start = span->_freeList;//将内存块start头插到span中
    span->_freeList = start;
    span->_useCount--;//还回来一次内存块,就将usecount--,减到0后就又把内存还给pagecache
    if (span->_useCount == 0)//此时说明span切分出去的所有小块儿内存都被还回来了,直接将整个span还给pagecache,pagecache再进行前后页的合并
    {
      _spanlist[index].Erase(span);
      span->_freeList = nullptr;//span中的小块内存已经打乱了,但我知道起始地址和结束地址,可直接置空
      span->_next = nullptr;
      span->_prev = nullptr;
      //还回来内存时不用加桶锁了,但是pagecache的整体大锁需要加上
      _spanlist[index]._mtx.unlock();
      PageCache::GetInstance()->_mtx.lock();
      PageCache::GetInstance()->ReleaseSpanToPageCache(span);
      PageCache::GetInstance()->_mtx.unlock();
      _spanlist[index]._mtx.lock();
    }
    start = next;
  }
  _spanlist[index]._mtx.unlock();
}

注:对于代码的解释都在注释中
并且ReleaseSpanToPageCache函数
是页缓存需要实现的,这里暂时放一放


4. 对于页号与span映射的代码补充

由于这份代码是在pagecache中存放的并且页缓存还没有具体解释,所以看不懂没关系,把页缓存部分学完就都明白了.再一个,存储页号和span的映射关系的哈希表是存储在页缓存中的!因为不止在中心缓存中会使用到这种映射关系,在页缓存时同样页面临相同的问题,所以将它放在了最上层的页缓存中

pagecache.h文件中:

std::unordered_map<PAGE_ID, SpanData*> _idSpanMap;//存储页号和桶中对应的span的映射,解决换回来的内存对应哪个span的问题
//给我一个地址,返回这个地址对应的span
SpanData* PageCache::MapObjectToSpan(void* obj)
{
  PAGE_ID pageId = (PAGE_ID)obj >> PAGE_SHIFT;//将地址右移13位就算出了页号
  std::unique_lock<std::mutex> lock(_mtx);//加锁
  auto ret = _idSpanMap.find(pageId);
  if (ret != _idSpanMap.end())//若找到了对应的页号,就返回对应的span
    return _idSpanMap[pageId];
  else assert(false);//没找到,证明前面的代码有问题
  assert(ret != nullptr);
  return ret;
}

5. 总结

中心缓存这一层的所有内容已经讲解完毕,很巧妙的是,中心缓存使用的是桶锁,只有两个不同的线程同时进入到同一个桶中才会有锁竞争问题,这也是这个项目比较快的原因之一.总的来说,中心缓存的作用是承上启下,负责给线程缓存分配切分好的小块儿内存,以及从线程缓存中回收内存.并且它也会向页缓存申请大块儿内存,并且会在合适的时候将大块儿内存还回去,方便页缓存结构进行内存合并!


🔎 下期预告:页缓存的具体实现🔍


相关文章
|
1月前
|
存储 缓存 NoSQL
把ruoyi-vue前后端分离项目的redis缓存改为本地缓存cache
把ruoyi-vue前后端分离项目的redis缓存改为本地缓存cache
244 0
|
1月前
|
缓存
【项目日记(八)】第三层: 页缓存的具体实现(下)
【项目日记(八)】第三层: 页缓存的具体实现(下)
|
1月前
|
缓存
【项目日记(七)】第三层: 页缓存的具体实现(上)
【项目日记(七)】第三层: 页缓存的具体实现(上)
|
1月前
|
缓存
【项目日记(五)】第二层: 中心缓存的具体实现(上)
【项目日记(五)】第二层: 中心缓存的具体实现(上)
|
1月前
|
缓存 算法
【项目日记(四)】第一层: 线程缓存的具体实现
【项目日记(四)】第一层: 线程缓存的具体实现
|
1月前
|
缓存 NoSQL Java
spring cache整合redis实现springboot项目中的缓存功能
spring cache整合redis实现springboot项目中的缓存功能
67 1
|
15天前
|
缓存 监控 NoSQL
redis 缓存穿透 击穿 雪崩 的原因及解决方法
redis 缓存穿透 击穿 雪崩 的原因及解决方法
|
5天前
|
缓存 NoSQL Java
Java一分钟之-Spring Data Redis:使用Redis做缓存
【6月更文挑战第10天】Spring Data Redis是Spring框架的一部分,简化了Java应用与Redis的集成,支持多种数据结构操作。本文介绍了其基本使用,包括添加依赖、配置Redis连接及使用RedisTemplate。还讨论了常见问题,如序列化、缓存穿透和雪崩,并提供解决方案。通过实战示例展示了缓存与数据库读写分离的实现,强调了Spring Data Redis在提升系统性能中的作用。
29 0
|
6天前
|
存储 消息中间件 缓存
Redis:内存数据存储与缓存系统的技术探索
**Redis 概述与最佳实践** Redis,全称Remote Dictionary Server,是流行的内存数据结构存储系统,常用于数据库、缓存和消息中介。它支持字符串、哈希、列表等数据结构,并具备持久化、主从复制、集群部署及发布/订阅功能。Redis适用于缓存系统、计数器、消息队列、分布式锁和实时系统等场景。最佳实践包括选择合适的数据结构、优化缓存策略、监控调优、主从复制与集群部署以及确保安全配置。
13 3
|
9天前
|
存储 缓存 NoSQL
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质