如何监控一个微博接口返回错误的请求和返回情况

3381人阅读
知识纵横(68)
&& &考虑以下这个非常常见的WEB开发部署场景:
&& &在开发环境下,如果要调试APPSERV1向APPSERV2的接口调用,我们通常可以直接用IDE跟代码,或者用wireshark抓包进行观察。完成 接口调用的监控是一件容易的事情。
&& &但如果场景发生在测试环境中,要监控SERV1与SERV2之间的通信就麻烦得多。我能够想到的可能的手段是:为这个请求单独写一个测试用例,直接执行观 察。或者在APPSERV1上也装上tshark或者wireshark,用UI的,还得再通过X11的x-forwarding转发。总之,不管是哪种 方法,之前都介于复杂性和较低的可操作性,被我们拒绝了。&& &其实我们很多人都知道在所有的Linux distro上,都会自带tcpdump。不少人也知道tcpdump是个强大的网络监测工具,但很少人把tcpdump用来监测。其主要原因,我想还是 因为tcpdump强大得有点让人望而生畏。&& &最近研究了下,觉得非常handy,只要在要监控的主机(APPSERV1)上运行以下命令,即可监控APPSERV1与APPSERV2之间的通信:&& &# tcpdump &A -n -i eth1 &s0 'host 10.20.156.9 and tcp port 9003 and (((ip[2:2] - ((ip[0]&0xf)&&2)) - ((tcp[12]&0xf0)&&2)) != 0)'&&& &参数简单介绍(其实man page上都有,觉得不够详细的,自己RTFM吧):&& &-A 显示抓取的包的内容&& &-n 不要作DNS反向解析。否则的话,软件会试图去查询这个IP的域名(或者主机名)。通常,关闭这一项可以提高速度。(插一句,反查在我看来相当可恶,没必 要又拖累速度。但几乎所有GNU的那些东西,如ping, traceroute,默认都会打开,真不了解那些开发是怎么想的。)&& &-i 后面必须指出发生接口调用通信的接口设备名称&& &-s 这个参数用来告诉tcpdump是否要对抓取的包进行truncate。这个参数费了我不少力气,因为不同版本的tcpdump,对这项的默认值是不一致 的。4.1.0及以后的版本,默认值都是65535字节。但我们测试环境里的tcpdump版本是3.9.4,这项值只有96字节。所以一开始在我本机运 行得好好的同一条命令,在测试环境下总是得到阉割的数据,非常郁闷,后来还是跑到tcpdump的邮件组里面问了后,才要到答案的。这里配置0,表示不作 truncate。&& &host:指出APPSERV2的地址,也可以是域名&& &tcp port:指出接口服务监听的端口,一般都配在antx.properties中&& &(((ip[2:2] - ((ip[0]&0xf)&&2)) - ((tcp[12]&0xf0)&&2)) != 0):这个不用细究了,说的简单点,就是告诉tcpdump忽略掉tcp包中的SYN和FIN,只留下携带数据,对我们有意义的ACK包。&& &执行情况(有测试环境的截图为证):&
&& &整个调用过程尽在眼底,一览无遗。&& &下面再来讲讲用tcpdump的不足。&& &1.&&& 权限问题。要从接口抓取包,自然也不是随随便便哪个用户都可以做的,需要root权限。这个可以考虑用SUID或者SGID解决。&& &2.&&& tcpdump只限于http接口的调用。因为被监测的数据流是ASCII编码的,所以对于序列化的hessian和dubbo接口,可能获取不到有意义 的数据。
&& &========================================================================
&& &PS:这里谈的是对外部应用提供的URL式的接口进行的,通常情况下我们也可以直接输入这个URL的地址进行访问,不过通过这样的监控方式不仅可以监控被调用方,也可以监控调用方,看调用方有没有将被调用方需要的参数传过去,以及被调用方的返回结果。
&& &PS:我们也可以结合FIDDER,就可以看到调用方在调用接口时,传递的参数是否正确。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:6764155次
积分:60188
积分:60188
排名:第43名
原创:462篇
转载:209篇
译文:28篇
评论:1766条
(3)(7)(7)(10)(3)(5)(2)(3)(10)(3)(1)(10)(8)(1)(1)(11)(13)(18)(9)(5)(2)(13)(14)(31)(1)(1)(2)(3)(4)(13)(3)(1)(12)(6)(4)(6)(4)(2)(1)(6)(15)(35)(11)(3)(2)(25)(2)(3)(13)(2)(5)(2)(1)(15)(5)(7)(8)(41)(33)(54)(40)(7)(2)(4)(1)(12)(17)(3)(8)(8)(1)(55)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'jQuery实现监控页面所有ajax请求的方法
作者:思齐_
字体:[ ] 类型:转载 时间:
这篇文章主要介绍了jQuery实现监控页面所有ajax请求的方法,涉及jQuery中ajax请求的判定技巧,具有一定参考借鉴价值,需要的朋友可以参考下
本文实例讲述了jQuery实现监控页面所有ajax请求的方法。分享给大家供大家参考,具体如下:
你是不是有遇到这样的问题:页面发起两个ajax请求,希望它们都成功以后,再做一个动作?
很容易想到的解决方案是,等其中一个结束以后,再发起另外一个,这个过程用回调函数来完成。
但是,如果其中一个ajax请求的代码不是你写,你改不了,怎么办?
又或者说,你只想知道某个url请求什么时候结束,不想管其他的请求,怎么弄?
&!DOCTYPE html&
&html lang="en"&
&meta charset="utf-8" /&
&p id="test"&&/p&
&script src="js/jquery-1.11.0.min.js"&&/script&
&!--首先在页面引入jquery的后面,紧接着以下代码:--&
//前提:所有ajax请求都是用jquery的$.ajax发起的,而非原生的XHR;
var ajaxBack = $.
var ajaxCount = 0;
var allAjaxDone = function(){$('#test').append('all done!&br&');} //一行代码,就可以知道所有ajax请求什么时候结束
//由于get/post/getJSON等,最后还是调用到ajax,因此只要改ajax函数即可
$.ajax = function(setting){
ajaxCount++;
plete = function(){
if($.isFunction(cb)){cb.apply(setting.context, arguments);}
ajaxCount--;
if(ajaxCount==0 && $.isFunction(allAjaxDone)){
allAjaxDone();
ajaxBack(setting);
&!--以下是别人的script--&
$.ajax({url: 'js/jquery-1.11.0.min.js', success: function(recv){$('#test').append('别人的ajax请求1,done&br&')}});
$.get('css/main.css', null, function(recv){$('#test').append('别人的get请求,done&br&')});
$.post('css/main.css', null, function(recv){$('#test').append('别人的post请求,done&br&')});
其他的相关函数:
$.ajax 中:
error:当出错时调用,可以用来上报错误的请求。
complete:无论成功还是失败都会调用
希望本文所述对大家jQuery程序设计有所帮助。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
http://test.php
method:post
param:{"name":"12333","id":"2"}
需要监控的情况
1.如果用户进行创建数据或更新数据,就要监控
2.对价格进行修改需要监控,然后发有邮件通知接口负责人,"有人修改了商品价格"
目前已经实现了,
method ip port 的记录
param : 请求参数
result 响应接口
user_id 接口请求者
请问还可以怎么细化这个接口监控的,谢谢?
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
我简单回答下,题主自行查阅相关文档
接口频率的限制
Laravel 5.2以上自带 throttle 中间件控制
Laravel 5 可以用第三方扩展包实现
接口更新可以直接监听 Eloquent 事件,相关事件文档地址
例如监听 User 的 saving 事件,事件中判断 User 的昵称是否修改,简要代码如下
User::saving(function(User $user){
// 判断昵称是否修改
if ($user-&isDirty('nick_name')) {
$old_nick_name = $user-&getOriginal('nick_name'); // 原始值
$new_nick_name = $user-&nick_
Mail::raw("$user-&name 把昵称 $old_nick_name 修改为 $new_nick_name"); // 发送邮件
请求参数,可以针对每个接口定义每个 Request 的参数规则 rules,统一拦截处理
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
在代码里不可以实现? 如果考虑性能,用消息队列不就可以了或者增加日志呗,让OP进行日志监控,匹配发邮件
同步到新浪微博
分享到微博?
Hi,欢迎来到 SegmentFault 技术社区!⊙▽⊙ 在这里,你可以提出编程相关的疑惑,关注感兴趣的问题,对认可的回答投赞同票;大家会帮你解决编程的问题,和你探讨技术更新,为你的回答投上赞同票。
明天提醒我
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"contributes":[{"sourceColumn":{"lastUpdated":,"description":"","permission":"COLUMN_PUBLIC","memberId":641448,"contributePermission":"COLUMN_PUBLIC","translatedCommentPermission":"all","canManage":true,"intro":"聊聊存储,聊聊分布式,聊聊高并发","urlToken":"c_","id":39946,"imagePath":"v2-49e7526620deff29ae1fe8fc0fa7ef7e.jpg","slug":"c_","applyReason":"0","name":"一个后端工程师的独白","title":"一个后端工程师的独白","url":"/c_","commentPermission":"COLUMN_ALL_CAN_COMMENT","canPost":true,"created":,"state":"COLUMN_NORMAL","followers":564,"avatar":{"id":"v2-49e7526620deff29ae1fe8fc0fa7ef7e","template":"/{id}_{size}.jpg"},"activateAuthorRequested":false,"following":false,"imageUrl":"/v2-49e7526620deff29ae1fe8fc0fa7ef7e_l.jpg","articlesCount":4},"state":"accepted","targetPost":{"titleImage":"/v2-d352647dbb9e0351e40afd987b0d156e_r.jpg","lastUpdated":,"imagePath":"v2-d352647dbb9e0351e40afd987b0d156e.jpg","permission":"ARTICLE_PUBLIC","topics":[2],"summary":"1. 背景此处所说的服务监控程序,是指通过模拟用户的请求,对一个系统的服务质量进行监控的程序。服务监控程序的主要目的是:从用户的角度,通过发送端到端的请求,确认系统对外提供的服务是否正常。简单来说,一个好的服务监控程序应该具备以下功能:检测…","copyPermission":"ARTICLE_COPYABLE","translatedCommentPermission":"all","likes":0,"origAuthorId":0,"publishedTime":"T11:28:39+08:00","sourceUrl":"","urlToken":,"id":3162214,"withContent":false,"slug":,"bigTitleImage":false,"title":"一个服务监控程序的实现和部署","url":"/p/","commentPermission":"ARTICLE_ALL_CAN_COMMENT","snapshotUrl":"","created":,"comments":0,"columnId":39946,"content":"","parentId":0,"state":"ARTICLE_PUBLISHED","imageUrl":"/v2-d352647dbb9e0351e40afd987b0d156e_r.jpg","author":{"bio":"","isFollowing":false,"hash":"00e480c7b79eda994eebd7dff5414ff9","uid":92,"isOrg":false,"slug":"nofrish","isFollowed":false,"description":"","name":"王健","profileUrl":"/people/nofrish","avatar":{"id":"v2-317ec69c59e24711cc25ebaa86090ac2","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"memberId":641448,"excerptTitle":"","voteType":"ARTICLE_VOTE_CLEAR"},"id":685650}],"title":"一个服务监控程序的实现和部署","author":"nofrish","content":"1. 背景此处所说的服务监控程序,是指通过模拟用户的请求,对一个系统的服务质量进行监控的程序。服务监控程序的主要目的是:从用户的角度,通过发送端到端的请求,确认系统对外提供的服务是否正常。简单来说,一个好的服务监控程序应该具备以下功能:检测服务是否存活检测服务质量是否正常检测服务质量的变化趋势对每日的监控数据做统计报表运维友好的……需要首先说明的是,监控一个系统服务是否正常是一个全方位的工作,需要进行如进程监控、请求错误率监控、网络流量监控,日志监控等工作。然而服务监控程序通常有着不可替代的作用,例如,通过将服务监控程序部署在用户的机器上,就可以从用户的角度对系统的服务质量进行监控,包括请求响应时间,请求错误率等等。本文接下来首先分析在开发一个服务监控程序时需要考虑的方方面面,接着介绍在部署和运维一个服务监控程序的时候需要注意的问题。 2. 监控目标要实现一个服务监控程序,首先需要明确要对服务可能出现的哪些异常情况进行监控,以下列举了一些可能出现的服务异常:Dead – 服务已经挂掉(如进程异常退出,系统异常重启等)Unreachable – 服务不可达(如DNS异常,网络中断等)Unresponsive – 请求可以正常发送,但是服务无响应(如服务器内部异常等)Slow – 服务可以正确响应,但是响应时间过长(如服务器过载等)Wrong – 服务可以及时响应,但是结果不符合预期(如服务器内部数据库异常等)……(以下还有很多异常)对于以上的一些错误,如服务进程异常退出等,可以通过进程监控等及时发现。然而对于服务不可达,或者服务响应时间变长等,通常服务内部的监控并不能及时发现,此时就需要依赖服务监控程序。 部署服务监控程序的一个重要目标,就是当由于系统内部的错误、或者系统外部依赖的环境的异常导致用户的请求受到影响时,可以在短时间(1分钟)发现并及时处理。同时,通过及时分析服务监控程序的请求记录,也可以对系统可能出现的瓶颈及时预警。举例来说:当服务监控程序的请求响应时间逐渐变长时,则说明系统可能需要扩容了。3. 服务监控程序的实现实现一个服务监控程序相对容易:只要模拟用户的请求发送到服务器,当请求超时或失败时则进行报警通知管理员即可。然而要实现一个好的监控程序,达到报警及时、准确——出问题时马上报警,不出问题不误报警,并不是一件容易的事情。以下介绍实现一个好的服务监控程序时需要注意的一些问题。3.1. 服务监控程序对系统的影响要尽量小服务监控程序对被监控系统的影响要尽量小,被监控系统不能为了因为处理服务监控程序的探测请求而消耗太多资源,这里的资源包括:网络资源、计算资源、存储资源等。举例来说,我们的对象存储服务对用户提供了以下功能:用户可以向自己的存储空间中上传和下载对象用户可以对一个图片对象进行图片处理相关操作(如裁剪,给图片加水印等)用户可以对一个视频对象进行视频处理相关操作(如截取视频帧,视频转码等)还提供了其他一些操作,如List自己存储空间中的对象,批量删除对象等操作我们的每一种服务都由一个相对独立的集群提供:上传下载服务有数据库和存储集群图片处理服务有图片处理集群视频处理服务有视频处理集群 我们的服务监控程序在实现时有以下考虑:为了检测服务上传下载服务,需要模拟用户发送对象上传下载请求。这里要注意的是,上传下载的对象不能太大,原因有两个:一是服务器处理逻辑对于大对象和小对象是一样的;二是避免因为上传或下载一个大对象出现的网络波动或磁盘负载抖动。为了检测图片处理服务,只需要对一个小图片进行最简单的图片处理即可。避免对一个大图片进行过于复杂的图片处理带来的CPU消耗。为了检测视频处理服务,只需要发送对一个小视频截取第一帧的请求即可。如果截取一个大视频的任意帧,程序可能需要将整个视频全部读到本地才能完成,这会带来巨大的网络和磁盘开销。我们实现的服务监控程序不会执行一些复杂的操作,如批量删除对象等。原因是批量删除对象实际上最终是调用删除单个对象的接口,而我们会在检测上传下载服务中对删除对象的接口进行调用(上传 -& 下载 -& 删除)。这里所要强调的是:在写服务监控程序的时候,要明确需要如何监控自己的系统,并且用最小的代价来进行探测。3.2. 及时发现系统异常服务监控程序周期性地向被监控系统发送探测请求,为了能及时发现系统异常,这个探测周期就需要设置的足够小。然而若探测周期设置的过小,必然对被监控系统的影响会提高。因此需要根据被监控系统的具体情况来设定该周期。例如对于我们的对象存储来说,文件读写是核心服务,而图片和视频处理是附加服务。因此对于核心的上传下载服务,探测周期均为1分钟一次,而图片和视频则可以将周期设置为5分钟。这里所要强调的是:为了设置合理的探测周期,要能够对被监控系统的重要程度进行区分,根据不同的重要性来设置不同的探测周期。当然,如果探测请求对系统的影响很小,那么所有的探测请求设置的足够频繁也没有问题。3.3. 精确报警所谓精确报警,就是做到“系统正常不报警,系统异常必报警”,做到了这一点,那一旦运维人员收到相关的报警,则需要将该报警作为第一优先级立即进行处理。此处有一个问题需要注意:即系统偶尔的小抖动(如网络丢包,或者系统偶尔负载提高导致响应时间变长等),不应该发送报警,而只需要记入日志并在次日发送邮件给运维人员即可。这就需要在开发服务监控程序时考虑相关的处理逻辑。以下给出我们的服务监控程序探测上传下载服务的相关处理逻辑:每分钟发送1次上传、下载、删除请求,判断请求返回值是否符合预期,同时记录每次请求的响应时间;如果请求的返回值符合预期,且响应时间没有超过设置的阈值,则等待一分钟再次发送请求;否则,将错误信息记入日志,并执行第3步;连续发送5次请求,并判断这5次请求的返回结果和执行时间。如果超过2次请求出错或超时,则发送报警,否则只记日志不发送报警。该处理逻辑中所有的次数(包括发送请求次数,报警阈值等)均可以在配置文件中设置,保证运维人员可以根据实际情况配置合理的值。这种实现方式,可以有效防止偶发的错误造成的误报警。当然,对于那些出错的请求,需要好好进行分析。3.4. 全面监控子系统 通常,一个服务由一个集群来提供,前端通过Openresty的负载均衡来将用户请求分发到该集群的各个工作节点。这里可能会出现的一个问题是:当一个工作节点出现异常时,前端用户的请求可能不会受到影响,然而系统的运维人员一定需要知道该信息。我们的做法是,在给各个工作节点配置如进程监控,日志监控等常规监控的基础上,通过部署在内部某节点的服务监控程序将请求直接发送到该工作节点上,用于监控该节点的服务质量。我们的服务监控程序同时监控了对象存储服务的外部域名、内部域名、前端Openresty的虚拟IP、真实IP,各个服务子节点的服务端口等。 3.5. 服务监控程序的监控 服务监控程序监控系统运行状况,然而作为一个服务本身,它的状态也应该被监控起来。否则,如果服务监控程序本身已经异常退出,则无法在系统故障时及时报警。服务监控程序的监控需要从开发和运维两个方面进行保障,在这里先谈谈开发时需要考虑的因素:要考虑运维误操作可能会kill掉服务监控程序,因此程序中需要捕获kill信号并发送报警或输出一条FATAL级别日志(用于日志监控);服务监控程序可以考虑提供HTTP访问接口,供外部程序监控服务监控程序的状态。当系统部署多套服务监控程序时,也可以利用该接口查看各个服务监控程序所在机器的服务质量;…… 3.6. 多重报警机制当服务监控程序可以及时、精确发现服务异常时,需要通过短信报警(或邮件等其他报警)将问题告诉给系统运维人员。在开发时需要考虑服务监控程序需要提供至少两套报警机制,防止其中一种报警方式失灵的情况。举例来说,服务监控程序至少可以采用以下几种方式进行报警:直接调用公共的报警短信接口(如运维平台提供的Http接口)发送报警;通过记录错误日志,进行日志监控,当日志中出现特定关键字时进行报警;将监控的数据推送到统一的监控平台,在监控平台中实现逻辑判断和报警发送 3.7. 其他若干注意事项以下再列举一些其他在开发服务监控程序时的注意事项:报警信息要准确发送报警信息很重要的一方面在于帮助开发和运维尽快定位问题,因此报警信息一定要准确。下面是一些推荐的报警信息:“192.168.0.1上对象读写监控程序连续5次请求超时”“10.10.0.138上的视频截图服务出现错误,错误信息为:...”可以对配置文件进行动态reload例如,如果服务监控程序有一个配置文件:hosts.conf,表示该监控程序需要探测的所有服务器地址。那当该配置文件修改时,程序可以在下次探测时去探测新加入的地址。这样减少了运维关闭服务监控程序和重启的操作,也就减小了出错的风险。服务监控程序的重试我们之前实现的服务监控程序,内部采用Java-SDK中提供的PutObject函数和GetObject等函数。这些函数在某些错误的情况下会进行重试,而这个重试很有可能导致屏蔽了某种可能的系统异常。服务监控程序的日志服务监控程序的日志也应该按照生产系统的标准,输出操作日志和错误日志等,规范的日志可以大大加速错误定位过程。防止服务监控程序导致系统过载在系统已经过载的情况下,请求可能被拒绝或超时。此时,当服务监控程序判断系统已经过载时,则发送报警。同时应该暂停(如暂停10分钟)向服务器发送探测请求,减轻系统负担。将所有服务的监控都放在一个程序中我们将所有服务的监控(上传下载、图片处理、视频处理)都集成在一套代码中,通过配置文件可以分别对各类监控进行配置。这样做的好处是:代码维护和运维成本低,因为只需要维护一套代码,部署一套代码即可。然而需要注意的是,所有的监控都部署在一台机器上,可能导致该机器的占用较多的带宽,因此需要特别注意采样数据不能太大。服务监控程序的代码应该纳入版本管理需要把服务监控程序作为整个系统的一个重要部分,进行代码的管理。4. 服务监控程序的部署与运维开发服务监控程序只走完了长征路的第一步,而剩下的两万五千里路都依赖服务的部署和运维。以下介绍部署和运维服务监控程序的注意事项。4.1. 多点部署 将服务监控程序进行多点部署至少有两个方面的目的:服务监控程序的高可用——防止一台服务监控程序挂掉的情况从用户角度监控服务——通过将服务监控程序部署在用户的服务器上,可以从用户的角度监控服务质量多点部署也有以下问题需要注意:服务监控程序必须能够支持多点部署,因此在开发的时候需要注意多个服务监控程序不能相互干扰;部署在用户服务器上的监控程序要占用尽量少的资源,避免对用户本身的系统产生影响,在部署时也可以通过配置若干参数:如探测周期、探测请求数量等,降低服务监控程序占用资源;4.2. 报警阈值调节在开发服务监控程序时,需要尽量将所有的参数做成可配置参数,同时可以动态reload,如:要监控哪些服务器要监控哪类操作监控周期是多少每个监控周期发送多少请求请求超时时间是多少一次请求出错后要连续发送多少请求报警接收人信息……而运维人员的工作是,需要根据被检测系统的实际情况设置这些参数,并且调节这些参数为一个合理的值。参数调节应该遵循“先紧后松”的原则,即开始是可以将报警阈值设置的敏感一些,然后去分析每次报警,如果确认是误报,再将报警阈值调高一点。同时,及时不报警,也应该及时分析系统的错误日志,确保没有报警遗漏。 4.3. 报警可用性检查 所谓报警可用性检查,就是运维人员需要定期对服务监控程序的报警逻辑进行验证,确保这些报警都能正确发出并被相关人员接收。验证报警可用性并不是一件容易的事情,因为有时候可能需要主动触发一些错误逻辑,这会使该验证过程耗时费力。因此在开发服务监控程序时,需要考虑如何给运维提供方便的可用性验证接口。例如,可以在配置文件中设置一个字段testAlarm,当该字段设为true时,服务监控程序主动触发一些错误,例如对一个文本文件进行图片缩略操作,或者下载对象时指定一个错误的文件名等,这样便可以很方便的触发一次报警,用于验证报警可用性。4.4. 报警处理当接收到一条报警时,运维人员应该有相对应的报警处理流程,哪怕该流程是“执行ping操作验证网络是否联通,并通知服务监控程序所在服务器的相关产品方”。有很多的文章强调报警必须要Actionable,即可被处理的报警。运维和开发需要一起确认哪些错误应该发送报警,而哪些错误只要计入日志或在次日邮件报表通知即可。如果在收到一条报警而无事可做,那设置该报警的意义不大。 4.5. 其他若干注意事项 以下再列举一些部署和运维服务监控程序时的注意事项:任何一个报警,需要发给至少两个相关人员,且有一个主要负责人员发给至少两个人,是为了确保报警不会被遗漏;而发给相关人员,是为了防止非相关人员因为经常收到与自己无关的报警短信而忽略了发给自己的重要报警短信。服务监控程序的上线流程服务监控程序也需要执行完整的从开发到测试到部署上线的流程。尽管服务监控程序只是一个外部程序,然而我们通常会在内网部署让它可以将请求直接发送到后端各个子节点,因此也存在一定的风险,顾需要严格按照上线流程执行。","updated":"T03:28:39.000Z","canComment":false,"commentPermission":"anyone","commentCount":1,"collapsedCount":1,"likeCount":13,"state":"published","isLiked":false,"slug":"","lastestTipjarors":[],"isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-d352647dbb9e0351e40afd987b0d156e_r.jpg","links":{"comments":"/api/posts//comments"},"reviewers":[],"topics":[{"url":"/topic/","id":"","name":"后端技术"},{"url":"/topic/","id":"","name":"云计算"},{"url":"/topic/","id":"","name":"分布式存储"}],"adminClosedComment":false,"titleImageSize":{"width":1000,"height":667},"href":"/api/posts/","excerptTitle":"","column":{"slug":"c_","name":"一个后端工程师的独白"},"tipjarState":"activated","tipjarTagLine":"真诚赞赏,手留余香","sourceUrl":"","pageCommentsCount":1,"tipjarorCount":0,"annotationAction":[],"hasPublishingDraft":false,"snapshotUrl":"","publishedTime":"T11:28:39+08:00","url":"/p/","lastestLikers":[{"bio":"","isFollowing":false,"hash":"f62fbf8f767c9f2b61381","uid":72,"isOrg":false,"slug":"ma-yt","isFollowed":false,"description":"谦虚求教","name":"小能人","profileUrl":"/people/ma-yt","avatar":{"id":"cfd1fcee19a3","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":null,"isFollowing":false,"hash":"96b35b892cd44ce85c9e91fa6d20975a","uid":40,"isOrg":false,"slug":"wan-die-he","isFollowed":false,"description":"","name":"晚昳禾","profileUrl":"/people/wan-die-he","avatar":{"id":"2c7dc7468","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":null,"isFollowing":false,"hash":"5fee9ef1fe5f31c6c6492f","uid":04,"isOrg":false,"slug":"rex-qian","isFollowed":false,"description":"","name":"Rex Qian","profileUrl":"/people/rex-qian","avatar":{"id":"195ef5cb3","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"知乎重度点赞用户/脑残果粉","isFollowing":false,"hash":"58daceb94d3af44f7680f5a","uid":40,"isOrg":false,"slug":"lojunren","isFollowed":false,"description":"","name":"lojunren","profileUrl":"/people/lojunren","avatar":{"id":"541a51c0e5be71f71e49b94c5923799b","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"Freelancer","isFollowing":false,"hash":"185fdadead757fb518d3fdd","uid":04,"isOrg":false,"slug":"isRwj","isFollowed":false,"description":"","name":"kongzZ","profileUrl":"/people/isRwj","avatar":{"id":"791eb89a6","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"summary":"1. 背景此处所说的服务监控程序,是指通过模拟用户的请求,对一个系统的服务质量进行监控的程序。服务监控程序的主要目的是:从用户的角度,通过发送端到端的请求,确认系统对外提供的服务是否正常。简单来说,一个好的服务监控程序应该具备以下功能:检测…","reviewingCommentsCount":0,"meta":{"previous":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-4e34dece690_r.jpg","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"云计算"},{"url":"/topic/","id":"","name":"分布式系统"},{"url":"/topic/","id":"","name":"后端技术"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"","isFollowing":false,"hash":"00e480c7b79eda994eebd7dff5414ff9","uid":92,"isOrg":false,"slug":"nofrish","isFollowed":false,"description":"","name":"王健","profileUrl":"/people/nofrish","avatar":{"id":"v2-317ec69c59e24711cc25ebaa86090ac2","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"c_","name":"一个后端工程师的独白"},"content":"0. 缘起大约在三年前,我曾经写过一篇 ,还被码农周刊选为那年的
之一。当时我任职于网易杭州研究院的存储平台组,主要做网易对象存储(NOS)的开发和部分运维工作。由于网易云音乐,易信等几个重要产品陆续上线,业务压力剧增,我们的系统在前前后后大约半年的时间里,出现了大大小小各种事故。通过不断总结事故原因、不断地优化代码、进化部署架构,才使整个系统逐渐稳定下来。那个时候组里人常常开玩笑说,我们采用的是TDD的开发模式,只是这个TDD不是测试驱动开发(Test Driven Development),而是悲剧驱动开发(Tragedy Driven Development)。最佳日志实践的第一版便是在那个时候完成的,里面包含了我们在开发和运维过程中的一些好的实践。最初起名“最佳日志实践”实在有些标题党,不过由于起名字是一件比写代码更困难的事儿,我就继续沿用这个名字吧。有几个原因让我一直想要对那篇文章进行整理和扩充:那篇文章里的一些内容太细节,涉及到了网易对象存储中的业务逻辑,对读者不够友好;那篇文章里一些内容基于Java语言来讨论,实际上之后我有很多的精力都在基于Go语言做开发,因此现在更想要讨论一些与语言无关的方面;最近几年又写了若干个系统,对于日志这件事情又有了一些心得和体会,也想拿来跟大家分享;开始正文吧...1. 什么是日志日志用来记录用户操作、系统运行状态等,是一个系统的重要组成部分。然而,由于日志通常不属于系统的核心功能,所以常常不被团队成员所重视。对于一些简单的小程序,可能并不需要在如何记录日志的问题上花费太多精力。但是对于作为基础平台为很多产品提供服务的后端程序,就必须要考虑如何依靠良好的日志来保证系统可靠的运行了。好的日志可以帮助系统的开发和运维人员:了解线上系统的运行状态快速准确定位线上问题发现系统瓶颈预警系统潜在风险挖掘产品最大价值……不好的日志导致:对系统的运行状态一知半解,甚至一无所知系统出现问题无法定位,或者需要花费巨大的时间和精力无法发现系统瓶颈,不知优化从何做起无法基于日志对系统运行过程中的错误和潜在风险进行监控和报警对挖掘用户行为和提升产品价值毫无帮助……2. 日志的分类日志从功能来说,可分为诊断日志、统计日志、审计日志。诊断日志, 典型的有:请求入口和出口外部服务调用和返回资源消耗操作: 如读写文件等容错行为: 如云硬盘的副本修复操作程序异常: 如数据库无法连接后台操作:定期执行删除的线程启动、关闭、配置加载统计日志:用户访问统计:用户IP、上传下载的数据量,请求耗时等计费日志(如记录用户使用的网络资源或磁盘占用,格式较为严格,便于统计)审计日志:管理操作对于简单的系统,可以将所有的日志输出到同一个日志文件中,并通过不同的关键字进行区分。而对于复杂的系统,将不同需求的日志输出到不同的日志文件中是必要的,通过对不同类型的文件采用不同的日志格式(例如对于计费日志可以直接输出为Json格式),可以方便接入其他的子系统。3. 日志中记录什么理想的日志中应该记录不多不少的信息。所谓不多,是指不要在日志中记录无用的信息。实践中常见到的无用的日志有:1)能够放在一条日志里的东西,放在多条日志中输出;2)预期会发生且能够被正常处理的异常,打印出一堆无用的堆栈;3)开发人员在开发过程中为了调试方便而加入的“临时”日志所谓不少,是指对于日志的使用者,能够从日志中得到所有需要的信息。在实践中经常发生日志不够的情况,例如:1)请求出错时不能通过日志直接来定位问题,而需要开发人员再临时增加日志并要求请求的发送者重新发送同样的请求才能定位问题;2)无法确定服务中的后台任务是否按照期望执行;3)无法确定服务的内存数据结构的状态;4)无法确定服务的异常处理逻辑(如重试)是否正确执行;5)无法确定服务启动时配置是否正确加载;6)等等等等输出日志时要考虑日志的使用者,例如如果日志主要由系统的运维人员来看,那就不能输出:[INFO] RequestID:bdd2611184, ErrorCode:1426 \n至少应该是:[INFO] RequestID:bdd2611184, ErrorCode:1426, Message: callback request (to /callback) failed due to socket timeout\n这样运维人员一眼就能清楚问题的原因,而不需要再通过开发来查看ErrorCode对应的具体错误。整理一下通常情况下会遗漏的日志:系统的配置参数:系统在启动过程中通常会首先读启动参数,可以在系统启动后将这些参数输出到日志中,方便确认系统是按照期望的参数启动的;后台定期执行的任务:如定期更新缓存的任务,可以记录任务开始时间,任务结束时间,更新了多少条缓存配置等等,这样可以掌握定期执行的任务的状态;异常处理逻辑:如对于分布式存储系统来说,当系统在一个存储节点上读数据失败时,需要去另一个数据节点上进行重试,可以将读数据失败这件事情记录下来,之后可以通过对日志的分析确认是否某些节点的磁盘可能存在故障。再比如,如果系统需要请求一个外部资源,可以将请求这个外部资源偶尔失败又重试成功这件事情记录下来,具体来说:[INFO] RequestID:bdd2611184, auth request (to /v2) timeout ... 1 try\n[INFO] RequestID:bdd2611184, auth request (to /v2) timeout ... 2 try\n[INFO] RequestID:bdd2611184, auth request (to /v2) success\n要好于[INFO] RequestID:bdd2611184, auth request (to /v2) success\n因为前者可以让我们预判被依赖的服务器服务质量有风险,也许需要进行扩容;日志中需要记录关键参数,出错时的关键原因等。例如:[INFO] RequestID:bdd2611184, auth failed\n[INFO] RequestID:bdd2611185, content digest does not match\n[INFO] RequestID:bdd2611186, request ip not in whitelist\n就不如:[INFO] RequestID:bdd2611184, auth failed due to token expiration\n[INFO] RequestID:bdd2611185, content digest does not match, expect 7b3f050bfa060b86bac953, actual ff302d048\n[INFO] RequestID:bdd2611186, request ip(=202.17.34.1) not in whitelist\n4. 关于日志级别我们通常使用的日志库,将日志基本分为以下几类(从低到高):TRACE – The TRACE Level designates finer-grained informational events than the DEBUGDEBUG – The DEBUG Level designates fine-grained informational events that are most useful to debug an application.INFO – The INFO level designates informational messages that highlight the progress of the application at coarse-grained level.WARN – The WARN level designates potentially harmful situations.ERROR – The ERROR level designates error events that might still allow the application to continue running.FATAL – The FATAL level designates very severe error events that will presumably lead the application to abort.开发人员对于何种日志输出为何种级别通常有自己的理解,那在实践中,是否所有的日志级别都有必要存在,哪些操作需要记入日志,哪种错误应该记为WARN级别,而哪种错误又为ERROR级别呢?关于该问题,可以参考StackOverflow上的一个。此处对贴子中的一些观点,加上我们在平时运维过程中遇到的相关问题进行归纳:一个项目各个日志级别的定义应该是清楚明确的,需要团队的每个开发人员共同遵守;即使是TRACE或者DEBUG级别的日志,也应该有一定的规范,要保证除了开发人员自己以外,包括测试人员和运维人员都可以方便地通过日志定位问题;对于日志级别的分类,有以下参考:FATAL — 表示需要立即被处理的系统级错误。当该错误发生时,表示服务已经出现了某种程度的不可用,系统管理员需要立即介入。这属于最严重的日志级别,因此该日志级别必须慎用,如果这种级别的日志经常出现,则该日志也失去了意义。通常情况下,一个进程的生命周期中应该只记录一次FATAL级别的日志,即该进程遇到无法恢复的错误而退出时。当然,如果某个系统的子系统遇到了不可恢复的错误,那该子系统的调用方也可以记入FATAL级别日志,以便通过日志报警提醒系统管理员修复;ERROR — 该级别的错误也需要马上被处理,但是紧急程度要低于FATAL级别。当ERROR错误发生时,已经影响了用户的正常访问。从该意义上来说,实际上ERROR错误和FATAL错误对用户的影响是相当的。FATAL相当于服务已经挂了,而ERROR相当于好死不如赖活着,然而活着却无法提供正常的服务,只能不断地打印ERROR日志。特别需要注意的是,ERROR和FATAL都属于服务器自己的异常,是需要马上得到人工介入并处理的。而对于用户自己操作不当,如请求参数错误等等,是绝对不应该记为ERROR日志的;WARN — 该日志表示系统可能出现问题,也可能没有,这种情况如网络的波动等。对于那些目前还不是错误,然而不及时处理也会变为错误的情况,也可以记为WARN日志,例如一个存储系统的磁盘使用量超过阀值,或者系统中某个用户的存储配额快用完等等。对于WARN级别的日志,虽然不需要系统管理员马上处理,也是需要及时查看并处理的。因此此种级别的日志也不应太多,能不打WARN级别的日志,就尽量不要打;INFO — 该种日志记录系统的正常运行状态,例如某个子系统的初始化,某个请求的成功执行等等。通过查看INFO级别的日志,可以很快地对系统中出现的 WARN,ERROR,FATAL错误进行定位。INFO日志不宜过多,通常情况下,INFO级别的日志应该不大于TRACE日志的10%;DEBUG or TRACE — 这两种日志具体的规范应该由项目组自己定义,该级别日志的主要作用是对系统每一步的运行状态进行精确的记录。通过该种日志,可以查看某一个操作每一步的执 行过程,可以准确定位是何种操作,何种参数,何种顺序导致了某种错误的发生。可以保证在不重现错误的情况下,也可以通过DEBUG(或TRACE)级别的日志对问题进行诊断。需要注意的是,DEBUG日志也需要规范日志格式,应该保证除了记录日志的开发人员自己外,其他的如运维,测试人员等也可以通过 DEBUG(或TRACE)日志来定位问题;5. 不断优化日志有一点可以肯定,好的日志就像好的文章一样,绝不是一遍就可以写好的,而需要在实际的运维过程中,结合线上问题的定位,不断地进行优化。最关键的一点是,团队要重视日志优化这件事情,不要让日志的质量持续降低(当项目变大时,项目的代码也存在一样的问题,越写越乱)。此处有以下几个比较好的实践:在定位问题的过程中完善日志,如果定位问题花费了很长时间,那就说明系统日志还存在问题,需要进一步完善和优化;需要思考是否可以通过优化日志,来提前预判该问题是否可能发生(如某种资源耗尽而导致的错误,可以对资源的使用情况进行记录)定义好整个团队记录日志的规范,保证每个开发记录的日志格式统一;特别需要说明的是,对于DEBUG/TRACE级别的日志,也需要定义好清晰的格式,而不是由开发人员自由发挥;整个团队(包括开发,运维和测试)定期对记录的日志内容进行Review;开发做运维,通过在查问题的过程来优化日志记录的方式;运维或测试在日志中发现的问题,都需要及时向开发人员反映;6. 关于RequestIDRequestID的作用一个系统通常通过RequestID来对请求进行唯一的标记,目的是可以通过RequestID将一个请求在系统中的执行过程串联起来。该RequestID通常会随着响应返回给调用者,如果调用出现问题,调用者也可以通过提供RequestID帮助服务提供者定位问题。RequestID的生成:需要根据实际的使用场景来选择:对于简单的系统,可以简单采用一个随机数即可,例如RequestID = md5(time.Now() + random.Int())\n这样简单的方式在一定的时间内是不用担心会冲突的对于复杂的系统,需要在RequestID中编码更多的内容,例如:可以将处理请求的服务器IP,接收到请求的时间等信息编码到RequestID中,这样通过RequestID可以快速的了解请求属于哪台机器,然后进一步定位:./decode.sh 4b2c009a0a9f42b8ca96\n Thu Nov 21 11:06:12 CST 2013\n 10.120.202.150\n 4b2c009a\n对于一些特别的系统,RequestID也可以进行针对性的调整,例如在我实现的一个直播服务里,RequestID由两部分组成,第一部分是一个随机字符串(通过MD5生成),第二部分是一个不断在自增的整数:对于直播系统,这样做的好处是通过RequestID的第一部分,可以快速搜索到一路直播流所有的日志,而第二部分自增的整数可以帮助快速定位一段时间的日志。RequestID串联起来的日志系统:通常一个服务由若干个子系统组成,拿网易对象存储举例,它包含了前端负载均衡节点、存储逻辑服务器、元数据集群、分布式存储集群、图片处理集群、音视频处理集群、缓存集群等。通常一个请求需要由若干个子系统,甚至所有的子系统的协同处理。这时,如果某个请求出错,再要定位到具体的出错原因就比较复杂了,因为常常需要到数十台机器上去定位日志。当时的思路在负载均衡节点接收到请求后,就为请求生成一个全局唯一的RequestID,该请求所经过所有子系统系统,均基于该RequestID记录日志,这样通过将所有的日志收集起来,就可以通过这一个RequestID来得到完整的系统处理日志了。然而这并不是一件容易做的事情:所有的系统间调用都需要进行改造,所有的日志输出的地方都要统一格式,而我们采用的有些开源组件实际上很难支持这种做法。不过,有了这样的认识,我们组在开发新的底层分布式文件系统时,接口传入的第一个参数就是RequestID了。7. 动态日志输出上文已经讨论过,DEBUG日志和INFO日志的一个重要的区别是,INFO日志用于记录常规的系统运行状态,请求的基本的输入和输出等,对于定位一般的问题已经足够了。而DEBUG日志则详细的记录了一个请求的处理过程,甚至是每一个函数的输入和输出结果,遇到一些隐藏比较深的问题时,必须要依赖DEBUG日志。然而,由于DEBUG级别的日志数量比INFO级别的数量多很多(通常差一个数量级),如果长期在线上服务器开启DEBUG级别的日志输出,日志量太大。再比如,有时候仅仅是由于某一个用户的访问模式比较特殊导致了问题,如果将整个服务(特别是一个服务部署了很多台节点时)都临时调整为DEBUG级别日志输出,也非常不方便。下面介绍一种我采用的方式:我们的系统采用如下的业务架构(简化版):在业务处理层的Proxy中,实现如下逻辑:当接收到的HTTP请求的QueryString中包含\"DEBUG=ON\"参数时,就将所有的DEBUG级别的日志也输出:在负载均衡层的Openresty中,实现如下接口:管理员可以配置将哪个用户的哪个桶的哪个对象的哪种操作(注:这是对象存储中的几个概念)输出为DEBUG日志,Openresty会对每个请求进行过滤,当发现请求和配置的DEBUG日志输出条件相匹配时,则在请求的QueryString中新增\"DEBUG=ON\"参数。通过这种方式,管理员可以随时配置哪些请求需要输出为DEBUG级别的日志,可以大大提高线上定位问题的效率。8. 慢操作日志服务在接收到一个请求的时候,记录请求的接收时间(T1),在请求处理完成待发送的时候,会记录请求发送时间(T2),通常一个请求的日志都记为INFO级别,然而当出现请求处理时间(T2-T1)超过一定时间(如10s)时,可以将该日志提升为WARN级别。通过该方法,可以预先发现系统可能存在的一些问题。同样的慢操作日志还可以用来记录系统一些外部依赖的处理时间,如一个服务可能依赖外部认证服务器来进行认证授权。通过记录每次认证请求的时间并将超出预期时间的请求日志采用WARN级别输出,可以尽早发现认证服务器是不是需要扩容等问题。慢日志的时间阈值应该是可以动态调整的,这样在进行系统优化时,可以将该报警时间阈值逐渐调小,不断地对系统进行优化。9. 日志监控通过对日志中的关键字进行监控,可以及时发现系统故障并报警,这对于保证服务的SLA至关重要。服务的监控和报警是一个很大的话题,此处只说日志监控报警需要注意的一些问题:能不报警的就不报警,只有需要运维马上处理的错误才需要发送报警。这样做的原因是避免长期的报警骚扰让运维人员对报警不再敏感,最后真的报警来了时,变成了狼来了的传说;明确报警关键字,例如用ERROR作为报警的关键字,而不是各种各样的复杂规则。这样做的原因是日志监控本质上是不断的进行字符串匹配操作,如果规则太多太复杂,就可能对线上服务产生影响;对于一些预警操作,例如某个服务需要重试多次才能成功,或者某个用户的配额快用完等等,可以通过每天一封报警邮件的方式来反馈;每一次系统出现故障,都需要及时检查日志报警是否灵敏,日志报警的描述是否准确等,不断优化日志报警;10. 其他的注意事项上线后日志观察每一次上线完成后,除了对系统进行完整的回归测试外,还需要对日志进行观察,特别是当上线新功能以后,可以通过日志确认新功能是否工作正常。日志输出到不同的文件在性能测试时遇到的另一个问题是,当并发量很大时,可能会有一些请求处理失败(如0.5%),为了对这些错误进行分析,需要去查这些错误请求的日志。而由于这种情况下日志量巨大,使得对错误日志的分析变得困难。这种情况下可以将所有的错误日志同时输出到一个单独的文件之中。日志文件的大小日志文件不宜过大,过大的日志文件对于日志监控,问题定位等都会带来不便。因此需要进行日志文件的切分,日志文件应该按天来分割,还是按照小时来分割,应该根据日志量来决定,原则就是方便开发或运维人员能快速查找日志。为了防止日志文件将整个磁盘空间占满,需要定期对日志文件进行删除。例如,在收到磁盘报警时,可以将两个月以前的日志文件删除。此处比较好的实践是:将所有日志文件收集起来,这样即使在记录日志的机器上删除,也可以通过收集的日志对之前的问题进行定位;每天通过定时任务来删除过期日志,如每天在凌晨4点删除60天前的日志11. 总结对文中提出的所有建议总结如下:充分认识到日志对于一个可靠的后端系统的关键作用整个团队(包括运维人员)需要对日志级别有明确的规定,什么日志输出为什么级别,什么级别的错误出现要如何处理等需要定期对日志内容进行优化更新,目的就是通过日志快速准确地定位问题要明确不同日志的用途,对日志内容进行分类绝不要打印没有用的日志,防止无用日志淹没重要信息日志信息要准确全面,努力做到仅凭日志就可以定位问题日志的优化是一件需要持续不断投入精力的事,需要不断从错误中学习根据不同的目的生成RequestID,必要时在RequestID中尽量编码更多的信息将一个请求的整个处理流程和唯一的RequestID关联起来支持动态日志输出,方便线上问题定位新上线服务器后一定要对日志进行观察,特别地,开发人员可以通过观察日志来确认新功能是否工作正常通过日志级别的提升来发现潜在问题对日志进行监控报警,比客户先发现系统问题通过日志中的关键字来确定系统的运行状态日志格式要统一规范将错误日志输出到一个单独的文件中进行分析要把日志的大小,如何切分,如何删除等作为规范建立起来","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T22:56:35+08:00","url":"/p/","title":"最佳日志实践(v2.0)","summary":"0. 缘起大约在三年前,我曾经写过一篇 ,还被码农周刊选为那年的
之一。当时我任职于网易杭州研究院的存储平台组,主要做网易对象存储(NOS)的开发和部分运维工作。由于网易云音乐,易信等几个重要产品陆续上线,业务压力剧…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":14,"likesCount":69},"next":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-e914ba8cad3d4cd710a5_r.jpg","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"后端技术"},{"url":"/topic/","id":"","name":"云计算"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"","isFollowing":false,"hash":"00e480c7b79eda994eebd7dff5414ff9","uid":92,"isOrg":false,"slug":"nofrish","isFollowed":false,"description":"","name":"王健","profileUrl":"/people/nofrish","avatar":{"id":"v2-317ec69c59e24711cc25ebaa86090ac2","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"c_","name":"一个后端工程师的独白"},"content":"0. 背景这是一篇非常经典的论文,它列举了开发和运维一个互联网级别的服务时要注意的方方面面,每一个后端开发工程师都应该好好阅读这篇论文。论文首要强调的概念是自动化,一个系统的自动化程度可以通过机器管理员比(system to administrator ratio)来体现,即一个很大的系统需要多少管理员来管理。尽管自动化听起来是一个运维的概念,作者却不断强调整个系统在设计和开发时更需要花大量的时间来考虑自动化的问题。以下是这篇论文的关键内容的总结,需要说明的是:我并没有完整地翻译论文,只是对论文里面一些观点的提炼,有些地方也会加入一些自己在开发和运维过程中的体会;我略过了某些章节:如硬件的选购,如何和客户建立沟通渠道,推广产品等,主要是我本人这方面的经验十分有限,对论文里的内容无法产生共鸣;文中的每一条内容,几乎都是作者数十年工作的经验,有些甚至是血淋淋的教训。每一点都可以扩充成一篇很长的文章,所以建议大家可以好好思考其中提到的每一点;我根据自己的一些经验,给每一条建议标注了重要程度(其实每一条都很重要,只是有一些更通用的建议和做法,即使做一个小型系统的时候依然非常有参考价值)。当然了,这只是我自己的经验;最后,建议大家有时间一定要好好读几遍;那就开始吧。(题图是论文的原作者James Hamilton,他已经从微软离职加盟了Amazon)1. 整体设计面向失败设计(design for failure)???1. 一个系统通常有很多组件构成,这些组件会出现故障,并且会经常出故障,一起出故障;2. 常规的故障决不能依赖管理员介入恢复(否则运维友好无从谈起)3. 故障恢复过程应该简单明了,并且经常进行测试;4. 可以考虑永远采用暴力方式停止服务(例如直接拔掉磁盘,直接给主机断电等),以此来验证故障恢复逻辑;冗余和故障恢复(Redundancy and fault recovery)???1. 设计原则:任意服务器在任意时刻出现故障都不会影响服务的SLA;2. 考虑每个组件失败或者多个组件同时失败的情况下系统如何继续运行(对于某些情况认为故障可接受,例如单分区部署的集群整个分区故障时服务中断);3. 当集群足够大时,多个组件同时失败的可能性会大大提高以至于肯定会发生;反思:之前在设计和开发过程中的确有考虑过每一个组件如果故障时应该如何处理,必要时还考虑过服务降级,然而很少考虑多个组件同时故障时系统面临的风险。通用硬件(Commodity hardware slice)?1. 大集群里的一台通用服务器故障带来的影响要远小于小集群里一台大型服务器故障产生的影响;2. 在同等计算/存储能力下,通用服务器组成的大集群远比大型服务器组成的小集群要廉价;单一版本软件(Single-version software)??1. 两个原因让单一版本软件开发和迭代更新的成本更低(大大节约人力成本):
a)软件只需要在内部环境部署一次
b)过去的软件版本不需要维护数十年2. 最经济的方式是,不给用户选择软件版本的机会且永远只部署一个版本的软件。这就必须做到:
a)各个版本之间的用户体验不应发生太大变化
b)愿意接受那些需要自己选择不同版本的用户流失,去使用其他厂商的服务多租户(Multi-tenancy)??让所有的用户共享一套物理环境,而不是给每一个用户一个单独的物理集群,可以大大降低运维成本(和单一版本软件的论点基本一致)快速服务健康检查(Quick service health check )?一个服务健康检查程序,用于在开发者自己的电脑上快速的进行回归测试(测试关键路径),如果所有的测试用例通过,则允许开发者提交代码反思:好的开发自测环境会大大影响开发效率,而这件事情常常不被重视。很多开发日常工作常常被复杂的开发环境干扰,开发效率低下,甚至因为没有方便的测试手段而逃避自测,带来更大的效率降低。在完整环境下进行开发(Develop in the full environment)?开发人员不仅应该对自己修改的模块进行测试,也需要对整个系统进行测试,确保自己新增的代码没有影响到整个系统。这需要整个环境可以都部署到开发者自己的机器上(single-server deployment),以及刚刚说的服务健康检查程序。反思:我们组曾经用过vagrant起虚拟机在本地部署一整套环境的方式解决了每个开发者一套自测环境的问题,避免了多个人协同开发时使用同一套环境进行测试带来的干扰。不信任依赖模块(Zero trust of underlying components)??假设服务依赖的模块一定会出现异常并考虑在它们出现异常时如何继续提供服务:1. 采用只读模式,使用缓存的数据继续服务(相当于服务降级)2. 在服务尝试模块的备份的那段时间,尽可能多得服务所有的用户不要在多个模块里实现同样的功能(Do not build the same functionality in multiple\ncomponents)?避免需要在多个模块的代码里重复修改同一个bug的情况。一个节点或集群不应该影响另一个节点或集群(One pod or cluster should not affect another pod\nor cluster)?1. 一个服务由若干节点或子集群构成,要保证每一个节点或集群的独立性;2. 被所有模块依赖的服务(global services)即使是冗余的,也可以认为是单点3. 当依赖不可避免时,尝试将所有的依赖放在自己的集群之内允许人工修复少数异常(Allow rare emergency human intervention)??1. 尽管我们希望系统可以自动处理各种异常,但是一些极端的情况(如多个模块同时故障或未预见的错误发生时),都需要进行人工介入2. 在灾难发生后的人工操作常常会导致灾难性的数据丢失(在压力下凌晨2点工作的人会犯错误)3. 不要将人工介入的过程一步步写在文档里,而是应该将这些过程写在脚本中,且这些脚本经过生成环境的测试4. 经常性地进行灾难演习测试这些脚本让一切简单而健壮(Keep things simple and robust)???1. 复杂的算法或模块交互让调试、部署的复杂度都成倍提升2. 除非更复杂的优化有数量级的提升效果,否则连考虑都不要在所有层级进行准入控制(Enforce admission control at all levels)???1. 通常服务在前端入口都会进行准入控制,目的是防止在系统过载时还在不断的接收新的请求导致系统进一步过载2. 应当在主要的模块边界处都进行准入控制,因为服务访问模式的变化可能会让某些子系统过载(即使服务整体的压力并不高)3. 基本的原则是尝试进行服务降级而不是等服务彻底失败以至于无法提供服务服务拆分(Partition the service)?通过一个索引表来拆分服务,而不是任何实体信息(如公司名,用户名前缀等,因为这样可能导致拆分不均衡);有索引表之后,拆分的服务(如用户的数据)还可以在服务器之间进行任意的迁移;理解网络拓扑(Understand the network design)?在早期就进行网络负载的测试,了解机架内、机架间、跨数据中心等的网络状况。分析吞吐量和延迟(Analyze throughput and latency)??1. 分析关键用户操作的吞吐量和延迟,了解这些交互对系统的影响;2. 需要同一些运维性操作一起分析:如数据库维护操作(备份),配置管理(新增/删除用户),这可以帮助发现周期性任务导致的问题;3. 对于每一个服务,都需要有一些统计数据来提供扩容方面的指导将工具作为系统的一部分(Treat operations utilities as part of the service)?运维工具需要和整个系统的其他代码一样进行开发、Code Review和测试。这些在某些时候扮演重要角色的工具常常都未经过测试。理解访问模式(Understand access patterns)?1. 开发者经常忽略某个新的功能导致的新的访问模式是否会给后端的系统带来过多的压力;2. 将“此新功能会给其他的基础服务带来什么影响”作为一个章节写在新功能的文档(如设计文档)中;版本化一切(Version everything)??目标是系统运行在一个单一的版本上,可是多个版本同时运行的情况一定会出现(如系统升级过程中);要确保多个版本的系统可以同时运行。保留上一次发布的所有测试用例(Keep the unit/functional tests from the last release)?1. 这些测试用例会保证新的开发没有影响之前的功能2. 经常性地在生成环境运行这些测试代码避免单点失败(Avoid single points of failure)???1. 选择无状态的实现,有利于水平扩展2. 不要将请求和服务器绑定,而应该采用负载均衡的方式将请求路由到一组服务器上3. 数据库很难解决单点问题,因此:要合理的拆分到partition;避免夸partition操作;有一个热备节点且经常性地在生产环境测试主备切换2. 自动化管理和配置所有的操作都支持重启,所有的持久化的数据都需要备份 ??支持跨分区部署 ?说明:如果设计上没有很好的支持跨分区部署,那就需要额外花很多的运维精力来支持跨分区部署,这会带来运维成本的显著提升自动配置和安装 ???说明:手动管理配置,手动部署安装会带来灾难配置和代码作为整体提交 ??说明:整个开发、测试和部署过程都将配置和代码作为整体进行;如果配置必须在线上环境修改,那一定要做好修改记录;要定期扫描线上服务的配置;按照角色管理而非按照服务器管理 ?说明:主要指的是管理系统的视角,是管理服务的角色而非管理服务器;认识到多个系统同时故障是常态 ???不要将重要数据存在服务器的本地,而应该依赖可靠的分布式存储 ??让部署过程简单 ??经常性的主动让服务下线 ??说明:下线一个数据中心、下线一个机架、给服务器断电等;经常性地测试这些异常逻辑以保证服务在真正出现灾难时能够恢复;3. 依赖管理以上讨论过,一个服务尽量不要有太多外在依赖,而应该将依赖放在集群内部。然而有两种情况,无法将依赖放在集群内部:被依赖的模块规模很大:如一个服务依赖一个分布式存储系统被依赖的模块需要以单实例的形式存在:如身份和组管理系统当这样的依赖情况出现时,需要进行依赖管理:考虑调用延时(Expect latency)???1. 对外部模块的调用可能需要很长的时间,不能让一个模块的超时导致了其他无关模块的超时;2. 保证所有的外部调用都设置了适当的超时;3. 通过采用幂等的协议,保证请求再超时后可以进行重试;4. 采用有限次数的重试,防止无谓消耗系统资源;隔离失败(Isolate failures)???1. 快速失败(fail fast),防止雪崩式的失败;2. 当外部依赖模块故障时,标记其为失败且不再像它发送请求;思考:所谓快速失败,就是当一个外部调用的延迟是10ms级别的时候,就不要把超时时间设置为10s或30s;而是设置为1s或2s,通过快速失败和重试来解决偶然出现的访问延迟。使用可靠的组件(Use shipping and proven components )??不论是软件还是硬件,都采用被充分验证的版本,绝不要为了一些新的特性而使用可能不太可靠的软件或硬件。服务间监控和报警(Implement inter-service monitoring and alerting)??通过监控和报警机制,保证一个服务导致另一个服务过载的情况可以被发现。依赖双方应该有一致的设计目标(Dependent services require the same design\npoint)??双方应该有一致的SLA目标,一个服务的SLA不会高于它依赖服务的SLA。模块解耦(Decouple components)??保证一个模块在其依赖模块故障时依然可以提供服务,哪怕提供的是降级了的服务。示例:一个服务依赖的认证服务失败时,虽然认证授权的功能已经不可用,不过可以服务在一个降级状态,如可以让系统处于一种只读状态,不允许修改和删除等。 4. 发布周期和测试在生成环境测试(Testing in production)?尽管很多的服务都有完整的单元测试,功能测试,和类似生产环境的集成测试,然而这些测试都和生产环境有活多或少的区别,而推荐的做法是直接在生产环境进行测试。为了保证在生产环境测试的安全性,需要做到:
1. 首先进行小规模的线上测试验证,并逐渐上线到所有节点(开始的时候先部署在一个数据中心的一个节点,然后再部署到每个数据中心的一个节点,最后再部署到所有数据中心的所有节点)
2. 生成环境的系统要有足够好的冗余性,保证即使发生灾难,也要保证数据和服务的可用性
3. 数据损坏等异常情况发生的可能性要极低(因为已经经过了完整的单元测试和功能测试等)
4. 开发团队(而不是运维团队)要实时监控被测试系统的健康状态
5. 要支持快速回滚操作,且该回滚操作也需要经过充分测试
6. 在白天上线,而不是在晚上上线(白天是工作时间,更不容易犯错误;即使出现异常,有更多的工程师在场也能更快恢复服务)频繁发布(Ship often)?频繁地发布新功能可以防止长时间发布一次时出现的重大更新,而这种重大更新的风险很高。使用生产环境的数据发现问题(Use production data to find problems)??超大型系统的质量保障是一个数据发觉和数据可视化的问题,而不是一个测试的问题;因此需要充分重视生产环境的数据,下面是一些策略:
1. 发布标准可量化 —— 例如系统的可用性是99.99%;
2. 在实践中设置目标 —— 不要拍脑袋设定一个99%或99.9%的目标,而应该根据实际情况定义一个可达的目标并向它迈进;
3. 永远收集最原始的数据 —— 而不是将系统的状态通过红灯绿灯来简单的表示;
4. 减少误报
5. 分析趋势 —— 观察数据的变化是否偏离了正常值;
6. 让系统的健康状况清晰可见 —— 让所有人都可以在内部系统上看到系统当前的健康状况;
7. 持续监控在开发中投入更多精力(Invest in engineering)???1. 有些看似是运维过程中的问题,实际上是由于不够优秀的设计和开发导致的;2. 更多的在开发中投入精力,可以避免问题在运维的过程中出现;3. 花更多的精力设计可扩展的、可靠的架构,而不是不断地扩充运维团队;支持回滚到特定的版本(Support version roll-back)??必须支持回滚到特定的版本,且回滚这一操作被充分的测试;否则任何的生产环境测试都是高危操作。支持向前向后兼容(Maintain forward and backward compatibility)?从运维的角度来说,向前向后兼容可以保证回滚之后系统还能正常工作,而不会因为由于回滚之后的系统由于无法解析磁盘上的数据结构,新的接口,运维工具等原因而失败。压力测试(Stress test for load)?给一些生成环境的系统两倍于正常的线上压力以保证系统不会因为负载提高而崩溃。说明:我们曾经通过调整Nginx Upstream的权重,将更多的流量导入少数线上机器的方式进行过压力测试,对系统的瓶颈有了更好的估计。在新版本发布之前进行容量和性能测试(Perform capacity and performance testing prior\nto new releases)?5. 运维和容量规划让开发团队负责(Make the development team responsible)??1. \"you built it, you manage it\"2. 如果开发人员经常在半夜被叫醒,那他会让系统更加的自动化;如果运维人员经常在半夜被叫醒,那可能会扩大运维团队;只进行标记删除(Soft delete only)??只进行标记删除(真正的数据删除可以在两周后进行),防止由于误操作导致的数据丢失。追踪资源分配情况(Track resource allocation)?1. 每一个服务都应该有如同时在线用户数、用户每秒请求数等监控;而这些监控数据需要和机器负载以及需要的硬件资源之间建立关系;2. 未来预计的负载情况需要有销售和市场的团队来提供支持;3. 根据服务的情况来制定硬件采购周期让一切都可配置(Make Everything Configurable)?任何可能会变化的值,都应该成为一个容易修改的配置(而不是写死在代码中);这样在线上需要调整该值时更加的快速和安全。6. 审计、监控和报警监控一切(Instrument everything)??监控系统的一切:用户请求、性能数据、健康数据、吞吐量等;一旦出现异常,则立即报警;将所有的配置变化计入审计日志,当系统异常时,首先检查近期系统配置上的变化;数据是最重要的资源(Data is the most valuable asset)??1. 如果不理解系统正常的运行状态,那就很难发现什么时候它不正常;要大量的收集系统运行过程中的数据来理解系统什么时候运行正常;2. 很多时候系统出现重大灾难,而系统的开发运维人员只能通过客户的电话才能发现系统已经出现问题(教育我们要善用数据)从用户角度看问题(Have a customer view of service)?进行端到端的系统测试,保证重要的和复杂的逻辑都被测试到,避免误报;说明:请参考本专栏的另一篇文章:在生产环境测试时进行系统监控(Instrumentation required for production testing)?为了能够在生产环境安全地进行测试,对系统的全面监控和报警必不可少,这保证了能够快速发现模块的异常;延迟是最难发现的问题(Latencies are the toughest problem)?例如一块磁盘读写数据变慢但是并不返回失败,需要通过仔细的监控来发现这些问题。有充足的生产环境的数据(Have sufficient production data)?1. 至少记录请求数和请求延时,这些数据的起伏是系统状态的重要信号;2. 记录所有的运维操作:谁在什么时刻做了什么,当问题出现时首先检查近期系统的变化;3. 记录所有的错误处理机制:容错机制可能会隐藏系统的问题4. 记录重要信息的变化5. 保存历史数据.说明:请参考本专栏的另一篇文章:可配置的日志(Configurable logging)??动态开启或关闭线上日志,调整日志级别,可以快速定位问题。所有发现的错误都有处理措施(Make all reported errors actionable)?如果一个错误被报告却没有明确的处理手段,那很有可能被忽略;长时间地忽略错误最终会导致灾难的发生。快速定位线上问题(Enable quick diagnosis of production problems)??1. 要提供足够多的信息来分析问题:日志,监控等2. 证据链:要提供一整条链路的日志帮助分析问题3. 允许在生产环境Debug4. 记录关键的操作:如一次网络操作中,请求方发送了什么,服务方返回了什么。这样可以大大提高定位问题的效率7. 优雅降级和准入控制当系统遭遇DDOS攻击或者访问模式变化导致出现请求剧增时,系统应该有机制进行服务降级和准入控制,下面分别讨论这两个问题。(过载保护是一个很大很值得思考的问题,我会再下一篇文章中好好谈谈这个概念和应对方法)支持“红按钮”(Support a ‘‘big red switch’’)?1. 系统支持红按钮的目的是能够在紧急时刻拒绝一些不重要(non-critical)的请求负载;2. 需要慎重选择哪些请求负载可以被拒绝或延后处理,例如:
2.1. 放入异步处理队列中的请求
2.2. 放弃一些复杂查询依然可以完成的操作关键是要辨别系统中哪些操作是必须要提供支持的;反思:在曾经做对象存储系统时我们也有过类似的考虑, 即当系统异常时,认为提供对象的读写操作是最重要的,而一些高级的功能,如音视频处理等则是第二重要的。有了对业务优先级的认知,即使系统彻底故障而需要恢复时,也清楚该优先恢复什么服务。准入控制(Control admission)??当系统已经过载时,需要适当地在最前端拒绝部分请求,以免系统进入一种完全无法恢复的状态。渐进式的准入控制(Meter admission)?当系统经历了巨大的灾难而慢慢恢复时,要可以逐渐让请求进入(而不是一次全部放开),例如先让一个用户请求进来,再允许每秒有10个请求,等等。一次性放开准入控制门槛可能导致大量涌入的请求让系统再次崩溃。","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T13:37:13+08:00","url":"/p/","title":"悦读:On Designing and Deploying Internet-Scale Services","summary":"0. 背景这是一篇非常经典的论文,它列举了开发和运维一个互联网级别的服务时要注意的方方面面,每一个后端开发工程师都应该好好阅读这篇论文。论文首要强调的概念是自动化,一个系统的自动化程度可以通过机器管理员比(system to administrator ratio)来体…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":0,"likesCount":20}},"annotationDetail":null,"commentsCount":1,"likesCount":13,"FULLINFO":true}},"User":{"nofrish":{"isFollowed":false,"name":"王健","headline":"","avatarUrl":"/v2-317ec69c59e24711cc25ebaa86090ac2_s.jpg","isFollowing":false,"type":"people","slug":"nofrish","bio":"","hash":"00e480c7b79eda994eebd7dff5414ff9","uid":92,"isOrg":false,"description":"","profileUrl":"/people/nofrish","avatar":{"id":"v2-317ec69c59e24711cc25ebaa86090ac2","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false,"badge":{"identity":null,"bestAnswerer":null}}},"Comment":{},"favlists":{}},"me":{},"global":{},"columns":{"next":{},"c_":{"following":false,"canManage":false,"href":"/api/columns/c_","name":"一个后端工程师的独白","creator":{"slug":"nofrish"},"url":"/c_","slug":"c_","avatar":{"id":"v2-49e7526620deff29ae1fe8fc0fa7ef7e","template":"/{id}_{size}.jpg"}}},"columnPosts":{},"columnSettings":{"colomnAuthor":[],"uploadAvatarDetails":"","contributeRequests":[],"contributeRequestsTotalCount":0,"inviteAuthor":""},"postComments":{},"postReviewComments":{"comments":[],"newComments":[],"hasMore":true},"favlistsByUser":{},"favlistRelations":{},"promotions":{},"switches":{"couldAddVideo":false},"draft":{"titleImage":"","titleImageSize":{},"isTitleImageFullScreen":false,"canTitleImageFullScreen":false,"title":"","titleImageUploading":false,"error":"","content":"","draftLoading":false,"globalLoading":false,"pendingVideo":{"resource":null,"error":null}},"drafts":{"draftsList":[],"next":{}},"config":{"userNotBindPhoneTipString":{}},"recommendPosts":{"articleRecommendations":[],"columnRecommendations":[]},"env":{"isAppView":false,"appViewConfig":{"content_padding_top":128,"content_padding_bottom":56,"content_padding_left":16,"content_padding_right":16,"title_font_size":22,"body_font_size":16,"is_dark_theme":false,"can_auto_load_image":true,"app_info":"OS=iOS"},"isApp":false},"sys":{}}

我要回帖

更多关于 微博 2接口返回错误 的文章

 

随机推荐