时间:2021-07-01 10:21:17 帮助过:64人阅读
Consider the case where lisa@gmail.com sends an email to two Axial members, Scuba and Doug. The SMTP envelope might look something like this:
From: lisa@gmail.comTo: scuba@mail.axial.net, doug@mail.axial.netSubject: Our next meetingMessage-ID: <123-abc@mail.google.com>Hey guys! Shall we meet tomorrow at 2 PM?
We use Postfix as an MTA, which means Postfix is responsible for receiving the message and invoking the AMS inbound processor as a maildrop_command. We’ve configured Postfix to deliver each message once per recipient, with the philosophy that failure to deliver to scuba@mail.axial.net should not prevent delivery to doug@mail.axial.net. This means the AMS inbound processor will be invoked twice, once with Delivered-To: scuba@mail.axial.net and another with Delivered-To: doug@mail.axial.net. The following diagram shows Postfix delivering to AMS once per recipient:
The steps for processing an inbound email look something like:
The last two steps involve storing and retrieving data. If you’ve ever dealt with two concurrent processes manipulating the same data at once, then you’re probably familiar with the need for inter-process synchronization. To illustrate this, the following diagram shows both processes appending to Lisa’s sent mailbox at once:
The arrows are red because there is a high chance the message gets appended to Lisa’s sent mailbox not once but twice. Although each process first checks to see if the message is already in Lisa’s sent mailbox, there is a chance they both check at the same time, in which case they both end up appending.
We simply need to ensure only one message is processed at a time. Afile system lock won’t do the trick given messages can be processed on different servers and each has its own file system. However, given all of our servers reference the same dedicated SQL server, can we somehow use that as a distributed locking mechanism? Yes! With a named lock, of course!
Remember this is still a single message with a unique Message-ID (in this case <123-abc@mail.google.com>). If we use the Message-ID as the name of our lock, we can use the following logic to get the mutual exclusion we’ve been longing for:
With the logic above we can guarantee each message will be processed sequentially. Specifics for using named locks in both MySQL and Postgres can be found below.
GET_LOCK(‘<123-abc@mail.google.com>’, 10)
Attempt to get the named lock, waiting up to 10 seconds. Return 1 if lock was obtained or 0 if not obtained.
RELEASE_LOCK(‘<123-abc@mail.google.com>’)
Release the named lock. Return 1 if lock was released, 0 if lock was obtained by another thread or NULL if lock does not exist
It just so happens that we recently switched from MySQL to Postgres. When migrating the locking mechanism above we learned Postgres providesadvisory locks in manyflavors. The big differences are:
We went with pg_try_advisory_xact_lock, which obtains an exclusive transaction level lock if available. Because this lock is at the transaction level it will automatically be released at the end of the transaction and cannot be released explicitly. This has a big advantage over the MySQL implementation, where cautious exception handling was required in order to ensure the lock is always released.
Thanks to: