Redis distributed locks for understanding

On Redis, through exclusive of key values to achieve distributed lock, on the surface, Redis can be achieved through simple and quick set key in this exclusive way, there are many repetitive wheels, but it is not the case.
Generally speaking, Redis achieve distributed lock, how to ensure the timely release of security & lock resource is the most critical factor in a distributed lock.
Some drill down as follows Redis implementation of distributed locks, as well as problems and solutions.

solution 1 :setnx

setnx command set key way to achieve an exclusive lock

1, # concurrent threads to seize the lock resources
SETNX an_special_lock 1
2, # 1 if to seize the current lock, concurrent threads in the current thread execution
if (success to obtain a lock)
  the Execute business_method ()
  3, # release lock
   del an_special_lock

Problem is obvious:
the lock to seize and concurrent threads in the current thread operations to the final release of the lock, not an atomic operation,
if the last lock is not successfully released (del an_special_lock), i.e. 2 to 3 exception occurs between, it will cause other threads will never be able to re-acquire the lock

solution 2:setnx + expire key

After the solution in order to avoid such a situation occurs, it is necessary to add a lock resource expiration time, such as 10 seconds, once the abnormal lock to release the lock from the accounting process occurs, ensure expires automatically release the resource lock

1, # concurrent threads to seize the lock resources
SETNX an_special_lock 1
2, # set the lock expiration time
The expire an_special_lock 10
3, # if preempted by the current lock, the current thread concurrent threads of execution
if (success to obtain a lock)
  the Execute business_method ()
  4 # release lock
   del an_special_lock

By setting the expiration time (expire an_special_lock 10), to avoid the problems caused by the abnormal release of the lock can not be accounted lock to release the lock process occurs,
but there is still a problem:
broke out between concurrent threads to seize the expiration time set to lock the lock success abnormal, that is an exception occurs between 1 and 2 here, still can not release the lock resource
solution solves the problem solution 2 in 1 lock resources can not be released, but at the same time, and the introduction of a non-atomic operation, the same can not to ensure that the set key to expire key atomically performed
so the current focus on the problem: how to set a lock that locks provided && timeout, i.e. where 1 to 2 operation, performed in a manner to ensure atoms?

solution 3 : set key value ex 10 nx

Redis atomic joined a set key && expire key after 2.8: set an_special_lock 1 ex 10 nx

1, # concurrent threads preemption lock resources, atomic operations
SET an_special_lock 1 EX 10 NX
2, # if preempted to the current lock, concurrent threads in the current thread of execution
if (successfully acquired the lock)
  business_method ()
  . 3, # release the lock
  del an_special_lock

After the present, lock && set the lock timeout to become an atomic operation can resolve the current thread exception, the lock can be released issue.

But the problem still exists:
If, after the lock timeout, such as after 10 seconds, execute_business_method () is still not executed, this time due to expire and passive lock release, other threads can still acquire the lock an_special_lock of concurrent threads access to exclusive resources remains Can not guarantee.

solution 4: strengthening the business code

So far, solution 3 is still not the perfect solution to the problem of concurrent threads to access exclusive resources.
I can think of is the way to solve the problem:
set business_method () execution timeout, if the application is still not completed after the implementation of the lock timeout, then take the initiative to roll back (to give up the implementation of the current thread), then take the initiative to release the lock, and instead of waiting passively release the lock (expire over time release)
If you can not ensure business_method () successful implementation or rollback put before the lock expires, the distributed lock is still unsafe.

1, # concurrent threads preemption lock resources, atomic operations
SET an_special_lock 1 EX 10 n-
2, # if preemption to the current lock, the current thread concurrent threads of execution
if (successfully acquired the lock)
  business_method () # at the application level control, business logic Redis operations before the lock timeout, take the initiative to roll back the
  3, # release lock
  del an_special_lock

solution 5 RedLock: Redis solve a single point of failure

Up to now, (if) can be considered to solve the solution 4 "accounting lock" "Lock the safe release" of the problem &&, still can not guarantee that "the initiative to release a lock resource":
Redis often by Sentinel or cluster to ensure high availability, even with the Sentinel or clusters, but the face of the failure of the current node Redis, and still can not guarantee that concurrent threads genuine exclusive lock on the resource.
Specifically is the current thread acquired the lock, but the lock current Redis node has not been synchronized to the slave node, this time caused by a single node because Cash "passive release" lock, other threads of the application (due to the failover) from a node still can not actually occupy the release of the lock.
Redlock require multiple Redis nodes, when RedLock locked, by way of a majority node to solve the failover situation Redis nodes, because the lock data inconsistency caused by failures.
Implementation principle, simply put, is in the locking process, if successfully achieved a majority node locked (non-clustered Redis nodes), the lock successfully solve the single node failure occurs after a failover data inconsistency caused lock failure.
And when the lock is released, only del operations need to be performed to all nodes.

