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

  经过了前两个章节的学习,分布式缓存的存储与新增我们已经实现了,并且对其做了高可用处理。本章节我们剥离和缓存强相关的逻辑,开始搭建一个HTTP服务器,毕竟缓存数据库搭建完之后别人没法访问也是没有用处的。这一章节我们重点学习go原生的HTTP标准库


前文链接

手撕分布式缓存之一 | 定义缓存结构体与实现底层功能函数
手撕分布式缓存之二 | 互斥锁的优化


系列目录

  • (1)HTTP Server 的搭建
    • (1.1)前言
    • (1.2)HTTP 核心数据结构的构造及初始化函数的实现
    • (1.3)单节点HTTP Server核心Get方法的实现
    • (1.4)代码实现及解释

(1)HTTP Server 的搭建

(1.1)前言

  通过本篇文章可以学习到 go 语言原生的 HTTP Server 的使用,虽然不像成熟的Web框架那样,方法、机制都很完全,但是通过学习底层的功能实现,对于我们整体项目结构搭建是非常重要的。如果将go语言、go衍生技术(gin,MiddleWare等)等一系列相关的技术看做一整个项目结构,那么哪些技术我们要实现哪些功能呢?这些功能做出来实际上使用的人有多少呢?能给我们带来多少价值?这些问题具象来看的话是与我们开发人员息息相关的,因此我们有必要去学习这种看似吃力不讨好实际却会给我们带来很深远的收益的知识。

(1.2)HTTP 核心数据结构的构造及初始化函数的实现

  go语言的HTTP服务为我们提供了 ListenAndServe(addr string, handler Handler) error 这个方法,handler是HTTP服务的主体处理函数,任何实现了 ServeHTTP 方法的对象都可以作为handler的值传递给ListenAndServe 方法。该方法除了handler参数还需要接收一个addr,用来标识服务启动的地址和端口,这个也是服务启动之后其他请求者访问的域名。因此我们可以定义一个数据结构HTTPPool,里面有属性 self string 和 basePath string,self是就是上面说的addr,basePath是用于区分服务器上其他服务的请求Url,避免重复,比如我们可以默认 /_geecache/ 作为我们请求URI(统一资源标识符)第一级的值

(1.3)单节点HTTP Server核心Get方法的实现

  在1.2小节我们解释了HTTP的核心数据结构,这一小节我们讨论下HTTP服务的核心服务ServeHTTP。在前言中我们概括性的讨论了go的HTTP原生包(下称HTTP原生包)与成熟框架是有区别的,从相关的代码中(gin和geeCache的相关实现中)了解到,HTTP原生包对于方法的直接操作只有ServeHTTP方法,也就是说如果只使用HTTP原生包构建HTTP Server,所有的请求都是由 ServeHTTP 方法首先处理的,而例如Gin框架,可以将方法绑定在URI上,在路由注册之后,可以通过URI访问与其绑定的功能方法;从HTTP原生包来看,我能想到的实现Gin这一功能的方法是,在ServeHTTP方法中通过判断请求的URI的值与if语句过滤来实现不同的URI访问不同的功能函数。
  HTTP Get方法的实现并不复杂,主要流程还是调用下层我们封装的Get方法(如何封装的可以阅读 分布式系列 的前两章内容)。

(1.4)代码实现及解释

自定义默认的URI第一级的值 和 定义HTTP的核心数据结构

  • 自定义URI的第一级的值为 _geecache ,使用下划线开头的值用作区分重复的可能性更低,因此我们使用下划线开头。
// this is the defaultPath, user it to distinguish geeCache Project and other Project.
const defaultBasePath = "/_geecache/"

type HTTPPool struct {
	// this peer's base URL, e.g. self is "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,
	}
}

核心功能Get函数的实现

  • 在通过URI获取GroupName之前,首先判断URI的第一级值是否与defaultPath一致
  • 通过strings.SplitN根据 字符 \ 进行截取,并获得groupName 和 key
  • 调用下层已经实现的函数功能,以从中获取key对应的value
  • 使用 w.Write(view.ByteSlice()),返回给 请求者一个 copy的对象,避免返回给请求者原对象后遭到恶意修改吗,view.ByteSlice() 的具体实现在上一章节有具体说明和实现 。
// ServeHTTP handle all http requests
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 1. judge the first layer of uri is defaultBasePath.
	if !strings.HasPrefix(r.URL.Path, p.basePath) {
		panic("HTTPPool serving unexpected path: " + r.URL.Path)
	}

	// begin from the defaultPath, then start to split the rest of uri and get groupName from this operation
	parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)

	// 2. judge the structure have two layers, first is groupName, second is key
	// the structure is  ///
	if len(parts) != 2 {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}

	// 3. get GroupName and key
	groupName := parts[0]
	key := parts[1]

	// 4. call Underlying interface to get Group
	group := GetGroup(groupName)
	if group == nil {
		http.Error(w, "no such group: "+groupName, http.StatusNotFound)
		return
	}

	// 5. call Underlying interface to get key-value
	view, err := group.Get(key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/octet-stream")
	// return key-value
	w.Write(view.ByteSlice())
}

HTTP Server 服务的启动(main函数的编写),参数的声明

  • db 用于验证GetterFunc方法是否生效。
  • 2<<10是二进制左移10位,对应的十进制为4096。
  • http.ListenAndServe 是服务的启动代码。
// not necessary, just is a test demo in here
var db = map[string]string{
	"Tom":  "630",
	"Jack": "589",
	"Sam":  "567",
}

func main() {
	// 2<<10 means Binary Left Shift 10 bits, its value is 2048
	// GetterFunc can ignore the data's addition, cause it initialize the group scores, and state the maxBytes
	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 the ListenAndServe's error
	log.Fatal(http.ListenAndServe(addr, peers))
}

你可能感兴趣的:(分布式缓存,分布式,http,go,web,缓存,数据结构)