Springboot使用redis的setnx和getset实现并发锁分布式锁
为什么需要分布式锁
在日常开发中,很多业务场景必须保证原子性。举几个例子:支付订单的操作,就不允许同一个订单,被同时支付,否则会产生错误的数据。拍卖一套房子的下单操作,商品只有一个,那只能一个人下单成功。
如果你只有一台服务器,只运行一个Java程序,那么可以使用Java语言自身的一些锁来实现原子性。但如果我们有多台服务器,甚至不同服务器上跑的是不同的语言。那这时候,我们就需要一个跨平台、跨语言的加锁方式。redis就是其中最方便的一种。核心操作和原理
使用redis实现并发锁,主要是靠两个redis的命令:setnx和getset。setnx的作用是,当一个key不存在的时候,给它赋值。如果key存在或赋值失败,都会返回错误。getset的作用是,先获取一个key的值,然后再给这个key赋新的值,该命令有原子性。
那我们的设计思路就是:先用setnx初步获取锁(set当前的时间戳),如果取不到,那么有两种可能,要么是锁被其他线程持有,要么是其他线程使用完锁后,没有正确释放。所以这个时候,我们需要验证这个锁是否过期。就是把setnx的值拿出来(一个时间戳),和当前时间戳求差,看看超时没有(超时时间是自己设定的)如果锁超时了,我们需要释放它,让它能重新工作。但第二步的操作,不是原子性的。可能有多个线程发现这个锁过期了,都想释放它。这时候,就需要getset这个原子操作,来保证只有一个线程成功。核心代码@Service public class RedisLockService { @Autowired private RedisService redisService; /** * 获取一个Redis分布式锁 * @param lockKey 锁的Key,全局不可重复 * @param lockExpire 锁超时时间,单位毫秒 * @return */ public boolean getLock(String lockKey, long lockExpire) { String redisKey = BaseCommonConfig.REDIS_LOCK_KEY + lockKey; if (!redisService.setnx(redisKey, String.valueOf(System.currentTimeMillis()))) { //没有拿到锁,但有可能是上一个加锁的人忘了释放锁。所以下面验证锁是否超时。 String lockString = redisService.getString(redisKey); if (lockString == null) { //前面setnx时,该值还存在,现在不存在了。要么是自然过期了,要么是被别人删掉,准备重新加锁了。稳妥起见,这里返回false return false; } long timestamp = Long.parseLong(lockString); if (System.currentTimeMillis() - timestamp > lockExpire) { //锁已经超时 //先get值,再set值。原子操作,确保不会多个线程进入后面的逻辑 String oldTimestamp = redisService.setGet(redisKey, String.valueOf(System.currentTimeMillis())); if (oldTimestamp != null && oldTimestamp.equals(lockString)) { //如果get不到值,或者get到的值不是前面取出来那个了,说明这个锁已经被别的线程占用了。 //第二次锁竞争成功 redisService.setExpireMills(redisKey, lockExpire); return true; } else { return false; } } else { return false; } } redisService.setExpireMills(redisKey, lockExpire); return true; } /** * 删除锁,释放锁 * * @param lockKey */ public void delLock(String lockKey) { String redisKey = BaseCommonConfig.REDIS_LOCK_KEY + lockKey; redisService.del(redisKey); } }
上面的代码使用了一个RedisService的类,里面主要是简单封装了一下redis的操作,你可以替换为自己的service。代码如下:@Service public class RedisService { @Autowired private StringRedisTemplate stringRedisTemplate; @Resource private RedisTemplate redisTemplate; public Boolean setnx(String key, String value) { ValueOperations ops = stringRedisTemplate.opsForValue(); return ops.setIfAbsent(key, value); } public String setGet(String key, String value) { return stringRedisTemplate.opsForValue().getAndSet(key, value); } public Long incr(String key) { return stringRedisTemplate.opsForValue().increment(key); } public Long incr(String key,long val) { return stringRedisTemplate.opsForValue().increment(key,val); } public Long decr(String key) { return stringRedisTemplate.opsForValue().decrement(key); } public Long decr(String key,long val) { return stringRedisTemplate.opsForValue().decrement(key,val); } public Long setPutString(String key, String value) { return stringRedisTemplate.opsForSet().add(key, value); } public Boolean setExist(String key, String member) { return stringRedisTemplate.opsForSet().isMember(key, member); } public Set setList(String key) { return stringRedisTemplate.opsForSet().members(key); } public Long setSize(String key) { return stringRedisTemplate.opsForSet().size(key); } /** * 逐渐废弃没有过期时间的Redis put操作 * @param key * @param value */ @Deprecated public void putString(String key, String value) { stringRedisTemplate.opsForValue().set(key, value); } /** * s为单位。 * * @param key * @param value * @param time */ public void putString(String key, String value, long time) { stringRedisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } public Boolean zPutString(String key, String value, long time) { return stringRedisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS); } public void putString(String key, String value, long time, TimeUnit unit) { stringRedisTemplate.opsForValue().set(key, value, time, unit); } public String getString(String key) { ValueOperations ops = stringRedisTemplate.opsForValue(); return ops.get(key); } public void putObject(String key, Object value, long time, TimeUnit unit) { redisTemplate.opsForValue().set(key, value, time, unit); } public Object getObject(String key) { return redisTemplate.opsForValue().get(key); } public Boolean exist(String key) { return stringRedisTemplate.hasKey(key); } /** * key有效时间 * * @param key * @return */ public Long expire(String key) { return stringRedisTemplate.getExpire(key, TimeUnit.SECONDS); } public Boolean changeExpire(String key, long time) { return stringRedisTemplate.expire(key, time, TimeUnit.SECONDS); } public Boolean del(String key) { return stringRedisTemplate.delete(key); } public void hset(String key, String field, String value) { HashOperations ops = stringRedisTemplate.opsForHash(); ops.put(key, field, value); } public String hget(String key, String field) { HashOperations ops = stringRedisTemplate.opsForHash(); return ops.get(key, field); } public Map hmget(String key) { HashOperations ops = stringRedisTemplate.opsForHash(); return ops.entries(key); } public void hmset(String key, HashMap data) { HashOperations ops = stringRedisTemplate.opsForHash(); ops.putAll(key,data); } public void hdel(String key, String field) { HashOperations ops = stringRedisTemplate.opsForHash(); ops.delete(key, field); } public void sadd(String key, String value) { stringRedisTemplate.opsForSet().add(key, value); } public Boolean setIsMember(String key,String val){ return stringRedisTemplate.opsForSet().isMember(key,val); } public Long ttl(String key) { return stringRedisTemplate.getExpire(key); } public void setExpire(String key, long time) { stringRedisTemplate.expire(key, time, TimeUnit.SECONDS); } public void setExpireMills(String key, long time) { stringRedisTemplate.expire(key, time, TimeUnit.MILLISECONDS); } public List mget(List keys) { return stringRedisTemplate.opsForValue().multiGet(keys); } } 交个朋友
以上代码有任何疑问,可以点击右侧边栏联系作者。收费5毛~交个朋友,欢迎来撩!
版权声明:《Springboot使用redis的setnx和getset实现并发锁、分布式锁》为CoderBBB作者「ʘᴗʘ」的原创文章,转载请附上原文出处链接及本声明。
原文链接:https://www.coderbbb.com/articles/2
TCL威5日正式来袭!上车TCL巨幕电视稳赚不亏年关将近,各大厂商都趁着这一个节点摩拳擦掌,推出各种花样十足福利满满的优惠活动,特别是如今的彩电圈,内卷竞争激烈,各大家电品牌方也都下血本推出钜惠福利,譬如现在正在进行的TCL超级
高灯科技获2021德勤中国明日之星本报讯近日,2021德勤中国高科技高成长50强和德勤中国明日之星榜单在北京揭晓。深圳高灯计算机科技有限公司(以下简称高灯科技)在评选中脱颖而出,获得2021德勤中国明日之星。据了解
微信上线新功能语音支持暂停和继续播放,可朗读文字消息,看视频号时可显示新消息微信iOS版今日发布更新,版本号升级到了8。0。17,此次更新除了解决一些已知问题外,也带来了一些新功能。据正观新闻报道,微信此次更新一个比较实用的功能是语音已经支持暂停和继续播放
Facebook因用户数据问题在英国面临诉讼报道,Facebook母公司MetaPlatformsInc。(FB)在英国被起诉,面临至少23亿英镑(合31。5亿美元)索赔,理由是该公司有超过4,400万用户的数据被用于牟利。
招商证券应用人工智能技术辅助银行流水识别与分析中证网讯(记者吴瞬)近日,招商证券宣布,经过近一年的课题攻关,招商证券成功破解了银行流水识别和分析这一行业难题,成为业内首家实现全格式银行流水智能分析辅助的金融企业。在人工智能的加
让技术流变资金流兴业银行支持科创企业出新招依托大数据,打破银行传统信贷评价模式,兴业银行创新推出的技术流专属评价体系,实现对科创企业科技创新能力的精准画像,成为解锁融资难问题的金钥匙。福建省微柏工业机器人有限公司是技术流专
数码党的开年好物,罗技虎年限定款礼盒已上线,安排用腻歪了机械键盘和电竞风格的鼠标,就琢磨着来一套颜值高能给氛围组加分,还功能强大的套装,尤其自从用过可以轻松在设备间切换的蓝牙耳机以后,这个功能的优势就深深的被印在需求里了。MXA
20220114涨停原因600933hr爱柯迪2021年我国汽车产销结束连续3年下降趋势,新能源汽车销售连续7年位居全球第一1公司产品包括新能源汽车电驱电控系统,应用于大众奔驰宝马等汽车600458hr时
Python之测试环境db自动同步分享主题多套测试环境,如何做基线的数据库级别的同步更新?应用场景工作中测试环境有多套时,为保证基础环境配置的一致性,就需要所有测试环境的数据库结构保持一致。例如A需求在beta1环
DOS未公开命令dos系统中有许多未公开的命令与参数。我经过收集整理,发现了很多。由于dos的有好几种,所以下面仅以msdos为例,介绍一下常用命令中的未公开的命令与参数。经过比较,我发现在msd
小米众多新品发布小米近期发布大量新品,包括手机手表耳机平板电脑等产品。1手机小米12Pro手机8128G4699元,8256G4999元,12256G5399元。小米12Pro12月28日晚10点