Rails 余额提取操作线程问题
Rails balance withdraw action threading issue
为了允许用户创建余额提取请求,我有一个 WithdrawalsController#create
操作。该代码在继续创建提款之前检查用户是否有足够的余额。
def create
if amount > current_user.available_balance
error! :bad_request, :metadata => {code: "002", description: "insufficient balance"}
return
end
withdrawal = current_user.withdrawals.create(amount: amount, billing_info: current_user.billing_info)
exposes withdrawal
end
这可能会在多线程服务器中造成严重问题。当两个创建请求同时到达,并且两个请求在创建取款前都通过了余额检查,那么即使两个取款之和可能超过原始余额,也可以创建两个取款。
class 变量 Mutex
不是一个好的解决方案,因为这会为所有用户锁定此操作,而需要在每个用户级别上锁定。
最好的解决方案是什么?
下图说明了我怀疑的线程问题,它可能发生在 Rails 中吗?
据我所知,您的代码在这里是安全的,多线程并不是什么大问题。即使您的 应用服务器 生成了更多应用实例,每个实例最终都会测试 amount > current_user.available_balance
。
如果你真的对此很偏执。你可以用 transacaction
:
包裹所有
ActiveRecord::Base.transaction do
withdrawal = current_user.withdrawals.create!(
amount: amount,
billing_info: current_user.billing_info
)
# if after saving the `available_balance` goes under 0
# the hole transaction will be rolled back and any
# change happened to the database will be undone.
raise ActiveRecord::Rollback if current_user.available_balance < 0
end
为了允许用户创建余额提取请求,我有一个 WithdrawalsController#create
操作。该代码在继续创建提款之前检查用户是否有足够的余额。
def create
if amount > current_user.available_balance
error! :bad_request, :metadata => {code: "002", description: "insufficient balance"}
return
end
withdrawal = current_user.withdrawals.create(amount: amount, billing_info: current_user.billing_info)
exposes withdrawal
end
这可能会在多线程服务器中造成严重问题。当两个创建请求同时到达,并且两个请求在创建取款前都通过了余额检查,那么即使两个取款之和可能超过原始余额,也可以创建两个取款。
class 变量 Mutex
不是一个好的解决方案,因为这会为所有用户锁定此操作,而需要在每个用户级别上锁定。
最好的解决方案是什么?
下图说明了我怀疑的线程问题,它可能发生在 Rails 中吗?
据我所知,您的代码在这里是安全的,多线程并不是什么大问题。即使您的 应用服务器 生成了更多应用实例,每个实例最终都会测试 amount > current_user.available_balance
。
如果你真的对此很偏执。你可以用 transacaction
:
ActiveRecord::Base.transaction do
withdrawal = current_user.withdrawals.create!(
amount: amount,
billing_info: current_user.billing_info
)
# if after saving the `available_balance` goes under 0
# the hole transaction will be rolled back and any
# change happened to the database will be undone.
raise ActiveRecord::Rollback if current_user.available_balance < 0
end