简体   繁体   English

乐观锁定用户信用管理

[英]optimistic locking user credit management

I have a central database for handling user credit with multiple servers reads and writes to it. 我有一个中央数据库,用于处理多个服务器的用户信用读写操作。 The application sits on top of these servers serve user requests by doing the following for each request: 该应用程序位于这些服务器之上,通过对每个请求执行以下操作来满足用户请求:

1. check if user has enough credit for the task by reading from db.
2. perform the time consuming request
3. deduct a credit from user account, save the new credit count back to db.

the application uses the database's optimistic locking. 应用程序使用数据库的乐观锁定。 So following might happen 因此可能会发生以下情况

1. request a comes in, see that user x has enough credit,
2. request b comes in, see that user x has enough credit,
3. a performs work
4. a saves the new credit count back to db
5. b performs work
6. b tries to save the new credit count back to db, application gets an exception and fails to account for this credit deduction.

With pessimistic locking, the application will need to explicitly get a lock on the user account to guarantee exclusive access, but this KILLs performance since the system have many concurrent requests. 使用悲观锁定,应用程序将需要显式获取用户帐户的锁定以保证独占访问,但是由于系统有许多并发请求,因此这会降低性能。
so what would be a good new design for this credit system? 那么对于这种学分制来说,什么是好的新设计呢?

Here are two "locking" mechanisms at avoid using InnoDB's locking mechanism for either of two reasons: 这里有两个“锁定”机制,可以避免使用InnoDB的锁定机制,原因有两个:

  • A task that takes longer than you should spend in a BEGIN...COMMIT of InnoDB. 花费比在InnoDB的BEGIN...COMMIT中花费的时间更长的任务。
  • A task that ends in a different program (or different web page) than it started in. 以与开始时不同的程序(或不同的网页)结束的任务。

Plan A. (This assumes the race condition is rare, and the time wasted for Step 2 is acceptable in those rare cases.) 计划A。 (这是假设比赛条件很少见,在这种罕见情况下,第二步所浪费的时间是可以接受的。)

  1. (same) check if user has enough credit for the task by reading from db. (相同)通过从db中读取来检查用户是否对任务有足够的功劳。
  2. (same) perform the time consuming request (相同)执行耗时的请求
  3. (added) START TRANSACTION; (添加) START TRANSACTION;
  4. (added) Again Check if the user has enough credit. (添加)再次检查用户是否有足够的信用额度。 ( ROLLABCK and abort if not.) ROLLABCK ,如果没有则中止。)
  5. (same as old #3) deduct a credit from user account, save the new credit count back to db. (与旧#3相同)从用户帐户中扣除信用,将新信用计数保存回db。
  6. (added) COMMIT; (添加) COMMIT;

START..COMMIT is InnoDB transaction stuff. START..COMMIT是InnoDB事务的东西。 If a race condition caused 'x' to not have credit by step 4, you will ROLLBACK and not perform steps 4 and 5. 如果在第4步中竞赛条件导致'x'不能获得积分,则您将ROLLBACK而不执行第4步和第5步。

Plan B. (This is more complex, but you might prefer it.) 计划B。 (这比较复杂,但是您可能更喜欢。)

  1. Have a table Locks for locking. 有一个表Locks用于锁定。 It contains user_id and a timestamp. 它包含user_id和时间戳。
  2. START TRANSACTION;
  3. If user_id is in Locks , abort ( ROLLBACK and exit). 如果user_id在Locks ,则中止( ROLLBACK并退出)。
  4. INSERT INTO Locks the user_id and current_timestamp in Locks (thereby "locking" 'x'). INSERT INTO Locks将user_id和current_timestamp Locks (从而“锁定”“ x”)。
  5. COMMIT;
  6. Perform the processing (original Steps 1,2,3) 执行处理(原始步骤1、2、3)
  7. DELETE FROM Locks WHERE user_id = 'x'; ( autocommit=1 suffices here.) (这里的autocommit=1就足够了。)

A potential problem: If the processing dies in step 6, not getting around to releasing the lock, that user will be locked out forever. 潜在的问题:如果处理在步骤6中终止,而无法解决解锁问题,则该用户将永远被锁定。 The 'solution' is to periodically check Locks for any timestamps that are 'very' old. “解决方案”是定期检查Locks是否有“很旧”的时间戳。 If any are found, assume that the processing died, and DELETE the row(s). 如果找到任何内容,则假定该处理已终止,然后删除该行。

You didn't state explicitly what you want to achieve, so I assume you don't want to perform the work just to realise it has been in vain due to low credit. 您没有明确说明要实现的目标,所以我想您不想执行该工作只是为了意识到由于信用低而使它徒劳无功。

No-lock 无锁

Implement credit hold on step (1) and associate the work (2) and the deduction (3) with the hold. 对步骤(1)实行信用保留,并将工作(2)和扣除额(3)与保留相关联。 This way low credit user won't pass step (1). 这样,信用度较低的用户就不会通过步骤(1)。

Optimistic locking 乐观锁

As a collision is detected in optimistic locking post factum, I don't think it fits the assumption. 由于在乐观锁定后发现了碰撞,因此我认为这不符合假设。

Pessimistic locking 悲观锁定

It isn't possible to tell definitely without knowing the schema, but I think it's an exaggeration about killing performance . 在不了解模式的情况下不可能确切地说出来,但是我认为这是夸大性能的夸大其词。 You can smartly incorporate MySQL InnoDB transaction isolation levels and locking reads at finer granularity than exclusively locking a user account completely. 您可以巧妙地合并MySQL InnoDB 事务隔离级别,并以比完全独占锁定用户帐户更好的粒度锁定读取 For instance, using SELECT ... LOCK IN SHARE MODE which sets shared locks and allows reads for other transactions. 例如,使用SELECT ... LOCK IN SHARE MODE设置共享锁并允许读取其他事务。

Rick's caution about the tasking taking longer then MySQL will wait ( innodb_lock_wait_timeout ) applies here. 里克关于任务需要花费更长的时间才能使MySQL等待的警告( innodb_lock_wait_timeout )在这里适用。

You want The Escrow Transactional Method . 您需要托管交易方法

You record the credit left after doling out some to each updating process and the credit doled out to (ie held in escrow for) them. 您可以为每个更新过程分配一些信贷,然后将信贷分配给他们(即代管),以记录剩余的信贷。 A process retries until success a transaction that increases the credit doled out by what it needs and decreases the credit that is left by what it needs; 一个过程重试直到成功,该事务增加了需要的信贷额度,而减少了需要的信贷额度; it succeeds only if that would leave the credit left non-negative. 它只有在使信用不为负的情况下才能成功。 Then it does its long calculation. 然后进行长时间的计算。 Regardless of the calculation's success it then applies a transaction that decreases the credit doled out. 不管计算成功与否,它都将应用减少已发放信贷的交易。 But on success it also increases the assets while on failure it increases the credit left. 但是成功则增加资产,失败则增加剩余的信用。

Use the timestamp/rowversion approach that you will find in all real database engines except MySQL. 使用您在除MySQL之外的所有实际数据库引擎中都可以找到的时间戳/行转换方法。

You can emulate them with MySQL in this way. 您可以通过这种方式使用MySQL进行仿真。 Have a TIMESTAMP column (updated) that gets updated whenever a row is updated. 有一个TIMESTAMP列(已更新),该列在每行更新时都会更新。 Select that column along with the rest of the data you require. 选择该列以及所需的其余数据。 Use the returned timestamp as a condition in your WHERE clause so that the row will only be updated when the timestamp is still the same as when you read the row. 将返回的时间戳记用作WHERE子句中的条件,以便仅在时间戳记与读取行相同时才更新行。

UPDATE table SET col1 = value WHERE id = 1 AND updated = timestamp_value_read

Now when you run the update and the timestamps do not match no update will be performed. 现在,当您运行更新并且时间戳不匹配时,将不执行任何更新。 You can test for this by using rows affected, if zero rows were updated then you know that the row was modified between read and write. 您可以通过使用受影响的行来对此进行测试,如果更新了零行,那么您知道该行在读写之间进行了修改。 Handle that condition in your code whichever way is best for your application and your users. 以哪种代码最适合您的应用程序和用户的方式处理该条件。

Timestamp tutorial 时间戳教程

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM