恩,这是名副其实的杯具(很抱歉我用这个词,shitstorm)。长话短说,如今MelonCard已经被TechCrunch使用,然而所有事情都可能突然出现问题。过去几天里,我们对MelonCard进行了巨大的改进,使用NodeJS长轮询机制黑龙江网站建设公司以及平滑的KnockoutJS动态jQuery Templates前端。在完成站点无缝升级,在达成所有功能都是最新目标的同时,成为外观更美、体验更好的产品。为了避免可能发生的不良影响,我们进行了手工测试和单元测试,并为Node结合使用了一整套Vows。完成所有的系统测试,朝着目标全速前进,对吗?事实并没有那么快。
译注:
长轮询(long 网站开发 总结 polling):是Comet的一种实现方式,也是Facebook,Plurk实现动态更新内容的方法,具体原理是发送一个长时间等待的request,当服务器有资料response的时候立刻断掉,接着再发送一个新的request。
Vows:Node.js的异步行为开发框架
我们的系统用的是NodeJS,根据用户输入决定他的当前状态,例如用户输入我正在等待更新这两条记录,服务器(基于时间戳检查)会返回您的记录已经是最新的或者记录xxx已更新为yyy。(实际的过程会比这个更加复杂,我们使用了Redis共享变量和会话,并对Rails、mySQL、Redis和Node之间的接口进行安全检查)。(这个过程)看上去非常简单,但当事情不如预期的那样时,即使是简单的NodeJS 代码也会成为噩梦。今天噩梦发生了。
处理完今天的日常工作之后,我们遇到了一个正常的新用户注册高峰(一个小时内有50-100个新用户注册)。突然之间,所有事情都开始出现问题,没有一个页面可以正常工作。我们的邮箱开始被你的产品挂掉了类似的邮件塞满了。我抓起一杯咖啡准备战斗。
我的第一个反应是:NodeJS能够很好地处理负载,这是众所周知的。50或100个用户不可能让系统崩溃。在得到Ryan Dahl的帮助之后,我们知道这不是Node本身的问题(后来的南京网店设计结果也验证了这一点)。服务器开始返回异常结果,用户输入我的记录是a、b和c然而服务的回答是你这个蠢货,删掉x、y和z这里的记录是a、b和c。即使能够确定问题的范围并可以重复错误,也几南通网站建设公司乎不可能通过Node可怜的错误处理和调试功能解决。采用的方法就是下面的unix命令(是的,我对production进行查询)
NODE_ENV='production' node/privacy.js | grep "Returned results"
你能够想象对这些结果进行分类是件多么恐怖的事。结果是,所有的分段测试和单元测试都正常,我已经束手无策了。最为重要的是,我们的系统再一次执行了大量的(安全性)会话检查。例如,如果用户在(浏览器)不同的标签页之间进行登录和退出时,会有大量的Unauthorized (未授权)错误弹出(这让企图了解真正问题的我们更加糊涂)。当我列出错误的时候,看上去像这样:
Trace: at EventEmitter.anonymous(//node/privacy.js:118:11) at EventEmitter.emit (events.js:81:20)
错误出现的那一行(或唯一返回给我的Node)如下:
process.on('uncaughtException', function (err) { console.log(['Caught exception', err]); console.trace(); });
虽然程序还没有崩溃,但我还是没有找到线索。在这里最佳实践也没有办法捕捉那一行的错误(手动前端测试,单元测试,错误处理,等等)。的确,我应当做负载测试,但是即使(测试)到达了竞争条件同样不能让我感到安全。
4个小时以后(当我翻到第503页 Temporarily Unavailable,这时我的联合创始人对每一位失望或好奇的用户回复了致歉的邮件),我意识到问题出在服务器将我的请求输入(请求参数)错误地当成随机用户请求参数。确切的说,服务器的设计只会对你的请求返回你的相关信息,但是对你的请求产生了错误的理解。比如你说我喜欢苹果和甜瓜,但是服务告诉你不要傻了,你喜欢的是芒果。所以,虽然(从安全的角度来说)一切都是安全的,但是结果是错的。为什么我的ExpressJS服务器不能理解我的请求呢。我继续追踪发现了下面的代码:
app.all('/apps/:user_id/status', function(req, res, next) {
// …
initial = extractVariables(req.body);
});
看上去很糟糕是吧?这是个该死的策略。我不是个JavaScript专家,但请允许我尽我所能为你解释(或者也刻意看看这里)。在JavaScript里,你可以声明变量为函数(局部)变量或者全局变量(在变量声明/引用的作用域链表上为其增加复杂性,直到最后确定为全局变量)。当我没有使用var来声明一个全局变量initial时,它会遍历作用域链表直到全局范围,最后会创建一个全局变量initial。当下一个请求来到时,它会遍历同样的链表并重写该变量(另一个请求仍然希望使用这个变量)。每个请求都会重复这一过程。当服务器试图在接下来的处理中回复每个请求时,它会读到不断被截断的变量,并返回的结果不正常,甚至可以说是荒唐。因此我应当这么修改:
var initial = extractVariables(req.body);
这样会在我的匿名函数作用域内创建这个变量,因此接下来的请求就不会截断这个值。这是新手才会犯的错误,但是在我做的所有调试或测试中都完全不会带来竞争/并发问题。
现在我要对你说的是:你应该用过 CoffeeScript并且可能对TameJS很在行。你可能是正确的。我想理解第一次执行NodeJS调用了哪些函数,但这对于我的公司是一个不必要的损失。在其他情况下,它可能带来更严重的问题(假如我在会话中错误的使用了变量会如何?)。最重要的是,缺乏真正好的错误处理(在Rails,我们通过backtrace跟踪错误并把错误发送给开发团队)以及真正的调试(依靠的是grep和less命令)让我觉得我们离好的开发差距很远。或者也许我应当更加小心。
在4个小时宕机并令上百个用户失望而归之后,我找到了问题的症结并修复了产品。风雨之后,拨云见日。我们开始对受到影响的用户致歉,盘点这次事故的损失并继续工作。但是,缺少一个关键字带来这样的损失还是让我感到难以释怀。少写了一个var会让我成为坏人吗?
英文原文:How One Missing `var` Ruined our Launch by -Geoff
翻译地址:唐尤华,伯乐在线。
南京牧狼文化传媒有限公司简介:
牧狼传媒,牧者之心,狼者之性,以牧之谦卑宽容之心待人,以狼之团结无畏之性做事!
公司注册资金100万,主营众筹全案服务、网站营销全案服务、网站建设、微信小程序开发、电商网店设计、H5页面设计、腾讯社交广告投放以及电商营销推广全案等相关业务,致力于为客户提供更有价值的服务,创造让用户满意的效果!
为百度官方及其大客户、苏宁易购、金山WPS秀堂、美的、创维家电、新东方在线、伊莱克斯、宝丽莱等国内国外知名品牌服务过,服务经验丰富!同时,公司也是南京电子商务协会会员单位、猪八戒网官方认证签约服务商、江苏八戒服务网联盟、南京浦口文化产业联合会会员单位,可以为您提供更好的服务!
主营项目:众筹全案服务、网站营销全案服务、网站建设、微信小程序开发、电商网店设计、H5页面设计、腾讯社交广告投放、竞价托管、网站优化、电商代运营等
合作客户:百度、苏宁易购、饿了么、美的、创维家电、新东方在线、宝丽莱、金山WPS秀堂、伊莱克斯
资质荣誉:百度商业服务市场2017年度最佳图片服务商、南京电子商务协会会员单位、猪八戒网官方认证签约服务商、江苏八戒服务网联盟、南京浦口文化产业联合会会员单位、八戒通TOP服务商、"易拍即合杯"H5创意大赛"三等奖"。