redis 系列之——分布式锁
Redis系列目录
redis系列之——数据类型geospatial:你隔壁有没有老王?
redis系列之——数据类型bitmaps:今天你签到了吗?
端午节最后一天了,三天假期过得太快了,更坏的消息是,下周还要上六天班呢!
趁着假期的尾巴,和大家再叨逼叨redis是如何实现分布式锁的。这期也是为了填之前《redis系列之——缓存穿透、缓存击穿、缓存雪崩》留下的坑。
这一期不是专门聊分布式锁的,所以不会涉及到各种分布式锁实现及相关的比较,只是聊一下如何使用redis实现分布式锁。感觉上一个坑还没填完,这里又挖了一个大坑,各种分布式锁实现及相关的比较后面有时间我会跪着填上的,请大家给我多一点点时间,多一点点温柔。
本期是硬核输出,也是面试高频,更是实战必会。
什么是分布式锁
先说一个场景,消费者在购物网站上下单或收银员在POS机上下单,由于网络等问题,在连续点击了两下,后端网站如何处理,如何响应?对于这个问题,前端需要处理,后端也需要处理。这里主要说后端,后端不光要处理重复订单问题,还有处理幂等问题。幂等问题简单来说就是相同的请求,要有相同的响应结果,这里就不展开了。重复订单该如何处理?
对于一个小的访问量不大的网站,部署了一个tomcat,这个问题可以简单的通过JVM提供的同步锁synchronized实现。但是当网站访问量越来越大时,需要扩展机器,synchronized就不能起作用了。相同的下单参数连续两次请求后端服务器,可能会被分发到两个tomcat上,就会出现synchronized失效问题。
分布式锁要解决的就是多机器部署时,相同请求并发访问时资源竞争问题。请求到达每个tomcat时,首先要去redis中注册锁,注册成功返回true则说明获得了锁,可以继续处理相关的业务,处理完成后释放锁。同一时刻只能有一个tomcat能获得锁,其他没获得锁的tomcat则多次尝试继续获得锁,没有获得锁不能处理业务。获得锁的tomcat释放锁后,其他的tomcat才能有一个获得锁。
这里是使用redis做外部存储介质存储锁的,使用zookeeper也是类似的。万变不离其宗,原理都一样,只是技术选型有差别。
redis实现
废话就不说了,直接上代码。
1.pom.xml
这里需要注意,我使用的是spring-boot。redis的相关jar使用的是spring-boot-starter-data-redis。
注意版本是2以上,版本1和版本2在redis锁的实现上有个关键的差异,后面会说到。
2.application.yml
3.RedisConfiguration.java
这个配置类就是为了解决redis的key和value在序列化时的乱码问题,将序列化的方式设置为string格式
4.RedisLock.java
这个就是redis锁的核心实现。上面提到的spring-boot版本问题就是这里的setNx方法中的redisTemplate的setIfAbsent的差异。
setIfAbsent的作用是,在存储key和value时,如果key不存在则保存后返回true,说明获得锁;如果key存在则保存后返回false,说明这个锁正在使用中,不能获取。
在获取锁时,有两步操作,首先是要存储这个key-value,然后需要对其设置一个过期时间,防止出现死锁。在spring-boot的版本为1时,只提供了setIfAbsent(key, value)的API,设置成功后需要调用redisTemplate.expire(key, expireTime, mimeUnit)对这个key设置过期时间,这是两步是非原子性的操作,如果第一步执行成功,第二步执行失败,就可能出现死锁,虽然概率很低。在spring-boot的版本为2时,提供了一个原子性的API setIfAbsent(key,value,expireTime, mimeUnit),在保存key-value的同时设置了过期时间,推荐使用。
4.RedisLockTest.java
这是测试类的实例。首先需要获得全局唯一的订单号,然后使用这个订单号为key。在try块的最前面先去redis中获得锁,根据是否获得锁做不同的操作。需要根据自己的业务定义waitTime,interval, expireTime这三个参数。一定要在最后的finally中释放锁。
完成,收工!!
【传播知识,共享价值】,感谢小伙伴们的关注和支持,我是【诸葛小猿】,一个彷徨中奋斗的互联网民工。
版权声明: 本文为 InfoQ 作者【诸葛小猿】的原创文章。
原文链接:【http://xie.infoq.cn/article/87bc72a56db6e378c66ae3b70】。文章转载请联系作者。
评论