Golang HTTP服务超时控制实现原理分析
作者:未来谁可知 发布时间:2024-05-08 10:52:16
前情提要
因为上一篇提过,每次来一个请求,然后就会起一个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的源码封装就是类似的~
我们下一篇再见
来源:https://blog.csdn.net/jiohfgj/article/details/125643189


猜你喜欢
- 前言本博主将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识,有兴趣的小伙伴可以关注博主!也许一个人独行,可以走的很快,但是一群人
- 1. 问题描述:同目录下,当多个文件之间有相互依赖的关系的时候,import无法识别自己写的模块,PyCharm中提示No Module.2
- 本文介绍基于Python语言,针对一个文件夹下大量的Excel表格文件,基于其中每一个文件的名称,从另一个文件夹中找到与这一文件夹中文件同名
- 本文实例讲述了python实现对一个完整url进行分割的方法。分享给大家供大家参考。具体分析如下:python对一个完整的url进行分割,将
- js原生方法map实现<!DOCTYPE html><html lang="en"><he
- MySQL 去除重复数据实例详解有两个意义上的重复记录,一是完全重复的记录,也即所有字段均都重复,二是部分字段重复的记录。对于第一种重复,比
- CSS Hack是在标准CSS没办法兼容各浏览器显示效果时才会用上的补救方法,在各浏览器厂商解析CSS没有达成一致前,我们只能用这样的方法来
- 平衡二叉树:在上一节二叉树的基础上我们实现,如何将生成平衡的二叉树所谓平衡二叉树:我自己定义就是:任何一个节点的左高度和右高度的差的绝对值都
- 原则一:注意WHERE子句中的连接顺序: ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE
- 我就废话不多说了,还是直接看代码吧!import matha=1;//边1b=1;//边2c=math.sqrt(2);//边3A=math
- 目录序列容器序列与扁平序列不可变序列与可变序列列表推导生成器表达式Tips小结序列序列是指一组数据,按存放类型分为容器序列与扁平序列,按能否
- 当完整备份数据库的时候,我们有时候可能会遇到一种极端情况,比如服务器上C,D,E三个盘符都只剩下5G空间了但是如果要完整备份业务库需要12G
- 阿里云服务器的带宽为2M,网站每日的备份包都3G多了,离线下载太费时间了,打算每日将备份包自动上传到自己的百度云盘里。 1、先安装
- 首先先说一下思路:1.本地django项目打包 主要用到的是 python自带的distutils.core 下的 setup,具体代码在下
- 说明1、导入模块pyplot,并指定别名plt,以避免重复输入pyplot。模块化pyplot包含许多用于制作图表的功能。2、将绘制的直线坐
- 前言今天我看到线性规划模型开头的介绍,特别不错,因此,我把它记录下来了,分享给大家在工程技术、经济管理、科学研究、军事作战训练及日常生活等众
- namedtuple是Python中存储数据类型,比较常见的数据类型还有有list和tuple数据类型。
- 最近着迷上了 Python用Python给小宝做的数学算数口算练习程序(2015年1月添加四则运算)!给小宝做的口算游戏:#用Python给
- 本文实例讲述了Python面向对象之继承原理与用法。分享给大家供大家参考,具体如下:目标单继承多继承面向对象三大特性封装 根据 职责 将 属
- str='python String function'生成字符串变量str='python String func