What are the five ways Redis implements distributed locks

2023-01-21 19:48:02

<p style="text-align: left;">The editor of this article will introduce in detail the five methods of Redis to implement distributed locks. The content is detailed, the steps are clear, and the details are handled properly. I hope this article on what are the five methods of Redis to implement distributed locks can help you To solve the doubts, let’s follow the editor’s ideas and slowly deepen, let’s learn new knowledge together.</p><h3 style="text-align: left;">1. Stand-alone data consistency</h3><p style="text-align: left;">The stand-alone data consistency architecture is shown in the figure below: Multiple clients can access the same server and connect to the same database .</p><p style="text-align: left;"><img src="//img.freeonlinedomain.com/uploads/allimg/20230121/1-23012119552XL.jpg" title="" alt="1.jpg"/></p><p style="text-align: left;">Scene description: The client simulates the process of purchasing goods, and sets the total inventory in <code>Redis</code> to 100 , more Clients can purchase concurrently at the same time.</p><p><br/></p><p style="text-align: left;">@RestController&nbsp;</p><p style="text-align: left;">public class IndexController1 { @Autowired StringRedisTemplate template; @RequestMapping(&quot;/buy1&quot;)&nbsp;</p><p style="text-align: left;">public String index(){&nbsp;</p><p style="text-align: left;">// Goods: No. 001 is stored in Redis, and the quantity is 100&nbsp;</p><p style="text-align: left;">String result = template.opsForValue().get(&quot;goods:001&quot;);&nbsp;</p><p style="text-align: left;">// Get the number of remaining items&nbsp;</p><p style="text-align: left;">int total = result == null ? 0 : Integer. parseInt(result); ``&nbsp;</p><p style="text-align: left;">if( total &gt; 0 ){ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">// If the number of remaining items is greater than 0, then the deduction will be performed&nbsp;</p><p style="text-align: left;">int realTotal = total -1;&nbsp;</p><p style="text-align: left;">// Write the number of commodities back to the database &nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">template.opsForValue().set(&quot;goods:001&quot;,String.valueOf(realTotal)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">System.out.println(&quot;Purchased successfully, inventory remaining: &quot;+realTotal +&quot; pieces, service port is 8001&quot;);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,&nbsp; }else{ &nbsp;System.out.println(&quot;Failed to purchase goods, service port is 8001&quot;); } return &quot;Failed to purchase goods, service port is 8001&quot;; } }</p><p style="text-align: left;">Using <code>Jmeter</code> to simulate high concurrency scenarios</p><p style="text-align: left;">Test results showed that multiple users purchased the same product, A data inconsistency has occurred!</p><p style="text-align: left;">Solution: In the case of a single application, lock the concurrent operation to ensure the atomicity of the data operation</p><ul class=" list-paddingleft-2"><li><p style="text-align: left;"><code> synchronized</code></p></li><li><p style="text-align: left;"><code>ReentrantLock</code></p></li></ul><p style="text-align: left;">@RestController public class IndexController2 { // Use the ReentrantLock lock to solve the concurrency problem of the single application Lock lock = new ReentrantLock(); @Autowired StringRedisTemplate template; @RequestMapping(&quot;/buy2&quot;) public String index() { lock. lock(); try { String result = template.opsForValue().get(&quot;goods:001&quot;); int total = result == null ? 0 : Integer. parseInt(result); `` if (total &gt; 0) { int realTotal = total - 1; &nbsp;&nbsp;&nbsp;&nbsp; template.opsForValue().set(&quot;goods:001&quot;, String.valueOf(realTotal)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Purchased successfully, remaining inventory: &quot; + realTotal + &quot;pieces, service port is 8001&quot;); return &quot;Purchase the product successfully, and the remaining inventory is: &quot; + realTotal + &quot;pieces, the service port is 8001&quot;; } else { ``System.out.println(&quot;Failed to purchase goods, service port is 8001&quot;); } } catch (Exception e) { lock. unlock(); } finally { lock. unlock(); } return &quot;Failed to purchase goods, the service port is 8001&quot;; } }</p><p><br/></p><h3 style="text-align: left;">2. Distributed data consistency</h3><p style="text-align: left;">The above solves the data consistency problem of a single application, but if it is a distributed architecture deployment, The architecture is as follows:</p><p style="text-align: left;">Provide two services, the ports are <code>8001</code> and <code>8002</code> respectively, connecting to the same <code>Redis</code> service, in There is a <code>Nginx</code> in front of the service as a load balancer</p><p><br/></p><p style="text-align: left;">The two service codes are the same, but the ports are different</p><p style="text-align: left;">Set <code>8001 </code>, <code>8002</code> Two services are started, each service is still locked with <code>ReentrantLock</code>, and <code>Jmeter</code> is used for concurrency testing, and it is found that Data consistency problem!</p><p><br/></p><h3 style="text-align: left;">3. Redis implements distributed lock</h3><h4 style="text-align: left;">3.1 Method 1</h4><p style="text-align: left;">Cancel stand-alone lock, use <code>redis&#39;s <code>set</code> command to realize distributed locking</code></p><p style="text-align: left;">SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]</p><ul class=" list-paddingleft-2"><li><p style="text-align: left;">EX seconds sets the specified expiration time (in seconds)</p></li><li><p style="text-align: left;">PX milliseconds sets the specified expiration time (in milliseconds)</p></li><li><p style="text-align: left;">NX set key only if key does not exist</p></li><li><p style="text-align: left;">XX set key only if it exists</p></li></ul><p style="text-align: left;">@RestController&nbsp;</p><p style="text-align: left;">public class IndexController4 {&nbsp;</p><p style="text-align: left;">&nbsp;// The key of the Redis distributed lock public static final String REDIS_LOCK = &quot;good_lock&quot;; @Autowired theStringRedisTemplate template;&nbsp;</p><p style="text-align: left;">@RequestMapping(&quot;/buy4&quot;) public String index(){&nbsp;</p><p style="text-align: left;">//Everyone must first lock when they come in, the key value is &quot;good_lock&quot;, and the value is randomly generated&nbsp;</p><p style="text-align: left;">String value = UUID.randomUUID().toString().replace(&quot;-&quot;,&quot;&quot;); try{&nbsp;</p><p style="text-align: left;">// lock &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value);&nbsp;</p><p style="text-align: left;">// Failed to lock if(!flag){ return &quot;Failed to grab the lock!&quot;; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">System.out.println( value+ &quot; lock grab successful&quot;);&nbsp;</p><p style="text-align: left;">String result = template.opsForValue().get(&quot;goods:001&quot;); &nbsp; int total = result == null ? 0 : Integer. parseInt(result); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">&nbsp;if (total &gt; 0) { int realTotal = total - 1; &nbsp;&nbsp;&nbsp;&nbsp; template.opsForValue().set(&quot;goods:001&quot;, String.valueOf(realTotal)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">// If an exception occurs after grabbing the lock but&nbsp;</p><p style="text-align: left;">before deleting the lock, the lock cannot be released, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">// The lock release operation cannot be performed here, it must be processed in finally // template.delete(REDIS_LOCK);&nbsp; &nbsp; System.out.println(&quot;Purchased successfully, inventory remaining: &quot; + realTotal + &quot;pieces, service port is 8001&quot;); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">return &quot;Purchased the product successfully, and the stock is still availableRemaining: &quot; + realTotal + &quot; pieces, the service port is 8001&quot;; } else { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">System.out.println(&quot;Failed to purchase goods, the service port is 8001&quot;); }&nbsp;</p><p style="text-align: left;">return &quot;Failed to purchase goods, service port is 8001&quot;; } finally {&nbsp;</p><p style="text-align: left;">// Release the lock &nbsp; template.delete(REDIS_LOCK); } } }</p><p style="text-align: left;">The above code can solve the data consistency problem in the distributed architecture. But if you think about it carefully, there will still be problems, and we will make improvements below.</p><h4 style="text-align: left;">3.2 Method 2 (improvement method 1)</h4><p style="text-align: left;">In the above code, if the program is running, the machine where the microservice <code>jar</code> package is deployed suddenly Hanging up, the code level has not reached the <code>finally</code> code block at all, that is to say, the lock has not been deleted before the downtime, so there is no way to guarantee unlocking</p><p style="text-align: left;">So, here you need to add an expiration time to this <code>key</code>. There are two ways to set the expiration time in <code>Redis</code>:</p><ul class=" list-paddingleft-2"><li><p style="text-align: left;"><code>template.expire(REDIS_LOCK,10, TimeUnit.SECONDS)</code></p></li><li><p style="text-align: left;"><code>template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS)</code></p></li></ul><p style="text-align: left;">The first method requires a single line of code, and it is not in the same step as locking, so it is not atomic , there will also be problems.</p><p style="text-align: left;">The second method sets the expiration time while locking, and there is no problem. This method is used here</p><p style="text-align: left;">Adjust the code and add it While locking, set the expiration time:</p><p style="text-align: left;">// Add an expiration time to the key, and the rest of the code remains unchanged Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK,value,10L,TimeUnit.SECONDS);</p><p style="text-align: left;">This method solves the problem that the lock cannot be released due to the sudden downtime of the service. But if you think about it, there will still beproblem, the following improvements are made.</p><h4 style="text-align: left;">3.3 Method 3 (improved method 2)</h4><p style="text-align: left;">Method 2 sets the expiration time of <code>key</code>, which solves the problem that <code>key</code> cannot be deleted</p><p style="text-align: left;">The expiration time of the <code>key</code> is set to <code>10</code> seconds. If the business logic is more complicated, you need to call other micro Service, the processing time needs <code>15</code> seconds (simulation scene</p><p style="text-align: left;">, don’t be serious), and when <code>10</code> seconds pass, the <code>key </code> expires, and other requests can set this <code>key</code> again. At this time, if the request that takes <code>15</code> seconds</p><p style="text-align: left;">is processed, If you come back and continue to execute the program, the <code>key</code> set by others will be deleted. This is a very serious problem!</p><p style="text-align: left;">So, whoever locks it can delete it</p><p style="text-align: left;">@RestController&nbsp;</p><p style="text-align: left;">public class IndexController6 {&nbsp;</p><p style="text-align: left;">public static final String REDIS_LOCK = &quot;good_lock&quot;;&nbsp;</p><p style="text-align: left;">@Autowired StringRedisTemplate template;&nbsp;</p><p style="text-align: left;">@RequestMapping(&quot;/buy6&quot;)&nbsp;</p><p style="text-align: left;">public String index(){&nbsp;</p><p style="text-align: left;">&nbsp;//Everyone must first lock when they come in, and the key value is &quot;good_lock&quot;&nbsp;</p><p style="text-align: left;">String value = UUID.randomUUID().toString().replace(&quot;-&quot;,&quot;&quot;); try{&nbsp;</p><p style="text-align: left;">// Add an expiration time to the key &nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L,TimeUnit.SECONDS);&nbsp;</p><p style="text-align: left;">// Failed to lock if(!flag){ return &quot;Failed to grab the lock!&quot;; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">System.out.println( value+ &quot; lock grabsuccess&quot;); String result = template.opsForValue().get(&quot;goods:001&quot;); &nbsp; int total = result == null ? 0 : Integer. parseInt(result); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (total &gt; 0) {&nbsp;</p><p style="text-align: left;">// If you need to call other microservices here, the processing time will be longer. . . int realTotal = total - 1; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">template.opsForValue().set(&quot;goods:001&quot;, String.valueOf(realTotal)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">System.out.println(&quot;Purchased successfully, inventory remaining: &quot; + realTotal + &quot;pieces, service port is 8001&quot;); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;,&nbsp; } else { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">&nbsp;System.out.println(&quot;Failed to purchase goods, the service port is 8001&quot;); } return &quot;Failed to purchase goods, service port is 8001&quot;; } finally {&nbsp;</p><p style="text-align: left;">// Whoever adds the lock can delete it! ! ! ! &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><p style="text-align: left;">if(template.opsForValue().get(REDIS_LOCK).equals(value)){ template.delete(REDIS_LOCK); } } } }</p><p style="text-align: left;">This method solves the problem of releasing other people&#39;s locks because the service processing time is too long. Is that all right?</p>


Copyright Description:No reproduction without permission。

Knowledge sharing community for developers。

Let more developers benefit from it。

Help developers share knowledge through the Internet。

Follow us