Gee教程7.错误恢复(Panic Recover)

这一章节是实现错误处理机制,是通过中间件形式来实现的。

panic

     Go 语言中,比较常见的错误处理方法是返回 error,由调用者决定后续如何处理。但是如果是无法恢复的错误,就会触发panic。用户也可以手动触发 panic,当然如果在程序运行过程中出现了类似于数组越界的错误,panic 也会被触发。

panic 会中止当前执行的程序,退出

defer

panic 会导致程序被中止,但是在退出前,会先处理完当前协程上已经defer 的任务,执行完成后再退出。

可以 defer 多个任务,在同一个函数中 defer 多个任务,会逆序执行。即先执行最后 defer 的任务。在这里,defer 的任务执行完成之后,panic 还会继续被抛出,导致程序非正常结束。

func main() {defer fmt.Println("defer func")arr := []int{1, 2}fmt.Println(arr[3])
}

 


recover

 Go 语言还提供了 recover 函数,可以避免因为 panic 发生而导致整个程序终止,recover 函数只在 defer 中生效

func main() {test_recover()//test_recover2()	//该函数内有两个defer函数fmt.Println("after recvoer")
}func test_recover() {defer func() {fmt.Println("defer func")if err := recover(); err != nil {fmt.Println("recvoer success")}}()arr := []int{1, 2}fmt.Println(arr[3])    //在该句发生painc,就会交由defer处理,下面的"after painc"不会打印fmt.Println("after painc")
}func test_recover2() {defer func() {fmt.Println("defer net/http")if err := recover(); err != nil {fmt.Println("net/http recover success")}}()defer func() {fmt.Println("defer 中间件")if err := recover(); err != nil {fmt.Println("中间件 recvoer success")}}()arr := []int{1, 2}fmt.Println(arr[3])fmt.Println("after painc")
}

test_recover结果

可以看到,recover 捕获了 panic,程序正常结束。当 panic 被触发时,控制权就被交给了 defer 。而在 main() 中打印了 after recover,说明程序已经恢复正常,继续往下执行直到结束。

$ go run main.go 
defer func
recover success
after recover

 test_recover2函数的效果可自行测试。

web框架的错误处理机制

 对一个 Web 框架而言,错误处理机制是很有必要的。可能是框架本身没有完备的测试,导致在某些情况下出现空指针异常等情况。也有可能用户不正确的参数,触发了某些异常,例如数组越界,空指针等。如果因为这些原因导致系统宕机,必然是不可接受的。

首先是要明确知道net/http是实现了recover机制捕获的。

//源码1.21.3  src/net/http/server.go文件
//3026行, accept一个新连接,并开启一个协程处理该新连接
func (srv *Server) Serve(l net.Listener) error {for {rw, err := l.Accept()......................go c.serve(connCtx)}
}// 1857行
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {............defer func() {if err := recover(); err != nil && err != ErrAbortHandler {....................}}()for {serverHandler{c.server}.ServeHTTP(w, w.req)}
}

我们将在该web框架中添加一个非常简单的错误处理机制,即在此类错误发生时,向用户返回 Internal Server Error,并且在日志中打印必要的错误信息,方便进行错误定位。

我们之前实现了中间件机制,错误处理也可以作为一个中间件,增强该web框架的能力。

中间件 Recovery代码实现

