网站nginx负载下因程序错误导致多节点重复处理请求的解决过程
目录
前言:
这是我上周工作过程中的一次解决问题的过程。解决的是nginx负载环境下,因为应用程序异常导致某一请求被多节点站点重复处理的问题。 我整理这个复盘的过程时,在给这个记叙文命名时思考了一段时间。 从业务角度说,应该是“一次短信重复发送问题的解决过程”,如果这样命名,过于土气;从技术角度说,应该是“nginx负载下当程序错误时会导致请求被多次重复处理”,如果这样命名,可能没人会注意了;最后,我懒得再费脑汁了,从人类智慧的角度我把它命名为“复杂的问题简单的解,小不慎则乱大谋”。
问题来了
3月16日,有客服、销售、运营人员不断反映,客户在saas预定机票完成后,会连续收到3条重复的支付提醒短信。 很多客户都投诉了这样的问题,一直以来的正常情况下这样的支付提醒短信是仅发1条的。
以下是短信平台上的截图:
前一天的3月15日周二是每周的上线日,当晚saas上过线。
迫于压力,当天下午不得不回滚3月15日的上线。回滚之后,问题不再重现。
3月15日saas的上线包括锦如和刘涛对saas所做的代码改动。刘涛涉及到的代码是审批接口,称可能不会导致这样的问题,锦如也肯定地表示不涉及到这块。 我让刘涛来排查原因,他在测试环境做了几次测试,并未发现这样的问题。我呢,由于最近忙于另一个项目,一直没来得及亲自和他一起查找原因。
问题又来了
3月17日是周四,上线日。要发布中新融创的对接程序。 晚上上线后,立即在线上订票,发现订单支付提醒短信还是会重复发3遍。
当时已经是晚上21点,我决定放下手头的活儿,来跟刘涛一起排查原因。
问题分析
客户在saas站点里预定机票,涉及到审批。审批是独立部署的一个站点。
订单的审批逻辑是这样的:客户在saas订票成功后,会跳转到统一审批页,提交审批后,统一审批系统会把审批状态通知给saas,然后重定向页面到订单流的下一个页面(由saas提供)。
Saas提供了一个由一般处理程序(文件是HandlerAirOrderExamineStatus.ashx)实现的接口,用来接收审批状态。内部大致逻辑是,修改订单的审批状态,然后会触发相应的对客通知短信。 客户重复收到的通知短信是支付提醒短信,意味着订单不需要审批。不需要审批在系统里表现在这些企业没有配置审批流。 根据现有代码逻辑,从填单页跳转到统一审批页,统一审批页初始化时没有获取到新审批,则会自动执行通知saas和页面重定向。
要补充说明一下的是,线上的审批站点和saas站点均是通过nginx做的3节点服务器的负载。
通过查看线上日志,审批系统正常,即只会在其中一个节点服务器来请求saas接口进行审批状态的通知,并未发现异常日志。而saas呢,却发现3个节点服务器都记录了同样的日志。
为什么会出现这样的情况呢? 审批在通知saas时,nginx接收到请求后应该分发给其中一个节点服务器来处理请求才对呀。
困惑
因为之前系统运行都是正常的,并未出现这种短信重发的情况,所以我们暂先不怀疑代码逻辑。
马上想到,记得运维说过,如果一个服务器处理时间超长,会自动分发给另一个节点服务器。 历史原因,获取订单详情和触发短信通知这两段代码执行比较慢,那么,我在这两段代码外面加了个Stopwatch来诊断其执行时长。
public void ProcessRequest(HttpContext context)
{
Stopwatch sw = new Stopwatch();
sw.Start(); if (context.Request.Params.Count == )
{
context.Response.Write("{'returnCode':'2','returnMsg':'no parameters','notifiedSMSContent':'no parameters'}");
return;
} string parametersStr = context.Request.Params[].AsToString();
SysLogToFile.WriteAuditMessage("dat修改订单状态传入参数_" + parametersStr); // 下面的是200行逻辑代码,包括获取订单详情和触发短信通知
...... ...... ...... ...... ...... ...... ...... ......
...... ...... ...... ...... ...... ...... ...... ......
...... ...... ...... ...... ...... ...... ...... ...... sw.Stop();
SysLogToFile.WriteAuditMessage("审批接口响应时间" + (sw.ElapsedMilliseconds / )); string returncode, returnmsg;
if (flag)
{ returncode = ""; returnmsg = "success"; }
else
{
returncode = ""; returnmsg = "fail";
} context.Response.Write("{'returnCode':'" + returncode + "','returnMsg':'" + returnmsg + "','notifiedSMSContent':'" + returnmsg + "'}");
}
发布后经测试,发现3个节点的执行时长都长达7~8秒。
那么,我们接下来就优化程序吧,以期把duration降到最小。20分钟后,我们把优化的代码再发布到服务器。发现处理时间已经下降到了1~2秒。
然而,响应时间已经都优化到2秒了,依然是3个服务器节点上都做了处理。这带给我们的是一个更大的问号。
转机
为什么会有一个更大的问号呢? 因为我们线上的系统都是分布式部署的,一个流程逻辑会涉及到多个系统之间的交互访问。 每个系统都是通过nginx做的3个节点的负载,并没有出现过这种一个请求被3个服务器节点同时响应的情况。
在这种情况,我的一贯做法是釜底抽薪。当然,这次我依然坚持釜底抽薪。
Q:怎么釜底抽薪呢?
A:把接口里的代码都注释掉。响应时间应该接近于0毫秒,难道还会出现3个节点都响应的情况?
于是,我们注释掉所有逻辑代码,只保留了最后的输出(context.Response.Write)语句,发布到服务器。
由于我们每次的测试步骤是:登陆saas,选择航班,然后订票下单,再看日志,这一系列的操作很耗时间,同时给线上系统带来了很多无效订单(垃圾数据)。这次呢,我让刘涛直接在ie里访问那个通知接口地址来测试性能。
转机来了,在ie里直接访问那个接口时,页面直接抛出了大黄页,报“System.FormatException:输入字符串的格式不正确”,是由主方法里最后的这条语句产生的:
context.Response.Write(string.Format("{'returnCode':'{0}','returnMsg':'{1}','notifiedSMSContent':'{2}'}", returncode, returnmsg, returnmsg));
即在string.Format方法里,你是不能乱用‘{’的~。
于是,修复后(改成字符串拼接了)再次上线。
再次访问那个接口,返回正常了。最大的惊喜是,SaaS那边只有一个服务器节点受理了请求。到这里呢,短信重发3次的问题终于得到了根治!
这时,我才想起来,nginx在接收到请求后,会分发给多个服务器节点中的一个节点来处理请求,但是,当出现错误响应时,nginx会自动分开给另一个节点来处理,直到没有节点可供分发,即直到所有可用节点都被分发为止。(当然,这可能是我们运维对nginx的配置策略)
后续
我们解决完这个问题,已经午夜23:40,如释重负的打车回家了。
在回去的路上,我的脑子像过电影似的过了一遍我们这一晚的处理过程。
l 复杂的问题,在你用心解决时,往往产生自很简单的一行代码。很戏剧性的是,这行代码可能是你不经意的疏忽写错了,也可能是技能受限写错的。如果,有做简单的测试,这样的问题完全是可以在开发时被发现的,而不至于花费那么长的时间来。
l 另外,我又想到,审批系统调用接口时难道没有判断响应结果就做下一步的重定向了?这个疑问在第二天咨询刘涛时得到了肯定。这样的实现不够严谨,于是,我让他加上对结果的判断。 如果事先有这个判断,那么,这样的问题在审批请求时就会被发现的,何至于经历那么漫长的排查过程呢。
网站nginx负载下因程序错误导致多节点重复处理请求的解决过程的更多相关文章
- nginx负载下站点错误响应会导致其他节点重复响应问题的解决过程
目录 前言 问题来了 问题又来了 问题分析 困惑 转机 后续 前言: 这是我上周工作过程中的一次解决问题的过程.解决的是nginx负载下站点错误响应导致其他节点重复响应. 我在整理这个记叙文时,在给这 ...
- eclipse下修改项目名导致tomcat内发布名不一致的解决方法 .
eclipse下修改项目名导致tomcat内发布名不一致的解决方法 . ------------------------------------------------------- 解决方案: 直接 ...
- 2种方式解决nginx负载下的Web API站点里swagger无法使用
Web API接口站点,引入了swagger来实时生成在线的api文档,也便于api接口的在线测试.swagger:The World's Most Popular Framework for API ...
- nginx 转发 由于php语法错误 导致的 50x
server { listen 8008; root /root/php-test; index index.php index.html index.htm ...
- Dynamics CRM 依赖组件类型为应用程序功能区导致的无法删除实体问题的解决方法
看到有人问到这个问题,这边就简单描述下解决方法,主要是针对第一次碰到这个问题云里雾里的朋友,错误如下 在我们建lookup关联的时候有下图中的这么个设置,对于很多新手默认就是下图这样不会去做改动,因为 ...
- centos7下安装elasticSearch错误总结(单节点模式)
1.首先确定你安装了jdk,版本需要1.8以上 2.上传elasticsearchjar包,只需配置一个文件即可 修改配置文件config/elasticsearch.yml network.h ...
- nginx负载均衡和tomcat热部署简单了解
简单说下几个名词 nginx 它是一个反向代理,实际上就是一台负责转发的代理服务器,貌似充当了真正服务器的功能,但实际上并不是,代理服务器只是充当了转发的作用,并且从真正的服务器那里取得返回的 ...
- 非常诡异的IIS下由配置文件加上svg的mime头导致整个网站的静态文件访问报错误
调试了两天遇到一个非常诡异的问题 一个系统稳定运行了很多年,是用mvc5+WIN2008R2 + .NET 4.5 +IIS环境下运行,非常稳定,最近想迁移到一台新的服务器,为了少麻烦在阿里云上买了 ...
- Linux(7)- Nginx.conf主配置文件、Nginx虚拟主机/访问日志/限制访问IP/错误页面优化、Nginx反向代理、Nginx负载均衡
一.Nginx.conf主配置文件 Nginx主配置文件conf/nginx.conf是一个纯文本类型的文件,整个配置文件是以区块的形式组织的.一般,每个区块以一对大括号{}来表示开始与结束. 核心模 ...
随机推荐
- jQuery缓存机制(三)
缓存机制提供的入口有: $.data([key],[value]) // 存取数据 $.hasData(elem) // 是否有数据 $.removeData([key]) // 删除数据 $.acc ...
- git分支的相关问题
场景:github上准备展示vue打包的项目,做预览的功能,首先创建了分支,gh-page,然后成功将dist文件上传到了gh-page分支中,但是,在Settings的时候,发现 并没有gh-pag ...
- sencha touch carousel 扩展 CardList 可绑定data/store
扩展代码: /* *扩展carousel *通过data,tpl,store配置数据 */ Ext.define('ux.CardList', { extend: 'Ext.carousel.Caro ...
- HDU 1455 Sticks(经典剪枝)
Sticks Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Sub ...
- Python中的str与bytes之间的转换的三种方法
# bytes object b = b"example" # str object s = "example" # str to bytes sb = byt ...
- javaAgent 参数
-javaagent 这个JVM参数是JDK 5引进的. Java -help的帮助里面写道: -javaagent:<jarpath>[=<options>] load Ja ...
- 【CF802L】Send the Fool Further! (hard) 高斯消元
[CF802L]Send the Fool Further! (hard) 题意:给你一棵n个节点的树,每条边有长度,从1号点开始,每次随机选择一个相邻的点走,走到一个叶子时就停止,问期望走的总路程. ...
- [图书] C++
作者 书名 Bjarne Stroustrup The Design and Evolution of C++Stanley B. Lippman C++ PrimerStanley B. ...
- Xcode 6 下添加pch头文件
没错了,Xcode 6 有着许多坑,例如新建的工程里没有默认的pch文件,当然本质上应该是为了提高编译的速度,但却让开发略微有点不方便. 话不多说,其实新建很简单 1.先新建一个PCH文件 2.设置头 ...
- 2018牛客网暑期ACM多校训练营(第二场) A - run - [DP]
题目链接:https://www.nowcoder.com/acm/contest/140/A 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 131072K,其他语言262144K ...