让我们想象一个问题: 我有一个REST服务,它使用Java / MySQL / Spring和HTTP / JSON技术实现。 REST服务的客户端是移动应用程序。 所以有人可能会反编译代码并获得REST服务的API。 (是的,代码是混淆等的,但无论如何)。
问题:有一种POST方法可以向应用程序的其他用户汇款。 我担心,有人可以获得API,编写机器人并使此POST请求每秒500或5,000或甚至50,000次。 结果,他可能会发送比实际更多的钱,因为如果同时处理1000个请求,那么余额检查可能是 对于所有1000个请求都是成功的,但是一个帐户的实际金额可能仅足以支付50个请求。
所以,基本上,它更像是具有多个线程的标准“竞争”条件。 问题是,我有多个服务器,无论如何它们彼此无关。 因此,300个请求可以来到服务器A,300个请求可以来到服务器B,其余的请求可以来到服务器C.
我最好的想法是使用“SELECT ... FOR UPDATE”之类的东西并在数据库级别上进行同步。 但是,我想考虑另一种解决方案。
有任何想法或建议吗?
答案 0 :(得分:2)
您有几个选择:
依靠数据库的ACID实现(在您的情况下为MySQL)。假设您使用的是InnoDB引擎,则需要选择正确的事务隔离级别(SET TRANSACTION syntax)并结合正确的锁定读取机制(SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE Locking Reads)。您需要很好地理解这些概念才能做出正确的选择。即使没有锁定读取,简单地使用正确的隔离级别也可能已经阻止了竞争条件。缺点是你在一致性方面是为了扩展性并将你的应用程序绑定到RDBMS数据库,因此你很难转移到NoSQL。
将您的后端分解为Web层和服务层(注释中atk建议的选项)。这将允许您在保留单个服务层实例的同时独立扩展Web层实例。拥有单个服务层实例可以使用Java同步机制,例如synchronised
块或ReadWriteLock
。虽然这个解决方案可行但我不推荐它,因为它会降低服务层的可扩展性。
这是前一个选项的增强功能。您可以使用Distributed lock manager而不是内置的Java同步机制。它允许您独立扩展Web层和服务层。
答案 1 :(得分:0)
对于关键任务应用程序,最好有多级锁定机制。
“SELECT ... FOR UPDATE”是一个很好的方法,但是它们非常昂贵,当你试图用Charles轰炸它时,你会发现你的上层API堆栈会受到影响,而且简单的机制将很容易削弱您的基础架构,就像DDoS事件一样。
首先在Load Balancer / Proxy层实现它,从单个IP地址限制每个指定时间间隔的N个请求数。
然后应用共享缓存层锁定,其中所有框都将根据您要锁定的关键事务在某些键上进行同步。例如,在输入关键代码路径之前,您可以使用Redis GETSET或INCR功能以原子方式设置标志。快速拒绝任何其他内容,以避免那些不良演员持有CPU /内存。
您还可以实现APC缓存(在访问Redis / Memcache群集之前)之类的操作,以便在每个盒子的基础上执行类似的锁定。由于不涉及网络延迟,因此速度更快。
除了使用“SELECT ... FOR UPDATE”
之外,还需要上述内容