首页 数据库 Redis 如何使用redis实现分布式锁

如何使用redis实现分布式锁

Jul 05, 2019 pm 04:45 PM

如何使用redis实现分布式锁

使用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) 0

redis> SETNX job "programmer"    # job 设置成功
(integer) 1

redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
(integer) 0

redis> 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访问工具类的具体实现:

@Component
public class RedisDao {

	@Autowired
	private 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);  //日志对象
	@Autowired
	private RedisDao redisDao;
	//定义的分布式锁key
	private 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() {
				@Override
				public 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访问工具类
*/
@Component
public class RedisDao {

	private static Logger logger = LoggerFactory.getLogger(RedisDao.class);
	
	@Autowired
	private 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)
@SpringBootTest
public class Demo1ApplicationTests {

	private static final Logger LOG = LoggerFactory.getLogger(Demo1ApplicationTests.class);
		
	@Autowired
	private RedisDao redisDao;
	
	@Test
	public void testDistributeLock () {
		String key = "MyDistributeLock";
		//设置分布式锁,失效时间20s
		boolean 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实现分布式锁的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何在Redis群集中选择一个碎片键? 如何在Redis群集中选择一个碎片键? Mar 17, 2025 pm 06:55 PM

本文讨论了在Redis群集中选择碎片键,并强调了它们对性能,可伸缩性和数据分布的影响。关键问题包括确保均匀数据分配,与访问模式保持一致以及避免常见错误l

如何在Redis中实施身份验证和授权? 如何在Redis中实施身份验证和授权? Mar 17, 2025 pm 06:57 PM

本文讨论了在REDIS中实施身份验证和授权,重点是实现身份验证,使用ACL以及确保REDIS的最佳实践。它还涵盖了管理用户权限和工具以增强重新安全性。

如何将Redis用于工作队列和背景处理? 如何将Redis用于工作队列和背景处理? Mar 17, 2025 pm 06:51 PM

本文讨论了使用REDIS进行工作队列和背景处理,详细的设置,作业定义和执行。它涵盖了原子运营和工作优先级等最佳实践,并解释了REDIS如何提高处理效率。

如何在REDIS中实施缓存无效策略? 如何在REDIS中实施缓存无效策略? Mar 17, 2025 pm 06:46 PM

本文讨论了在REDIS中实施和管理缓存无效的策略,包括基于时间的到期,事件驱动的方法和版本控制。它还涵盖了缓存到期的最佳实践和监视和自动的工具

如何监视REDIS群集的性能? 如何监视REDIS群集的性能? Mar 17, 2025 pm 06:56 PM

文章讨论了使用Redis CLI,Redis Insight和Datadog和Prometheus等工具等工具进行监视REDIS群集的性能和健康。

如何将Redis用于酒吧/子消息传递? 如何将Redis用于酒吧/子消息传递? Mar 17, 2025 pm 06:48 PM

本文介绍了如何将Redis用于酒吧/子消息传递,涵盖设置,最佳实践,确保消息可靠性和监视性能。

如何在Web应用程序中使用REDI进行会话管理? 如何在Web应用程序中使用REDI进行会话管理? Mar 17, 2025 pm 06:47 PM

本文讨论了在Web应用程序中使用REDIS进行会话管理,详细介绍设置,诸如可伸缩性和性能以及安全措施之类的好处。

如何确保重新侵害常见漏洞? 如何确保重新侵害常见漏洞? Mar 17, 2025 pm 06:57 PM

文章讨论了确保重新侵害漏洞,重点关注强密码,网络绑定,命令禁用,身份验证,加密,更新和监视。

See all articles