7天用Go从零实现分布式缓存GeeCache(学习)

参考资料

前置知识

在 Go 的 HTTP 服务器开发中,ServeHTTP 方法的参数 w http.ResponseWriterr *http.Request 用于处理 HTTP 请求和构建响应。以下是它们的详细解释:

1. w http.ResponseWriter

  • w 是一个 http.ResponseWriter 类型,用于构建并发送 HTTP 响应。
  • 它提供了一组方法,用于向客户端写入响应数据或设置 HTTP 响应头。
  • 常见的操作包括:
    • 写入响应内容:使用 w.Write([]byte("response content")) 将字节内容写入响应。
    • 设置响应状态码:使用 w.WriteHeader(http.StatusCode),例如 w.WriteHeader(http.StatusNotFound) 设置状态为 404。
    • 设置响应头:通过 w.Header().Set("Content-Type", "application/json") 设置响应头,比如设置 Content-Typeapplication/json

2. r *http.Request

  • r 是一个指向 http.Request 的指针,包含了关于 HTTP 请求的所有信息。
  • http.Request 是一个结构体,包含许多字段和方法,帮助开发者获取请求的详细信息。
  • 常用字段和方法包括:
    • r.Method:获取请求方法,例如 GETPOST 等。
    • r.URL:包含请求的 URL 和路径信息,如 r.URL.Path 可以获取请求的路径部分。
    • r.Header:包含请求头信息,可以通过 r.Header.Get("Header-Name") 获取指定的请求头。
    • r.Body:包含请求的主体数据(通常用于 POST 请求),可以通过 io.ReadAll(r.Body) 读取内容。
    • r.Form:包含解析后的表单数据,适用于 POST 表单提交的数据。

示例说明

