侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

如何使用redis实现分布式锁

Redis  /  管理员 发布于 7年前   228

使用Redis实现分布式锁

redis特性介绍

1、支持丰富的数据类型,如String、List、Map、Set、ZSet等。

2、支持数据持久化,RDB和AOF两种方式

3、支持集群工作模式,分区容错性强

4、单线程,顺序处理命令

5、支持事务

6、支持发布与订阅

Redis实现分布式锁使用了SETNX命令:

SETNX key value

将key的值设为value ,当且仅当key不存在。

若给定的key已经存在,则SETNX不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

可用版本:>= 1.0.0时间复杂度:O(1)返回值:

设置成功,返回 1 。

设置失败,返回 0 。

redis> EXISTS job    # job 不存在(integer) 0redis> SETNX job "programmer"    # job 设置成功(integer) 1redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败(integer) 0redis> GET job       # 没有被覆盖"programmer"

首先,我们需要封装一个公共的Redis访问工具类。该类需要注入RedisTemplate实例和ValueOperations实例,使用ValueOperations实例是因为Redis实现的分布式锁使用了最简单的String类型。另外,我们需要封装3个方法,分别是setIfObsent (String key, String value)、 expire (String key, long timeout, TimeUnit unit) 、delete (String key) ,分别对应Redis的SETNX、expire、del命令。以下是Redis访问工具类的具体实现:

@Componentpublic class RedisDao {@Autowiredprivate RedisTemplate redisTemplate;@Resource(name="redisTemplate")private ValueOperations<Object, Object> valOpsObj;/** * 如果key不存在,就存储一个key-value,相当于SETNX命令 * @param key      键 * @param value    值,可以为空 * @return */public boolean setIfObsent (String key, String value) {return valOpsObj.setIfAbsent(key, value);}/** * 为key设置失效时间 * @param key       键 * @param timeout   时间大小 * @param unit      时间单位 */public boolean expire (String key, long timeout, TimeUnit unit) {return redisTemplate.expire(key, timeout, unit);}/** * 删除key * @param key 键 */public void delete (String key) {redisTemplate.delete(key);}}

完成了Redis访问工具类的实现,现在需要考虑的是如何去模拟竞争分布式锁。因为Redis本身就是支持分布式集群的,所以只需要模拟出多线程处理业务场景。这里采用线程池来模拟,以下是测试类的具体实现:

@RestController@RequestMapping("test")public class TestController {private static final Logger LOG = LoggerFactory.getLogger(TestController.class);  //日志对象@Autowiredprivate RedisDao redisDao;//定义的分布式锁keyprivate static final String LOCK_KEY = "MyTestLock";@RequestMapping(value={"testRedisLock"}, method=RequestMethod.GET)public void testRedisLock () {ExecutorService executorService = Executors.newFixedThreadPool(5);for (int i = 0; i < 5; i++) {executorService.submit(new Runnable() {@Overridepublic void run() {    //获取分布式锁boolean flag = redisDao.setIfObsent(LOCK_KEY, "lock");if (flag) {LOG.info(Thread.currentThread().getName() + ":获取Redis分布式锁成功");//获取锁成功后设置失效时间redisDao.expire(LOCK_KEY, 2, TimeUnit.SECONDS);try {LOG.info(Thread.currentThread().getName() + ":处理业务开始");Thread.sleep(1000); //睡眠1000ms模拟处理业务LOG.info(Thread.currentThread().getName() + ":处理业务结束");//处理业务完成后删除锁redisDao.delete(LOCK_KEY);} catch (InterruptedException e) {LOG.error("处理业务异常:", e);}} else {LOG.info(Thread.currentThread().getName() + ":获取Redis分布式锁失败");}}});}}}

通过上面这段代码,可能会产生以下几个疑问:

线程如果获取分布式锁失败,为什么不尝试重新获取锁?

线程获取分布式锁成功后,设置了锁的失效时间,这个失效时间长短如何确定?

线程业务处理结束后,为什么要做删除锁的操作?

针对这几个疑问,我们可以来讨论下。

第一,Redis的SETNX命令,如果key已经存在,则不会做任何操作,所以SETNX实现的分布式锁并不是可重入锁。当然,也可以自己通过代码实现重试n次或者直至获取到分布式锁为止。但是,这不能保证竞争的公平性,某个线程会因为一直等待锁而阻塞。因此,Redis实现的分布式锁更适用于对共享资源一写多读的场景。

第二,分布式锁必须设置失效时间,而且失效时间必须大于业务处理所需的时间(保证数据一致性)。所以,在测试阶段尽可能准确的预测出业务正常处理所需的时间,设置失效时间是防止因为业务处理过程的某些原因导致死锁的情况。

第三,业务处理结束,必须要做删除锁的操作。

上面设置分布式锁和为锁设置失效时间是通过两个操作步骤完成的,更合理的方式应该是把设置分布式锁和为锁设置失效时间通过一个操作完成。要么都成功,要么都失败。实现代码如下:

/*** Redis访问工具类*/@Componentpublic class RedisDao {private static Logger logger = LoggerFactory.getLogger(RedisDao.class);@Autowiredprivate StringRedisTemplate stringRedisTemplate;/** * 设置分布式锁     * @param key     键 * @param value   值 * @param timeout 失效时间 * @return */public boolean setDistributeLock (String key, String value, long timeout) {RedisConnection connection = null;boolean flag = false;try {//获取一个连接connection = stringRedisTemplate.getConnectionFactory().getConnection();//设置分布式锁的同时为锁设置失效时间connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(timeout), RedisStringCommands.SetOption.SET_IF_ABSENT);flag = true;} catch (Exception e) {logger.error("set automic lock error:", e);} finally {//使用后关闭连接connection.close();}return flag;}/** * 查询key的失效时间 * @param key       键 * @param timeUnit  时间单位 * @return */public long ttl (String key, TimeUnit timeUnit) {return stringRedisTemplate.getExpire(key, timeUnit);}}/*** 单元测试类*/@RunWith(SpringRunner.class)@SpringBootTestpublic class Demo1ApplicationTests {private static final Logger LOG = LoggerFactory.getLogger(Demo1ApplicationTests.class);@Autowiredprivate RedisDao redisDao;@Testpublic void testDistributeLock () {String key = "MyDistributeLock";//设置分布式锁,失效时间20sboolean result = redisDao.setDistributeLock(key, "1", 20);if (result) {LOG.info("设置分布式锁成功");long ttl = redisDao.ttl(key, TimeUnit.SECONDS);LOG.info("{}距离失效还有{}s", key, ttl);}}}

运行单元测试类,结果如下:

2019-05-15 13:07:10.827 - 设置分布式锁成功2019-05-15 13:07:10.838 - MyDistributeLock距离失效还有19s

更多Redis相关知识,请访问Redis使用教程栏目!

以上就是如何使用redis实现分布式锁的详细内容,更多请关注其它相关文章!


  • 上一条:
    es和redis区别
    下一条:
    redis如何解决秒杀超卖问题
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在Redis中能实现的功能、常见应用介绍(0个评论)
    • 2024年Redis面试题之一(0个评论)
    • 在redis缓存常见出错及解决方案(0个评论)
    • 在redis中三种特殊数据类型:地理位置、基数(cardinality)估计、位图(Bitmap)使用场景介绍浅析(2个评论)
    • Redis 删除 key用 del 和 unlink 有啥区别?(1个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2017-12
    • 2020-03
    • 2020-05
    • 2021-04
    • 2022-03
    • 2022-05
    • 2022-08
    • 2023-02
    • 2023-04
    • 2023-07
    • 2024-01
    • 2024-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客