部署与维护

到目前为止,我们前面已经介绍了如何开发程序、调试程序以及测试程序,正如人们常说的:开发最后的10%需要花费90%的时间,所以这我们将强调这最后的10%部分,要真正成为让人信任并使用的优秀应用,需要考虑到一些细节,以上所说的10%就是指这些小细节。

应用日志

我们期望开发的Web应用程序能够把整个程序运行过程中出现的各种事件一一记录下来,Go语言中提供了一个简易的log包,我们使用该包可以方便的实现日志记录的功能,这些日志都是基于fmt包的打印再结合panic之类的函数来进行一般的打印、抛出错误处理。Go目前标准包只是包含了简单的功能,如果我们想把我们的应用日志保存到文件,然后又能够结合日志实现很多复杂的功能(编写过Java或者C++的读者应该都使用过log4j和log4cpp之类的日志工具),可以使用第三方开发的日志系统:logrus和seelog,它们实现了很强大的日志功能,可以结合自己项目选择。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

logrus介绍

logrus是用Go语言实现的一个日志系统,与标准库log完全兼容并且核心API很稳定,是Go语言目前最活跃的日志库

seelog介绍

seelog是用Go语言实现的一个日志系统,它提供了一些简单的函数来实现复杂的日志分配、过滤和格式化。主要有如下特性:

  • XML的动态配置,可以不用重新编译程序而动态的加载配置信息

  • 支持热更新,能够动态改变配置而不需要重启应用

  • 支持多输出流,能够同时把日志输出到多种流中、例如文件流、网络流等

  • 支持不同的日志输出

    • 命令行输出
    • 文件输出
    • 缓存输出
    • 支持log rotate
    • SMTP邮件
      上面只列举了部分特性,seelog是一个特别强大的日志处理系统,详细的内容请参看官方wiki。

使用应用日志

对于应用日志,每个人的应用场景可能会各不相同,有些人利用应用日志来做数据分析,有些人利用应用日志来做性能分析,有些人来做用户行为分析,还有些就是纯粹的记录,以方便应用出现问题的时候辅助查找问题。

举一个例子,我们需要跟踪用户尝试登陆系统的操作。这里会把成功与不成功的尝试都记录下来。记录成功的使用"Info"日志级别,而不成功的使用"warn"级别。如果想查找所有不成功的登陆,我们可以利用linux的grep之类的命令工具,如下:

# cat /data/logs/roll.log | grep "failed login"
2012-12-11 11:12:00 WARN : failed login attempt from 11.22.33.44 username password

通过这种方式我们就可以很方便的查找相应的信息,这样有利于我们针对应用日志做一些统计和分析。另外我们还需要考虑日志的大小,对于一个高流量的Web应用来说,日志的增长是相当可怕的,所以我们在seelog的配置文件里面设置了logrotate,这样就能保证日志文件不会因为不断变大而导致我们的磁盘空间不够引起问题。

通过上面对seelog系统及如何基于它进行自定义日志系统的学习,现在我们可以很轻松的随需构建一个合适的功能强大的日志处理系统了。日志处理系统为数据分析提供了可靠的数据源,比如通过对日志的分析,我们可以进一步优化系统,或者应用出现问题时方便查找定位问题,另外seelog也提供了日志分级功能,通过对minlevel的配置,我们可以很方便的设置测试或发布版本的输出消息级别。

网站错误处理

