REST服务和竞态条件

7
让我们假设一个问题:我有一个使用Java / MySQL / Spring和HTTP / JSON技术实现的REST服务。 REST服务的客户端是移动应用程序。因此,有可能有人反编译代码并获取REST服务的API。(是的,代码已混淆等,但无论如何)。
问题:有一个POST方法将钱发送给应用程序的其他用户。我担心,有人可以获取API,编写机器人,并使此POST请求每秒500次,5000次甚至50000次。结果,他可能会发送比他实际拥有的更多的钱,因为如果同时处理1000个请求,则所有1000个请求的余额检查可能都成功,然而账户上的实际金额可能仅足够处理50个请求。
因此,基本上它更像是具有多个线程的标准“竞争”条件。问题是,我有多个服务器,它们没有任何关系。因此,300个请求可以发送到服务器A,300个请求可以发送到服务器B,其余请求可以发送到服务器C。
我所拥有的最好的想法是使用类似于“SELECT ... FOR UPDATE”的东西,并在数据库级别上进行同步。但是,我想考虑另一种解决方案。
有任何想法或建议吗?

1
你难道没有登录、会话和反跨站请求伪造令牌来确保转账请求只能来自已登录的授权用户吗?你难道没有授权检查来确保只有转移自己的资金的请求得到尊重吗?你难道没有三层应用程序,使前端只处理表示层,业务逻辑在幕后处理吗?你难道没有将类似请求(相同的捐赠者、相同的目标等)的处理分段到单个业务逻辑服务器中的能力吗? - atk
登录/会话如何可以防止这种情况?如果有人黑掉 API,他可以黑掉登录/会话并使用有效的身份验证机制发送此请求。顺便说一下,身份验证是基于令牌的,即 OAuth。它是一个 REST 服务,我不使用 csrf 令牌。 - user3489820
如果您让用户登录并限制只能使用自己的资金,那么您就可以防止攻击者花费他人的资金。这并不能防止他们透支自己的账户,但是可以防止他们透支别人的账户。 - atk
你可能还想了解透支费用,就像银行一样... - atk
2个回答

2
您有几种选择:
  1. 依靠数据库的ACID实现(在您的情况下是MySQL)。假设您正在使用InnoDB引擎,您需要选择正确的事务隔离级别(SET TRANSACTION语法)以及正确的锁定读取机制(SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE锁定读取)。您需要深入了解这些概念,才能做出正确的选择。可能只是使用正确的隔离级别就足以防止竞争条件,即使没有锁定读取。缺点是您在可伸缩性方面正在权衡一致性,并将您的应用程序绑定到RDBMS数据库,因此移动到NoSQL等其他数据库将更加困难。

  2. 将后端分解为Web层和服务层(来自评论中atk提出的选项)。这将允许您独立扩展Web层实例,同时保持单个服务层实例。拥有单个服务层实例可以使用Java同步机制,如synchronized块或ReadWriteLock。虽然这个解决方案可以工作,但我不建议它,因为它降低了您的服务层的可扩展性。

  3. 这是上一个选项的增强版。您可以使用分布式锁管理器而不是内置的Java同步机制。它将允许您独立扩展Web层和服务层。


分布式锁管理器可能不利于可扩展性。我将尝试使用关系型数据库管理系统(RDBMS)。 - user3489820

0

对于关键任务应用程序,最好采用多级锁定机制。

"SELECT ... FOR UPDATE"是一种好的方法,但它们非常昂贵,当你试图用Charles轰炸它时,你会发现你的API堆栈将受到影响,而这个简单的机制很容易瘫痪你的基础架构,就像DDoS事件一样。

首先在负载均衡器/代理层实现它,以限制来自单个IP地址的N个请求在指定时间间隔内。

然后应用共享缓存层锁,在其中所有框架都同步于某些关键事务上的键,具体取决于您要锁定的关键交易。例如,您可以使用Redis GETSET或INCR功能原子地设置标志,然后进入临界代码路径。快速拒绝其他任何内容,以避免那些不良行为者占用CPU /内存。

您还可以像在APC缓存中实现类似锁定(在访问您的Redis/Memcache集群之前)在每个框架上进行。由于没有网络延迟,这更快。

以上措施是在使用“SELECT ... FOR UPDATE”之上必不可少的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接