mgofencedlock
Distributed locks using mongodb, with fencing
Setup
Installing:
go get -u github.com/aarondwi/mgofencedlock
Run this script below in your mongo database:
db.lock.createIndex( { "Key": 1 }, { unique: true } )
db.lock.createIndex( { "last_seen": 1 }, { expireAfterSeconds: 600 } )
Notes
Unique index is required, to prevent multiple same keys from acquiring the lock in the database
ExpireAfterSeconds is not set to remove lock directly,
but at much higher time than lock TTL
This is used to implement fencing token generation,
and getting out of really small timing issue which restarts the version
It is also for not bloating the storage, in case it is not deleted normally
You will need to the above function manually
While doing that, you can also change default expiry to meet your use case
100ms before lock timeouts, it will refresh the lock automatically.
The time resolution for lock expiry time is 1 second, to reduce errors caused
by NTP ~250ms bound
All of the read/write operation are using majority
concern
Usage
m, err := New(connUrl, dbname, workerUniqueId, lockExpiryTimeSecond)
if err != nil {
// handle the errors, failed creating connection to mongodb
}
// this lock will automatically refresh each (lockExpiryTimeSecond - 100ms)
mgolock, err := m.AcquireLock("some-key")
if err != nil {
// failed acquiring lock, maybe some others have taken it
// or there is some error in-between
}
if mgolock.IsValid() {
// your code goes here
// always need to check IsValid() before starting
// to ensure the lock doesn't expire even before the code starts
// can't do anything if the lock becomes invalid in the middle of your code
// see https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
}
// release the lock
// it returns nothing, as error may mean some other worker has taken it
m.DeleteLock(mgolock)
See main_test.go
for examples
Queries use internally
acquire_lock:
db.lock.update({
key: 'random-id',
$or: [
{last_seen: null},
{last_seen: {$lt: new Date() - expiryTimeSecond}}]
}, {
$inc: {version: 1},
$set:{'owner': 'me', last_seen: new Date()}},
{upsert: true})
get_lock_data:
db.lock.find({key: 'random-id', 'owner': 'me'}, {_id: 0})
delete_lock:
db.lock.remove({key: 'random-id', 'owner': 'me', version: 1})
refresh_lock:
db.lock.update(
{key: 'random-id', 'owner': 'me', version: 1},
{$set: {last_seen: new Date()}}
)
Document Schema
{
"version": 1,
"owner": "random id generated by each lock, or supplied",
"key": "unique id for your resource",
"last_seen": "to check for expiry (probably stale)"
}