Reputation: 1244
On a running RabbitMQ server user PHP for the workers,
I have a event that will fire off a message "Bring User X Account to $5" to the "charge" queue. However, many events may send this, and if there is more than one worker working on the system, I can see an issue where there is a race condition that happens where the user gets double charge.
$accountBalance = -25.00 // starts
event 1: "Bring User X Account to $5"
event 1: check balance, $balance = -25.00
event 2: "Bring User X Account to $5"
event 1: $chargeAmount = 30
event 2: check balance, $balance = -25.00
event 1: Charge Card $30 // takes 3s
event 2: $chargeAmount = 30
event 2: Charge Card $30 // takes 6s
event 3: "Bring User X Account to $5"
event 3: check balance, $balance = -25.00
event 1: add transaction $30 // $accountBalance = 5.00
event 1: END
event 4: "Bring User X Account to $5"
event 4: check balance, $balance = 5.00
event 4: END
event 2: add transaction $30 // $accountBalance = 35.00
event 2: END
event 3: $chargeAmount = 30
event 3: Charge Card $30 // takes 3s
event 3: add transaction $30 // $accountBalance = 65.00
event 2: END
$accountBalance = 65.00
As you can see 4 events came in, and instead of charging $30 once, it charges three times. Only event 4 exited, but because Event 1 ended soon.
I have played with the idea of a lock, but that is a little vague since each worker may be on an independent server.
Is there any know Out of the box solutions that are easy to work with?
Upvotes: 0
Views: 106
Reputation: 7606
You need to use a distributed lock to prevent two tasks from occurring at the same time along with making your task idempotent. Even if you do have locks, the tasks need to be able to run and detect that the work was done and ignore. The two concepts work together.
You can use something like memcached for distributed locking. You can try something like LockBundle, for example.
$memcached = new Memcached();
$this->adapter = new MemcachedAdapter($memcached);
$lock = new Lock("test", $this->adapter);
$lock->acquire();
// Do stuff here //
$lock->release();
Just make your lock key whatever granularity that you want to protect. There are some issues around HA though if you are trying to make something resilient to memcached crashes.
You can also build something similar that uses a database table where you have a PK on a field and have the process attempt to insert a value into the table. The PK will block any second attempt and once the first one commits, the subsequent ones would unblock and fail due to a PK violation.
Then there is maybe using something like Zookeeper, although I have never done it. This would be more resilient than memcached I would think.
Upvotes: 1