手撕分布式缓存---HTTP Client搭建

  经过上个章节的学习,我们已经实现了一致性哈希算法,这个算法保证我们可以在节点发生变动时,最少的key请求受到影响,并返回这个节点的名称;这很大程度上避免了哈希雪崩和哈希穿透的问题。这个章节我们要基于此实现完整的服务器端在处理客户端请求时,内部如何进行选择节点,并从此节点中找到key-value


前文链接

手撕分布式缓存之一 | 定义缓存结构体与实现底层功能函数
手撕分布式缓存之二 | 互斥锁的优化
手撕分布式缓存之三 | HTTP Server搭建
手撕分布式缓存之四 | 多节点的调取策略


由于战线拉的太长了,导致后面几个章节有点失去了热情,因此就不复现代码了,采用人工理解+AI注释的方式记录

系列目录

  • (1)多节点情况下的交互
    • (1.2)原理讲解
    • (1.3)代码注释
  • (2)防止缓存击穿
    • (2.1)缓存常有的三种问题
    • (2.2)缓存击穿的解决方案
    • (2.3)代码注释
  • (3)引入Protobuf优化服务性能

(1)多节点情况下的交互

(1.2)原理讲解

  当我们有多个缓存节点时,请求key发送时应该发送给谁,例如Redis这种分布式缓存采用的方法均是:客户端发送请求时是随机发送的;接收的服务端也不一定就存有这个key-value,但他会先检查本地的缓存是否存有这个key-value,如果没有,服务器端会通过一致性hash算法计算应该去哪个节点上去查询,并去调用对应服务器端的查询接口,获取到数据后统一返回给客户端,不再让客户端去调取。

  当新的节点加入后,需要广播通知其他节点自己的存在,如果是非主从复制节点关系的情况下,由于一致性hash算法,原本需要查看节点B才可以获取到的key-value现在需要通过新增的节点A去获取,如果当时节点B的压力过大,那么可以在请求查询B查询不到时,通过节点B获取的key-value逐步的存储在节点A,以实现符合一致性hash的预期;如果当时节点B的压力并不大,那么可以直接通过计算,查询出节点B的哪些key现在会被定位到节点A,由节点B主动的将数据同步给节点A。如果是删除节点也是同理。

(1.3)代码注释

package geecacheimport ("errors""fmt""net/http""strings""github.com/gorilla/mux"
)// httpGetter 结构体实现了 PeerGetter 接口,并使用 HTTP GET 方法从指定 URL 检索数据。
//如果没有错误,则返回字节数组;否则返回错误。
type httpGetter struct {baseURL string // baseURL 是该对等机的基本 URL(协议 + IP地址 + 端口)
}func (h *httpGetter) Get(key string) ([]byte, error) {resp, err := http.Get(h.baseURL + "/cache/" + key) // 向给定的 URL 发出 GET 请求if err != nil {return nil, fmt.Errorf("HTTPPool: Error fetching %s from peer: %v", key, err)}defer resp.Body.Close()if resp.StatusCode == http.StatusNotFound {return nil, errors.New("HTTPPool: Key not found")} else if resp.StatusCode != http.StatusOK {return nil, fmt.Errorf("HTTPPool: Peer returned HTTP status code %d", resp.StatusCode)}body, err := io.ReadAll(resp.Body) // 从响应中读取数据if err != nil {return nil, fmt.Errorf("HTTPPool: Error reading response body: %v", err)}return body, nil
}// 这是一个简单的 Getter,用于检索来自对等机(通过 HTTP)的键值。如果未找到该键或发生错误,则返回错误。 
// func (h *httpGetter) Get(key string) ([]byte, error) { 
// 向给定 URL 发出 GET 请求 resp, err := http.Get(h.baseURL + "/cache/" + key)

(2)防止缓存击穿

在这里插入图片描述

(2.1)缓存常有的三种问题

  1. 缓存雪崩:缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。
  2. 缓存击穿:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。
  3. 缓存穿透:查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求 DB,如果瞬间流量过大,穿透到 DB,导致宕机。

三者的直接原因均是缓存中的key失效,请求只能通过查询DB才能够获取key-value,但是它们出现的场景和解决方式不同,这才是我们区分三者的方式。

(2.2)缓存击穿的解决方案

  缓存击穿的本质问题是:当存在一个时刻存在key失效后,有大量的key请求同时发送,且都落在了DB上。 针对这一问题,我们无法限制客户端同时发送大量key这一问题,但是我们可以限制当请求没有在缓存阶段找到key-value时,只有一个请求可以落到DB上。例如:当有大量的请求进行访问,我们可以通过互斥锁的方式进行限制,比如我们先判断如果缓存中存在key-value,那么可以直接返回结果,也不会造成缓存击穿的影响;但如果在缓存中找不到对应的key-value,那么我们可以允许第一个请求此不存在的key进行接下来的操作(DB操作),其他的请求则需要进行等待,等到唯一的一个请求处理结束之后,该对应key的互斥锁会打开,之后等待的请求直接返回结果即可。也就是说我们可以声明一个map,这个map的key是缓存中的key,map的value是一个对象,这个对象不仅可以表示当前的key是否已经有请求进行访问(是不是已经被锁定),也可以存储获取此key的第一个请求获取到的value值或异常信息。

(2.3)代码注释

