龙空技术网

golang实现分布式缓存笔记(一)基于http的缓存服务

音视频流媒体开发 282

前言:

此时兄弟们对“form methodget数据限制为”大体比较讲究,看官们都需要了解一些“form methodget数据限制为”的相关知识。那么小编在网上收集了一些关于“form methodget数据限制为””的相关资讯,希望各位老铁们能喜欢,看官们快快来了解一下吧!

目录

前言cache缓存服务接口cache包实现golang http包使用介绍hello.goRedirect.gohttp-cache-server 实现cacheHandler程序测试与redis的比较前言

这个月我想学一下go语言,于是决定学习一个go实现的缓存服务。

首先本文基于golang的http包实现一个简单http的缓存服务,因为用golang自带的http包实现一个处理请求的服务端十分便利,我们只需要写一个简单的map保存数据,写一个http的handler处理请求即可,你不需要考虑任何复杂的并发问题,因为golang的http服务框架会帮你处理好底层的一切。

cache缓存服务接口

本文实现的简单缓存具备三种基本接口 : SET GET DEL 分别通过http协议的PUT、GET、DELETE、操作进行。

put

PUT /cache/<key>content<value>

GET

GET /cache/<key>content<value>

DELETE

DELETE /cache/<key>
cache包实现

本缓存服务里面通过一个cache包实现缓存功能。

cache包接口定义:

package cachetype Cache interface {	Set(string, []byte) error	Get(string) ([]byte, error)	Del(string) error	GetStat() Stat}

cache 接口实现

Cache 结构很简单,一张map,另加一把锁保护即可.

package cacheimport "sync"type SimpleCache struct {	c     map[string][]byte	mutex sync.RWMutex	Stat}func (c *SimpleCache) Set(k string, v []byte) error {	c.mutex.Lock()	defer c.mutex.Unlock()	tmp, exist := c.c[k]	if exist {		c.del(k, tmp)	}	c.c[k] = v	c.add(k, v)	return nil}func (c *SimpleCache) Get(k string) ([]byte, error) {	c.mutex.RLock()	defer c.mutex.RUnlock()	return c.c[k], nil}func (c *SimpleCache) Del(k string) error {	c.mutex.Lock()	defer c.mutex.Unlock()	v, exist := c.c[k]	if exist {		delete(c.c, k)		c.del(k, v)	}	return nil}func (c *SimpleCache) GetStat() Stat {	return c.Stat}func newInMemoryCache() *SimpleCache {	return &SimpleCache{make(map[string][]byte), sync.RWMutex{}, Stat{}}}

cache包测试:

package mainimport (	"./cache"  "fmt")func main() {	c := cache.New("inmemory")  k, v := "sola", []byte{'a','i','l','u','m','i','y','a'}  c.Set(k, v)  tmp, _ := c.Get(k)  fmt.Println("key: ", k, " value: ", tmp)  c.Del(k)  tmp, _ = c.Get(k)  fmt.Println("key: ", k, " value: ", tmp)}
sola@sola:~/Coder/GitHub/go-cache/http-cache/server$ go run main.go 2019/02/10 00:07:15 inmemory ready to servekey:  sola  value:  [97 105 108 117 109 105 121 97]sola@sola:~/Coder/GitHub/go-cache/http-cache/server$ go run main.go 2019/02/10 00:07:28 inmemory ready to servekey:  sola  value:  [97 105 108 117 109 105 121 97]key:  sola  value:  []
golang http包使用介绍

Golang自带的http包已经实现了htpp客户端和服务端,我们可以利用它更为快速的开发http服务。本章仅介绍一下http包服务端的使用。

Golang中处理 HTTP 请求主要跟两个东西相关:ServeMux 和 Handler。

ServrMux 本质上是一个 HTTP 请求路由器(或者叫多路复用器,Multiplexor)。它把收到的请求与一组预先定义的 URL 路径列表做对比,然后在匹配到路径的时候调用关联的处理器(Handler)。

处理器(Handler)负责输出HTTP响应的头和正文。任何满足了http.Handler接口的对象都可作为一个处理器。通俗的说,对象只要有个如下签名的ServeHTTP方法即可:

ServeHTTP(http.ResponseWriter, *http.Request)

Golang的 HTTP 包自带了几个函数用作常用处理器,比如NotFoundHandler 和 RedirectHandler。

NotFoundHandler返回一个简单的请求处理器,该处理器会对每个请求都回复"404 page not found"。

RedirectHandler返回一个请求处理器,该处理器会对每个请求都使用状态码code重定向到网址url。

接着,我们来看两个简单的样例:

hello.go

