08 自研or借力(上):集成Gin替换已有核心

news/2024/4/26 3:21:36/文章来源:https://blog.csdn.net/Zerore/article/details/129276010

我们的框架和这些顶级的框架相比,差了什么呢?如何才能快速地把我们的框架可用性,和这些框架提升到同一个级别?我们做这个框架除了演示每个实现细节,它的优势是什么呢?

不妨带着这些问题,把我们新出炉的框架和 GitHub 上 star 数最高的Gin 框架比对一下,思考下之间的差距到底是什么。

和Gin对比

Gin 框架无疑是现在最火的框架,你能想出很多它的好处,但是在我看来,它之所以那么成功,最主要的原因在于两点:细节和生态

其实框架之间的实现原理都差不多,但是生产级别的框架和我们写的示例级别的框架相比,差别就在于细节,这个细节是需要很多人、很多时间去不断打磨的。

如果你的 Golang 经验积累到一定时间,那肯定能很轻松实现一个示例级别的框架,但是往往是没有开源市场的,因为你的框架,在细节上的设计没有经过很多人验证,也没有经过在生产环境中的实战。这些都需要一个较为庞大的使用群体和较长时间才能慢慢打磨出来。

Recovery的错误捕获

之前我们的错误捕获:


// recovery 机制,将协程中的函数异常进行捕获
func Recovery() framework.ControllerHandler {// 使用函数回调return func(c *framework.Context) error {// 核心在增加这个 recover 机制,捕获 c.Next()出现的 panicdefer func() {if err := recover(); err != nil {c.Json(500, err)}}()// 使用 next 执行具体的业务逻辑c.Next()return nil}
}

异常类型可能是底层连接出错,这种情况已经不同通过设置http状体码让浏览器感知,因为消息都发不出去了。这种情况下应该直接中断后续逻辑,gin的处理如下:


return func(c *Context) {defer func() {if err := recover(); err != nil {// 判断是否是底层连接异常,如果是的话,则标记 brokenPipevar brokenPipe boolif ne, ok := err.(*net.OpError); ok {if se, ok := ne.Err.(*os.SyscallError); ok {if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {brokenPipe = true}}}...if brokenPipe {// 如果有标记位,我们不能写任何的状态码c.Error(err.(error)) // nolint: errcheckc.Abort()} else {handle(c, err)}}}()c.Next()}

先判断了底层抛出的异常是否是网络异常(net.OpError),如果是的话,再根据异常内容是否包含“broken pipe”或者“connection reset by peer”,来判断这个异常是否是连接中断的异常。如果是,就设置标记位,并且直接使用 c.Abort() 来中断后续的处理逻辑。

这个处理异常的逻辑可以说是非常细节了,区分了网络连接错误的异常和普通的逻辑异常,并且进行了不同的处理逻辑。这一点,可能是绝大多数的开发者都没有考虑到的。

Recovery 的日志打印

  1. logger.Printf打印异常内容

logger.Printf("%s\n%s%s", err, ...)
  1. 异常是由某个请求触发的,所以触发这个异常的请求内容,也是必要的调试信息,需要打印

httpRequest, _ := httputil.DumpRequest(c.Request, false)
headers := strings.Split(string(httpRequest), "\r\n")
// 如果 header 头中有认证信息,隐藏,不打印。
for idx, header := range headers {
current := strings.Split(header, ":")if current[0] == "Authorization" {headers[idx] = current[0] + ": *"}
}
headersToStr := strings.Join(headers, "\r\n")

使用DumpRequest输出请求内容,包含header和body。
为了安全考虑 Gin 还注意到了,如果请求头中包含 Authorization 字段,即包含 HTTP 请求认证信息,在输出的地方会进行隐藏处理,不会由于 panic 就把请求的认证信息输出在日志中。

  1. 堆栈打印
    我们打印堆栈一般是使用 runtime 库的 Caller 来打印:

// 打印堆栈信息,是否有这个堆栈
func Caller(skip int) (pc uintptr, file string, line int, ok bool)

caller 方法返回的是堆栈函数所在的函数指针、文件名、函数所在行号信息。但是在使用过程中你就会发现,使用 Caller 是打印不出真实代码的。比如下面这个例子:


// 在 prog.go 文件,main 库中调用 call 方法
func call(skip int) bool {   //24 行pc,file,line,ok := runtime.Caller(skip) //25 行pcName := runtime.FuncForPC(pc).Name()  //26 行fmt.Println(fmt.Sprintf("%v %s %d %s",pc,file,line,pcName)) //27 行return ok //28 行
} //29 行

打印出的第一层堆栈函数的信息:

4821380 /tmp/sandbox064396492/prog.go 25 main.call

这个堆栈信息并不友好,它告诉我们,第一层信息所在地址为 prog.go 文件的第 25 行,在 main 库的 call 函数里面。所以如果想了解下第 25 行有什么内容,用这个堆栈信息去源码中进行文本查找,是做不到的。这个时候就非常希望信息能打印出具体的真实代码。

在 Gin 中,打印堆栈的时候就有这么一个逻辑:先去本地查找是否有这个源代码文件,如果有的话,获取堆栈所在的代码行数,将这个代码行数直接打印到控制台中。


// 打印具体的堆栈信息
func stack(skip int) []byte {...// 循环从第 skip 层堆栈到最后一层for i := skip; ; i++ { pc, file, line, ok := runtime.Caller(i)// 获取堆栈函数所在源文件if file != lastFile {data, err := ioutil.ReadFile(file)if err != nil {continue}lines = bytes.Split(data, []byte{'\n'})lastFile = file}// 打印源代码的内容fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))}return buf.Bytes()
}

这样,打印出来的堆栈信息形如:


/Users/yejianfeng/Documents/gopath/pkg/mod/github.com/gin-gonic/gin@v1.7.2/context.go:165 (0x1385b5a)(*Context).Next: c.handlers[c.index](c)

这个堆栈信息就友好多了,它告诉我们,这个堆栈函数发生在文件的 165 行,它的代码为 c.handlersc.index, 这行代码所在的父级别函数为 (*Context).Next。

最终,Gin 打印出来的 panic 信息形式如下:


2021/08/15 14:18:57 [Recovery] 2021/08/15 - 14:18:57 panic recovered:
GET /first HTTP/1.1
Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/ *;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
...
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36%!s(int=121321321)
/Users/yejianfeng/Documents/UGit/gindemo/main.go:19 (0x1394214)main.func1: panic(121321321)
/Users/yejianfeng/Documents/gopath/pkg/mod/github.com/gin-gonic/gin@v1.7.2/context.go:165 (0x1385b5a)(*Context).Next: c.handlers[c.index](c)
/Users/yejianfeng/Documents/gopath/pkg/mod/github.com/gin-gonic/gin@v1.7.2/recovery.go:99 (0x1392c48)CustomRecoveryWithWriter.func1: c.Next()
/Users/yejianfeng/Documents/gopath/pkg/mod/github.com/gin-gonic/gin@v1.7.2/context.go:165 (0x1385b5a)(*Context).Next: c.handlers[c.index](c)
...
/usr/local/Cellar/go/1.15.5/libexec/src/net/http/server.go:1925 (0x12494ac)(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/Cellar/go/1.15.5/libexec/src/runtime/asm_amd64.s:1374 (0x106bb00)goexit: BYTE    $0x90   // NOP

这里可以参考log日志包的实现,看日志包里是怎么打印的

路由对比

之前我们将url按斜杠分隔,每段保存在一个节点中。Gin的路由选择是一种压缩后的基数树(radix tree), 它把整个URL当作字符串,尽可能匹配相同的字符串作为公共前缀。

在这里插入图片描述
radix tree 和 trie 树相比,最大的区别就在于它节点的压缩比例最大化。直观比较上面两个图就能看得出来,对于 URL 比较长的路由规则,trie 树的节点数就比 radix tree 的节点数更多,整个数的层级也更深。

针对路由这种功能模块,创建路由树的频率远低于查找路由点频率,那么减少节点层级,无异于能提高查找路由的效率,整体路由模块的性能也能得到提高,所以 Gin 用 radix tree 是更好的选择。

另外,gin的路由查找中,每个节点有indices 字符串,这个字符串的每个字符代表子节点的第一个字符,这个查找子节点不用遍历,可以节省时间。

生态

其实不然。一个开源项目的成功,最重要的是两个事情,第一个是质量,开源项目的代码质量是摆在第一位的,但是还有一个是不能被忽略的:生态的完善。

一个好的开源框架项目,不仅仅只有代码,还需要围绕着核心代码打造文档、框架扩展、辅助命令等。这些代码周边的组件和功能的打造,虽然从难度上看,并没有核心代码那么重要,但是它是一个长期推进和完善的过程。

【小结】:

  1. 现代框架的理念不在于实现,而更多在于组合。基于某些基础组件或者基础实现,不断按照自己或者公司的需求,进行二次改造和二次开发,从而打造出适合需求的形态。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ClickHouse的架构与基本概念

一、ClickHouse的定义 ClickHouse是一个完全的列式分布式数据库管理系统(DBMS),允许在运行时创建表和数据库,加载数据和运行查询,而无需重新配置和重新启动服务器,支持线性扩展,简单方便,高可靠性&#xf…

C++学习笔记-内存空间

考虑这样一种情况,当我们使用相同的名称,叫Zara的两个人在同一个班级。我们需要明确区分它们将不得不使用一些额外的信息,如他们的名字,如他们生活在不同的区域或母亲或父亲的名字等等。 同样的情况也出现在C应用程序中。例如&am…

iphone系统崩溃数据能恢复吗?教你三招方法

最近有些苹果用户反应自己手机的屏幕无法滑动,桌面上APP也无法点开,想要关机重启下试试,可是,连关机都关不了,甚至连Siri都罢工了。苹果手机系统崩溃,出现黑屏、白屏、无限重启之类的故障,导致手…

大数据处理学习笔记1.6 Scala数据结构

文章目录零、本讲学习目标一、数组 (Array)(一)定长数组1、数组定义(1)定义数组时初始化数据(2)定义时指定数组长度,后赋值2、数组遍历(1)传统for循环方式(2&…

Databend 开源周报 第 82 期

Databend 是一款现代云数仓。专为弹性和高效设计,为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务:https://app.databend.com 。Whats New探索 Databend 本周新进展,遇到更贴近你心意的 Databend 。Features & Improvements :…

【沐风老师】3dmax一键窗户生成器插件使用方法详解

3dmax一键窗户生成器插件教程 3dMax一键窗户生成器是一个在3dMax中自动创建3D窗户模型的脚本。它有28种风格的窗户样式,可以在Archviz项目中灵活应用,同时为3D艺术家节省大量时间。 【适用版本】 适用3dMax 2018.2及更高版本 【安装方法】 1.解压缩包&…

林心如常驻《向往的生活》,周杰却陷地域黑,做人的差别太大了吧

十年前如果有人提起周杰,就算是不能如雷贯耳,最起码也是妇孺皆知,毕竟那时候他太有名气了。因为拍摄《还珠格格》,让他和林心如等人一起爆红,不过此后的林心如,却很少再有优秀作品问世。 而周杰却不一样&am…

AOP在PowerJob中的使用,缓存锁保证并发安全,知识细节全总结

这是一篇简简单单的文章,需要你简简单单看一眼就好,如果有不明白的地方,欢迎留言讨论。 在之前的文章中出现过一次AOP的使用,就是在运行任务之前,需要判断一下,触发该任务执行的server,是不是数…

AIGC被ChatGPT带火!底层基础算力有望爆发式增长

ChatGPT火爆全球的背后,可以窥见伴随人工智能技术的发展,数字内容的生产方式向着更加高效迈进。ChatGPT属于AIGC的具体应用,而AIGC是技术驱动的数字内容新生产方式。AIGC类产品未来有望成为5G时代新的流量入口,率先受益的有望是AI…

MySQL实战之深入浅出索引(下)

1.前言 在上一篇文章中,我们介绍了InnoDB索引的数据结构模型,今天我们再继续聊一下跟MySQL索引有关的概念。 在介绍之前,我们先看一个问题: 表初始化语句 mysql> create table T ( ID int primary key, k int NOT NULL DEFA…

03、SVN 建立版本库

SVN 建立版本库1 版本库2 版本库的建立步骤2.1 创建版本库的根目录2.2 创建子目录2.3 通过命令创建版本库2.4 生成目的介绍1 版本库 Subversion 是将文件数据信息保存到版本库中进行管理的Subversion 允许用户对版本库目录进行定制 2 版本库的建立步骤 2.1 创建版本库的根目…

RK3568平台开发系列讲解(驱动基础篇)Makefile 详解

🚀返回专栏总目录 文章目录 一、Makefile是什么二、Makefile 详解三、Makefile 语法沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将详细介绍Makefile。 一、Makefile是什么 如果只编译一个hello.c文件,非常简单,所以直接执行下面的指令非常方便: gcc hel…

Java List去重 Lis集合去重 List去重效率对比 List去重复元素效率对比 List去重效率

Java List去重 Lis集合去重 List去重效率对比 List去重复元素效率对比 List去重效率 --- List 去重复元素的几种办法 一、概述 面试的时候,有个常见的问题:“List集合如何去除重复元素”。 常见的回答是:“set集合,for循环对比&a…

KingbaseES V8R3 表加密

前言 透明加密是指将数据库page加密后写入磁盘,当需要读取对应page时进行加密读取。此过程对于用户是透明, 用户无需干预。 该文档进行数据库V8R3版本测试透明加密功能,需要说明,该版本发布时间早于V8R6,所以只能进行表…

SQLite安装及常用语句

SQLite简介SQLite 是一个软件库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是在世界上最广泛部署的 SQL 数据库引擎。SQLite 源代码不受版权限制。SQLite安装官网下载 SQLite Download Page新建一个sqlite文件夹,将下载…

【Servlet篇2】创建一个web项目

在上一篇文章当中,已经提到了什么是Maven,以及如何使用maven从中央仓库下载jar包。【Tomcat与Servlet篇1】认识Tomcat与Maven_革凡成圣211的博客-CSDN博客Tomcat,mavenhttps://blog.csdn.net/weixin_56738054/article/details/129228140?spm…

2023年java春招面试题及答案

2023年java春招面试题1、下面有关jdbc statement的说法错误的是?2、下面有关JVM内存,说法错误的是?3、下面有关servlet service描述错误的是?4、下面有关servlet和cgi的描述,说法错误的是?5、下面有关SPRIN…

LeetCode 1237. Find Positive Integer Solution for a Given Equation【双指针,二分,交互】

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…

开发场景中前端交付的对于后端数据的获取功能书写+页面简繁体转换+页面链接跳转新页面

1,开发场景中前端交付对于后端数据的获取功能书写 首先,我们明确基本逻辑概念,前端获取数据本质是利用ajax中的api接口来获取变量,再将其导入我们的data; 明确基本概念开发就可以进行ajax的定义 下文中e变量是获取前端…

全志T3+FPGA国产核心板——Pango Design Suite的FPGA程序加载固化

本文主要基于紫光同创Pango Design Suite(PDS)开发软件,演示FPGA程序的加载、固化,以及程序编译等方法。适用的开发环境为Windows 7/10 64bit。 测试板卡为全志T3+Logos FPGA核心板,它是一款基于全志科技T3四核ARM Cortex-A7处理器 + 紫光同创Logos PGL25G/PGL50G FPGA设计…