SpringBoot使用Redis实现分布式锁
前言
在单机应用时代,我们对一个共享的对象进行多线程访问的时候,使用java的synchronized关键字或者ReentrantLock类对操作的对象加锁就可以解决对象的线程安全问题。
分布式应用时代这个方法却行不通了,我们的应用可能被部署到多台机器上,运行在不同的JVM里,一个对象可能同时存在多台机器的内存中,怎样使共享对象同时只被一个线程处理就成了一个问题。
在分布式系统中为了保证一个对象在高并发的情况下只能被一个线程使用,我们需要一种跨JVM的互斥机制来控制共享资源的访问,此时就需要用到我们的分布式锁了。
分布式锁一般有三种实现方式:1.通过数据库实现分布式锁;2.通过缓存(Redis等)实现分布式锁;3.通过Zookeeper实现分布式锁。本篇文章主要介绍第二种通过Redis实现分布式锁的方式。
分布式锁的需要具备的条件
为了保证分布式锁的可用性,需要具备一下五点条件:
1、在同一时间保证只有一台机器的一个线程可以持有锁。
2、不能发生死锁,无论何时持有锁的机器崩溃挂掉了都要能自动释放锁。
3、高效的获取和释放锁。
4、具备非阻塞性,一旦获取不到锁就立刻返回加锁失败。
5、独占性,即自己加的锁只有自己才能释放。
代码实现
组件依赖
首先在pom.xml文件中添加依赖:
org.springframework.boot spring-boot-starter-data-redis
加锁代码
代码如下:
/** *获取锁 *@paramlockKey锁 *@paramidentity身份标识(保证锁不会被其他人释放) *@paramexpireTime锁的过期时间(单位:秒) *@return */ publicbooleanlock(StringlockKey,Stringidentity,longexpireTime){ booleanlockResult=redisTemplate.opsForValue().setIfAbsent(lockKey,identity,expireTime,TimeUnit.SECONDS); returnopsForValue; }
加锁的方法只需要三个参数:lockKey、identity、expireTime。
- 第一个参数lockKey为key,一个资源对应一个唯一的key。
- 第二个参数identity为身份标识,作为此key对应的value存储,为了判断在释放锁时是不是和加锁的身份相同,防止别人释放锁。
- 第三个参数expireTime为过期时间,此参数保证程序加锁后崩溃导致不能主动释放锁的时候自动释放锁,防止出现死锁。
为什么使用setIfAbsent方法呢?这个方法的好处就是,如果redis中已经存在这个key了,就会返回失败,并且不改变redis中的数据,这样就不会把别的线程的加的锁给覆盖掉。
解锁代码
代码如下:
/** *释放锁 *@paramlockKey锁 *@paramidentity身份标识(保证锁不会被其他人释放) *@return */ publicbooleanreleaseLock(StringlockKey,Stringidentity){ StringluaScript= "if"+ "redis.call('get',KEYS[1])==ARGV[1]"+ "then"+ "returnredis.call('del',KEYS[1])"+ "else"+ "return0"+ "end"; DefaultRedisScriptredisScript=newDefaultRedisScript<>(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List keys=newArrayList<>(); keys.add(lockKey); booleanresult=redisTemplate.execute(redisScript,keys,identity); returnresult; }
解锁的方法只需两个参数:lockKey、identity。
- 第一个参数lockKey为key,一个资源对应一个唯一的key。
- 第二个参数identity为身份标识,作为此key对应的value存储,为了判断在释放锁时是不是和加锁的身份相同,防止别人释放锁。
此处使用Lua脚本来判断身份,身份相同就删除,身份不同就不对数据做操作并返回失败。为什么要使用Lua脚本呢?这是为了要保证操作的原子性,redis在执行Lua脚本的时候是把脚本当作一个命令来执行的,我们都知道redis的命令是都是原子操作,这样就保证了操作的原子性。
测试代码
packagecom.qixi.lock.demo.lockdemo.controller; importcom.qixi.lock.demo.lockdemo.util.RedisLock; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.web.bind.annotation.GetMapping; importorg.springframework.web.bind.annotation.RequestMapping; importorg.springframework.web.bind.annotation.RequestParam; importorg.springframework.web.bind.annotation.RestController; /** *测试分布式锁 *@authorZhengNC *@date2020/5/1317:27 */ @RestController @RequestMapping("test") publicclassTestRedisLockController{ privatefinalStringlockKeyName="testKey"; @Autowired privateRedisLockredisLock; /** *测试加锁 *@paramid加锁的资源id *@paramidentity身份标识 *@return */ @GetMapping("lock") publicStringlock(@RequestParam("id")Stringid, @RequestParam("identity")Stringidentity){ StringlockKey=lockKeyName+":"+id; booleanlockSuccess=redisLock.lock(lockKey,identity,60); Stringresult="lockfailed"; if(lockSuccess){ result="locksuccess"; } returnresult; } /** *测试释放锁 *@paramid释放锁的资源id *@paramidentity身份标识 *@return */ @GetMapping("release") publicStringrelease(@RequestParam("id")Stringid, @RequestParam("identity")Stringidentity){ StringlockKey=lockKeyName+":"+id; booleanreleaseSuccess=redisLock.releaseLock(lockKey,identity); Stringresult="releasefailed"; if(releaseSuccess){ result="releasesuccess"; } returnresult; } }
packagecom.qixi.lock.demo.lockdemo.util; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.data.redis.core.RedisTemplate; importorg.springframework.data.redis.core.script.DefaultRedisScript; importorg.springframework.stereotype.Component; importjava.util.ArrayList; importjava.util.List; importjava.util.concurrent.TimeUnit; /** *分布式锁Redis工具类 *@authorZhengNC *@date2020/5/1317:27 */ @Component publicclassRedisLock{ @Autowired privateRedisTemplateredisTemplate; /** *获取锁 *@paramlockKey锁 *@paramidentity身份标识(保证锁不会被其他人释放) *@paramexpireTime锁的过期时间(单位:秒) *@return */ publicbooleanlock(StringlockKey,Stringidentity,longexpireTime){ booleanlockResult=redisTemplate.opsForValue().setIfAbsent(lockKey,identity,expireTime,TimeUnit.SECONDS); returnlockResult; } /** *释放锁 *@paramlockKey锁 *@paramidentity身份标识(保证锁不会被其他人释放) *@return */ publicbooleanreleaseLock(StringlockKey,Stringidentity){ StringluaScript= "if"+ "redis.call('get',KEYS[1])==ARGV[1]"+ "then"+ "returnredis.call('del',KEYS[1])"+ "else"+ "return0"+ "end"; DefaultRedisScript redisScript=newDefaultRedisScript<>(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List keys=newArrayList<>(); keys.add(lockKey); booleanresult=redisTemplate.execute(redisScript,keys,identity); returnresult; } }
结语
感谢大家阅读我的文章,更欢迎大家指出我的问题,希望能在这里通过讨论取得共同的进步。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。