package mainimport (    "io"    "log"    "net/http")func HelloGoServer(w http.ResponseWriter, req *http.Request) {    io.WriteString(w, "Hello, this is a GoServer")}func main() {    http.HandleFunc("/", HelloGoServer)    err := http.ListenAndServe(":9090", nil)    if err != nil {        log.Fatal("ListenAndServer ", err)    }}

浏览器看看我们的hello程序:

1、 http.HandleFunc("/", HelloGoServer)

http提供的外部方法HandleFunc实际也是调用ServeMux的内部方法,只是它使用的是http包默认的ServeMux,注册一个处理器函数handler(HelloGoServer)和对应的模式pattern(/)(注册到DefaultServeMux)。ServeMux的文档解释了模式的匹配机制。

2、http.ListenAndServe(":9090", nil)

ListenAndServe同字面意思监听并服务。这里是监听9090端口,它其实也是一个外部方法,调用内部Server类型的ListenAndServe。

Redirect.go

package mainimport (  "log"  "net/http")func main() {  mux := http.NewServeMux()  rh := http.RedirectHandler(";, 307)  mux.Handle("/foo", rh)  log.Println("Listening...")  http.ListenAndServe(":3000", mux)}

1、这个样例中我们没用默认的ServeMux,而是通过 http.NewServeMux 函数来创建一个空的 ServeMux。

2、http.RedirectHandler 函数创建了一个重定向处理器,这个处理器会对收到的所有请求,都执行307重定向操作到 。

3、ServeMux.Handle 函数将处理器注册到新创建的 ServeMux,所以它在 URL 路径/foo 上收到所有的请求都交给这个处理器。

4、最后通过 http.ListenAndServe 函数启动服务处理请求,通过传递刚才创建的 ServeMux来为请求去匹配对应处理器。

键入后你会跳转到百度。

http-cache-server 实现

最后来实现我们的cache-server

cache已经有了,我们只需要写一个http的Handler来分别处理GET,PUT,DELETE请求即可。

上面提过任何满足了http.Handler接口的对象即ServeHTTP(http.ResponseWriter, *http.Request)都可作为一个处理器,那么我们先来看看这个接口的参数.

ResponseWriter接口被HTTP处理器用于构造HTTP回复。

type ResponseWriter interface {    // Header返回一个Header类型值,该值会被WriteHeader方法发送。    // 在调用WriteHeader或Write方法后再改变该对象是没有意义的。    Header() Header    // WriteHeader该方法发送HTTP回复的头域和状态码。    // 如果没有被显式调用,第一次调用Write时会触发隐式调用WriteHeader(http.StatusOK)    // WriterHeader的显式调用主要用于发送错误码。    WriteHeader(int)    // Write向连接中写入作为HTTP的一部分回复的数据。    // 如果被调用时还未调用WriteHeader,本方法会先调用WriteHeader(http.StatusOK)    // 如果Header中没有"Content-Type"键,    // 本方法会使用包函数DetectContentType检查数据的前512字节,将返回值作为该键的值。    Write([]byte) (int, error)}

Request类型代表一个服务端接受到的或者客户端发送出去的HTTP请求。Request各字段的意义和用途在服务端和客户端是不同的。

type Request struct {    // Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。    Method string    // URL在服务端表示被请求的URI,在客户端表示要访问的URL。    //    // 在服务端,URL字段是解析请求行的URI(保存在RequestURI字段)得到的,    // 对大多数请求来说,除了Path和RawQuery之外的字段都是空字符串。    // (参见RFC 2616, Section 5.1.2)    //    // 在客户端,URL的Host字段指定了要连接的服务器,    // 而Request的Host字段(可选地)指定要发送的HTTP请求的Host头的值。    URL *url.URL    // 接收到的请求的协议版本。本包生产的Request总是使用HTTP/1.1    Proto      string // "HTTP/1.0"    ProtoMajor int    // 1    ProtoMinor int    // 0    // Header字段用来表示HTTP请求的头域。如果头域(多行键值对格式)为:    //	accept-encoding: gzip, deflate    //	Accept-Language: en-us    //	Connection: keep-alive    // 则:    //	Header = map[string][]string{    //		"Accept-Encoding": {"gzip, deflate"},    //		"Accept-Language": {"en-us"},    //		"Connection": {"keep-alive"},    //	}    // HTTP规定头域的键名(头名)是大小写敏感的,请求的解析器通过规范化头域的键名来实现这点。    // 在客户端的请求,可能会被自动添加或重写Header中的特定的头,参见Request.Write方法。    Header Header    // Body是请求的主体。    //    // 在客户端,如果Body是nil表示该请求没有主体买入GET请求。    // Client的Transport字段会负责调用Body的Close方法。    //    // 在服务端,Body字段总是非nil的;但在没有主体时,读取Body会立刻返回EOF。    // Server会关闭请求的主体,ServeHTTP处理器不需要关闭Body字段。    Body io.ReadCloser    // ContentLength记录相关内容的长度。    // 如果为-1,表示长度未知,如果>=0,表示可以从Body字段读取ContentLength字节数据。    // 在客户端,如果Body非nil而该字段为0,表示不知道Body的长度。    ContentLength int64    // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。    // 本字段一般会被忽略。当发送或接受请求时,会自动添加或移除"chunked"传输编码。    TransferEncoding []string    // Close在服务端指定是否在回复请求后关闭连接,在客户端指定是否在发送请求后关闭连接。    Close bool    // 在服务端,Host指定URL会在其上寻找资源的主机。    // 根据RFC 2616,该值可以是Host头的值,或者URL自身提供的主机名。    // Host的格式可以是"host:port"。    //    // 在客户端,请求的Host字段(可选地)用来重写请求的Host头。    // 如过该字段为"",Request.Write方法会使用URL字段的Host。    Host string    // Form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据。    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。    Form url.Values    // PostForm是解析好的POST或PUT的表单数据。    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。    PostForm url.Values    // MultipartForm是解析好的多部件表单,包括上传的文件。    // 本字段只有在调用ParseMultipartForm后才有效。    // 在客户端,会忽略请求中的本字段而使用Body替代。    MultipartForm *multipart.Form    // Trailer指定了会在请求主体之后发送的额外的头域。    //    // 在服务端,Trailer字段必须初始化为只有trailer键,所有键都对应nil值。    // (客户端会声明哪些trailer会发送)    // 在处理器从Body读取时,不能使用本字段。    // 在从Body的读取返回EOF后,Trailer字段会被更新完毕并包含非nil的值。    // (如果客户端发送了这些键值对),此时才可以访问本字段。    //    // 在客户端,Trail必须初始化为一个包含将要发送的键值对的映射。(值可以是nil或其终值)    // ContentLength字段必须是0或-1,以启用"chunked"传输编码发送请求。    // 在开始发送请求后,Trailer可以在读取请求主体期间被修改,    // 一旦请求主体返回EOF,调用者就不可再修改Trailer。    //    // 很少有HTTP客户端、服务端或代理支持HTTP trailer。    Trailer Header    // RemoteAddr允许HTTP服务器和其他软件记录该请求的来源地址,一般用于日志。    // 本字段不是ReadRequest函数填写的,也没有定义格式。    // 本包的HTTP服务器会在调用处理器之前设置RemoteAddr为"IP:port"格式的地址。    // 客户端会忽略请求中的RemoteAddr字段。    RemoteAddr string    // RequestURI是被客户端发送到服务端的请求的请求行中未修改的请求URI    // (参见RFC 2616, Section 5.1)    // 一般应使用URI字段,在客户端设置请求的本字段会导致错误。    RequestURI string    // TLS字段允许HTTP服务器和其他软件记录接收到该请求的TLS连接的信息    // 本字段不是ReadRequest函数填写的。    // 对启用了TLS的连接,本包的HTTP服务器会在调用处理器之前设置TLS字段,否则将设TLS为nil。    // 客户端会忽略请求中的TLS字段。    TLS *tls.ConnectionState}

golang请求及应答中涉及到的常量.

golang中的HTTP状态码

const (    StatusContinue           = 100    StatusSwitchingProtocols = 101    StatusOK                   = 200    StatusCreated              = 201    StatusAccepted             = 202    StatusNonAuthoritativeInfo = 203    StatusNoContent            = 204    StatusResetContent         = 205    StatusPartialContent       = 206    StatusMultipleChoices   = 300    StatusMovedPermanently  = 301    StatusFound             = 302    StatusSeeOther          = 303    StatusNotModified       = 304    StatusUseProxy          = 305    StatusTemporaryRedirect = 307    StatusBadRequest                   = 400    StatusUnauthorized                 = 401    StatusPaymentRequired              = 402    StatusForbidden                    = 403    StatusNotFound                     = 404    StatusMethodNotAllowed             = 405    StatusNotAcceptable                = 406    StatusProxyAuthRequired            = 407    StatusRequestTimeout               = 408    StatusConflict                     = 409    StatusGone                         = 410    StatusLengthRequired               = 411    StatusPreconditionFailed           = 412    StatusRequestEntityTooLarge        = 413    StatusRequestURITooLong            = 414    StatusUnsupportedMediaType         = 415    StatusRequestedRangeNotSatisfiable = 416    StatusExpectationFailed            = 417    StatusTeapot                       = 418    StatusInternalServerError     = 500    StatusNotImplemented          = 501    StatusBadGateway              = 502    StatusServiceUnavailable      = 503    StatusGatewayTimeout          = 504    StatusHTTPVersionNotSupported = 505)

golang 中的HTTP行为常量定义

  5 package http  6   7 // Common HTTP methods.  8 //  9 // Unless otherwise noted, these are defined in RFC 7231 section 4.3. 10 const ( 11     MethodGet     = "GET" 12     MethodHead    = "HEAD" 13     MethodPost    = "POST" 14     MethodPut     = "PUT" 15     MethodPatch   = "PATCH" // RFC 5789 16     MethodDelete  = "DELETE" 17     MethodConnect = "CONNECT" 18     MethodOptions = "OPTIONS" 19     MethodTrace   = "TRACE" 20 )
cacheHandler

到这里所有用到的http包中结构都已经说明了,开始写main包,

我们定义一个cacheHandler类型,用我们的inMemoryCache接口初始化它,并实现他的ServeHTTP方法。

最后将cacheHandler类型的CacheHandler方法注册到http包默认的ServeMux路由,绑定端口26316,启动服务。

package mainimport (  "./cache"  "io/ioutil"  "net/http"  "log"  "strings")type cacheHandler struct {  cache.Cache}func (h *cacheHandler) CacheHandler(w http.ResponseWriter, r *http.Request) {  log.Println("url ", r.URL, " Method ", r.Method)  //Split Get Key  key := strings.Split(r.URL.EscapedPath(), "/")[2]  if len(key) == 0 {    w.WriteHeader(http.StatusBadRequest)    return  }  m := r.Method  if m == http.MethodPut {    h.HandlePut(key, w, r)    return  } else if m == http.MethodGet {    h.HandleGet(key, w, r)    return  } else if m == http.MethodDelete {    h.HandleDelete(key, w, r)    return  }  w.WriteHeader(http.StatusMethodNotAllowed)}func (h *cacheHandler) HandlePut(k string, w http.ResponseWriter, r *http.Request){  b, _ := ioutil.ReadAll(r.Body)  if len(b) != 0 {    e := h.Set(k, b)    if e != nil {      log.Println(e)      w.WriteHeader(http.StatusInternalServerError)    } else {      w.Write([]byte("successful"))    }  }}func (h *cacheHandler) HandleGet(k string, w http.ResponseWriter, r *http.Request){  b, e := h.Get(k)    if e != nil {    log.Println(e)    w.WriteHeader(http.StatusInternalServerError)    return  }  if len(b) == 0 {    w.WriteHeader(http.StatusNotFound)    return  }  w.Write(b)}func (h *cacheHandler) HandleDelete(k string, w http.ResponseWriter, r *http.Request){  e := h.Del(k)  if e != nil {    log.Println(e)    w.WriteHeader(http.StatusInternalServerError)  } else {    w.Write([]byte("successful"))  }}func main() {  c := cache.New("inmemory")  h := cacheHandler{c}  http.HandleFunc("/cache/", h.CacheHandler)  http.ListenAndServe(":26316", nil)}
程序测试

使用postman测试put

浏览器直接测试Get

使用postman测试Delete

再次Get会返回404

与redis的比较

缓存功能的服务已经实现了,那么它的性能怎样呢,键值对缓存服务中比较有名的是redis,我们和它做下比较。

redis是一款in memory数据结构存储,可以被用作数据库、缓存及消息中间件。支持包括字符串、散列、列表及集合在内的多种数据结构、支持范围查询、具备内建的复制功能、lua脚本、LRU缓存淘汰策略、事务处理及两种不同的磁盘持久化方案(RDB和AOF)还能建立redis集群提供高可用性能。

redis的RDB持久化方案会在指定时间点将内存数据集快照存入磁盘。RDB开始工作时,会自己fork出一个持久化进程,此时原服务进程的一切内存数据相当于保存了一份快照、然后持久化进程将它的内存压缩并写入磁盘。

redis的AOF方案则是将服务接受到的所有写操作记入磁盘上的日志文件、将日志文件的格式和redis协议保持一致且只允许添加。

RDB方案对性能的影响比AOF小,因为它不占用原服务进程的磁盘IO、RDB的缺点在于系统死机时丢失的数据比AOF要多,因为它只保留得到数据到上一次持久化进程运行的那个时间点,而AOF可以一直记录到系统死机之前的最后一次写操作的数据。

本篇实现的是一个简单的内存缓存,不包含持久化方案,也不会保存进磁盘,一旦服务器重启所有数据就会丢失。

性能方面只有redis的1/4,主要原因在于REST协议的解析上,REST基于HTTP,HTTP基于TCP,而redis是直接建立在TCP上的。

总结:更多免费视频资料+学习路线+疑难解答+视频教程文档资料免费领取后台私信【学习】自行获取内容包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体, WebRTC ,CDN,P2P,K8S,Docker,Golang, TCP/IP,MTK , 嵌入式 , 协程,DPDK等等 。

标签: #form methodget数据限制为