用redis实现分布式锁

一、简介

通常我们的程序会部署在多个容器上运行(负载均衡),但是我们程序中有时需要加锁(比如多台机器同时运行定时任务,但是我们其实只希望运行一次)。Redis采用的是基于内存的采用的是单进程单线程模型的KV数据库,由C语言编写。我们可以根据Redis单线程特性可以用来实现分布式锁。比较常见的错误示例就是使用jedis.setnx()和jedis.expire()组合实现加锁,该锁通过两步完成不具有原子性,如果两步中间发生异常,就会锁死。又有同学在上面的基础上做改进,使用jedis.setnx()命令实现加锁,其中key是锁,value是锁的过期时间。这样也是有问题的,当服务器之间时间不同步时,锁也会失效(这两种错误情况的详情说明请查看参考文档1)。下面介绍一种比较完美的方式(当然下面这种方式在“redis master节点崩溃,主从切换进行同步”的时候,锁可能会失效,这种情况比较极端可以忽略)。

二、实现

package com.example.demo.common.redis;

import java.util.Collections;
import java.util.UUID;

import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import com.example.demo.common.util.BeanUtil;

import redis.clients.jedis.Jedis;

/**
 * 用redis实现分布式锁
 * 
 * @author Horace
 * @date 2018年7月25日 下午8:14:26
 */
public class DistributedLock {

	private static final String LOCK_SUCCESS = "OK";

	private static final String SET_IF_NOT_EXIST = "NX";

	private static final String SET_WITH_EXPIRE_TIME = "PX";

	private String lockKey;

	/**
	 * 锁前缀(项目名称,防止重复)
	 */
	private static final String LOCKPREFIX = "TEST_DEMO";

	/**
	 * 单个业务持有锁的时间最长3s,防止锁死
	 */
	private long lockExpireTime = 3 * 1000L;

	private final String uuid = UUID.randomUUID().toString();

	private StringRedisTemplate stringRedisTemplate;

	public DistributedLock(String lockKey) {
		initRedis();
		this.lockKey = LOCKPREFIX + lockKey;
	}

	/**
	 * 生成分布式锁
	 * 
	 * @param lockKey
	 *            锁key
	 * @param lockExpireTime
	 *            锁的过期时间(单位毫秒)
	 */
	public DistributedLock(String lockKey, long lockExpireTime) {
		initRedis();
		this.lockKey = LOCKPREFIX + lockKey;
		this.lockExpireTime = lockExpireTime;
	}

	/**
	 * 因为是一个普通类(通过new创建,没有交给spring容器管理),所以不能直接使用注解的方式
	 */
	private void initRedis() {
		// TODO Auto-generated method stub
		if (stringRedisTemplate == null) {
			stringRedisTemplate = BeanUtil.getBean(StringRedisTemplate.class);
		}
	}

	/**
	 * 获取锁
	 * 
	 * @return
	 */
	public boolean getLock() {
		JedisConnection jc = (JedisConnection) stringRedisTemplate.getConnectionFactory().getConnection();
		Jedis jedis = jc.getNativeConnection();
		try {
			String key = jedis.set(lockKey, uuid, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, lockExpireTime);
			return LOCK_SUCCESS.equalsIgnoreCase(key);
		}finally {
			jedis.close();
		}
	}
	
	/**
	 * 释放锁
	 */
	public void releaseLock() {
		if(lockKey != null && "".equals(lockKey)) {
			RedisScript<Long> script = new DefaultRedisScript<>(
					"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end",
					Long.class);
			// 删除本地
			stringRedisTemplate.execute(script, Collections.singletonList(lockKey), uuid);
		}
	}
	
}

三、总结

上面的实现代码(加锁和释放锁),能比较完美的实现分布式锁,主要是利用了Redis的单线程特性+原子性方法操作。该方法也是Redis官网介绍的实现方法。

参考文档:

1、http://www.sohu.com/a/208019016_355142 Redis分布式锁的正确实现方式( Java 版 );

2、https://redis.io/topics/distlock Distributed locks with Redis

3、https://www.cnblogs.com/s648667069/p/6489557.html 普通类获取Spring容器中的bean

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值