TA的每日心情 | 开心 2016-10-18 06:23 |
---|
签到天数: 72 天 连续签到: 1 天 [LV.6]常住居民II 扫一扫,手机访问本帖
|
我们大家都时不时地犯错误而且,有时我们会一次犯很多错误!Grzegorz Ziemonski以一种令人愉快的方式和坦诚的态度与我们一起回顾了他在一次关键性Java应用程序发行调试过程中一路走来所犯的错误。
来自于DZone团队的Michael Tharrington最近向我建议,我应该写一写作为一个开发者我所犯过的一些错误和从中取得的教训。好吧,现在做这件事实在是最恰当不过我的团队刚刚上线一款关键的应用程序,而一切可能出错的地方都出现错误了!当然,并非下文提供的每一个解决方案都完全是由我编写的其中一些是我们在敏捷开发中结对编程时所产生的,而一切都通过一个严格的代码审查过程也正是这一点使得问题更加可笑。
问题产生的背景
我们正在开发一款简单的Web应用程序,此程序能够暴露一个REST API以便与一个外部供应者程序进行通信并能够把一些结果永久地存储起来;这样的话,我们就不需要做太多的调用,因为我们不得不为每一次API调用付费。该应用程序主要依赖于Spring Boot这个Web框架及数据功能。该应用程序的两个实例将部署在公司的支持负载平衡器的服务器上。
错误1进程并发错误
对外部提供者程序的调用需要花费很长的时间;自然,我们的客户端应用程序都不愿意等待这种类型的调用。正因为如此,我们都同意通过后台处理方式加以改进第一次调用时,我们返回一条消息"DONT_KNOW_YET_COME_BACK_LATER..."。下次调用时,我们返回一个持久性的结果。于是,自然就出现这样一个问题:如果我们在足够短的时间内进行两次相同的调用,那么会发生什么情况呢?灵感突然出现:我们必须以某种方式保护自己避免使用相同的数据两次调用外部提供者程序。下面给出我的初步的编程方案:
private SetLong currentlyProcessedIds = ConcurrentHashMap.newKeySet(); // further down in the code: if (currentlyProcessedIds.add(id) { doTheJob(id); currentlyProcessedIds.remove(id); }
你能找到这段代码有什么毛病吗?如果你马上发现不了我的愚蠢错误,慢慢来就是。
当然,如果doTheJob部分抛出异常的话,整段程序会中断!这是典型的进程并发错误,导致明显的内存破坏!遗憾的是,四个人盯着这段代码却没人注意到,其实我们可以阻止id再次被正确处理。下面是更正后的代码的正确版本:
if (currentlyProcessedIds.add(id) { try { doTheJob(id); } finally { currentlyProcessedIds.remove(id); } }
错误2负载已平衡,但问题仍存在
如果你足够聪明,你可能已经注意到哪里出现错误了上面的这段代码仍然存在问题!我们在一台负载平衡器上部署了应用程序的两个独立的实例。这意味着,问题并不只是并发方面的,还与分布式有关系!
目前,我们还没有实现一个分布式的解决方案,但我们明显需要实现两个应用程序实例间对当前处理过的数据集进行同步。
错误3喝早咖啡意外发现性能问题
后台处理的想法来自于我观察旧式解决方案的结果。这种老式解决方案严重依赖于数据库的性能,但即使在最糟糕的情况下,也比外部调用要快。一开始,我们建议负责客户端应用程序的程序员可以允许第一次(长)调用超时,然后稍后再试。但是,实际运行情况表明:当整个系统负载很重时这种方案还很不理想。我负责测算我们的应用程序响应一个时间帧需要多长时间。我找到旧解决方案中所有的数据库查询并一个接一个地针对我们的数据库进行测试。我收集这些结果,作出一些计算,最后制作成一个不错的表并把它放在我们公司的Wiki上。
到底哪里出错了?好,我一边喝着早餐咖啡一边手工运行查询,结果发现这次的系统性能大大不同于在重负荷下运行所有系统的性能。这意味着,我的计算以前过于乐观,而在产品上线之后我们看到的第一件事成为客户端应用程序超时的"一面墙壁"。
错误4使用过少的测试数据
现在来看,系统负载只是我们高估了系统性能表现的原因之一。第二个原因更令人尴尬!我们的应用程序和客户端程序之间的所有测试都基于一组相当有限的数据集大约1000条记录。结果看起来非常成功,因为测试过程中没有出现过一点警告标志。
其实,我们忽略了一个小小的细节,那就是:系统上线前,我们使用从旧解决方案中迁移的数据填充数据库这些数据大约有100万条记录!数据集比原来增加了三个数量级!我们这才发现自己忘记了设置关键的数据库索引这是一款关键应用程序上线前你必须且心甘情愿要做的事情!
错误5度量并非统计结果
商界人士要求我们准备一份有关我们向外部提供者程序发出请求的报告。因为我们做了大量的调用,而且这些调用都很昂贵,所以我们想给他们提供一种方式来计算预期成本和验证我们从供应商那里得到的发票。当然,业务人员不懂SQL,所以他们要求通过电子邮件发送给他们一个Excel报表。哦,我的娘要求我们生成Excel文件并发送电子邮件?如今都是2016年了。实在是没有办法!最终,我们还是提供了一个漂亮的界面来实现统计度量。以后我们还将添加一个针对发出请求次数的计数器!
因为我们有了如上文所述的那些最初的问题,所以我们发出一些修补程序并再次发布应用程序。经过重新部署,显示有计数器的仪表板看起来很不正常。请求的次数发生了什么事?情况是:度量数据保存在应用程序端,并每隔固定的时间间隔发送到度量服务端。这意味着,每次我们重新启动应用程序,计数器的值都将消失了。简单是废话!
幸运的是,我们有一种方法能够提供有关请求的正确信息,而没有使用仪表板上的漂亮计数器,但我们仍然在试图实现一个真正的计数器,以保持业务的统计数字。
总结
就在我们的产品最终上线之前,我曾经告诉我的同事说:我有些担心,因为从项目开始到测试阶段都不曾遇到过重大的问题,一切显得那么顺利,没有出现过重大障碍。事实证明我是正确的,错误的东西注定会发生!虽然我在这篇文章中提到的错误现在看起来都是很明显的,但是当我们赶时间进行开发和测试时它们却不是那么明显。现在,写出关于我们的项目开发中的这些羞于启齿的事是有点儿尴尬;但是,我希望作为读者的您至少能够从中获得一些乐趣,并且以后不会再犯和我们同样的错误!
作者:朱先忠
|
|