gin源码分析

news2024/11/15 7:24:43
一、高性能
  • 使用sync.pool解决频繁创建的context对象,在百万并发的场景下能大大提供访问性能和减少GC

    // ServeHTTP conforms to the http.Handler interface.
    // 每次的http请求都会从sync.pool中获取context,用完之后归还到pool中
    func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    	c := engine.pool.Get().(*Context)
    	c.writermem.reset(w) //重置 responsewriter
    	c.Request = req
    	c.reset() //重置使用过的context各个属性
    
    	engine.handleHTTPRequest(c)
    
    	engine.pool.Put(c)
    }
    
    // 这里给pool指定了一个创建新对象的函数,注意不是所有的请求共用一个context,context在高并发场景下可能会分配多个,但是远远小于并发协程数量。 sync.pool.new是可能并发调用的,所以内部的逻辑需要保障线程安全
    func New(opts ...OptionFunc) *Engine {
    	... 
    	engine.RouterGroup.engine = engine
    	engine.pool.New = func() any {
    		return engine.allocateContext(engine.maxParams)
    	}
    	return engine.With(opts...)
    }
    
    //每次http请求都需要分配一个context,这个初始context初始化了两个数组的最大容量
    func (engine *Engine) allocateContext(maxParams uint16) *Context {
    	v := make(Params, 0, maxParams)
    	skippedNodes := make([]skippedNode, 0, engine.maxSections)
    	return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
    }
    
  • 前缀树路由(类似httprouter的路由,提升性能近40倍)

    gin在v1.0版本开始放弃了 github.com/julienschmidt/httprouter,重新实现了一套路由;对比gin新实现的路由基本上采用了httprouter的逻辑,但是和框架结合的更加完整,比如说把httprouter中router的能力提到了engine中。

  • json序列化优化

    gin提供了四种可选的json序列化方式,默认情况下会使用encoding/json

    /github.com/gin-gonic/gin@v1.10.0/internal/json
    	-- go_json.go ("github.com/goccy/go-json")
    	-- json.go  ("encoding/json")
    	-- jsoniter.go ("github.com/json-iterator/go")
    	-- sonic.go ("github.com/bytedance/sonic")
    

    需要在编译期间指定tag来决定使用哪种序列化工具

    go run -tags={go_json|jsoniter|sonic|(不指定默认encoding)} main.go
    

    通过一个简单的基准测试看看哪种json序列化效率更高

    package gan
    
    import (
    	"encoding/json"
    	sonicjson "github.com/bytedance/sonic"
    	gojson "github.com/goccy/go-json"
    	itjson "github.com/json-iterator/go"
    	"testing"
    )
    
    func BenchmarkJson(b *testing.B) {
    	jsonStr := `{"name":"zhangsan","age":18,"address":"beijing"}`
    	m := &map[string]interface{}{}
    	for i := 0; i < b.N; i++ {
    		json.Unmarshal([]byte(jsonStr), m)
    		json.Marshal(m)
    	}
    }
    
    func BenchmarkGOJson(b *testing.B) {
    	jsonStr := `{"name":"zhangsan","age":18,"address":"beijing"}`
    	m := &map[string]interface{}{}
    	for i := 0; i < b.N; i++ {
    		gojson.Unmarshal([]byte(jsonStr), m)
    		gojson.Marshal(m)
    	}
    }
    
    func BenchmarkItJson(b *testing.B) {
    	m := &map[string]interface{}{}
    	jsonStr := `{"name":"zhangsan","age":18,"address":"beijing"}`
    	for i := 0; i < b.N; i++ {
    		itjson.Unmarshal([]byte(jsonStr), m)
    		itjson.Marshal(m)
    	}
    }
    
    func BenchmarkSonicJson(b *testing.B) {
    	m := &map[string]interface{}{}
    	jsonStr := `{"name":"zhangsan","age":18,"address":"beijing"}`
    	for i := 0; i < b.N; i++ {
    		sonicjson.Unmarshal([]byte(jsonStr), m)
    		sonicjson.Marshal(m)
    	}
    }
    

    测试结果: sonic > go_json > json_iterator > encoding

    $ go test -bench='Json$' -benchtime=5s -benchmem .
    goos: windows
    goarch: amd64
    pkg: gan
    cpu: Intel(R) Core(TM) i5-10400 CPU @ 2.90GHz
    BenchmarkJson-12                 2632854              2260 ns/op             616 B/op         24 allocs/op
    BenchmarkGOJson-12               5226374              1142 ns/op             248 B/op         11 allocs/op
    BenchmarkItJson-12               4811112              1260 ns/op             400 B/op         19 allocs/op
    BenchmarkSonicJson-12            6616218               913.0 ns/op           333 B/op         10 allocs/op
    PASS
    ok      gan     30.313s
    