我们的Web应用一旦上线之后,那么各种错误出现的概率都有,Web应用日常运行中可能出现多种错误,具体如下所示:

  • 数据库错误:指与访问数据库服务器或数据相关的错误。例如,以下可能出现的一些数据库错误。

    • 连接错误:这一类错误可能是数据库服务器网络断开、用户名密码不正确、或者数据库不存在。
    • 查询错误:使用的SQL非法导致错误,这样子SQL错误如果程序经过严格的测试应该可以避免。
    • 数据错误:数据库中的约束冲突,例如一个唯一字段中插入一条重复主键的值就会报错,但是如果你的应用程序在上线之前经过了严格的测试也是可以避免这类问题。
    • 应用运行时错误:这类错误范围很广,涵盖了代码中出现的几乎所有错误。可能的应用错误的情况如下:

    • 文件系统和权限:应用读取不存在的文件,或者读取没有权限的文件、或者写入一个不允许写入的文件,这些都会导致一个错误。应用读取的文件如果格式不正确也会报错,例如配置文件应该是ini 的配置格式,而设置成了json格式就会报错。
    • 第三方应用:如果我们的应用程序耦合了其他第三方接口程序,例如应用程序发表文章之后自动调用接发微博的接口,所以这个接口必须正常运行才能完成我们发表一篇文章的功能。
  • HTTP错误:这些错误是根据用户的请求出现的错误,最常见的就是404错误。虽然可能会出现很多不同的错误,但其中比较常见的错误还有401未授权错误(需要认证才能访问的资源)、403禁止错误(不允许用户访问的资源)和503错误(程序内部出错)。

  • 操作系统出错:这类错误都是由于应用程序上的操作系统出现错误引起的,主要有操作系统的资源被分配完了,导致死机,还有操作系统的磁盘满了,导致无法写入,这样就会引起很多错误。

  • 网络出错:指两方面的错误,一方面是用户请求应用程序的时候出现网络断开,这样就导致连接中断,这种错误不会造成应用程序的崩溃,但是会影响用户访问的效果;另一方面是应用程序读取其他网络上的数据,其他网络断开会导致读取失败,这种需要对应用程序做有效的测试,能够避免这类问题出现的情况下程序崩溃。

错误处理的目标

在实现错误处理之前,我们必须明确错误处理想要达到的目标是什么,错误处理系统应该完成以下工作:

  • 通知访问用户出现错误了:不论出现的是一个系统错误还是用户错误,用户都应当知道Web应用出了问题,用户的这次请求无法正确的完成了。例如,对于用户的错误请求,我们显示一个统一的错误页面(404.html)。出现系统错误时,我们通过自定义的错误页面显示系统暂时不可用之类的错误页面(error.html)。
  • 记录错误:系统出现错误,一般就是我们调用函数的时候返回err不为nil的情况,可以使用前面小节介绍的日志系统记录到日志文件。如果是一些致命错误,则通过邮件通知系统管理员。一般404之类的错误不需要发送邮件,只需要记录到日志系统。
  • 回滚当前的请求操作:如果一个用户请求过程中出现了一个服务器错误,那么已完成的操作需要回滚。下面来看一个例子:一个系统将用户递交的表单保存到数据库,并将这个数据递交到一个第三方服务器,但是第三方服务器挂了,这就导致一个错误,那么先前存储到数据库的表单数据应该删除(应告知无效),而且应该通知用户系统出现错误了。
  • 保证现有程序可运行可服务:我们知道没有人能保证程序一定能够一直正常的运行着,万一哪一天程序崩溃了,那么我们就需要记录错误,然后立刻让程序重新运行起来,让程序继续提供服务,然后再通知系统管理员,通过日志等找出问题。

如何处理异常

我们知道在很多其他语言中有try...catch关键词,用来捕获异常情况,但是其实很多错误都是可以预期发生的,而不需要异常处理,应该当做错误来处理,这也是为什么Go语言采用了函数返回错误的设计,这些函数不会panic,例如如果一个文件找不到,os.Open返回一个错误,它不会panic;如果你向一个中断的网络连接写数据,net.Conn系列类型的Write函数返回一个错误,它们不会panic。这些状态在这样的程序里都是可以预期的。你知道这些操作可能会失败,因为设计者已经用返回错误清楚地表明了这一点。这就是上面所讲的可以预期发生的错误。

但是还有一种情况,有一些操作几乎不可能失败,而且在一些特定的情况下也没有办法返回错误,也无法继续执行,这样情况就应该panic。举个例子:如果一个程序计算x[j],但是j越界了,这部分代码就会导致panic,像这样的一个不可预期严重错误就会引起panic,在默认情况下它会杀掉进程,它允许一个正在运行这部分代码的goroutine从发生错误的panic中恢复运行,发生panic之后,这部分代码后面的函数和代码都不会继续执行,这是Go特意这样设计的,因为要区别于错误和异常,panic其实就是异常处理。

规则很简单:如果你定义的函数有可能失败,它就应该返回一个错误。当我调用其他package的函数时,如果这个函数实现的很好,我不需要担心它会panic,除非有真正的异常情况发生,即使那样也不应该是我去处理它。而panic和recover是针对自己开发package里面实现的逻辑,针对一些特殊情况来设计。

应用部署

程序开发完毕之后,我们现在要部署Web应用程序了,但是我们如何来部署这些应用程序呢?因为Go程序编译之后是一个可执行文件,编写过C程序的读者一定知道采用daemon就可以完美的实现程序后台持续运行,但是目前Go还无法完美的实现daemon,因此,针对Go的应用程序部署,我们可以利用第三方工具来管理,第三方的工具有很多,例如Supervisord、upstart、daemontools等,