func Recovery() HandlerFunc {return func(c *Context) {defer func() {if err := recover(); err != nil {message := fmt.Sprintf("%s", err)log.Printf("%s\n\n", trace(message))c.Fail(http.StatusInternalServerError, "Internal Server Error")}}()c.Next()    //可以试试注释该行代码,看看效果}
}
// print stack trace for debug
func trace(message string) string {var pcs [32]uintptrn := runtime.Callers(3, pcs[:]) // skip first 3 callervar str strings.Builderstr.WriteString(message + "\nTraceback:")for _, pc := range pcs[:n] {fn := runtime.FuncForPC(pc)file, line := fn.FileLine(pc)str.WriteString(fmt.Sprintf("\n\t%s:%d", file, line))}return str.String()
}

Recovery 的实现非常简单,使用 defer 挂载上错误恢复的函数,在这个函数中调用 recover(),捕获 panic,并且将堆栈信息打印在日志中,向用户返回 Internal Server Error,其中 trace() 函数是用来获取触发 panic 的堆栈信息。可以不用过于深入了解trace函数,我们主要是要实现这个错误处理机制。不使用第六行代码也可以实现该错误处理机制,只是没有打印出来错误的堆栈信息

trace函数

在 trace() 中,调用了 runtime.Callers(3, pcs[:]),Callers 用来返回调用栈的程序计数器, 第 0 个 Caller 是 Callers 本身,第 1 个是上一层 trace,第 2 个是再上一层的 defer func。因此,为了日志简洁一点,我们跳过了前 3 个 Caller。接着,通过 runtime.FuncForPC(pc) 获取对应的函数,在通过 fn.FileLine(pc) 获取到调用该函数的文件名和行号,打印在日志中。

再说回Recovery的实现,重点讲讲第11行的c.Next()。要是不使用该行代码的话,该中间件就立即结束了,就会执行defer中的函数,那要是出现了panic的错误的话,这里就不能恢复。

使用该行代码的话,那该中间件就一直在执行。等到执行路由Handler时,要是此时出现panic错误的话,控制权就被交给了该中间件的defer,这样就可以进行错误恢复了,不会宕机。

其实不使用也不会宕机的,因为net/http是实现了recover机制捕获的。

疑问:net/http是实现了recover机制捕获,为什么web框架还要自定义recver机制捕获?

自定义 recovery 中间件是一种常见的做法,用于捕获并处理应用程序的运行时错误,以避免整个应用程序崩溃并返回对应格式的响应数据

我认为主要是可以返回对应格式的响应数据。

按照下面的测试例子,不使用自写的recovery中间件的话,在浏览器访问会显示 无法显示此网页。

而使用recovery中间件,那可以显示{"message":"Internal Server Error"},即是返回我们给定的格式的响应数据。

测试

func main() {r := gee.Default() //Default是使用了recovery中间件的r.GET("/", func(c *gee.Context) {c.String(http.StatusOK, "Hello Gee\n")})// index out of range for testing Recovery()r.GET("/panic", func(c *gee.Context) {names := []string{"gee"}c.String(http.StatusOK, names[100])})r.Run("localhost:10000")
}

思考:在其他协程中发生了panic,会被recover捕捉到吗?

先思考一个问题。在没有使用自写的recovery中间件的时候,触发panic情况时候,是net/http中的recover捕获了panic。而在使用自写的recovery中间件时,是其中间件捕获了panic。这是怎样的?

这就回到recover中的test_recover2函数了。再回顾下该函数,其内有两个defer函数。

func test_recover2() {defer func() {fmt.Println("defer net/http")if err := recover(); err != nil {fmt.Println("net/http recover success")}}()defer func() {fmt.Println("defer 中间件")if err := recover(); err != nil {fmt.Println("中间件 recvoer success")}}()arr := []int{1, 2}fmt.Println(arr[3])fmt.Println("after painc")
}

这就很像是使用了recvoery中间件的例子。第一个defer的函数就是net/http中的serve方法的defer recvoer,而第二个defer就是中间件的defer。

多个defer可以想象成是栈结构,先进后出嘛。这结果就是中间件的recover捕获到了panic

再回到该主题,在其他协程触发了panic,中间件或者net/http会捕获到吗?回答是不会

在 Go 语言中,我们无法在父协程里捕获子协程的异常(panic)。如果服务有异常没有处理,整个进程都会退出。

net/http和中间件的recover都是作用在路由Handler所在的协程的。

来看例子:

func main() {r := gee.Default() //Default是使用了recovery中间件的// index out of range for testing Recovery()r.GET("/panic", func(c *gee.Context) {names := []string{"geek"}c.String(http.StatusOK, names[100])})//开启子线程go func() {arr := []int{1, 2}fmt.Println(arr[4])}()r.Run("localhost:10000")
}

结果:开启新协程,recovery中间件是捕获不到其他协程的panic。

再看另一种情况,在路由Handler协程中开启新协程

func main() {r := gee.Default() //Default是使用了recovery中间件的r.GET("/bad", func(c *gee.Context) {//在路由Handler协程中去开启新线程go func() {arr := []int{1, 2}fmt.Println(arr[4])}()c.String(http.StatusOK, "hello")})r.Run("localhost:10000")
}

结果:也一样是捕获不到panic的。 

 所以,在其他协程触发了panic,中间件或者net/http不会捕捉到的,那整个进程就会退出。

所以,不能认为gin框架不会宕机的。在开启新协程中有某些操作使用不当的话,会触发panic,那就会宕机的。

因此,尽量少在路由Handler协程中再开启新协程;或者开启新协程后,需要谨慎处理。

完整代码:https://github.com/liwook/Go-projects/tree/main/gee-web/7-recover

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

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

相关文章

【PTA题目】7-31 前世档案 分数 20

7-31 前世档案 分数 20 全屏浏览题目 切换布局 作者 陈越 单位 浙江大学 网络世界中时常会遇到这类滑稽的算命小程序,实现原理很简单,随便设计几个问题,根据玩家对每个问题的回答选择一条判断树中的路径(如下图所示&#xff09…

【算法】约瑟夫环

约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N6,M5,被杀掉的顺序是:5,4,6,2&a…

搭梯子之后电脑连接WIFI打不开浏览器网页:远程计算机或者设备不接受连接

问题描述: 打不开网页,但是能正常使用微信等app windows网络诊断: 远程计算机或者设备不接受连接 解决办法: 电脑搜索【internet选项】 进入连接,点击局域网设置,将里面的代理服务器选项关掉就可以正常打开…

【Angular开发】Angular中的高级组件

在这个博客中,我将解释Angular中的几个高级组件和机制,它们增强了灵活性、可重用性和性能。 通过熟悉这些高级组件和机制,您可以提高您的Angular开发技能,并在应用程序中利用灵活性、可重用性和性能优化的能力。让我们开始吧&…

flink中处理kafka分区的消息顺序

背景 kafka分区的消息是有序的,那么flink在消费kafka分区的时候消息的顺序是怎么样的呢?还能保持这个有序性吗,本文就来记录下 flink消费kafka分区的顺序性 从上图可知,flink的转换算子比如map,flatMap,f…

一次重新加载所有 maven 项目产生的 OOM

1、解决什么问题? 忘了截图了,用文字描述就是由于Reload All Maven Projects导致的 OOM 异常。 2、尝试与解决 2.1、尝试 2.1.1、尝试清理idea缓存(无效) 2.1.2、重启idea(无效) 2.1.3、重启电脑&am…

Python替代Adobe从PDF提取数据

大家好,PDF文件是官方报告、发票和数据表的通用格式,然而从PDF文件中提取表格数据是一项挑战。尽管Adobe Acrobat等工具提供了解决方案,但它们并不总是易于获取或可自动化运行,而Python则是编程语言中的瑞士军刀。本文将探讨如何利…

SpringMVC修炼之旅(2)基础入门

一、第一个程序 1.1环境配置 略 1.2代码实现 package com.itheima.controller;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;//定义…

Oozie创建定时执行脚本

1,前期创建的有test.sql表数据脚本,一个脚本my_workflow_1,可以看我的上一篇博客Oozie调度工具–一次性脚本文件的创建和执行 2,点击这个查询下面的计划程序中的计划 3,先修改这个项目的名字,然后选择Workflow里面之前自己测试执…

class051 二分答案法与相关题目【算法】

class051 二分答案法与相关题目【算法】 算法讲解051【必备】二分答案法与相关题目 code1 875. 爱吃香蕉的珂珂 // 爱吃香蕉的珂珂 // 珂珂喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉 // 警卫已经离开了,将在 h 小时后回来。 // 珂珂…

Windows利用MMDeploy部署OpenMMLab 模型并使用Python进行部署

目录 前言 一、准备工作 二、安装 MMDeploy 总结 前言 近期在用OpenMMLab构建模型,然后需要使用MMDeploy对模型进行部署。虽然官方文档提供了详细的说明,但是写的太繁琐了,而且在实际部署过程中,发现并不是所有步骤和内容都需要&…

MySQL数据库基础篇

文章目录 前言1.MySQL数据库概述2.SQL2.1 SQL通用语法2.2 SQL分类2.3 DDL2.3.1 数据库操作2.3.2 表操作 2.4 图形化界面工具2.5 DML2.5.1 添加数据2.5.2 修改数据2.5.3 删除数据 2.6 DQL2.6.1 基本语法2.6.2 基础查询2.6.3 条件查询2.6.4 聚合函数2.6.5 分组查询2.6.6 排序查询…

deepflow本地部署过程

本地服务器配置,32C,48G内存 整个过程需要配置k8s,安装helm, 安装grafana, 安装deepflow以及deepflow-ctl,以及部署demo 在采用sealos进行ALL-IN-ONE部署之前, grafana 先安装它 wget -q -O /usr/share/keyrings/gr…

智能优化算法应用:基于蜜獾算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于蜜獾算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于蜜獾算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蜜獾算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

python+requests+excel 接口测试

1、EXCEL文件接口保存方式,如图。 2、然后就是读取EXCEL文件中的数据方法,如下: 1 import xlrd2 3 4 class readExcel(object):5 def __init__(self, path):6 self.path path7 8 property9 def getSheet(self): 10 …

【JavaWeb学习笔记】6 - Tomcat

项目代码 零、在线文档 Apache Tomcat 8 (8.0.53) - Documentation Index WEB开发 1. WEB,在英语中web表示网/网络资源(页面,图片,css,js)意思,它用于表示WEB服务器(主机)供浏览器访问的资源 2. WEB服务器(主机)上供外界访问的Web资源分为: 静态web…

区块链创新应用场景不断拓展,实现去中心化

小编介绍:10年专注商业模式设计及软件开发,擅长企业生态商业模式,商业零售会员增长裂变模式策划、商业闭环模式设计及方案落地;扶持10余个电商平台做到营收过千万,数百个平台达到百万会员,欢迎咨询。 区块…

python自动化测试框架:unittest测试用例编写及执行

本文将介绍 unittest 自动化测试用例编写及执行的相关内容,包括测试用例编写、测试用例执行、测试报告等内容。 官方文档: https://docs.python.org/zh-cn/3/library/unittest.mock.html 1. 测试用例编写 在 unittest 中,一个测试用例通常…

外包干了3个月,技术倒退2年。。。

先说情况,大专毕业,18年通过校招进入湖南某软件公司,干了接近6年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&#xf…

01-Redis核心数据结构与高性能原理

一、Redis的单线程和高性能 1. Redis是单线程吗? Redis的单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的(说白了也就是执行命令的时候是由一个线程来完成的),这也是 Redis 对外提供键值存储服务的主要流程。…