微服务 - 多个服务副本之间的竞争条件
Micro services - race condition between multiple service replica
我正在学习微服务 - Spring 现在是云。在学习过程中,我在思考如何处理下面几种情况下的race condition。
假设我有一个“ProductOrder”服务,它有 2 个副本。现在有两个订单请求,负载均衡器为每个副本分配一个 api 请求。请注意,它们共享同一个数据库(所以我不是在谈论数据同步。或者我是?)。那么服务如何确保存储仍然有足够的产品来满足订购请求。
我知道 java 中的关键字“synchronized”用于多线程。但是,副本不是多线程而是多进程。我对么?有什么想法吗?
数据库将有一个 table 像 Product(id, amount)
。下订单需要更新该产品的记录。
它的实现看起来像这样。首先,应用程序将从数据库中读取产品数量:
SELECT id, amount
FROM Product
WHERE id = {some_id}
FOR UPDATE;
然后应用程序代码将检查是否有请求的金额。然后它将更新数据库中的值:
UPDATE Product
SET amount = {new_amount}
WHERE id = {some_id}
注意:
- 所有这些都应该在一个数据库事务中发生
- 第一个查询使用
FOR UPDATE
子句来防止其他并发事务在我们的事务 运行ning 时修改此产品。将其视为 synchronized
但不是在 JVM 级别而是在数据库级别。
- 如果另一个用户将尝试购买相同的产品,则该用户的交易(我们称之为 T2)将尝试做同样的事情,第一步是查询带锁的记录。这将阻塞事务 T2 直到记录被解除阻塞,这将在当前持有锁的事务提交或回滚时发生。届时 T2 将继续,如果 T1 更改了产品,它将看到产品的最新值。
这种方法使用悲观锁,即我们在整个事务期间锁定要更新的记录。
还有另一种方法——乐观。
乐观锁要求您在产品中有一个版本列table。然后第一个查询没有锁定数据库中的记录,但我们得到了记录的版本:
SELECT id, amount, version
FROM Product
WHERE id = {some_id}
签入应用程序的工作方式与悲观场景中的相同。但最后的更新是不同的:
UPDATE Product
SET amount = {new_amount}
WHERE id = {some_id}
and version = {version_we_read_initially}
更新操作总是returns更新记录的数量。如果我们得到 0,则表示在我们的交易 运行 时记录已更改。这意味着我们最初(在第一个查询中)看到的数量可能已经改变。为了继续,我们需要重试从步骤 1 开始的整个操作,即再次读取产品的数量和版本,检查是否存在所需数量并再次尝试更新数量。
当然,我们要重试的次数应该有一些限制。在需要产品的繁忙系统上,这可能会成为一个问题。
我正在学习微服务 - Spring 现在是云。在学习过程中,我在思考如何处理下面几种情况下的race condition。
假设我有一个“ProductOrder”服务,它有 2 个副本。现在有两个订单请求,负载均衡器为每个副本分配一个 api 请求。请注意,它们共享同一个数据库(所以我不是在谈论数据同步。或者我是?)。那么服务如何确保存储仍然有足够的产品来满足订购请求。
我知道 java 中的关键字“synchronized”用于多线程。但是,副本不是多线程而是多进程。我对么?有什么想法吗?
数据库将有一个 table 像 Product(id, amount)
。下订单需要更新该产品的记录。
它的实现看起来像这样。首先,应用程序将从数据库中读取产品数量:
SELECT id, amount
FROM Product
WHERE id = {some_id}
FOR UPDATE;
然后应用程序代码将检查是否有请求的金额。然后它将更新数据库中的值:
UPDATE Product
SET amount = {new_amount}
WHERE id = {some_id}
注意:
- 所有这些都应该在一个数据库事务中发生
- 第一个查询使用
FOR UPDATE
子句来防止其他并发事务在我们的事务 运行ning 时修改此产品。将其视为synchronized
但不是在 JVM 级别而是在数据库级别。 - 如果另一个用户将尝试购买相同的产品,则该用户的交易(我们称之为 T2)将尝试做同样的事情,第一步是查询带锁的记录。这将阻塞事务 T2 直到记录被解除阻塞,这将在当前持有锁的事务提交或回滚时发生。届时 T2 将继续,如果 T1 更改了产品,它将看到产品的最新值。
这种方法使用悲观锁,即我们在整个事务期间锁定要更新的记录。
还有另一种方法——乐观。
乐观锁要求您在产品中有一个版本列table。然后第一个查询没有锁定数据库中的记录,但我们得到了记录的版本:
SELECT id, amount, version
FROM Product
WHERE id = {some_id}
签入应用程序的工作方式与悲观场景中的相同。但最后的更新是不同的:
UPDATE Product
SET amount = {new_amount}
WHERE id = {some_id}
and version = {version_we_read_initially}
更新操作总是returns更新记录的数量。如果我们得到 0,则表示在我们的交易 运行 时记录已更改。这意味着我们最初(在第一个查询中)看到的数量可能已经改变。为了继续,我们需要重试从步骤 1 开始的整个操作,即再次读取产品的数量和版本,检查是否存在所需数量并再次尝试更新数量。
当然,我们要重试的次数应该有一些限制。在需要产品的繁忙系统上,这可能会成为一个问题。