daemon

目前Go程序还不能实现daemon,详细的见这个Go语言的bug:http://code.google.com/p/go/issues/detail?id=227,大概的意思说很难从现有的使用的线程中fork一个出来,因为没有一种简单的方法来确保所有已经使用的线程的状态一致性问题。

备份和恢复

应用备份

在大多数集群环境下,Web应用程序基本不需要备份,因为这个其实就是一个代码副本,我们在本地开发环境中,或者版本控制系统中已经保持这些代码。但是很多时候,一些开发的站点需要用户来上传文件,那么我们需要对这些用户上传的文件进行备份。目前其实有一种合适的做法就是把和网站相关的需要存储的文件存储到云储存,这样即使系统崩溃,只要我们的文件还在云存储上,至少数据不会丢失。

如果我们没有采用云储存的情况下,如何做到网站的备份呢?这里我们介绍一个文件同步工具rsync:rsync能够实现网站的备份,不同系统的文件的同步,如果是windows的话,需要windows版本cwrsync。

MySQL备份

应用数据库目前还是MySQL为主流,目前MySQL的备份有两种方式:热备份和冷备份,热备份目前主要是采用master/slave方式(master/slave方式的同步目前主要用于数据库读写分离,也可以用于热备份数据),关于如何配置这方面的资料,大家可以找到很多。冷备份的话就是数据有一定的延迟,但是可以保证该时间段之前的数据完整,例如有些时候可能我们的误操作引起了数据的丢失,那么master/slave模式是无法找回丢失数据的,但是通过冷备份可以部分恢复数据。

冷备份一般使用shell脚本来实现定时备份数据库,然后通过上面的rsync同步非本地机房的一台服务器。

MySQL恢复

前面介绍MySQL备份分为热备份和冷备份,热备份主要的目的是为了能够实时的恢复,例如应用服务器出现了硬盘故障,那么我们可以通过修改配置文件把数据库的读取和写入改成slave,这样就可以尽量少时间的中断服务。

但是有时候我们需要通过冷备份的SQL来进行数据恢复,既然有了数据库的备份,就可以通过命令导入:

mysql -u username -p databse < backup.sql
可以看到,导出和导入数据库数据都是相当简单,不过如果还需要管理权限,或者其他的一些字符集的设置的话,可能会稍微复杂一些,但是这些都是可以通过一些命令来完成的。

redis备份

redis是目前我们使用最多的NoSQL,它的备份也分为两种:热备份和冷备份,redis也支持master/slave模式,所以我们的热备份可以通过这种方式实现,相应的配置大家可以参考官方的文档配置,相当的简单。我们这里介绍冷备份的方式:redis其实会定时的把内存里面的缓存数据保存到数据库文件里面,我们备份只要备份相应的文件就可以,就是利用前面介绍的rsync备份到非本地机房就可以实现。

redis恢复

redis的恢复分为热备份恢复和冷备份恢复,热备份恢复的目的和方法同MySQL的恢复一样,只要修改应用的相应的数据库连接即可。

但是有时候我们需要根据冷备份来恢复数据,redis的冷备份恢复其实就是只要把保存的数据库文件copy到redis的工作目录,然后启动redis就可以了,redis在启动的时候会自动加载数据库文件到内存中,启动的速度根据数据库的文件大小来决定。

看到这我明白了我对数据库了解的还是太少o(╥﹏╥)o

最后的小结

本章讨论了如何部署和维护我们开发的Web应用相关的一些话题。这些内容非常重要,要创建一个能够基于最小维护平滑运行的应用,必须考虑这些问题。

具体而言,本章讨论的内容包括:

  • 创建一个强健的日志系统,可以在出现问题时记录错误并且通知系统管理员
  • 处理运行时可能出现的错误,包括记录日志,并如何友好的显示给用户系统出现了问题
  • 处理404错误,告诉用户请求的页面找不到
  • 将应用部署到一个生产环境中(包括如何部署更新)
  • 如何让部署的应用程序具有高可用
  • 备份和恢复文件以及数据库
    读完本章内容后,对于从头开始开发一个Web应用需要考虑那些问题,你应该已经有了全面的了解。本章内容将有助于你在实际环境中管理前面各章介绍开发的代码。

好刺激呀这一块

链接

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