func (h *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {// 打印请求的路径log.Println("Request URL Path:", r.URL.Path)// 设置响应头w.Header().Set("Content-Type", "text/plain")// 写入响应内容w.Write([]byte("Hello World!"))
}

在这个示例中:

  • r.URL.Path 获取请求路径并打印出来。
  • 使用 w.Header().Set 设置 Content-Type 响应头为 text/plain
  • w.Write([]byte("Hello World!")) 向客户端写入响应内容 Hello World!

这样一来,w http.ResponseWriterr *http.Request 就构成了一个完整的 HTTP 请求-响应流程,服务器能够根据请求内容生成并返回相应的响应。

http.ListenAndServeServeHTTP 的隐式调用

http.ListenAndServe("localhost:9999", &s) 中,ServeHTTP 的调用是由 Go 的 HTTP 服务器框架隐式完成的。虽然代码中没有直接调用 ServeHTTP,但当服务器接收到 HTTP 请求时,Go 的 HTTP 服务器会自动调用 ServeHTTP 方法来处理该请求。这是通过接口机制实现的。以下是具体的过程:

1. http.Handler 接口

  • Go 的 net/http 包定义了一个 http.Handler 接口,其中只有一个方法:

    type Handler interface {ServeHTTP(ResponseWriter, *Request)
    }
    
    • 任何实现了 ServeHTTP 方法的类型都满足 http.Handler 接口。
    • 在这个例子中,server 类型实现了 ServeHTTP 方法,因此 server 类型满足 http.Handler 接口。

2. ListenAndServe 函数

  • http.ListenAndServe 函数的签名如下:

    func ListenAndServe(addr string, handler Handler) error
    
    • 第二个参数是一个 Handler 接口(即 http.Handler)。
    • 传递给 ListenAndServe&sserver 类型的一个指针,而 server 类型实现了 ServeHTTP 方法,因此 &s 是一个有效的 http.Handler 实例。

3. 隐式调用 ServeHTTP

  • ListenAndServe 启动服务器并监听 localhost:9999 后,每当收到一个 HTTP 请求,它会检查传入的 handler(即 &s)并调用其 ServeHTTP 方法。
  • 这个调用过程是 Go 的 HTTP 服务器框架自动处理的,因此不需要在代码中显式调用 ServeHTTP

运行流程概述

  1. http.ListenAndServe("localhost:9999", &s) 启动服务器并监听端口 9999
  2. 当收到一个请求时,服务器会自动调用 &sServeHTTP 方法来处理该请求。
  3. ServeHTTP 中,使用 wr 参数构建响应。

因此,ServeHTTP 的调用是 http.ListenAndServe 函数在处理请求时自动完成的。

http 标准库

Go 语言提供了 http 标准库,可以方便地搭建 HTTP 服务端和客户端。

实现服务端

type server intfunc (h *server) ServeHTTP(w http.ResponseWriter, r *http.Request){log.Println(r.URL.Path)w.Write([]byte("Hello World!"))
}func main(){var s serverhttp.ListenAndServe("localhost:9999", &s)
}
  • ServeHTTPhttp.Handler 接口的一个方法,用于处理 HTTP 请求。
  • 参数 w http.ResponseWriter:用于构造 HTTP 响应。
  • 参数 r *http.Request:包含了 HTTP 请求的信息。
  • log.Println(r.URL.Path):将请求的 URL 路径记录到控制台。
  • w.Write([]byte("Hello World!")):向客户端返回一个简单的 Hello World! 字符串作为响应内容。

http.ListenAndServe 接收 2 个参数,第一个参数是服务启动的地址,第二个参数是 Handler,任何实现了 ServeHTTP 方法的对象都可以作为 HTTP 的 Handler。

GeeCache HTTP服务器

分布式缓存需要实现节点间通信,==建立基于 HTTP 的通信机制是比较常见和简单的做法。如果一个节点启动了 HTTP 服务,那么这个节点就可以被其他节点访问。==今天我们就为单机节点搭建 HTTP Server。

首先我们创建一个结构体 HTTPPool,作为承载节点间 HTTP 通信的核心数据结构(包括服务端和客户端,今天只实现服务端)。

const defaultBasePath = "/_geecache/"type HTTPPool struct{self stringbasePath string
}func NewHTTPPool(self string) *HTTPPool{return &HTTPPool{self: self,basePath: defaultBasePath,}
}
  • HTTPPool 只有 2 个参数,一个是 self,用来记录自己的地址,包括主机名/IP 和端口。
  • 另一个是 basePath,作为节点间通讯地址的前缀,默认是 /_geecache/,那么 http://example.com/_geecache/ 开头的请求,就用于节点间的访问。因为一个主机上还可能承载其他的服务,加一段 Path 是一个好习惯。比如,大部分网站的 API 接口,一般以 /api 作为前缀。
func (p *HTTPPool) Log(format string, v ...interface{}){// 自定义日志方法,使用特定的格式来输出日志信息// fmt.Sprintf(format, v...) 格式化输出,将所有参数 v 格式化成字符串log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {// 检查请求的路径是否以 basePath 开头,确保请求路径正确if !strings.HasPrefix(r.URL.Path, p.basePath) {panic("HTTPPool serving unexpected path: " + r.URL.Path)  // 如果路径不匹配,触发 panic}// 使用自定义的 Log 方法记录请求方法和路径p.Log("%s %s", r.Method, r.URL.Path)// 将路径去除 basePath 前缀部分后按 '/' 分割为两部分,以获取 <groupname> 和 <key>// 格式要求为 /<basepath>/<groupname>/<key>parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)if len(parts) != 2 {// 如果分割后的部分长度不为 2,说明格式不正确,返回 400 错误http.Error(w, "bad request", http.StatusBadRequest)return}// 提取分割后的第一个部分作为 groupName,第二个部分作为 keygroupName := parts[0]key := parts[1]// 获取 group 对象,使用 groupName 在缓存中查找对应的 Groupgroup := GetGroup(groupName)if group == nil {// 如果没有找到对应的 group,返回 404 错误http.Error(w, "no such group: " + groupName, http.StatusNotFound)return}// 尝试在 group 中查找对应的 key 值,获取缓存数据view, err := group.Get(key)if err != nil {// 如果查找过程中出现错误,返回 500 错误http.Error(w, err.Error(), http.StatusInternalServerError)return}// 设置响应头的内容类型为 "application/octet-stream"// 这表示响应的内容是二进制数据w.Header().Set("Content-Type", "application/octet-stream")// 将缓存的内容写入响应体,发送给客户端w.Write(view.ByteSlice())
}
  • ServeHTTP 的实现逻辑是比较简单的,首先判断访问路径的前缀是否是 basePath,不是返回错误。
  • 我们约定访问路径格式为 /<basepath>/<groupname>/<key>,通过 groupname 得到 group 实例,再使用 group.Get(key) 获取缓存数据。
  • 最终使用 w.Write() 将缓存值作为 httpResponse 的 body 返回。

到这里,HTTP 服务端已经完整地实现了。接下来,我们将在单机上启动 HTTP 服务,使用 curl 进行测试。

package geecacheimport ("fmt""log""net/http""strings"
)const defaultBasePath = "/_geecache/"// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {// this peer's base URL, e.g. "https://example.net:8000"self     stringbasePath string
}// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {return &HTTPPool{self:     self,basePath: defaultBasePath,}
}// Log info with server name
func (p *HTTPPool) Log(format string, v ...interface{}) {log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}// ServeHTTP handle all http requests
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {if !strings.HasPrefix(r.URL.Path, p.basePath) {panic("HTTPPool serving unexpected path: " + r.URL.Path)}p.Log("%s %s", r.Method, r.URL.Path)// /<basepath>/<groupname>/<key> requiredparts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)if len(parts) != 2 {http.Error(w, "bad request", http.StatusBadRequest)return}groupName := parts[0]key := parts[1]group := GetGroup(groupName)if group == nil {http.Error(w, "no such group: "+groupName, http.StatusNotFound)return}view, err := group.Get(key)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}w.Header().Set("Content-Type", "application/octet-stream")w.Write(view.ByteSlice())
}