Redlock require multiple Redis nodes, due to a multi-stage Redis Redis instance from one instance, Redlock achieve distributed lock, although safer, but must be accompanied by decrease in efficiency.

At this point, the solution 1 -> solution 2 -> solution 3 - solution 4 -> solution 5, in order to solve a problem the previous step, but it is still a non-perfect realization of distributed lock.

The following Redlock to verify the effect by a simple test.

A typical case is the database "presence update, insert does not exist" concurrent operations (database-level locks omitted here), by comparison against the effect of view Redis distributed lock control.

class RedLockTest:

    _connection_list = None
    _lock_resource = None
    _ttl = 10  #ttl

    def __init__(self, *args, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

    get_conn DEF (Self):
        the try:
            # not lock if the current thread acquired, retries and retry latency
            Conn = Redlock (self._connection_list, Retry_count = 100, retry_delay = 10)
        the except:
            The raise
        return Conn

    def execute_under_lock(self,thread_id):
        conn = self.get_conn()
        lock = conn.lock(self._lock_resource, self._ttl)
        if lock :
            self.business_method(thread_id)
            conn.unlock(lock)
        else:
            print("try later")

    '' '
    Simulate a classic does not exist is inserted, there is the update, since multi-threaded operation
    in practice may be a very complex require atomicity operations, exclusive of
    ' ''
    DEF business_method (Self, thread_id):
        Print ( " } {0 ----- ------ Thread Business Method Execute the begin ".format (thread_id))
        Conn = pymssql.connect (Host =" 127.0.0.1 ", Server =" SQL2014 ", Port = 50503, = Database "DB01")
        Cursor = conn.cursor ()
        ID the random.randint = (0, 100)
        sql_script = '' '. 1 from the TestTable WHERE SELECT Id = {0}' '' .format (ID)
        the cursor.execute ( sql_script)
        IF Not (cursor.fetchone ()):
            sql_script = '' 'INSERT INTO the TestTable values ({0}, {}. 1, {}. 1, getdate (), getdate ())' ''.format(id,thread_id)
        else:
            sql_script = ''' update TestTable set LastUpdateThreadId ={0} ,LastUpdate = getdate() where Id = {1} '''.format(thread_id,id)
        cursor.execute(sql_script)
        conn.commit()
        cursor.close()
        conn.close()
        print(" thread -----{0}------ execute business method finish".format(thread_id))


if __name__ == "__main__":

    redis_servers = [{"host": "*.*.*.*","port": 9000,"db": 0},
                    {"host": "*.*.*.*","port": 9001,"db": 0},
                    {"host": "*.*.*.*","port": 9002,"db": 0},]
    lock_resource = "mylock"
    ttl = 2000 #毫秒
    redlock_test = RedLockTest(_connection_list = redis_servers,_lock_resource=lock_resource, _ttl=ttl)

    redlock_test.execute_under_lock # (redlock_test.business_method)
    Threads = []
    for I in Range (50):
        # normal mode concurrent business logic invocation method, a large amount of the primary key conflict
        #t = threading.Thread (target = redlock_test.business_method , args = (i,))
        multithreaded distributed lock control under #Redis
        T of the threading.Thread = (target = redlock_test.execute_under_lock, args = (i,))
        threads.append (T)
    BEGIN_TIME the ctime = ()
    for T Threads in:
        t.setDaemon (True)
        t.start ()
    for t in Threads:
        t.join ()

Test 1, a simple multi-threaded

Since multi-threaded execution simple test method, the test there are two obvious problems
1, primary key violation occurs (the error)
2, the print log from the point of view, where each thread crossing performed in the presence of the test method ( cross means of cross-thread execution log information)

Test 2, Redis lock control in multi-threaded

Redlock of Redis distributed lock into three separate Redis nodes without making cluster

After the addition of Redis distributed lock, you can see that, although the simultaneous multi-threading operations, but when do the actual testing methods, are exclusive to perform,
from the log can see, is a thread execution is completed after that, another thread before entering the critical resource region.

Redlock relatively safe solution to the potential problem of a distributed lock to start, at the same time, also increases the complexity, while reducing the efficiency to a certain extent.

Guess you like

Origin www.linuxidc.com/Linux/2019-08/159781.htm