7天用Go从零实现分布式缓存GeeCache(改进)(未完待续)

news/2024/11/14 5:53:12/

lru包

好的,我来为您完整地解说这段代码,指出其中的问题并给出改进方案。

代码分析:

您提供的 Add 方法用于将一个键值对添加到缓存中,或者更新已有的键值对。代码如下:

// Add 将一个值添加到缓存中。
func (c *Cache) Add(key string, value Value) {if ele, ok := c.cache[key]; ok {c.ll.MoveToFront(ele)kv := ele.Value.(*entry)c.nbytes += int64(value.Len()) - int64(kv.value.Len())kv.value = value} else {ele := c.ll.PushFront(&entry{key, value})c.cache[key] = elec.nbytes += int64(len(key)) + int64(value.Len())}for c.maxBytes != 0 && c.maxBytes < c.nbytes {c.RemoveOldest()}
}

存在的问题:

  1. 无限循环的风险:

    • 当添加或更新的缓存项非常大,其大小超过了 maxBytes,并且此时缓存为空(c.ll.Len() == 0)时,RemoveOldest() 方法无法移除任何项,因为缓存中没有项可移除。
    • 此时,c.nbytes 始终大于 c.maxBytes,循环条件 c.maxBytes != 0 && c.maxBytes < c.nbytes 一直为真,导致陷入无限循环。
  2. 更新操作可能导致缓存超限:

    • 当更新已有的缓存项时,如果新值比旧值大,可能会导致 c.nbytes 超过 maxBytes,需要移除旧的缓存项来腾出空间。

改进方案:

为了解决上述问题,需要在循环条件中增加对缓存长度的判断,避免在缓存为空时进入无限循环。修改后的循环条件如下:

for c.maxBytes != 0 && c.nbytes > c.maxBytes && c.ll.Len() > 0 {c.RemoveOldest()
}

改进后的代码:

// Add 将一个值添加到缓存中。
func (c *Cache) Add(key string, value Value) {if ele, ok := c.cache[key]; ok {// 更新已有的缓存c.ll.MoveToFront(ele)kv := ele.Value.(*entry)delta := int64(value.Len()) - int64(kv.value.Len())c.nbytes += deltakv.value = value} else {// 添加新的缓存ele := c.ll.PushFront(&entry{key, value})c.cache[key] = elec.nbytes += int64(len(key)) + int64(value.Len())}// 检查并移除超出大小限制的缓存for c.maxBytes != 0 && c.nbytes > c.maxBytes && c.ll.Len() > 0 {c.RemoveOldest()}
}

详细解说:

  1. 循环条件的修改:

    • 增加 c.ll.Len() > 0 判断: 防止在缓存为空时进入无限循环。当缓存中没有任何项可供移除时,循环会终止,避免程序卡死。

    • 调整比较符号为 >c.maxBytes < c.nbytes 修改为 c.nbytes > c.maxBytes,使逻辑更直观,表示当前缓存大小超过了最大限制。

  2. 更新操作的处理:

    • 正确计算缓存大小的变化: 在更新已有缓存项时,使用临时变量 delta 计算新旧值大小的差异,并更新 c.nbytes。这有助于避免计算错误,提高代码可读性。

    • 可能需要移除项: 如果更新后的缓存大小超过了 maxBytes,上述循环会自动移除最久未使用的缓存项,直到缓存大小符合限制。

  3. 添加新项的处理:

    • 在添加新项时,计算并增加 c.nbytes,包括键和值的大小。

额外建议:

  • 处理单个缓存项超过最大限制的情况:

    • 如果添加的单个缓存项大小就已经超过了 maxBytes,可以选择:

      • 拒绝添加该项: 直接返回,不将其添加到缓存中。

      • 特殊处理: 临时允许缓存大小超过限制,或者调整 maxBytes

  • 线程安全性:

    • 如果缓存会被多个 goroutine 同时访问,建议使用 sync.Mutexsync.RWMutex 来保护对缓存的读写操作,确保线程安全。
  • 完善 RemoveOldest() 方法:

    • 确保 RemoveOldest() 方法在缓存为空时能够安全地处理,不会导致程序崩溃。
  • 日志和监控:

    • 在移除缓存项或遇到异常情况时,记录日志,方便调试和监控缓存的运行状态。

http://www.ppmy.cn/news/1546853.html

相关文章

Prometheus面试内容整理-Prometheus 的架构和工作原理

Prometheus 的架构设计基于分布式系统中的监控需求,能够高效地收集、存储和查询时间序列数据。它采用拉取(pull)模型、自动服务发现、数据持久化存储等方式来满足现代系统的监控和告警需求。 Prometheus 的架构 Prometheus 的架构包含多个核心组件,各自负责不同的功能模块,…

【大语言模型学习】LORA微调方法

LORA: Low-Rank Adaptation of Large Language Models 摘要 LoRA (Low-Rank Adaptation) 提出了一种高效的语言模型适应方法,针对预训练模型的适配问题: 目标:减少下游任务所需的可训练参数,降低硬件要求。方法:冻结预训练模型权重,注入低秩分解矩阵,从而在不影响推理…

微服务电商平台课程三:搭建后台服务

前言 上节课,我们一起完成基础环境搭建,这节课, 我们利用上节课搭建我们电商平台.这节课我们采用开源代码进行搭建, 不论大家后续从事什么行业,都要学会站在巨人的肩膀上. 之前所说的,整个微服务平台的技术栈也是非常多的, 由于时间和效果的关系, 我们不可能从每个技术一步一…

MyBatisPlus 用法详解

MyBatisPlus 用法详解 MyBatis-Plus&#xff08;简称MP&#xff09;是一个MyBatis的增强工具&#xff0c;在MyBatis的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。它提供了丰富的功能&#xff0c;包括强大的CRUD操作、条件构造器、自动填充、分页插件等&…

vueRouter路由切换时实现页面子元素动画效果, 左右两侧滑入滑出效果

说明 vue路由切换时&#xff0c;当前页面左侧和右侧容器分别从两侧滑出&#xff0c;新页面左右分别从两侧滑入 效果展示 路由切换-滑入滑出效果 难点和踩坑 现路由和新路由始终存在一个页面根容器&#xff0c;通过<transition>组件&#xff0c;效果只能对页面根容器有效…

docker overlay磁盘空间过高的处理方案

近期&#xff0c;在运维服务器时&#xff0c;时常会发现/var/lib/docker/overlay2下的磁盘空间不足&#xff0c;先记录一下排查思路与清理方案。 一、清理images 查看images和container占用信息。 docker system df 如果是images占用较高&#xff0c;可考虑使用以下命令清理…

第十三天 概率论与统计学

概率论与统计学是两个紧密相连但又有所区别的数学领域。以下是对这两个领域的详细解释&#xff1a; 一、概率论 概率论是一门研究随机现象的数学学科&#xff0c;它有一套公理化的纯数学理论&#xff0c;具有严格的公理基础。概率论起源于文艺复兴时期的赌博活动和棋盘游戏&a…

如何绕过Captcha并使用OCR技术抓取数据

背景/引言 在现代的网页数据抓取中&#xff0c;Captcha&#xff08;全自动区分计算机和人类的图灵测试&#xff09;作为一种防止爬虫和恶意访问的有效措施&#xff0c;广泛应用于各种网站。Captcha的主要目的是区分用户是人类还是程序&#xff0c;因此对于爬虫技术来说&#x…