1. 常量定义

const defaultBasePath = "/_geecache/"
  • 定义了 defaultBasePath 常量,表示缓存服务的默认路径前缀,所有请求都应以该路径前缀开头(如 /_geecache/)。

2. 结构体定义:HTTPPool

type HTTPPool struct {self     stringbasePath string
}
  • HTTPPool 是一个结构体,代表一组 HTTP 节点组成的缓存池。它实现了 PeerPicker 接口,可以在集群中选择适当的节点。
  • 字段解释:
    • self string:表示当前节点的基本 URL,例如 https://example.net:8000
    • basePath string:当前节点路径的前缀。默认是 defaultBasePath,用于区分普通的 HTTP 请求和缓存服务的请求。

3. 构造函数:NewHTTPPool

func NewHTTPPool(self string) *HTTPPool {return &HTTPPool{self:     self,basePath: defaultBasePath,}
}
  • NewHTTPPool 是一个构造函数,用于初始化一个 HTTPPool 实例。
  • 接收 self string 参数作为当前节点的 URL,并将默认路径前缀 defaultBasePath 赋给 basePath
  • 返回值类型为指针 *HTTPPool,可以有效避免数据拷贝,便于共享该实例。

4. 方法:Log

func (p *HTTPPool) Log(format string, v ...interface{}) {log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}
  • Log 是一个日志方法,用于记录日志信息,便于调试。
  • 接收 format string 和可变参数 v ...interface{},通过 fmt.Sprintf 格式化后输出到日志中。
  • 日志信息前包含 [Server <self>],用来标记日志属于哪个节点,方便排查分布式系统中的问题。

5. 方法:ServeHTTP

func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {// 检查请求路径是否符合预期格式if !strings.HasPrefix(r.URL.Path, p.basePath) {panic("HTTPPool serving unexpected path: " + r.URL.Path)}p.Log("%s %s", r.Method, r.URL.Path)// 解析路径为 <groupname>/<key>parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)if len(parts) != 2 {http.Error(w, "bad request", http.StatusBadRequest)return}groupName := parts[0]key := parts[1]group := GetGroup(groupName)if group == nil {http.Error(w, "no such group: "+groupName, http.StatusNotFound)return}view, err := group.Get(key)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}w.Header().Set("Content-Type", "application/octet-stream")w.Write(view.ByteSlice())
}
  • ServeHTTP 方法用于处理所有 HTTP 请求。它使 HTTPPool 结构体实现了 http.Handler 接口,这样它可以作为 HTTP 处理器。
