架构师的能力源于你的信任
普瑞特有幸争取到了为一个架构师的架构师工作的机会。这位架构师的架构师能够设计出功能更强,性能更佳,比任何其它设计更优的系统。任何出自这个家伙之手的设计或编码,跟其它任何人设计的相似的组件相比,跟任何地方已经有了的相似系统相比,都会具有更全的功能,更强的能力,更坚不可破的性能。无人能企及。
回溯到我们要讲的这个系统的设计之初,架构师的架构师命令说这个系统的通信机制将采用JMS消息传送。并且,要准备两个队列;一个传送普通消息,一个传送高优先级消息。其它应用软件对这个新系统的服务的要求是,普通优先级的消息,系统收集后批量处理,高优先级的消息在用户间直接传送。这个两个消息队列的活动情况都受应用软件的监控,但高优先级的消息能跳到队列的最前面以达到最快的响应。还有,由于这个系统将要去替代的系统是长久以来以bug多多和稳定性极差而闻名,所以,这些新系统必须要刀枪不入。它绝对不允许宕机。
当普瑞特接受培训了解这个系统时,他问架构师的架构师,当如果有这样或那样的错误发生时,系统会做什么响应?
架构师的架构师高傲的宣称,绝对不会有任何事情能导致系统宕机。没有任何错误能阻挡它运行。没有任何情况能使它失去控制。它是无可阻挡的。它会绝对可靠,直到最后一分钟。你要相信我!
普瑞特明白这位架构师的架构师对自己深信不疑。然而,事情并不是总会按计划运行。
一天,普瑞特接到一个负责监控这个系统运行的人的电话:“你们的这个系统的日志体积增长出现异常。你们的系统在过去24小时里产生了以前一个月才会有的体积量,系统负载上没有特别的增长;请检查一下。”
普瑞特登录生产服务器,发现了一排排按序号排列的日志,每个日志文件都有20GB,全是这一天产生的!没有任何编辑器能打开这样大的文件。办法只能是用grep搜寻里面的异常。异常多的如火山汹涌。看起来系统会每秒钟吐出数百次异常事件和堆栈信息。用tail获取日志尾部片段,显示所有的异常都相同:“无法访问队列:队列控制器似乎已经停止运行。”
不幸的是,队列控制器并不认可这种说法;它很健康,并在处理着消息。
与此同时,客户对系统性能的抱怨不断升级。经理跑过来询问怎么回事。耳朵在冒烟。眼睛在喷火。普瑞特感觉到死神正在上面盯着他。
进一步调查显示,虽然有一部分应用的实例在不断的出现并抛出大量的异常,但有些却在顺利的收发信息、处理数据。
深挖之后,普瑞特发现处理消息的外层代码是这样写的:
Connection conn; // injected via Spring Queue receiveQueueNormal; // injected via Spring Queue receiveQueuePriority; // injected via Spring ... Session session = conn.createSession(true,Session.SESSION_TRANSACTED); MessageConsumer consumerNormal = session.createConsumer(receiveQueueNormal); MessageConsumer consumerPriority = session.createConsumer(receiveQueuePriority); ... while (true) { // AA: This will never fail: trust me! try { Message msg = consumerPriority.receiveNoWait(); if (msg == null) { msg = consumerNormal.receiveNoWait(); if (msg == null) { continue; } } // process the message here } catch (Throwable t) { log.error("...",t); } }
进一步在队列配置文件里发现问题:
<policyEntry topic=">" producerFlowControl="true" memoryLimit="1mb">
当消息的体积很小或消息很少时,这段代码会呼啸的运行,它能每秒钟检查无数次队列看有没有消息可处理。
但随着业务的增长,当消息的体积增长到某种程度,消息控制器就会耗尽内存,结果就会放弃一些消息,但并不是放弃所有的连接(可能是采用了自我保护模式)。这个系统——设计和开发都基于不会出现意外情况的假设——仍然在快速的最大限度的利用着CPU,但现在却因为无法和队列管理器取得联系,每个循环都会抛出异常日志信息。
没有重新获取连接的代码。没有让连续相同的日志错误信息做合并延迟输出。没有邮件发送给支持人员说“不好的事情发生了!
出自架构师的架构师的源代码里只有一句孤单的注释:”相信我!“