How to avoid double processing of data on a busy site?
On my website, people can add an item to their wishlist. When X numb开发者_如何学编程er of people have added it to their list, then all those peoples' credit cards are charged.
The problem I'm facing is how to ensure that if two customers add it to their wishlist at the same time, then the payment processing code won't run twice. Any ideas?
An example of what can happen is:
- We are waiting for 20 people to add the item to their wishlist, and we have 19.
- Bob and Sally visit the site and click the 'add to wishlist' button
- The server receives Bob's request, sees that 20 requests are now met, and charges the payments.
- At the same time the server receives Sally's request, and still seeing 19 requests in db since Bob's order was simultaneously received, begins to process the payments. Hence, the payments are charged twice.
Any ideas on how to avoid this?
I am using a MySQL database and PHP for the programing.
This is the type of thing for which transactions are designed. The charging of the cards and the reseting of the wislist count must be in the same transaction so that they occur as an atomic unit. Furthermore, to avoid the problem you are describing, you must set the transaction isolation level to at least "Read Committed" "Repeatable Read".
Additional information:
Here's how to do it: 1. The app opens a transaction on the database. 2. The app does a select on the wishlish tables to retrieve the count. 3. If the count is >= n, the app does another select on the wishlist and related tables to retrive the pending wishlist orders, users, card info, etc. 4. Depending on the business rules regarding card transactions, the app then deletes the pending orders, or whatever to reset the wishlist count back to zero. 5. The app then closes the transaction.
Here's why it works: when the app does a select on the wishlist tables to retrieve the count inside a transaction, the db places a read lock on the tables associated with this query. If another transaction that opened during the pendency of the prior transaction tries to read those same tables, it must wait until the prior transaction has either a COMMIT or a ROLLBACK. If the prior transaction COMMITS, then the next transaction will see a count of 0 and all the other modifications. Otherwise, if the app executes a ROLLBACK for any reason, none of the data changes and the next transaction sees the data as it existed prior to the first transaction.
I am doing a similar site at the moment. Seems to be popular...
It is important that your processes are idempotent. In this context, this means that if you run our charging service multiple times, the orders which have already been charged are not charged twice.
I accomplish this by setting the OrderStatus to 'NotProcessed' when the order is placed. Once the service runs and charges for an order the OrderStatus changes to 'PaymentPending'. I charge for the order only if the OrderStatus is 'NotProcessed'.
PSEUDO CODE:
void ProcessPendingOrders()
{
var orders = getAllOrders();
foreach(Order order in orders)
{
if (order.OrderStatus == NotProcessed)
ChargeOrder(order)
}
}
精彩评论