??經過了前兩個章節的學習,分布式緩存的存儲與新增我們已經實現了,并且對其做了高可用處理。本章節我們剝離和緩存強相關的邏輯,開始搭建一個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 stringbasePath 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 operationparts := 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 /<basepath>/<groupname>/<key>if len(parts) != 2 {http.Error(w, "bad request", http.StatusBadRequest)return}// 3. get GroupName and keygroupName := parts[0]key := parts[1]// 4. call Underlying interface to get Groupgroup := GetGroup(groupName)if group == nil {http.Error(w, "no such group: "+groupName, http.StatusNotFound)return}// 5. call Underlying interface to get key-valueview, 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-valuew.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 maxBytesgeecache.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 errorlog.Fatal(http.ListenAndServe(addr, peers))
}