diff --git a/12.2.md b/12.2.md index 32f025f0..85fffafb 100644 --- a/12.2.md +++ b/12.2.md @@ -16,17 +16,107 @@ - 网络出错:指两方便的错误,一方面是用户请求应用程序的时候出现网络断开,这样就导致连接中断,这种错误不会造成应用程序的崩溃,但是会影响用户访问的效果,另一方面是应用程序读取其他网络上的数据,这种网络断开会导致读取失败,这种需要对应用程序做有效的测试,能够避免这类问题出现的情况下程序崩溃。 ## 错误处理的目标 -在实现错误处理之前,我们必须明确错误处理想要达到的目标是什么,错误处理系统应该完成一下工作: +在实现错误处理之前,我们必须明确错误处理想要达到的目标是什么,错误处理系统应该完成以下工作: -- 通知访问用户出现错误了 -- 记录错误 -- 回滚当前的请求 -- 根据错误级别发送邮件 -- 保证现有程序可运行可服务 +- 通知访问用户出现错误了:不论出现的是一个系统错误还是用户错误,用户都应当知道Web应用出了问题,用户的这次请求无法正确的完成了。例如用户的错误请求我们显示一个统一的错误页面(404.html),出现系统错误我们通过自定义的错误页面显示系统暂时不可用之类的错误页面(error.html) +- 记录错误:系统出错错误时,一般就是我们调用函数的时候返回err不为nil的情况下,使用前面小节介绍的日志系统记录到日志文件,如果是一些致命错误通过邮件通知系统管理员,例如一般404之类的错误不需要发送邮件,只需要记录到日志系统。 +- 回滚当前的请求:如果一个用户请求过程中出现了一个服务器错误,那么已完成的操作需要回滚。下面来看一个例子:一个系统讲用户递交的表单保存到数据库,并将这个数据递交到一个第三方服务器,但是第三方服务器挂了,这就导致一个错误,那么先前存储到数据库的表单数据应该删除,而且应该通知用户系统出现错误了。 +- 保证现有程序可运行可服务:我们知道没有人能保证程序一定能够一直正常的运行着,万一哪一天程序崩溃了,那么我们就需要记录错误,然后立刻让程序重新运行起来,让程序继续提供服务,然后再通知系统管理员,通过日志等找出问题。 ## 如何处理错误 +错误处理其实我们已经在十一章第一小节里面有过介绍如何设计错误处理,这里我们再从一个例子详细的讲解一下,如何来处理不同的错误: + +- 通知用户出现错误: + + 通知用户在访问页面的时候我们可以有两种错误:404.html和error.html,下面分别显示了错误页面的源码: + + + + + 找不到页面 + + + + +
+
+
+
+

404!

+

{{.ErrorInfo}}

+
+
+
+
+ + + 另一个源码: + + + + + 系统错误页面 + + + + +
+
+
+
+

系统暂时不可用!

+

{{.ErrorInfo}}

+
+
+
+
+ + + + 404的错误处理逻辑,如果是系统的错误也是类似的操作,同时我们看到在: + + func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + NotFound404(w, r) + return + } + + func NotFound404(w http.ResponseWriter, r *http.Request) { + log.Error("页面找不到") //记录错误日志 + t, _ = t.ParseFiles("tmpl/404.html", nil) //解析模板文件 + ErrorInfo := "文件找不到" //获取当前用户信息 + t.Execute(w, ErrorInfo) //执行模板的merger操作 + } + + func SystemError(w http.ResponseWriter, r *http.Request) { + log.Critical("系统错误") //系统错误触发了Critical,那么不仅会记录日志还会发送邮件 + t, _ = t.ParseFiles("tmpl/error.html", nil) //解析模板文件 + ErrorInfo := "系统暂时不可用" //获取当前用户信息 + t.Execute(w, ErrorInfo) //执行模板的merger操作 + } + +## 如何处理异常 +我们知道在很多其他语言中有try...catch操作,可以捕获异常情况,但是其实很多的错误我们都是可以预期的,而不是异常处理,应该当做错误来处理,这也是为什么Go语言采用了函数返回错误的设计,这些函数不会panic,例如如果一个文件找不到,os.Open返回一个错误,它不会panic;如果你向一个中断的网络连接写数据,net.Conn系列类型的Write函数返回一个错误,它们不会panic。这些状态在这样的程序里都是可以预期的。你知道这些操作可能会失败,因为设计者已经用返回错误清楚地表明了这一点。这就是上面所讲的可以预期的错误。 + +但是还有一种情况,有一些操作几乎不可能失败,而且在一些特定的情况下也没有办法返回错误,也无法继续执行,这样情况就应该panic。举个例子:如果一个程序计算x[j],但是j越界了,这部分代码就会导致panic,像这样的一个不可预期严重错误就会引起panic,在默认情况下它会杀掉进程,它允许一个正在运行这部分代码的goroutine从发生错误的panic中恢复运行,发生panic之后,这部分代码后面的函数和代码都不会继续执行,这是Go特意这样设计的,因为要区别于错误和异常,panic其实就是异常处理。如下代码,我们期望通过uid来获取User中的username信息,但是如果uid越界了就会抛出异常,这个时候如果我们没有recover机制,进程就会被杀死,从而导致程序不可服务。因此为了我们程序的健壮性,在一些地方我们需要建立recover机制。 + + func GetUser(uid int) (username string) { + defer func() { + if x := recover(); x != nil { + username = "" + } + }() + + username = User[uid] + return + } + +上面我们介绍了错误和异常的区别,那么我们在开发程序的时候如何来设计呢?规则很简单:如果你的函数无论如何有可能失败,它就应该返回一个错误。当我调用其他package的函数时,如果这个函数实现的很好,我不需要担心它会panic,除非有真正的异常情况发生,即使那样也不应该是我去处理它。而panic和recover是针对自己开发package里面实现的逻辑,针对一些特殊情况来设计。 ## 小结 - +本小节总结了当我们的Web应用部署之后如何处理各种错误:网络错误、数据库错误、操作系统错误等,当错误发生时,我们的程序如何来正确的处理,显示友好的出错界面、回滚操作、记录日志、通知管理员等操作,最后介绍了如何来正确的处理错误和异常,一般的程序中错误和异常都是混淆的,但是在Go程序中错误和异常是有区分的,所以最后介绍了程序设计的时候如何来遵循这样的原则。 ## links * [目录]() * 上一章: [应用日志](<12.1.md>) diff --git a/12.3.md b/12.3.md index 213503a6..92299517 100644 --- a/12.3.md +++ b/12.3.md @@ -1,6 +1,15 @@ # 12.3 应用部署 +程序开发完毕之后,我们现在要部署Web应用程序了,但是我们如何来部署这些应用程序呢?因为Go程序编译之后是一个可执行文件,编写过C程序的读者一定知道使用demon就可以完美的实现程序后台运行,但是目前Go还无法完美的实现demon,因此,针对Go的应用程序部署,我们可以利用第三方工具来管理,第三方的工具有很多,例如Supervisord、upstart、daemontools等,这小节我介绍目前自己系统中采用的工具Supervisord。 +## deamon +我们可以看到很多网上的一些实现demon的方法,例如下面两种方式: http://code.google.com/p/go/issues/detail?id=227 +## Supervisord +Supervisord是用Python实现的一款非常实用的进程管理工具。 + +## 小结 + + ## links * [目录]()