博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Springboot RedisTemplate 分布式锁
阅读量:4109 次
发布时间:2019-05-25

本文共 7168 字,大约阅读时间需要 23 分钟。

Springboot RedisTemplate 分布式锁

引言

其中有一些幂等性的细节。

测试环境

  • springboot 2.2.6

RedisTemplate.execute

execute 有很多参数

常用的有execute(RedisCallback)execute(SessionCallback)
通常都是使用SessionCallback,因为封装的更好用,不需要自己转byte
execute中,你可以在回调中随意获取值,这和executePipelined有很大区别。

看看两者的区别

redisTemplate.execute(new SessionCallback() {
@Override public
Object execute(RedisOperations
operations) throws DataAccessException {
operations.watch("监控key"); if ("蛇皮".equals(operations.opsForValue().get("监控key"))){
operations.multi(); operations.delete("监控key"); List
exec = operations.exec(); //这里就可以看到结果 System.out.println(exec); return exec; } operations.unwatch(); return null; } });

RedisTemplate.executePipelined

它和execute差不多,但是使用了Pipeline,也就是说传入的SessionCallback,里面exec的代码你是无法真实的看到返回内容,有点绕口,并且你不能在回调中返回非null的值,它会被管道覆盖,看个例子:

redisTemplate.executePipelined(new SessionCallback() {
@Override public
Object execute(RedisOperations
operations) throws DataAccessException {
operations.multi(); operations.delete("删掉这个"); List
exec = operations.exec(); //这里你无法看到任何内容 //事实上 executePipelined中,不让你返回除了null的任何东西 System.out.println(exec); //如果这里需要判断exec.get(0) xxxx是不行的 return null;//这里你不返回null会出错,你不能返回exec } });
  • 正确的方式
List results = redisTemplate.executePipelined(new SessionCallback() {
@Override public
Object execute(RedisOperations
operations) throws DataAccessException {
operations.multi(); operations.delete("删掉这个"); operations.exec(); return null; } }); //在这里可以使用results了 System.out.println(results);

executePipelined 只适合串行操作,并且中途不需要判断,比如批量的set操作,或者get的值,你最后拿出来只是读,并不需要它在中途去操作事务。

实现锁

  • 清楚了上面了executePipelinedexecute 后,为了保证幂等性,锁的实现我们就需要使用到excute

一个简单的例子

@Componentpublic class MyLock {
@Autowired private RedisTemplate
objectRedisTemplate; public String acquire(String lockName, Duration expire){
String identifier = UUID.randomUUID().toString(); //尝试获取锁的等待时间,如果需要,这里可以改为while(true),看实际情况 long timeOut = System.currentTimeMillis() + 5000; while(timeOut > System.currentTimeMillis()) {
//这里使用了setIfAbsent,其实就是SET key value [EX seconds] [PX milliseconds] NX Boolean setBoolean = objectRedisTemplate.opsForValue().setIfAbsent(lockName, identifier, expire); if (setBoolean) return identifier; } return null; } public String tryLock(String lockName, Duration expire){
String identifier = UUID.randomUUID().toString(); int retryLimit = 0; long delay = 10; while(retryLimit<10) {
try {
//每次多尝试一次,就增加延迟时间 Thread.sleep(delay << retryLimit); } catch (InterruptedException e) {
//nothing to do } retryLimit++; Boolean setBoolean = objectRedisTemplate.opsForValue().setIfAbsent(lockName, identifier, expire); if (setBoolean) return identifier; } return null; } public Object release(String lockName, String identifier){
assert lockName!=null && identifier!=null; return objectRedisTemplate.execute(new SessionCallback
() {
@Override public Object execute(RedisOperations operations) throws DataAccessException {
List results = null; //如果在watch中被改,results是个长度为0的list,并不是null //result为null的情况是锁刚好自动到期,并被其他线程获取到了锁 //标识符不同了,没有执行到operations.multi();我们unwatch返回就行了 while(results==null||results.size()==0){
operations.watch(lockName); if (identifier.equals(operations.opsForValue().get(lockName))){
operations.multi(); operations.delete(lockName); results = operations.exec(); }else{
operations.unwatch(); return null; } } return results; } }); }}

测试锁

@Test    public void testLock() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3); new Thread(()->{
String identifier = myLock.acquire("Lock007", Duration.ofSeconds(5)); if (StringUtils.isNotBlank(identifier)){
System.out.println(Thread.currentThread().getName()+" 拿到了锁"); try {
Thread.sleep(5000); } catch (InterruptedException e) {
e.printStackTrace(); } myLock.release("Lock007",identifier); } else System.out.println(Thread.currentThread().getName()+" 没拿到锁"); latch.countDown(); },"一号线程").start(); new Thread(()->{
String identifier = myLock.acquire("Lock007", Duration.ofMillis(5500)); if (StringUtils.isNotBlank(identifier)){
System.out.println(Thread.currentThread().getName()+" 拿到了锁"); try {
Thread.sleep(5000); } catch (InterruptedException e) {
e.printStackTrace(); } myLock.release("Lock007",identifier); } else System.out.println(Thread.currentThread().getName()+" 没拿到锁"); latch.countDown(); },"二号线程").start(); new Thread(()->{
String identifier = myLock.acquire("Lock007", Duration.ofMillis(5500)); if (StringUtils.isNotBlank(identifier)){
System.out.println(Thread.currentThread().getName()+" 拿到了锁"); try {
Thread.sleep(5000); } catch (InterruptedException e) {
e.printStackTrace(); } myLock.release("Lock007",identifier); } else System.out.println(Thread.currentThread().getName()+" 没拿到锁"); latch.countDown(); },"三号线程").start(); latch.await(); }
  • 细说一下 在watch中,如果被监视的key被篡改,那么exec一定会返回一个长度为0的list,并不是一个null,注意这点。我看好多文章都写的是个null,事实上并不是
  1. 在watch中,无论如何都会返回list,不会是个null,只是如果list中没有元素,则表明中途被人篡改。
  2. 在watch中 如果自动到期,那么执行delete后exec,返回值list一定包含0(del 返回0 表示删除失败),不影响事务。
  3. 在watch中 如果另外一个线程手动删除key, 那么返回的一定是个[](长度为0的list),而不是返回删除失败。
  4. 自动到期和人为删除watch的表现是不同的。

锁可能失效的原因

当对redis集群使用上面的获取锁代码后,如果从服务器同步数据过程中主服务器挂了,此时从服务器变为master但从服务器会丢失这个锁的KEY其他worker当前worker没有释放锁的情况下,仍然可以获取到一把锁,这类问题解决 要么用rediswait命令,要么读写分离后,setnx到锁后用get再读一次,直到从服务器有这条数据为止。

转载地址:http://uxlsi.baihongyu.com/

你可能感兴趣的文章
leetcode----150. Evaluate Reverse Polish Notation
查看>>
leetcode----151. Reverse Words in a String
查看>>
leetcode----153. Find Minimum in Rotated Sorted Array
查看>>
leetcode----162. Find Peak Element
查看>>
leetcode----152. Maximum Product Subarray
查看>>
leetcode----165. Compare Version Numbers
查看>>
leetcode----166. Fraction to Recurring Decimal
查看>>
leetcode----173. Binary Search Tree Iterator
查看>>
leetcode----179. Largest Number
查看>>
leetcode----187. Repeated DNA Sequences
查看>>
leetcode----199. Binary Tree Right Side View
查看>>
leetcode----200. Number of Islands
查看>>
leetcode----201. Bitwise AND of Numbers Range
查看>>
leetcode----207. Course Schedule
查看>>
leetcode----208. Implement Trie (Prefix Tree)
查看>>
leetcode----209. Minimum Size Subarray Sum
查看>>
leetcode----211. Add and Search Word - Data structure design
查看>>
leetcode----215. Kth Largest Element in an Array
查看>>
leetcode----216. Combination Sum III
查看>>
leetcode----220. Contains Duplicate III
查看>>