关于SpringBoot 使用 Redis 分布式锁解决并发问题
现在的应用程序架构中,很多服务都是多副本运行,从而保证服务的稳定性。一个服务实例挂了,其他服务依旧可以接收请求。但是服务的多副本运行随之也会引来一些分布式问题,比如某个接口的处理逻辑是这样的:接收到请求后,先查询 DB 看是否有相关的数据,如果没有则插入数据,如果有则更新数据。在这种场景下如果相同的 N 个请求并发发到后端服务实例,就会出现重复插入数据的情况:
针对上面问题,一般的解决方案是使用分布式锁来解决。同一个进程内的话用本进程内的锁即可解决,但是服务多实例部署的话是分布式的,各自进程独立,这种情况下可以设置一个全局获取锁的地方,各个进程都可以通过某种方式获取这个全局锁,获得到锁后就可以执行相关业务逻辑代码,没有拿到锁则跳过不执行,这个全局锁就是我们所说的分布式锁。分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
我们这里介绍如何基于 Redis 的分布式锁来解决分布式并发问题:Redis 充当获取全局锁的地方,每个实例在接收到请求的时候首先从 Redis 获取锁,获取到锁后执行业务逻辑代码,没争抢到锁则放弃执行。
Redis 锁主要利用 Redis 的 setnx 命令:
加锁命令:SETNX key value,当键不存在时,对键进行设置操作并返回成功,否则返回失败。KEY 是锁的唯一标识,一般按业务来决定命名。Value 一般用 UUID 标识,确保锁不被误解。
解锁命令:DEL key,通过删除键值对释放锁,以便其他线程可以通过 SETNX 命令来获取锁。
锁超时:EXPIRE key timeout, 设置 key 的超时时间,以保证即使锁没有被显式释放,锁也可以在一定时间后自动释放,避免资源被永远锁住。
可靠性:为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
互斥性。在任意时刻,保证只有一台机器的一个线程可以持有锁; 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁; 具备非阻塞性。一旦获取不到锁就立刻返回加锁失败; 加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了; SpringBoot 集成使用 Redis 分布式锁写了一个 RedisLock 工具类,用于业务逻辑执行前加锁和业务逻辑执行完解锁操作。这里的加锁操作可能实现的不是很完善,有加锁和锁过期两个操作原子性问题,如果 SpringBoot 版本是2.x的话是可以用注释中的代码在加锁的时候同时设置锁过期时间,如果 SpringBoot 版本是2.x以下的话建议使用 Lua 脚本来确保操作的原子性,这里为了简单就先这样写:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; * @description: Redis分布式锁实现工具类 * @author: qianghaohao * @time: 2021/7/19 @Component public class RedisLock { @Autowired StringRedisTemplate redisTemplate; * 获取锁 * @param lockKey 锁 * @param identity 身份标识(保证锁不会被其他人释放) * @param expireTime 锁的过期时间(单位:秒) * @return public boolean lock(String lockKey, String identity, long expireTime) { // 由于我们目前 springboot 版本比较低,1.5.9,因此还不支持下面这种写法 // return redisTemplate.opsForValue().setIfAbsent(lockKey, identity, expireTime, TimeUnit.SECONDS); if (redisTemplate.opsForValue().setIfAbsent(lockKey, identity)) { redisTemplate.expire(lockKey, expireTime, TimeUnit.SECONDS); return true; return false; * 释放锁 * @param lockKey 锁 * @param identity 身份标识(保证锁不会被其他人释放) * @return public boolean releaseLock(String lockKey, String identity) { String luaScript = "if " + " redis.call("get", KEYS[1]) == ARGV[1] " + "then " + " return redis.call("del", KEYS[1]) " + "else " + " return 0 " + "end"; DefaultRedisScript Boolean redisScript = new DefaultRedisScript (); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List String keys = new ArrayList (); keys.add(lockKey); Object result = redisTemplate.execute(redisScript, keys, identity); return (boolean) result;
这里只贴出关键的使用代码,注意:锁的 key 根据自己的业务逻辑命名,能唯一标示同一个请求即可。value 这里设置为 UUID,为了确保释放锁的时候能正确释放(只释放自己加的锁)。
@Autowired private RedisLock redisLock; // redis 分布式锁
String redisLockKey = String.format("%s:docker-image:%s", REDIS_LOCK_PREFIX, imageVo.getImageRepository()); String redisLockValue = UUID.randomUUID().toString(); try { if (!redisLock.lock(redisLockKey, redisLockValue, REDIS_LOCK_TIMEOUT)) { logger.info("redisLockKey [" + redisLockKey + "] 已存在,不执行镜像插入和更新"); result.setMessage("新建镜像频繁,稍后重试,锁占用"); return result; ... // 执行业务逻辑 catch (Execpion e) { ... // 异常处理 } finally { // 释放锁 if (!redisLock.releaseLock(redisLockKey, redisLockValue)) { logger.error("释放redis锁 [" + redisLockKey + "] 失败); } else { logger.error("释放redis锁 [" + redisLockKey + "] 成功");
https://www.jianshu.com/p/6c2f85e2c586
https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/
到此这篇关于SpringBoot 使用 Redis 分布式锁解决并发问题的文章就介绍到这了,更多相关SpringBoot Redis 分布式锁内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
我想要获取技术服务或软件
服务范围:MySQL、ORACLE、SQLSERVER、MongoDB、PostgreSQL 、程序问题
服务方式:远程服务、电话支持、现场服务,沟通指定方式服务
技术标签:数据恢复、安装配置、数据迁移、集群容灾、异常处理、其它问题
本站部分文章参考或来源于网络,如有侵权请联系站长。
数据库远程运维 关于SpringBoot 使用 Redis 分布式锁解决并发问题
相关文章
- 解决Redis击穿: 把安全门放在网关(redis击穿)
- Redis与数据库:协同发展,轻松应对海量高并发数据处理。(redis与数据库)
- Redis与协程的完美结合:提高性能,提升效率(redis协程)
- 教程Redis延时队列视频教程让你真正掌握缓存技术(延时队列redis视频)
- 实现高效的Redis并发操作(并发操作redis)
- 使用Redis技术实现高效的并发计数(并发 redis 计数)
- Redis学习精通它掌握Redis,精通它的知乎旅程(精通redis 知乎)
- 电商搜索ES与Redis的黄金组合(电商搜索es和redis)
- 电商瞬间流量压力下的Redis应用(电商 并发 redis)
- 以Redis加速爬虫去重的突破(爬虫去重redis)
- 深入清理,优化Redis缓存(清理redis缓存数据)
- 简单步骤,快速安装新版Redis(新版redis安装)
- 新浪微博率先使用Redis技术加速社交服务(新浪微博redis使用)
- 深究Redis的持久化机制(为什么会持久化redis)
- 利用Redis跨越单核极限(redis默认单核)
- 解决Redis高并发性能问题的锁机制方案(redis高并发锁方案)
- 红色高并发集群慢Redis可靠性测试报告(redis 高并发连接慢)
- Redis实现高并发消息订阅(redis高并发订阅)
- Redis实现高并发自增序列号(redis 高并发 自增)
- Redis在高并发环境中实现一致性保障(redis高并发下一致性)
- 解决Redis集群的高并发性能挑战(redis集群高并发问题)
- 实现高可用的Redis集群采用Proxy节点(redis集群 代理节点)
- Redis集群ZSet实现高可用性(redis集群zset)
- 利用Redis锁有效解决并发问题(redis锁处理并发问题)
- 命令快速精通 Redis 常用命令(redis要学会哪些)