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

news2024/11/17 16:34:00

  经过上个章节的学习,我们已经实现了一致性哈希算法,这个算法保证我们可以在节点发生变动时,最少的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 geecache

import (
	"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为空则初始化map
        g.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,表示新增一个需要等待的goroutine
    g.m[key] = c  // 将当前任务添加到map中,用于标记正在执行或者等待中的任务
    g.mu.Unlock()

    go func() {
        defer c.wg.Done() // 函数执行完成后,WaitGroup减1
        c.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.coloradmin.cn/o/1322952.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

tensorflow入门

一、怎样入手TensorFlow TensorFlow是一个用于机器学习和深度学习的开源框架,它提供了一种灵活的方式来构建和训练神经网络模型。以下是一些TensorFlow框架入门的建议: 学习Python语言:TensorFlow主要使用Python语言进行开发,因此…

自动驾驶技术入门平台分享:百度Apollo开放平台9.0全方位升级

目录 平台全方位的升级 全新的架构 工具服务 应用软件(场景应用) 软件核心 硬件设备 更强的算法能力 9.0版本算法升级总结 更易用的工程框架 Apollo开放平台9.0版本的技术升级为开发者提供了许多显著的好处,特别是对于深度开发需求…

【Java】工业园区高精准UWB定位系统源码

UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。UWB定位系统依托在移动通信,雷达,微波电路,云计算与大数据…

Vim命令大全(超详细,适合反复阅读学习)

Vim命令大全 Vim简介Vim中的模式光标移动命令滚屏与跳转文本插入操作文本删除操作文本复制、剪切与粘贴文本的修改与替换文本的查找与替换撤销修改、重做与保存编辑多个文件标签页与折叠栏多窗口操作总结 Vim是一款文本编辑器,是Vi编辑器的增强版。Vim的特点是快速、…

Node.js使用Express框架写服务端接口时,如何将接口拆分到不同文件中

项目目录结构说明: node.js连接mysql数据库步骤可参考:Node.js 连接 MySQL | 菜鸟教程 1、拆分之前的写法,未区分模块,所有接口api都写在了入口文件app.js中; 需求:想要将接口api拆分成根据不同的业务模块…

导致OpenAI内乱的罪魁祸首,背后的技术是什么?

前几天围绕Sam 和 Greg和OpenAI board之间的爱恨情仇,我觉得比乡村爱情15还有意思,也达到了美剧多年未有的高度,反转反转再反转。 围绕争端的根本原因,那也是众说纷纭,不过其实有一条新闻我觉得挺值得玩味的,也是我所相信的,就是Sam在OpenAI day上发布了一个叫GP…

C++设计模式——装饰器模式

装饰器设计模式 概念应用场景优点示例示例一代码实现运行结果 示例二代码实现运行结果 示例三实现代码运行结果 总结 概念 装饰器设计模式,是C设计模式中的一种。它是一种结构型设计模式,允许向现有对象动态地添加新功能,同时又不改变其结构…

使用Python将OSS文件免费下载到本地:第一步 列举OSS文件

大家好,我是水滴~~ 本文将介绍了使用的知识点、以及列举OSS文件的代码、并对该代码进行详细解析、最后给出部署方案,希望能对你有所帮助! 《Python入门核心技术》专栏总目录・点这里 文章目录 1. 本文知识点1.1 datetime 模块1.2 OSS Python…

PyQt5连接mysql失败解决

一:背景 最近研究一个项目,里面用的Pyqt5编写的桌面应用,跑了下源码发现连接数据库那块出来问题,最终调试发现里面用的QtSql去连接mysql提示驱动找不到。 具体报错信息如下: Could not parse stylesheet of object …

使用VBA字典,进行数据分类汇总

使用VBA字典,进行数据分类汇总 VBA的字典共有两列,第一列是key,不允许有重复的元素;第二列是item,也就是key对应的值,item的值是可以有重复的值的。 字典的主要操作有读和写。 写操作 d(key)item&#…

直流电、交流电和发电机、接地、变压器

直流电 此节内容主要摘录自:图文详解直流电与直流电路基本知识 直流电是指电流方向不随时间作周期性变化,由正极流向负极,但电流的大小可能会变化的电流。直流电可以分为稳定(恒定)直流和脉动直流两种,如下…

迪文屏开发保姆级教程—背景图ICL文件生成

本篇文章主要介绍了在DGBUS平台上生成页面背景图片库,32xx.ICL文件的方法。 文章目录 一、前言 开发环境 二、具体步骤 1.打开软件 2.选定参数 3.导入背景图片 4.然后点击生成,​编辑 三、容易踩得坑 一、前言 本篇文章主要介绍了在DGBUS平台上生…

SQLturning:定位连续值范围起点和终点

在上一篇blog说到,如何去优化查询连续值范围,没看过的朋友,上篇blog链接[在此]。(https://blog.csdn.net/weixin_42575078/article/details/135067645?spm1001.2014.3001.5501) 那么今天来说说怎么将连续的数据合并,然后返回合并…

红米k40刷机澎湃OS

红米k40线刷澎湃OS,MIUI→HyperOS 1.0.23.12.9 博主自己也是个小白 rom包免费获取与体验请关注公众号:YouLinw的ROM日常 资料备份 使用小米自带的打包备份工具,将文件备份到电脑上 或使用小米自带的云服务功能,我开了会员。换了…

【数学建模】《实战数学建模:例题与讲解》第十三讲-相关分析(含Matlab代码)

【数学建模】《实战数学建模:例题与讲解》第十三讲-相关分析(含Matlab代码) 基本概念典型相关分析综合评价模型对应分析因子分析聚类分析 习题10.41. 题目要求2.解题过程3.程序 习题10.51. 题目要求2.解题过程3.程序 习题10.6(1&a…

C : DS二叉排序树之删除(详细思路解答)

Description 给出一个数据序列,建立二叉排序树,并实现删除功能 对二叉排序树进行中序遍历,可以得到有序的数据序列 Input 第一行输入t,表示有t个数据序列 第二行输入n,表示首个序列包含n个数据 第三行输入n个数据…

《PySpark大数据分析实战》-15.云服务模式Databricks介绍创建集群

📋 博主简介 💖 作者简介:大家好,我是wux_labs。😜 热衷于各种主流技术,热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员(PCTA)、TiDB数据库专家(PCTP…

FastAPI访问/docs接口文档显示空白、js/css无法加载

如图: 原因是FastAPI的接口文档默认使用https://cdn.jsdelivr.net/npm/swagger-ui-dist5.9.0/swagger-ui.css 和https://cdn.jsdelivr.net/npm/swagger-ui-dist5.9.0/swagger-ui-bundle.js 来渲染页面,而这两个URL是外网的CDN,在国内响应超…

云仓酒庄的品牌雷盛红酒LEESON分享红酒存放几年质量最佳?

云仓酒庄的品牌雷盛红酒LEESON分享对于酒的看法,有人认为“酒是陈的香”,酒越老越好。不过对于葡萄酒来说,这种说法不完全对,如果一款葡萄酒等待的时间太久,未必是件好事。对待葡萄酒也要把握一个“度”,既…

STM32启动过程

STM32启动模式(自举模式) M3/3/7等内核,复位后做的第一件事: 从地址0x0000 0000处取出栈指针MSP的初始值,该值就是栈顶地址。从地址0x0000 0004处取出程序计数器指针PC的初始值,该值是复位向量。 芯片厂商…