二、基于前缀树的路由设计
  • go语言中原生net/http包在负载路由下的缺陷

    动态路由:缺少例如hello/:name,hello/*这类的规则。
    鉴权:没有分组/统一鉴权的能力,需要在每个路由映射的handler中实现。

  • http请求怎么进入的gin的处理逻辑中去的?

    1. gin框架中调用了net/http包,监听address,请求都会进入顶层路由,也就是engine结构实现的ServeHTTP函数中

      func (engine *Engine) Run(addr ...string) (err error) {
          ......
      	debugPrint("Listening and serving HTTP on %s\n", address)
      	err = http.ListenAndServe(address, engine.Handler())
      	return
      }
      func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
          ......
      	engine.handleHTTPRequest(c)
          ......
      }
      
    2. 在engine.handleHTTPRequest© 中,根据request中的path,在路由表中获取路由对应的handler然执行

      func (engine *Engine) handleHTTPRequest(c *Context) {
          httpMethod := c.Request.Method
      	rPath := c.Request.URL.Path
          ......
      	// Find root of the tree for the given HTTP method
      	t := engine.trees
      	for i, tl := 0, len(t); i < tl; i++ {
      		if t[i].method != httpMethod {
      			continue
      		}
      		root := t[i].root
      		// Find route in tree
      		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
      		if value.params != nil {
      			c.Params = *value.params
      		}
      		if value.handlers != nil {
      			c.handlers = value.handlers
      			c.fullPath = value.fullPath
      			c.Next() //最终执行的handler
      			c.writermem.WriteHeaderNow()
      			return
      		}
      		......
      	}
      }
      
  • 路由的注册和匹配

    1. Engine继承了RouterGroup的能力,当我们注册handler时,或者在分组路由下去注册handler时,其实都是调用的RouterGroup的能力

      type Engine struct {
         RouterGroup
         ......
      }
      
    2. RouterGroup中保留了一个Engine指针,最终的路由是注册到了Engine中的路由表中,这个路由表没有设计在RouterGroup中,保证了RouterGroup的职责单一性,也就是只做路由分组。

      func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
          ......
      	group.engine.addRoute(httpMethod, absolutePath, handlers)
      	return group.returnObj()
      }
      
    3. Engine中保存了一个methodTree对象,这个就是路由表,是一个树状结构

      img

      type methodTree struct {
      	method string
      	root   *node
      }
      
      type node struct {
      	path      string
      	indices   string
      	wildChild bool
      	nType     nodeType
      	priority  uint32
      	children  []*node // child nodes, at most 1 :param style node at the end of the array
      	handlers  HandlersChain
      	fullPath  string
      }
      
      type methodTrees []methodTree
      
三、中间件执行流程
  • 中间件执行链路

    1. 中间件保存在GroupRouter中

      func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
         group.Handlers = append(group.Handlers, middleware...)
         return group.returnObj()
      }
      
    2. 每声明一次分组,都会将handler进行一次合并

      func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
      	return &RouterGroup{
      		Handlers: group.combineHandlers(handlers), //合并中间处理链路
      		basePath: group.calculateAbsolutePath(relativePath),
      		engine:   group.engine,
      	}
      }
      
    3. 以上只是将中间件合并成了一条链路,最终的业务逻辑会在执行GET.POST等请求方法时,拼接到执行链路末端

      func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
      	absolutePath := group.calculateAbsolutePath(relativePath)
      	handlers = group.combineHandlers(handlers) //这里参数的handlers是业务逻辑,合并到链路末尾。
      	group.engine.addRoute(httpMethod, absolutePath, handlers)
      	return group.returnObj()
      }
      
      
    4. 最终效果img

  • 前置处理和后置处理

    1. 假设需要对一段业务逻辑采集它的执行耗时,一般我们需要在执行逻辑前声明一个时间点,执行完后再用当前时间减去执行前的时间得到耗时。

      img

    2. 在gin中的实现方式是通过context的next方法去实现的,结合上面的流程,在中间件中每声明一次next,执行链会先去执行下一个handler,最终的效果应该是ABBA方式的执行。

      func (c *Context) Next() {
         c.index++
         for c.index < int8(len(c.handlers)) {
            c.handlers[c.index](c)
            c.index++
         }
      }
      
其他:

Gin的核心特性

  • 高性能:Gin的HTTP请求处理速度极快,能够支持大量的并发连接(gin并不是基准测试中最快的框架,但是相对较快,简单好用,用户体量大)
  • 简单易用:Gin的API设计直观,使得开发者可以快速上手并构建应用。
  • 中间件支持:Gin允许开发者通过中间件来扩展其功能,这为构建复杂的Web应用提供了可能。
  • 路由和参数绑定:Gin提供了强大的路由功能,支持参数绑定,使得URL处理更加灵活。
  • 数据渲染:Gin支持多种数据渲染方式,包括JSON、XML、HTML等,方便开发者根据需求选择合适的输出格式。

参考:https://geektutu.com/post/gee-day4.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1923012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端Canvas入门——Canvas的一些注意事项

如果我们使用标签的方式去创建Canvas&#xff0c;可以发现&#xff1a; 在我们创建画笔的时候&#xff0c;是不会有任何提示的。 这意味着&#xff0c;你需要记住所有API代码——这就很烦人。 那么&#xff0c;又没有一种优雅的、不失格调的方法&#xff1f; 那就是使用JS去创…

内网对抗-代理通讯篇不出网或不可达SockS全协议规则配置C2正反向上线解决方案

知识点 1、代理隧道篇-代理通讯网络不可达-正反向连接上线 2、代理隧道篇-代理通讯网络不可达-SockS代理配置 实现步骤 1、在被控机器上获取下一级网段 2、在被控及其上建立SockS节点 3、在工具上配置连接属性和规则触发演示案例-代理技术-正反向监听/SockS配置-网络不可达…

LabVIEW航空发动机试验器数据监测分析

1. 概述 为了适应航空发动机试验器的智能化发展&#xff0c;本文基于图形化编程工具LabVIEW为平台&#xff0c;结合航空发动机试验器原有的软硬件设备&#xff0c;设计开发了一套数据监测分析功能模块。主要阐述了数据监测分析功能设计中的设计思路和主要功能&#xff0c;以及…

Ubuntu使用Nginx部署uniapp打包的项目

使用uniapp导出web项目&#xff1a; 安装&#xff1a; sudo apt install nginx解压web.zip unzip web.zip移动到/var/www/html目录下&#xff1a; sudo cp -r ~/web/h5/ /var/www/html/重启Nginx&#xff1a; sudo service nginx restart浏览器访问&#xff1a;http://19…

Docker安装RabbitMQ(带web管理端)

1.拉取带web管理的镜像 可以拉取rabbitmq对应版本的web管理端&#xff0c;比如&#xff1a;rabbitmq:3.9.11-management&#xff0c;也可以直接拉取带web管理端的最新版本 rabbitmq:management. docker pull rabbitmq:3.9.11-management 注意&#xff1a;如果docker pull ra…

图像处理:使用 OpenCV-Python 卡通化你的图像(2)

一、说明 在图像处理领域&#xff0c;将图像卡通化是一种新趋势。人们使用不同的应用程序将他们的图像转换为卡通图像。如今&#xff0c;玩弄图像是许多人的爱好。人们通常会点击图片并添加滤镜或使用不同的东西自定义图像并将其发布到社交媒体上。但我们是程序员&#xff0c;…

基于重叠群稀疏的总变分信号降噪及在旋转机械故障诊断中的应用(MATLAB)

基于振动分析的故障诊断方法基本流程主要由以下五个步骤组成&#xff0c;分别是信号采集、信号处理、特征提取、状态识别与诊断结果。这五个步骤中信号采集与特征提取是故障诊断中最为重要的步骤&#xff0c;而故障微弱特征信息又是其中最难解决的问题。“故障微弱特征信息”站…

【Pro Controller连接PC】

软件下载连接 教学视频 安装64位驱动 按键修改

麦克风设计指南

前言&#xff1a; 本指南基于乐鑫的 ESP32-S3 系列语音开发板。 整机 mic 要求 麦克风电器性能推荐 麦克类型&#xff1a;全向型 MEMS 麦克风 SMD-4P,2.8x1.9mm MEMS 麦克风 顶视图 MEMS 麦克风 底视图 灵敏度 1 Pa 声压下模拟麦灵敏度不低于 -38 dBV&#xff0c;数字麦灵…

详细分析Sql Server中的declare基本知识

目录 前言1. 基本知识2. Demo3. 拓展Mysql4. 彩蛋 前言 实战探讨主要来源于触发器的Demo 1. 基本知识 DECLARE 语句用于声明变量 声明的变量可以用于存储临时数据&#xff0c;并在 SQL 查询中多次引用 声明变量&#xff1a;使用 DECLARE 语句声明一个或多个变量变量命名&a…

无人机之遥控器分类篇

一、传统遥控器 传统无人机遥控器一般包括开关键、遥控天线等基础装置。但是会随着无人机具体的应用和功能而开发不同的按键。它的信号稳定性远超对比其他遥控&#xff0c;而且遥控距离也更远&#xff08;一般遥控范围在100米或以上&#xff09;传统遥控器对于初学者来说比较难…

单体、分布式、微服务、Serverless及新兴部署模式分析

在数字化时代&#xff0c;软件架构的选择对于企业的技术战略至关重要。从单体架构到Serverless&#xff0c;再到服务网格和服务化模型&#xff0c;每一种架构模式都反映了特定时期内技术发展和业务需求的特点。本文将对这些架构模式的优缺点进行讨论&#xff0c;供大家参考。 部…

C++ | Leetcode C++题解之第231题2的幂

题目&#xff1a; 题解&#xff1a; class Solution { private:static constexpr int BIG 1 << 30;public:bool isPowerOfTwo(int n) {return n > 0 && BIG % n 0;} };

C#开发:Git的安装和使用

一、安装git 二、如何克隆代码&#xff1f; 1.找到某个本地目录&#xff0c;右键-gitbash 2. 输入以下代码&#xff08;红色是地址&#xff0c;在gitlab获取或联系管理员获取&#xff0c;下图为复制地址&#xff09;&#xff1a; git clone http://xxxxxxxxx.git 输入帐号和令…

【RAG检索增强生成】有道QAnything深度解析与实践

目录 引言1、QAnything简介2、技术解析2.1 RAG技术核心2.2 QAnything架构2.3 模型与算法 3、应用场景3.1 企业知识管理3.2 教育与学术研究3.3 客户服务 4、部署实践4.1 环境准备4.2 安装QAnything4.3 启动运行4.4 访问UI界面4.4 API使用4.4.1 新建知识库&#xff08;POST&#…

美团收银Android一面凉经(2024)

美团收银Android一面凉经(2024) 笔者作为一名双非二本毕业7年老Android, 最近面试了不少公司, 目前已告一段落, 整理一下各家的面试问题, 打算陆续发布出来, 供有缘人参考。今天给大家带来的是《美团收银Android一面凉经(2024)》。 应聘岗位: 美团餐饮PaaS平台Android开发工程师…

我的第128天创作纪念日

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;初阶数据结构笔记专栏&#xff1a; 初阶数据结构笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 文章…

从LeetCode215看排序算法

目录 LeetCode215 数组的第K个最大元素 ① 第一反应&#xff1a;java的内置排序Arrays.sort() ② 冒泡排序 ③归并排序&#xff08;先分解再合并&#xff09; ④快速排序&#xff08;边分解边排序&#xff09; ⑤堆排序 LeetCode215 数组的第K个最大元素 给定整数数组 nums…

谈谈软件交互设计

谈谈软件交互设计 交互设计的由来 交互设计(Interaction Design)这一概念,最初是由IDEO创始人之一Bill.Moggridge(莫格里奇)1984年在一次会议上提出。他设计了世界上第一台笔记本电脑Compass,并写作出版了在交互设计领域影响深远的《Designing Interactions》一书,被称…

【分库】分库的核心原则

目录 分库的核心原则 前言 分区透明性与一致性保证 弹性伸缩性与容错性设计 数据安全与访问控制机制 分库的核心原则 前言 在设计和实施分库策略时&#xff0c;遵循一系列核心原则是至关重要的&#xff0c;以确保系统不仅能够在当前规模下高效运行&#xff0c;还能够随着…