Golang HTTP服务超时控制实现原理分析

目录
  • 前情提要
  • Context
  • 封装自定义的Context
    • context.go
    • main.go
    • Core.go
    • router.go
    • main.go

前情提要

因为上一篇提过,每次来一个请求,然后就会起一个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服务超时控制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang中HTTP路由设计的使用与实现

    目录 Golang之HTTP路由设计 动手编写自己的路由 framework/core.go framework/group.go 如何实现动态路由 改造一下core.go 验证 Golang之HTTP路由设计 为什么要设计路由规则,路由规则是HTTP的请求按照一定的规则 ,匹配查找到对应的控制器并传递执行的逻辑! 自己编写路由的话需要注意一下一共有几种路由! 一种是支持原生的restful四种类型的访问方法!Get,Post,Delete,Put 需要支持自定义的路径,也就是静态路由 批量通用

  • Golang中基于HTTP协议的网络服务

    目录 一.HTTP协议的网络服务 1.1 使用http.Get函数访问HTTP协议的网络服务 1.2 使用缺省客户端DefaultClient(类型为*http.Client ) 1.3 使用http.Client访问HTTP协议的网络服务 二.http.Client中的Transport字段 (1)http.Transport类型中的DialContext字段 (2)http.Transport类型中的其它字段 三.为什么会出现空闲的连接 3.1 空闲连接的产生 3.2 杜绝空闲连接的产生 四

  • Golang HTTP编程的源码解析详解

    目录 1.网络基础 2.Golang HTTP编程 2.1 代码示例 2.2 源码分析 3. 总结 1.网络基础 基本TCP客户-服务器程序Socket编程流程如如下图所示. TCP服务器绑定到特定端口并阻塞监听客户端端连接, TCP客户端则通过IP+端口向服务器发起请求,客户-服务器建立连接之后就能开始进行数据传输. Golang的TCP编程也是基于上述流程的. 2.Golang HTTP编程 2.1 代码示例 func timeHandler(w http.ResponseWriter, r

  • Golang实现简单http服务器的示例详解

    目录 一.基本描述 二 .具体方法 2.1 连接的建立 2.2 http请求解析 2.3 http请求处理 2.4 http请求响应 三.完整示例 一.基本描述 完成一个http请求的处理和响应,主要有以下几个步骤: 监听端口 建立连接 解析http请求 处理请求 返回http响应 完成上面几个步骤,便能够实现一个简单的http服务器,完成对基本的http请求的处理 二 .具体方法 2.1 连接的建立 go中net包下有提供Listen和Accept两个方法,可以完成连接的建立,可以简单看下示例

  • Golang搭建HTTP服务器

    目录 一. 安装Golang 二. 搭建HTTP服务器 三. 扩展HTTP服务器 处理HTTP请求 处理参数 处理静态文件 四. 总结 一. 安装Golang 在开始之前,我们需要先安装好Golang.你可以在官网下载Golang的安装包,然后安装到你的电脑上.安装好后,通过命令行工具验证Golang是否安装成功,可以输入下面的命令: go version 如果输出Golang的版本号,说明Golang安装成功. 二. 搭建HTTP服务器 下面我们开始搭建HTTP服务器.首先我们需要将下面的代码

  • Golang Http请求返回结果处理

    在 Go 中 Http 请求的返回结果为 *http.Response 类型,Response.Body 类型为 io.Reader,把请求结果转化为Map需要进行一些处理. 写一个公共方法来进行Response转Map处理: package util import (     "encoding/json"     "net/http"     "io/ioutil" ) func ParseResponse(response *http.Re

  • Golang实现HTTP编程请求和响应

    请求: HTTP 请求报文由请求行.请求头部.空行.请求包体4个部分组成,如下图所示: 请求行: 请求行由方法字段.URL 字段 和HTTP 协议版本字段 3个部分组成,他们之间使用空格隔开.常用的 HTTP 请求方法有 GET.POST. GET: 当客户端要从服务器中读取某个资源时,使用GET 方法.GET 方法要求服务器将URL 定位的资源放在响应报文的数据部分,回送给客户端,即向服务器请求某个资源. 使用GET方法时,请求参数和对应的值附加在 URL 后面,利用一个问号(“?”)代表UR

  • go-micro开发RPC服务以及运行原理介绍

    go-micro是一个知名的golang微服务框架,最新版本是v4,这篇文章将介绍go-micro v4开发RPC服务的方法及其运作原理. 基本概念 go-micro有几个重要的概念,后边开发RPC服务和介绍其运行原理的时候会用到,这里先熟悉下: Service:代表一个go-micro应用程序,Service中包括:Server.Client.Broker.Transport.Registry.Config.Store.Cache等程序运行所需的各个模块. Server:代表一个go-micr

  • Golang errgroup 设计及实现原理解析

    目录 开篇 errgroup 源码拆解 Group WithContext Wait Go SetLimit TryGo 使用方法 结束语 开篇 继上次学习了信号量 semaphore 扩展库的设计思路和实现之后,今天我们继续来看 golang.org/x/sync 包下的另一个经常被 Golang 开发者使用的大杀器:errgroup. 业务研发中我们经常会遇到需要调用多个下游的场景,比如加载一个商品的详情页,你可能需要访问商品服务,库存服务,券服务,用户服务等,才能从各个数据源获取到所需要的

  • VBS脚本病毒原理分析与防范

    网络的流行,让我们的世界变得更加美好,但它也有让人不愉快的时候.当您收到一封主题为"I Love You"的邮件,用兴奋得几乎快发抖的鼠标去点击附件的时候:当您浏览一个信任的网站之后,发现打开每个文件夹的速度非常慢的时候,您是否察觉病毒已经闯进了您的世界呢?2000年5月4日欧美爆发的"爱虫"网络蠕虫病毒.由于通过电子邮件系统传播,爱虫病毒在短短几天内狂袭全球数百万计的电脑.微软.Intel等在内的众多大型企业网络系统瘫痪,全球经济损失达几十亿美元.而去年爆发的新欢

  • spring boot启动加载数据原理分析

    实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来实现. 创建实现接口 CommandLineRunner 的类,通过@Component注解,就可以实现启动时加载数据项.使用@Order 注解来定义执行顺序. IndexStartupRunner.Java类: import org.springframework.boot.CommandLine

  • Spring Cloud Hystrix入门和Hystrix命令原理分析

    断路由器模式 在分布式架构中,当某个服务单元发生故障之后,通过断路由器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待.这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延. Spring Cloud Hystrix针对上述问题实现了断路由器.线程隔离等一系列服务保护功能.它是基于Netflix Hystrix实现,该框架的目标在于通过控制那些访问远程系统.服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力. Hystrix具备服务

  • Golang HTTP 服务平滑重启及升级的思路

    Golang HTTP服务在上线时,需要重新编译可执行文件,关闭正在运行的进程,然后再启动新的运行进程.对于访问频率比较高的面向终端用户的产品,关闭.重启的过程中会出现无法访问(nginx表现为502)的情况,影响终端用户的使用体验. 实现的一般思路 一般情况下,要实现平滑重启或升级,需要执行以下几个步骤: 发布新的bin文件覆盖老的bin文件 发送一个信号量(USR2),告诉正在运行的进程,进行重启 正在运行的进程接受到信号后,以子进程的方式启动新的bin文件 新进程接收并处理新的请求 老进程

  • 浅谈Mybatis版本升级踩坑及背后原理分析

    1.背景 某一天的晚上,系统服务正在进行常规需求的上线,因为发布时,提示统一的pom版本需要升级,于是从 1.3.9.6 升级至 1.4.2.1. 当服务开始上线后,开始陆续出现了一些更新系统交互日志方面的报警,属于系统辅助流程,报警下图所示, 具体系统数据已脱敏,内容是Mybatis相关的报警,在进行类型转换的时候,产生了强转错误. 更新开票请求返回日志, id:{#######}, response:{{"code":XXX,"data":{"call

  • Android 系统服务TelecomService启动过程原理分析

    由于一直负责的是Android Telephony部分的开发工作,对于通信过程的上层部分Telecom服务以及UI都没有认真研究过.最近恰好碰到一个通话方面的问题,涉及到了Telecom部分,因而就花时间仔细研究了下相关的代码.这里做一个简单的总结.这篇文章,主要以下两个部分的内容: 什么是Telecom服务?其作用是什么? Telecom模块的启动与初始化过程: 接下来一篇文章,主要以实际通话过程为例,分析下telephony收到来电后如何将电话信息发送到Telecom模块以及Telecom是

  • Java Servlet 运行原理分析

    1 Servlet基本执行过程 Web容器(如Tomcat)判断当前请求是否第一次请求Servlet程序 . 如果是第一次,则Web容器执行以下任务: 加载Servlet类. 实例化Servlet类. 调用init方法并传入ServletConfig对象 如果不第一次执行,则: 调用service方法,并传入request和response对象 Web容器在需要删除Servlet时(例如,在停止服务器或重新部署项目时)将调用destroy方法. 2 Web容器如何处理Servlet请求 Web容

  • Golang 语言map底层实现原理解析

    在开发过程中,map是必不可少的数据结构,在Golang中,使用map或多或少会遇到与其他语言不一样的体验,比如访问不存在的元素会返回其类型的空值.map的大小究竟是多少,为什么会报"cannot take the address of"错误,遍历map的随机性等等. 本文希望通过研究map的底层实现,以解答这些疑惑. 基于Golang 1.8.3 1. 数据结构及内存管理 hashmap的定义位于 src/runtime/hashmap.go 中,首先我们看下hashmap和buck

随机推荐

其他