Golang HTTP服务超时控制实现原理分析
前情提要
因为上一篇提过,每次来一个请求,然后就会起一个goroutinue
那么导致的可能就是一个树形结构的请求图,底下节点在执行中如果发生了超时,那么就有协程会堆积,所以超时控制是有必要的,一般的实现都由一个顶层设计一个Context进行自顶向下传递,这样可以从一个地方去避免多处执行异常,对于Context的过多细节我不在这里一一阐述,有需要的我将单独出一篇关于Context的介绍,下面我们就来看一下源码是如何设计的:
Context
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
// 当Context被取消或者到了deadline,返回一个被关闭的channel
Done() <-chan struct{}
}
// 函数句柄
type CancelFunc func()
设计初衷最关注的两个点就是一个是如何主动结束下游,另一个是如何通知上游结束下游时
前者利用CancelFunc
后者利用Done
,后者需要不断监听所以利用channel的返回值做监听
//创建退出Context
func WithCancel(parent Context)(ctx Context,cancel CancelFunc){}
//创建有超时时间的Context
func WithTimeout(parent Context,timeout time.Duration)(Context,CancelFunc){}
//创建有截止时间的Context
func WithDeadline(parent Context,d time.Time)(Context,CancelFunc){}
WithCancel/WithTimeout/WithDeadline
都是通过定时器来自动触发终结通知的,也就是说为父节点生成一个Done
的子节点,并且返回子节点的CancelFunc
函数句柄.
封装自定义的Context
context.go
可以定义一个自己的Context,里面先拥有最基本的request和response两个参数,最后是因为思考到并发写resposne的writer所以需要加入锁成员变量以及防止重复写的超时标志位
package framework
import (
"context"
"encoding/JSON"
"net/Http"
"sync"
)
type Context struct {
Request *http.Request
ResponseWriter http.ResponseWriter
hasTimeOut bool // 是否超时标记位
writerMux *sync.Mutex
}
func NewContext()*Context{
return &Context{}
}
func (ctx *Context) BaseContext() context.Context {
return ctx.Request.Context()
}
func (ctx *Context) Done() <-chan struct{} {
return ctx.BaseContext().Done()
}
func (ctx *Context)SetHasTimeOut(){
ctx.hasTimeOut=true
}
func (ctx *Context)HasTimeOut()bool{
return ctx.hasTimeOut
}
// 自行封装一个json的方法
func (ctx *Context) Json(status int, obj interface{}) (err error) {
if ctx.HasTimeOut(){
return nil
}
bytes, err := json.Marshal(obj)
ctx.ResponseWriter.WriteHeader(status)
_, err = ctx.ResponseWriter.Write(bytes)
return
}
// 对外暴露锁
func (ctx *Context) WriterMux() *sync.Mutex {
return ctx.writerMux
}
// 统一处理器Controller方法
type ControllerHandler func(c *Context) error
main.go
业务方法使用一下自己封装的Context,里面考虑到了超时控制以及并发读写,以及处理panic
package main
import (
"context"
"fmt"
"testdemo1/coredemo/framework"
"time"
)
func FooController(ctx *framework.Context) error {
durationCtx, cancel := context.WithTimeout(ctx.BaseContext(), time.Second)
defer cancel()
finish := make(chan struct{}, 1)
panicChan := make(chan interface{}, 1)
go func() {
defer func() {
if p := recover(); p != nil {
panicChan <- p
}
}()
time.Sleep(time.Second * 10)
finish <- struct{}{}
}()
select {
case p := <-panicChan: // panic
fmt.Println("panic:",p)
ctx.WriterMux().Lock() // 防止多个协程之前writer的消息乱序
defer ctx.WriterMux().Unlock()
ctx.Json(500, "panic")
case <-finish: // 正常退出
ctx.Json(200, "ok")
fmt.Println("finish")
case <-durationCtx.Done(): // 超时事件
ctx.WriterMux().Lock()
defer ctx.WriterMux().Unlock()
ctx.Json(500, "timed out")
ctx.SetHasTimeOut() // 防止多次协程重复写入超时日志
}
return nil
}
Core.go
serverHandler的类,进行处理请求的逻辑,可以先注册对应的映射器和方法
package framework
import (
"net/http"
)
type Core struct {
RouterMap map[string]ControllerHandler
}
func (c Core) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
http.DefaultServeMux.ServeHTTP(writer, request)
}
func NewCore() *Core {
return &Core{
RouterMap:make(map[string]ControllerHandler,0),
}
}
// 注册Get方法
func (c *Core) Get(pattern string, handler ControllerHandler) {
c.RouterMap["get"+"-"+pattern]=handler
}
// 注册Post方法
func (c *Core) Post(pattern string, handler ControllerHandler) {
c.RouterMap["post"+"-"+pattern]=handler
}
router.go
router统一管理注册进我们对应的http方法到我们的请求逻辑类里去
package main
import "testdemo1/coredemo/framework"
func reGISterRouter(core *framework.Core){
// 设置控制器
core.Get("foo",FooController)
}
main.go
最后是主程序的执行http服务监听和调用初始化router的注册!传入我们自定义的Context
package main
import (
"log"
"net/http"
"testdemo1/coredemo/framework"
)
func main() {
server:=&http.Server{Addr: ":8080",Handler: framework.NewCore()}
// 注册router
registerRouter(framework.NewCore())
err := server.ListenAndServe()
if err!=nil{
log.Fatal(err)
}
}
本文到此结束!可以自行实现一遍,体验一下,实际和gin的源码封装就是类似的~
我们下一篇再见
到此这篇关于golang HTTP服务超时控制实现原理分析的文章就介绍到这了,更多相关Golang HTTP服务超时控制内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
相关文章