终极优化!数千个并发请求

本文概述

  • Lojinha和戏剧!构架
  • 不变性和缓存
  • 内存消耗
  • 异步支持
  • 总结
Scala Web开发人员经常无法考虑成千上万的用户同时访问我们的应用程序的后果。也许是因为我们喜欢快速原型制作;也许是因为测试这样的场景非常困难。
【终极优化!数千个并发请求】无论如何, 我要争辩的是, 如果你使用适当的工具集并遵循良好的开发实践, 那么忽略可伸缩性并没有听起来那么糟糕。
如果使用适当的工具, 则忽略可伸缩性并不像听起来那样糟糕。
Lojinha和戏剧!构架 不久前, 我启动了一个名为Lojinha的项目(将其翻译成葡萄牙语的” 小商店” ), 我试图建立一个拍卖场。 (顺便说一下, 这个项目是开源的)。我的动机如下:
  • 我真的很想卖掉一些我不再使用的旧东西。
  • 我不喜欢传统的拍卖网站, 尤其是我们在巴西的拍卖网站。
  • 我想和Play一起” 玩” !框架2(双关语意)。
显然, 如上所述, 我决定使用Play!框架。我没有确切的时间来计算建造时间, 但肯定是不久之后, 我就开始使用http://lojinha.jcranky.com上部署的简单系统启动并运行我的网站。实际上, 我至少花了一半的开发时间在使用Twitter Bootstrap的设计上(请记住:我不是设计师……)。
上面的段落至少应该使一件事明确:创建Lojinha时, 我根本不必担心性能。
这正是我的观点:使用正确的工具具有强大的力量-可以使你保持正确轨道的工具, 可以鼓励你按照自己的构想遵循最佳开发实践的工具。
在这种情况下, 这些工具就是Play!框架和Scala语言, Akka进行了一些” 访客外观” 。
让我告诉你我的意思。
不变性和缓存 人们普遍认为, 最小化可变性是一种好习惯。简而言之, 可变性使推理代码变得更加困难, 尤其是当你尝试引入任何并行性或并发性时。
表演! Scala框架使你可以在大部分时间使用不变性, Scala语言本身也是如此。例如, 控制器生成的结果是不可变的。有时你可能会认为这种不变性” 令人讨厌” 或” 令人讨厌” , 但是这些” 良好实践” 之所以是” 良好” , 是有原因的。
在这种情况下, 当我最终决定进行一些性能测试时, 控制器的不变性绝对至关重要:我发现了一个瓶颈, 并且要解决它, 只需缓存此不变的响应即可。
通过缓存, 我的意思是保存响应对象并为所有新客户端按原样提供相同的实例。这使服务器不必重新重新计算结果。如果此结果是可变的, 则不可能向多个客户提供相同的响应。
缺点:在很短的时间内(缓存过期时间), 客户端可以接收过时的信息。仅在你绝对需要客户端访问最新数据且无延迟的情况下, 这才是问题。
作为参考, 以下是Scala代码, 用于在不缓存的情况下加载包含产品列表的起始页:
def index = Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) }

现在, 添加缓存:
def index = Cached("index", 5) { Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu =mainMenu)) } }

很简单, 不是吗?此处, “ 索引” 是要在缓存系统中使用的密钥, 而5是到期时间(以秒为单位)。
缓存后, 吞吐量高达每秒800个请求。对于不到两行代码, 这是超过4倍的改进。
为了测试此更改的效果, 我在本地运行了一些JMeter测试(包括在GitHub存储库中)。在添加缓存之前, 我实现了大约每秒180个请求的吞吐量。缓存后, 吞吐量高达每秒800个请求。对于不到两行代码, 这比以前提高了4倍。
终极优化!数千个并发请求

文章图片
内存消耗 合适的Scala工具可以带来很大变化的另一个领域是内存消耗。在这里, 再次播放!将你推向正确(可扩展)的方向。在Java世界中, 对于使用Servlet API编写的” 普通” Web应用程序(即, 几乎所有的Java或Scala框架), 由于该API提供了易于操作的功能, 因此很容易在用户会话中放入大量垃圾允许你这样做的调用方法:
session.setAttribute("attrName", attrValue);

由于将信息添加到用户会话非常容易, 因此经常被滥用。结果, 由于可能没有充分原因而占用过多内存的风险同样很高。
随着玩!框架, 这不是一个选择-框架根本没有服务器端会话空间。表演!框架用户会话保存在浏览器cookie中, 你必须忍受它。这意味着会话空间的大小和类型受到限制:你只能存储字符串。如果你需要存储对象, 则必须使用我们之前讨论的缓存机制。例如, 你可能想在会话中存储当前用户的电子邮件地址或用户名, 但是如果需要存储域模型中的整个用户对象, 则必须使用缓存。
玩!使你处于正确的轨道上, 迫使你仔细考虑内存使用情况, 这将产生实际上适合集群使用的首过代码。
再次, 这乍看起来似乎很痛苦, 但实际上, 玩!使你处于正确的轨道上, 迫使你仔细考虑内存的使用情况, 这将产生实际上适合集群使用的首过代码-尤其是因为无需在整个集群中传播服务器端会话无限地容易。
异步支持 此剧的下一个!框架审查, 我们将研究如何玩!在异步(hronous)支持中也很出色。除了其本机功能, Play!允许你嵌入Akka, 这是用于异步处理的强大工具。
Altough Lojinha尚未充分利用Akka, 它与Play的简单集成!使其非常容易:
  1. 安排异步电子邮件服务。
  2. 同时处理各种产品的报价。
简而言之, Akka是Erlang著名的Actor Model的实现。如果你不熟悉Akka Actor模型, 请将其想象成一个仅通过消息进行通信的小单元。
要异步发送电子邮件, 我首先创建适当的消息和参与者。然后, 我需要做的是:
EMail.actor ! BidToppedMessage(item.name, itemUrl, bidderEmail)

电子邮件发送逻辑是在参与者内部实现的, 消息告诉参与者我们想要发送哪封电子邮件。这是一劳永逸方案, 这意味着上面的行发送请求, 然后继续执行此后的所有操作(即, 它不会阻塞)。
有关Play!原生Async的更多信息, 请参阅官方文档。
总结 简介:我迅速开发了一个小型应用程序Lojinha, 能够很好地进行扩展和扩展。当我遇到问题或发现瓶颈时, 由于我使用的工具(Play!, Scala, Akka等)而使修复迅速且容易, 并且功劳颇丰, 这促使我遵循有关效率和效率的最佳实践。可扩展性。无需担心性能, 我便可以扩展到数千个并发请求。
在开发下一个应用程序时, 请仔细考虑你的工具。

    推荐阅读