方法流程
  1. 路径验证

    • 检查请求路径是否以 basePath 开头。如果不符合,则触发 panic,说明请求路径异常。
  2. 日志记录

    • 调用 p.Log 记录请求的 HTTP 方法和路径。
  3. 路径解析

    • 去除 basePath 前缀后,将路径按 / 分割成两部分,期望格式为 <groupname>/<key>
    • 如果解析失败(如路径格式不正确),返回 400 Bad Request 错误。
  4. 缓存分组获取

    • 使用 GetGroup(groupName) 获取指定的缓存分组 group
    • 如果找不到对应的缓存分组,返回 404 Not Found 错误。
  5. 获取缓存数据

    • 调用 group.Get(key) 获取指定 key 的缓存数据 view
    • 如果获取数据时出现错误,返回 500 Internal Server Error
  6. 返回数据

    • 设置响应头 Content-Typeapplication/octet-stream 表示二进制数据。
    • 将缓存数据写入响应体,返回给客户端。

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

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

相关文章

[HarmonyOS]简单说一下鸿蒙架构

鸿蒙操作系统&#xff08;HarmonyOS&#xff09;是由华为公司开发的一款面向全场景的操作分布式系统。它旨在提供一个统一的操作系统平台&#xff0c;支持多种设备&#xff0c;包括智能手机、平板电脑、智能电视、可穿戴设备、智能家居等。鸿蒙架构的设计目标是实现设备之间的无…

centos7.9安装mysql5.7完整版

centos7.9安装mysql5.7完整版 1. 更新yum源 [rootlocalhost ~]# cd /etc/yum.repos.d/ [rootlocalhost yum.repos.d]# ls -lh #备份镜像源 [rootlocalhost yum.repos.d]# mv CentOS-Base.repo CentOS-Base.repo.backup #下载阿里云centos7镜像 [rootlocalhost yum.repos.d]# …

2024年AI办公工具API:高效办公的智能选择

在2024年&#xff0c;AI技术已经深入到我们工作生活的方方面面&#xff0c;极大地提高了办公效率和质量。这些工具通过集成先进的算法和模型&#xff0c;使得日常任务自动化、数据分析智能化、内容创作高效化。以下是2024年最受欢迎的AI办公工具API&#xff0c;它们正在重新定义…

[UnLua]动态创建SceneCapture2d相机,并且添加渲染目标纹理

在 Unlua 开发中&#xff0c;相机相关的操作是构建场景视觉效果的重要部分。以下我们来详细分析一段涉及相机实例化和为相机赋予纹理目标的 Unlua 代码。 -- 实例化相机local World self:GetWorld()maskCamera World:SpawnActor(UE.ASceneCapture2D)-- 给相机赋值纹理目标lo…

力扣 LeetCode 142. 环形链表II(Day2:链表)

解题思路&#xff1a; 使用set判断是否重复添加&#xff0c;如果set加入不进去证明之前到达过该节点&#xff0c;有环 public class Solution {public ListNode detectCycle(ListNode head) {Set<ListNode> set new HashSet<>();ListNode cur head;while (cur …

基于Spring Boot的电子商务系统设计

5 系统实现 系统实现部分就是将系统分析&#xff0c;系统设计部分的内容通过编码进行功能实现&#xff0c;以一个实际应用系统的形式展示系统分析与系统设计的结果。前面提到的系统分析&#xff0c;系统设计最主要还是进行功能&#xff0c;系统操作逻辑的设计&#xff0c;也包括…

数据驱动的投资分析:民锋科技的量化模型探索

在全球金融市场中&#xff0c;数据驱动的投资分析正在变革传统投资方式。民锋科技通过精密的量化模型和智能算法&#xff0c;为投资者提供更加科学的市场预测和投资分析工具&#xff0c;以帮助他们更好地理解市场波动、优化投资组合&#xff0c;实现风险管理。 #### 一、数据驱…

论文阅读《BEVFormer v2》

BEVFormer v2: Adapting Modern Image Backbones to Bird’s-Eye-View Recognition via Perspective Supervision 目录 摘要1 介绍2 相关工作2.1 BEV三维目标检测器 摘要 我们提出了一种具有透视监督的新型鸟瞰图&#xff08;BEV&#xff09;检测器&#xff0c;其收敛速度更快…

使用 Vue 配合豆包MarsCode 实现“小恐龙酷跑“小游戏

作者&#xff1a;BLACK595 “小恐龙酷跑”&#xff0c;它是一款有趣的离线游戏&#xff0c;是Google给Chrome浏览器加的一个有趣的彩蛋。当我们浏览器断网时一只像素小恐龙便会出来提示断网。许多人认为这只是一个可爱的小图标&#xff0c; 但当我们按下空格后&#xff0c;小恐…

Attention is all you need详细解读

transformer目的是解决NLP中使用RNN不能并行计算问题。 Encoder-Decoder, Attention, Transformer 三者关系 1.基础储备 &#xff08;1&#xff09;CV基础知识 1.首先拿CV中的分类任务来说&#xff0c;训练前我们会有以下几个常见步骤&#xff1a; 2.获取图片 3.定义待分类的类…

面向对象的需求分析和设计(一)

[toc] 1. 引言 前一篇文章《我对需求分析的理解》提到了面向对象分析和设计&#xff0c;正好最近又重新有重点的读了谭云杰著的《Think in UML》&#xff0c;感觉有必要写把书中一些核心内容观点以及自己的想法整理出来&#xff0c;一是方便自己日后的复习&#xff0c;另外也…

Window下PHP安装最新sg11(php5.3-php8.3)

链接: https://pan.baidu.com/s/10yyqTJdwH_oQJnQtWcwIeA 提取码: qz8y 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 (链接失效联系L88467872) 1.下载后解压文件&#xff0c;将对应版本的ixed.xx.win文件放进php对应的ext目录下&#xff0c;如图所示 2.修改ph…

基于Spring Boot与Redis的令牌主动失效机制实现

目录 前言1. 项目结构和依赖配置1.1 项目依赖配置1.2 Redis连接配置 2. 令牌主动失效机制的实现流程2.1 登录成功后将令牌存储到Redis中2.2 使用拦截器验证令牌2.3 用户修改密码后删除旧令牌 3. Redis的配置与测试4. 可能的扩展与优化结语 前言 在现代Web系统中&#xff0c;用…

sql专题 之 sql的执行顺序

文章目录 sql的执行顺序sql语句的格式实际的执行顺序&#xff1a;虚拟表 vs 数据集虚拟表 结果集总结嵌套查询在sql查询中的执行顺序 前文我们了解了sql常用的语句&#xff0c;这次我们对于这些语句来个小思索 戳这里→ sql专题 之 常用命令 sql的执行顺序 SQL语句的执行顺序是…

Linux学习笔记之组管理和权限管理

组管理 文件/目录 所有者 一般文件所有者是文件的创建者&#xff0c;谁创建了该文件&#xff0c;就自然成为该文件的所有者 ls -ahl &#xff08;查看文件的所有者&#xff09; chown 用户名 文件名 &#xff08;修改文件所有者&#xff09; 文件/目录 所在组 当某个用户…

『黄河遗韵』数字非遗馆的守护之旅

创意定位 腾讯云推出的以“守护黄河文明&#xff0c;探索数字非遗馆”为主题的创意H5作品&#xff0c;通过长卷寻宝VR展馆形式&#xff0c;描绘了黄河文明中的非物质文化遗产&#xff0c;旨在唤起公众对黄河文明的保护意识&#xff0c;并邀请大家参与互动&#xff0c;深入了解…

前端监控与埋点 全总结

一、概念 前端埋点是指在网页或者应用程序中插入特定的代码&#xff0c;用于收集用户的行为数据并发送给服务器进行分析。这些数据可以包括用户的点击、浏览、输入等操作&#xff0c;帮助开发者了解用户的在其网站中的行为&#xff0c;从而进行针对性的优化和改进。 前端埋点…

2411d,右值与移动

原文 概述 添加语言内部__rvalue(Expression)函数,指示对匹配函数参数,按右值对待式.这在用非引用语义调用函数时启用移动语义. 移动语义对运行时和资源效率是可取的,因为可移动资源到新对象,而不是复制然后析构.其他语言(如C)有流行的移动语义. 先前的工作 C移动语义这里…

全面介绍软件安全测试分类,安全测试方法、安全防护技术、安全测试流程

一、软件系统设计开发运行安全 1、注重OpenSource组件安全检查和版本更新&#xff08;black duck&#xff09; 现在很多云、云服务器都是由开源的组件去搭成的&#xff0c;对于OpenSource组件应该去做一些安全检查和版本更新&#xff0c;尤其是版本管理&#xff0c;定期对在运…

十、文件上传和下载

文章目录 1. 文件下载2. 文件上传 1. 文件下载 使用ResponseEntity实现下载文件的功能 2. 文件上传