springboot 中单机 redis 实现分布式锁
2020-01-03 16:05:32来源:博客园 阅读 ()
springboot 中单机 redis 实现分布式锁
在微服务中经常需要使用分布式锁,来执行一些任务。例如定期删除过期数据,在多个服务中只需要一个去执行即可。
以下说明非严格意义的分布式锁,因为 redis 实现严格意义的分布式锁还是比较复杂的,对于日常简单使用使用如下简单方法即可。即偶尔不执行任务不影响业务。
实现要点
1)获得锁、释放锁需要是原子操作。要么获取成功,要么失败。释放要么成功,要么失败
2)任务完成需要自己释放自己的锁,不能释放别人的锁。
3)锁要有过期时间限制,防止任务崩溃没有释放锁,导致其他节点无法获得锁。
4)执行节点超时长时间不释放锁,到下次任务开始执行并行存在的情况
要考虑的风险点
1)获取锁失败,偶尔不执行任务要不影响业务或告警人工干预
2)redis 宕机,导致无法获取锁
方案一:低版本使用 jedis 实现
1 添加依赖,高版本或低版本有些方法可能没有
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.10.2</version>
</dependency>
2 实现方法
@Slf4j public abstract class AbsDistributeLock { private Jedis jedis; public void initDistributeLock(String ip, int port, Integer database, String password) { jedis = new Jedis(ip, port); if (password != null && !password.isEmpty()) { jedis.auth(password.trim()); } if (database == null || database < 0 || database > 15) { database = 0; } jedis.select(database); } private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; private static final Long RELEASE_SUCCESS = 1L; /** * 具体的任务需要子类去实现 * * @throws RTException 分布式锁异常 */ public abstract void taskService() throws RTException; /** * 任一执行,ANY OF * 所有节点任意一个执行任务 taskService 即可,没有获得锁的节点不执行任务 * * @param lockKey lockKey * @param keyValue keyValue * @param expireTime 过期时间 ms */ public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) { boolean getLock = false; try { if ((getLock = getDistributedLock(jedis, lockKey, keyValue, expireTime))) { taskService(); } } finally { if (getLock) { releaseDistributedLock(jedis, lockKey, keyValue); } } } /** * 所有串行执行,ALL IN LINE * 所有节点都必须执行该任务,每次只能一个执行。 * * @param lockKey lockKey * @param keyValue keyValue * @param expireTime 过期时间 ms */ public void allNodeExecute(String lockKey, String keyValue, int expireTime) { try { while (!(getDistributedLock(jedis, lockKey, keyValue, expireTime))) { try { Thread.sleep(200); } catch (InterruptedException e) { log.info(e.getMessage()); } } taskService(); } finally { releaseDistributedLock(jedis, lockKey, keyValue); } } /** * @param jedis 客户端 * @param lockKey key * @param keyValue key值 * @param expireTime 过期时间,ms */ public static boolean getDistributedLock(Jedis jedis, String lockKey, String keyValue, int expireTime) { String result = jedis.set(lockKey, keyValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { log.info("ip:[{}] get lock:[{}], value:[{}], getLock result:[{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result); return true; } else { return false; } } public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String keyValue) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(keyValue)); log.info("ip:[{}] release lock:[{}], value:[{}], release result: [{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }
方案二:高版本的springboot,使用 lua 脚本执行
1 添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.2.0.RELEASE</version> </dependency>
2 代码实现
@Slf4j public abstract class AbsDistributeLockLua { private RedisTemplate<String, String> redisTemplate; public AbsDistributeLockLua(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } /** * 具体的任务需要子类去实现 * * @throws RTException 分布式锁异常 */ public abstract void taskService() throws RTException; /** * 任一执行,ANY OF * 所有节点任意一个执行任务 taskService 即可,没有获得锁的节点不执行任务 * * @param lockKey lockKey * @param keyValue keyValue * @param expireTime 过期时间 ms */ public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) { boolean getLock = false; try { if ((getLock = getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) { taskService(); } } finally { if (getLock) { releaseDistributeLock(redisTemplate, lockKey, keyValue); } } } /** * 所有串行执行,ALL IN LINE * 所有节点都必须执行该任务,每次只能一个执行。 * * @param lockKey lockKey * @param keyValue keyValue * @param expireTime 过期时间 ms */ public void allNodeExecute(String lockKey, String keyValue, int expireTime) { try { while (!(getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) { try { Thread.sleep(200); } catch (InterruptedException e) { log.info(e.getMessage()); } } taskService(); } finally { releaseDistributeLock(redisTemplate, lockKey, keyValue); } } /** * 通过lua脚本 加锁并设置过期时间 * * @param key 锁key值 * @param value 锁value值 * @param expire 过期时间,单位毫秒 * @return true:加锁成功,false:加锁失败 */ public boolean getDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value, int expire) { DefaultRedisScript<String> redisScript = new DefaultRedisScript<String>(); redisScript.setResultType(String.class); String strScript = "if redis.call('setNx',KEYS[1],ARGV[1])==1 then return redis.call('pexpire',KEYS[1],ARGV[2]) else return 0 end"; redisScript.setScriptText(strScript); try { Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value, expire); System.out.println("redis返回:" + result); return "1".equals("" + result); } catch (Exception e) { //可以自己做异常处理 return false; } } /** * 通过lua脚本释放锁 * * @param key 锁key值 * @param value 锁value值(仅当redis里面的value值和传入的相同时才释放,避免释放其他线程的锁) * @return true:释放锁成功,false:释放锁失败(可能已过期或者已被释放) */ public boolean releaseDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value) { DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(String.class); String strScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; redisScript.setScriptText(strScript); try { Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value); return "1".equals("" + result); } catch (Exception e) { //可以自己做异常处理 return false; } } }
代码地址:https://github.com/crazyCodeLove/distribute-lock
参考文献:
https://www.cnblogs.com/bcde/p/11132479.html
https://blog.csdn.net/u013985664/article/details/94459529
原文链接:https://www.cnblogs.com/zhaopengcheng/p/12144022.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:for循环语句
下一篇:习题两则的简化(利用for循环)
- redis缓存 2020-06-12
- springboot2配置JavaMelody与springMVC配置JavaMelody 2020-06-11
- SpringBoot 2.3 整合最新版 ShardingJdbc + Druid + MyBatis 2020-06-11
- 掌握SpringBoot-2.3的容器探针:实战篇 2020-06-11
- Spring Boot 2.3.0 新特性Redis 拓扑动态感应 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash