-
A
Lockis a struct consisting of:client: theRedisClientkey: user-defined valueopts: an object ofLockOptionstoken: generated by the library on various life cycles from obtaining a lock, refreshing to releasing itmutex: an instance ofsync.Mutexto enure atomicity/exclusivity of certain operations, such as:- checking when it
IsLocked() - performing a
Lock() - performing an
Unlock()
- checking when it
-
It has
LockOptionswhich is a struct consisting ofLockTimeout(def 5s)WaitTimeout(def 0; no wait)WaitRetry(def 100ms)
User doesn't have to construct this struct even if this struct is required in all operations of the library. This is because this object will be instantiated with default values if user doesn't specify it with anything.
-
Common lock methods interface is a
RedisClient, and the string key, and also anoptswhich is a pointer toLockOptions -
NewLockis for creating a new lock, without performing any locking. At this stage, aLockOptionswill be created if the user provided none. This operation can't result in an error. -
ObtainLockis creating aNewLock()and thenLockit (if there's no error)--otherwise, an error is returned. This operation may result in an error, in which caseniland anerrorwill be returned. -
IsLockedsimply check if token is equal to empty string or not, if it is then it is locked. The checking operation is synchronized through themutex. -
Lockwillcreate()a lock if there's no token (apparently, not yet locked), otherwise if there's a token value set (apparently, has been locked before), the token is simplyrefresh()-ed. The operation is synchronized.-
In whichever case,
create()orrefresh(),Lock()ended up withcreate() -
create()will replace the token (if exist, that is) with a new one if a lock can beobtain()-ed. If locks couldn't be obtained after several retries (and sleep), it won't change the old token (if exists) and will return false alongside the error; otherwise true and nil. -
When lock is being
obtain()-ed, a key will be set on Redis usingSETNX. The key itself is user defined, whereby the value of the key is the library-generated tokenNote however, SETNX has been discouraged in favor of the redlock algorithm. SETNX means: SET if not exists.
-
refresh()will reinstate the same key with the (potentially) same expiry period usingPEXPIRE. It's said potentially becaue user can change theLockOptionsbefore callingLock(). This operation will be performed, instead ofcreate()if the token isn't empty.- A token is simply a randomly generated string, this is an internal data
-
-
Unlockreleases the lock. The operation is synchronized. It will set the token to an empty string, if there's no issue with the process. The process is deleting the key that has been set on redis. -
When lock is being created:
- a random token is generated
- an attempt to obtain the token by registering it to Redis is performed,
- It has internal error "class" such as
ErrNotObtainedandErrLockNotHeld. Library users (read: we) should be more comfortable checking against those predefined set of errors. - The
RedisClienttype is self-defined interface (which modern/newer driver should conform). Noticable different with theRedisClientused byredis-lockis that, functions (such asSetNXwhich are used in both cases) now acceptcontext.Contextas the first argument of the client. This result in functions such asObtain()to require context as well, something thatredis-lockdoesn't need.contextis from the context package in the Go standard library.
- A
Lockis a struct consisting of:clientwhich is of typeClient(defined in the package), consisting of:clientthe actualRedisClienttmpa byte arraytmpMua mutex instance, along withtmpused by internalrandomToken()function to generate random key
key, a string, user-defined valuevalue, a string, library-generated, but we can append metadata to it viaOptions.Metadataif desired
- An
Optionsis a struct consisting of:RetryStrategyof typeRetryStrategy, if null, will return the value ofNoRetry()which acquire the lock only one.Metadata, a string appended to the lock tokengetMetadata()returning""if theMetadatais not set (=nil).getRetryStrategy()returning value ofNoRetry()if theRetryStrategyis not set.
RetryStrategyis an interface consisting of onlyNextBackoff()function that returns atime.Duration.- There's an internal type
linearBackoffthat "inherits"time.Durationbut having thisNextBackoff()function implementedNextBackoff()of this simply returns an instance oftime.Duration
- Provided functions that returns
RetryStrategy:LinearBackoffgiven a duration, retries regularly with customized intervalsNoRetryreturns alinearBackoff(0)so there's no further attempt of retriesLimitRetryretries only as much as desired. This constructor function requires anotherRetryStrategyto be passed of which, itsNextBackoff()will be called when possible. Possible only if maximum retries has not exceeded the set max value.ExponentialBackoffis time-based, so it requires two arguments both of typetime.Duration:minandmax. TheNextBackoff()is computed to be2**nmilliseconds wherenmeans number of times, which will set to increase as time went by.
- There's an internal type
- A
Client.Obtain()is used to try to obtain a new lock within certain deadline. The deadline system is calculated internally, and it requiresttland thus it's an argument we need to pass when calling the function. It will try to retry as much as possible as long as still not yet out of the deadline, andNextBackoff()of the retry strategy (passed viaoptwhich is an instance ofOptions) is not less than1. It returns a*Lockif everything is okay. Otherwise, error it might return:ErrNotObtainedwhen backoff returns a value less than 1.
- The
Lock.Release()can be used to release a lock. However, it's considered anErrLockNotHeldif no lock has been optained at the time the function is called. Release simply performs quite similar things with the olderredis-locklibrary: simply deleting the key from a Redis instance/cluster. Lock.Key()returns the user-defined key set when obtaining a lockLock.Token()returns randomly generated valueLock.Metadata()returns metadata of the lock that is set by the user, if setLock.TTL()returns the remaining time to live of the lock, much better thanIsLocked()of the older library. If there's an error when calling this function, whatever the error might be, the returned time to live will be0.Lock.Refresh()to refresh/renew the lock
- They both still want to achieve the same thing.
redislockhas equally simple public methods, but they are much more feature-richredislockis much more accommodating to modern/later version of Go and librariesredislockhas noLockOptions, configuring the timing and stuff is done by "playing around" with theOptions.redislockhas far superior and customizableRetryStrategythroughOptions(there is no such thing inLockOptions, their way of doing it is very rigid in the old library).redislockReleaseis similar in terms of purpose toredis-lockUnlock, andIsLocked()is nowTTLwhich is better since it also tell us the remaining time the lock is validredislockallowed us to append certain metadata (in the form of a string). This is a feature entirely not possible with the older implementation.