It’s really dangerous. Some people went to jail for it; Companies have lost billions.

Imagine you’re on a dark, windy night, about 10 o ‘clock, coming home from work and going to the concession stand to get a pack of cigarettes. The night is cold and windy, and you wrap your coat around it. There’s the cashier girl you have a crush on. Working day and night, only these ten minutes can make you feel some meaning in life. He reached into his shy wallet for the one hundred dollar bill he had left and settled the bill. Then he took the cashier’s change with trembling hands. It wasn’t because I touched her fingertips. And not because of her flowery smile.

Just because, in my mind unexpectedly not up to the emergence of such a process.

balance = dao.getBalance(userid)
balance = balance - cost
dao.setBalance(userid,balance)
Copy the code

Return is really a dog can not change to eat excrement ah, as expected or a yard animal. Remind oneself, abased of buried face, quick step away.

What’s this? This is our little gift to 996.

Operation of a wave of MA 6

Balance modification is the most common operation in a trading system. Take out the balance, then deduct the consumption, and then write back the balance. Normally this is not a problem.

Unless it’s high concurrency, it doesn’t matter if you’re single.

High concurrency on a single balance is not normal, the system is under attack, or MQ is being used in a self-righteous way. The above operations are vulnerable to attack.

Take the most serious example: making a request to spend $20 and $5 at the same time. After a flurry of aggressive action, both requests were paid successfully, but only $5 was deducted. That’s like spending $5 on 25 things.

To highlight

protectiveWay to

SQL solution

update user set balance = balance - 20
    where userid=id
Copy the code

This statement is a lot safer, and you can refine your SQL a bit more if you consider that the balance can’t be negative.

update user set balance = balance - 20
    where userid=id and balance >= 20
Copy the code

Above SQL, you can ensure the security of the balance, high concurrency under the attack becomes less meaningful. But there are other problems, like repeated deductions.

Solve by locking

In reality, this kind of direct deduction through SQL application is relatively small. Read first and set values later is common when your business is getting more complex without a good split. For example, some marketing operations, discounts, points exchange, etc.

In this case, you can introduce distributed locks. Simple point, just need to use Redis setNX or ZK to control can be; For more complex scenarios, you can use two-phase commit or something like that.

The business granularity of distributed transactions should be thick enough to protect these balance operations; The granularity of locking should be fine enough to ensure the efficiency of the system.

begin transition(userid)
    balance = dao.getBalance(userid)
    balance = balance - cost
    dao.setBalance(userid,balance)
end
Copy the code

Cas-like mode

For Those of you who are Java, recall the solution to the Concurrent package. That’s the introduction of CAS, Compare And Set.

This strategy can also be applied to distributed environments. That is, compare and set. If the initial value has changed, the set value is not allowed.

Cas generally performs status updates through a loop of retry, but the balance operation is usually a single operation. You can also terminate the operation and warn of the risk.

SQL is similar to:

update user set balance = balance - 20
    where userid=id 
    and balance >= 20
    and balance = $old_balance
Copy the code

Of course, you can control this process by adding a version number concept instead of a balance field, but it’s similar.

Variant: version number

Concurrency is controlled by adding an additional field version to the table. This approach is more scalable without paying attention to balances.

The default value for version is generally 1, which is the default value when the record is created.

The pseudocode for the operation is as follows:

version,balance = dao.getBalance(userid)
balance = balance - cost
dao.exec("
    update user 
    set balance = balance - 20
    version = version + 1
    where userid=id 
    and balance >= 20
    and version = $old_version
")
Copy the code

With the above concurrent attack, only one operation will succeed and our balance is safe.

End

Take a quick look at your balance operations to see if they are also exposed. You can choose to accept the beatitudes and remain brothers, or you can return the beatitudes to the capitalists.

One recitation becomes a Buddha, one recitation becomes a demon. You are the master of yourself.