golang分布式缓存项目 Day3 HTTP服务端

注:该项目原作者:https://geektutu.com/post/geecache-day1.html。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习。

HTTP基础

go 复制代码
package main

import (
	"log"
	"net/http"
)

type server int

func (h *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println(r.URL.Path)
	w.Write([]byte("Hello World!"))
}

func main() {
	var s server
	http.ListenAndServe("localhost:9999", &s)
}
  • 创建任意类型 server,并实现 ServeHTTP 方法。
  • 调用 http.ListenAndServe 在 9999 端口启动 http 服务,处理请求的对象为 s server。

在网页:http://localhost:9999 中会显示"hello world"

在标准库中,http.Handler 接口的定义如下:

go 复制代码
package http

type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

ListenAndServe 方法里面会去自动调用 handler.ServeHTTP() 方法

(在ListenAndServe 方法中传入Handler接口就会自动实现其ServeHTTP方法)

例如:

go 复制代码
package main

import (
	"fmt"
	"log"
	"net/http"
)

// Engine is the uni handler for all requests
type Engine struct{}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	switch req.URL.Path {
	case "/":
		fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
	case "/hello":
		for k, v := range req.Header {
			fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
		}
	default:
		fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
	}
}

func main() {
	engine := new(Engine)
	log.Fatal(http.ListenAndServe(":9999", engine))
}

GeeCache HTTP服务端

代码结构

go 复制代码
package geecache

import (
	"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     string
	basePath string
}

// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {
	return &HTTPPool{
		self:     self,
		basePath: defaultBasePath,
	}
}
  • HTTPPool 只有 2 个参数,一个是 self,用来记录自己的地址,包括主机名/IP 和端口。
  • 另一个是 basePath,作为节点间通讯地址的前缀,默认是 /_geecache/,那么 http://example.com/_geecache/ 开头的请求,就用于节点间的访问。因为一个主机上还可能承载其他的服务,加一段 Path 是一个好习惯。比如,大部分网站的 API 接口,一般以 /api 作为前缀。

接下来,实现最为核心的 ServeHTTP 方法。

go 复制代码
// 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> required
	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 的实现逻辑是比较简单的,首先判断访问路径的前缀是否是 basePath,不是返回错误。
  • 我们约定访问路径格式为 /basepath/groupname/key,通过 groupname 得到 group 实例,再使用 group.Get(key) 获取缓存数据。
  • 最终使用 w.Write() 将缓存值作为 httpResponse 的 body 返回。

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

测试

HTTP服务只能在main package中的main函数启动,不能在其它包中的main函数启动!

go 复制代码
package main

import (
	"fmt"
	"geecache"
	"log"
	"net/http"
)

var db = map[string]string{
	"Tom":  "630",
	"Jack": "589",
	"Sam":  "567",
}

func main() {
	geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
		func(key string) ([]byte, error) {
			log.Println("[SlowDB] search key", key)
			if v, ok := db[key]; ok {
				return []byte(v), nil
			}
			return nil, fmt.Errorf("%s not exist", key)
		}))

	addr := "localhost:9999"
	peers := geecache.NewHTTPPool(addr)
	log.Println("geecache is running at", addr)
	log.Fatal(http.ListenAndServe(addr, peers))
}
  • 同样地,我们使用 map 模拟了数据源 db。
  • 创建一个名为 scores 的 Group,若缓存为空,回调函数会从 db 中获取数据并返回。
  • 使用 http.ListenAndServe 在 9999 端口启动了 HTTP 服务。

测试结果:重复访问http://localhost:9999/_geecache/score/Tom

http.go的文件中下述代码已被对注释,防止结果输出过多报错,因为在请求时会多出一个/favicon.ico的访问请求(不知道为什么?)

go 复制代码
	if !strings.HasPrefix(r.URL.Path, p.basePath) {
		panic("HTTPPool serving unexpected path: " + r.URL.Path)
	}
相关推荐
努力也学不会java6 小时前
【缓存算法】一篇文章带你彻底搞懂面试高频题LRU/LFU
java·数据结构·人工智能·算法·缓存·面试
发际线还在9 小时前
互联网大厂Java三轮面试全流程实战问答与解析
java·数据库·分布式·面试·并发·系统设计·大厂
fy1216313 小时前
GO 快速升级Go版本
开发语言·redis·golang
czlczl2002092514 小时前
Redis数据编码
数据库·redis·缓存
Wpa.wk14 小时前
pb协议接口测试
数据库·redis·缓存
童话ing15 小时前
【Golang】Golang Map数据结构底层原理
数据结构·golang·哈希算法
GDAL16 小时前
go.mod 文件讲解
golang·go.mod
Francek Chen16 小时前
【大数据存储与管理】分布式数据库HBase:05 HBase运行机制
大数据·数据库·hadoop·分布式·hdfs·hbase
zzzzzwbetter16 小时前
Hadoop完全分布式部署-Master的NameNode以及Slaver2的DataNode未启动
大数据·hadoop·分布式
Java面试题总结16 小时前
Go图像处理基础: image包深度指南
图像处理·算法·golang