Thứ Bảy, 31 tháng 3, 2018

Race condition with redis transaction

Suppose that you have amount of item X need to be sold. And there are a lot of user log into your website and buy item X at same time. How will you sell as much as possible?

There are two phase when a user want to buy an item X.
- Checking phase : Checking if item X is available.
- Selling phase: If there is available item X, you will sell it to user and decrease amount of available X.
For example, you have 5 item X. There are 10 users buy item X at same time. 10 orders happen at same time. What if they all are at checking phase? Your system will accept 10 order while you just have 5 item. So how you solve it? This post will give a solution for this by using redis transaction.
To know more about redis transaction, you can read it here:
https://redis.io/topics/transactions

If you just need to execute a sequence commands without checking logic between commands, you can use MULTI - EXEC. But in this case, we need to check the logic if item X is available before deciding to sell it, so we need to use an other option: WATCH.

I will use GO language to show how we deal with it.
Item's status will be save in redis with two key
- Quantity: Amount of item available in ware house.
- Reserved: Amount of success order(but it haven't delivery to user yet)


WATCHed keys are monitored in order to detect changes against them. If at least one watched key is modified before the EXEC command, the whole transaction aborts, and EXEC returns a Null reply to notify that the transaction failed

When the transaction failed, we will recall the function that process order until there is no conflicted transaction.

func reserve(key string) error {
    err := client.Watch(func(tx *redis.Tx) error {
        curQuantity, _ := tx.Get(keyQuantity).Int64()
        curReserved, _ := tx.Get(keyReserved).Int64()

        if((curQuantity - curReserved) > 1){
          _, err = tx.Pipelined(func(pipe *redis.Pipeline) error {
            pipe.Inc(key)
            return nil
          })
        }
        return err
    }, keyQuantity, keyReserved)
    if err == redis.TxFailedErr {
        return reserve(key)
    }
    return err
}




Không có nhận xét nào:

Đăng nhận xét