type call struct {wg sync.WaitGroup // WaitGroup用于同步等待所有goroutine完成任务后再返回结果val interface{} // 保存函数fn()的返回值err error      // 保存函数fn()的错误信息
}type Group struct {mu sync.Mutex // 互斥锁,保证map操作的原子性m map[string]*call   
}func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { // 传入一个字符串类型的key和接收两个参数的函数指针fn,返回一个interface{}类型的值和error类型的错误信息g.mu.Lock()if g.m == nil { // 如果map为空则初始化mapg.m = make(map[string]*call)}if c, ok := g.m[key]; ok { // 判断是否已经有正在运行或者等待中的goroutine处理该任务g.mu.Unlock()     // 解锁以防止死锁c.wg.Wait()      // 等待任务完成后获取结果return c.val, c.err // 返回结果}c := new(call)   c.wg.Add(1)    // WaitGroup加1,表示新增一个需要等待的goroutineg.m[key] = c  // 将当前任务添加到map中,用于标记正在执行或者等待中的任务g.mu.Unlock()go func() {defer c.wg.Done() // 函数执行完成后,WaitGroup减1c.val, c.err = fn() // 执行传入的fn函数并保存值和错误信息}()g.mu.Lock()delete(g.m, key) // 从map中删除已经完成的任务g.mu.Unlock()return c.val, c.err // 返回结果
}

(3)引入Protobuf优化服务性能

简单看了下介绍,个人理解Protobuf是非常值得学习的一门技术,对于服务性能的优化有很大的作用,准备深入了解一下,然后再完善这一部分,感兴趣的同学可以留个眼,更新之后一一通知。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/286961.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker小白第六天

docker小白第六天 容器数据卷是什么 首先,容器卷有个坑:容器卷需要加入privilegedtrue,如下图所示,是为了解决permission denied的问题。其中“挂载”的意思是相当于一个硬盘插到主机上。使用该命令。是扩大容器的权限解决挂载目…

PyQt6 QSpacerItem弹簧控件

锋哥原创的PyQt6视频教程: 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计46条视频,包括:2024版 PyQt6 Python桌面开发 视频教程(无废话版…

二维码搭建意见建议反馈留言系统

传统纸质投诉建议需要用笔进行填写,反馈意见费时费力,相关单位收到留言反馈意见后也无法进行直接进行回复。 将投诉建议的记录单制作成二维码,放在公告栏等位置,用户可以扫描二维码随时随地进行反馈,常见的有评价表、投诉建议表、反馈意见表、留言表等,也就是通过扫码实现对用…

2023 英特尔On技术创新大会直播 |让更多人了解AI魅力

2023 英特尔On技术创新大会直播 |让更多人了解AI魅力 前言:主要领域:人工智能:使用 OpenVINO™ 落地边缘端生成式 AIOpenVINO™学习总结: 新一代 AI PC计算平台:新一代至强平台:边云协同:先进技术&#xff…

一款电压检测LVD

一、基本概述 The TX61C series devices are a set of three terminal low power voltage detectors implemented in CMOS technology. Each voltage detector in the series detects a particular fixed voltage ranging from 0.9V to 5.0V. The voltage detectors consist…

HTML_有哪些字体样式及使用

文章目录 🐱‍🐉一、字体样式的基本概念:🐱‍🐉二、css字体样式属性有:🤣1、设置字体类型(font-family)🤣2、设置字体大小(font-size)…

Nginx+keepalived实现高可用负载群集

实现方式 使用Nginx作为负载调度器,通过四层代理转发给web器处理请求,实现负载均衡; 在Nginx调度器上配置脚本监控(健康检查),实现主备热备份,当主失效切换至备工作。 实验 实验准备 Web 服…

中伟视界:天然气站安全隐患AI解决方案, 人工智能, 安全风险评估, 预测维护, 智能管理

近年来,随着人工智能技术的不断发展,越来越多的行业开始将人工智能应用于生产和管理中。在天然气行业,利用人工智能AI算法排除安全隐患已经成为一种新的趋势。那么,天然气站如何利用人工智能AI算法排除安全隐患呢?接下…

sqlserver-事物日志

文章目录 前言事务日志逻辑体系结构事务日志物理体系结构虚拟日志文件 (VLF)事务日志的循环性质日志截断事务日志备份事务日志支持的操作恢复个别的事务。启动事务时恢复所有未完成SQL Server事务。将还原的数据库、文件、文件组或页前滚至故障点。支持事务复制。支持高可用性和…

林杰:程序员依然是草根跨越阶级的最佳途径之一 | 程客有话说

《程客有话说》是我们最新推出的一个访谈栏目,邀请一些国内外有趣的程序员来分享他们的经验、观点与成长故事,尝试建立一个程序员交流与学习的平台,也欢迎大家推荐朋友或自己来参加我们的节目,一起加油。 本期我们邀请的程序员林…

【异常解决】MySQL数据库:Lock wait timeout exceeded; try restarting transaction问题解析及解决方案

MySQL数据库:Lock wait timeout exceeded; try restarting transaction问题解析及解决方案 一、背景描述二、原因分析三、解决方案3.1 方案一 事务信息查询3.2 方案二 如果杀掉线程依然不能解决,可以查找执行线程耗时比较久的任务,kill掉3.3 …

智能优化算法应用:基于阿基米德优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于阿基米德优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于阿基米德优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.阿基米德优化算法4.实验参数设定…