分布式锁三种实现方式:
- 数据库乐观锁
- 基于redis的分布式锁
- 基于zookeeper的分布式锁
为了保证分布式锁的高可用,我们至少要确保所得实现同时满足一下几个条件:
- 互斥性,即就是在任意时刻只有一个客户端能持有锁
- 不会发生死锁,即就是说计算有一个客户端持有锁期间崩了但是锁没有主动释放,也需要能保证后续其他客户端能获取到锁
- 具有容错性,只要大部分redis节点正常运行,任何客户端来都可以做加锁或者解锁
- 加锁和解锁必须是同一个客户端,别人不能把你的锁给释放了,你也不可能释放别人的锁
redis官网地址:http://www.redis.io/
我的版本是:2.8.3
安装步骤:
- 下载源码:wget http://download.redis.io/releases/redis-2.8.3.tar.gz
- 下载完毕后 解压源码:tar xzf redis-2.8.3.tar.gz
- 安装redis:进入cd redis-2.8.3 然后编译安装 make
- 编译完成后,在src目录下有四个可执行文件:
- redis-server,redis-benchmark,redis-cli,redis.config
- 新建文件目录:mkdir /usr/redis
- 把上面的四个文件都拷贝到这个新建的文件目录下:cp redis-server /usr/redis ,cp redis-benchmark /usr/redis ,cp redis-cli /usr/redis ,cp redis.conf /usr/redis
- 进入这个:cd /usr/redis
- 修改部分配置信息:vi redis.config 修改 daemonize no改为yes后台方式启动,
- 修改bind为127.0.0.1就可以外部访问了
- 启动redis需要到redis节点目录下执行:redis-server /etc/redis.conf
- 查看是否已经启动了redis: ps -ef|grep redis
- redis关闭:需要到redis节点目录下执行 redis-cli -p 端口号 shutdown
分布式事务锁代码:
1:pom添加引用
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
第一个类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 链接redis工具类
* @author lawt
* @date 2018-07-27 9:56
**/
public class MyJedisPool {
private final static Logger logger = LoggerFactory.getLogger(MyJedisPool.class);
private static JedisPool readPool = null;
//静态代码初始化池配置
static {
try {
/* Properties props = new Properties();
InputStream in = MyJedisPool.class.getResourceAsStream("/jedis.properties");
props.load(in);*/
//创建jedis池配置实例
JedisPoolConfig config = new JedisPoolConfig();
//设置池配置项值
/*config.setMaxTotal(Integer.valueOf(props.getProperty("jedis.pool.maxActive")));
config.setMaxIdle(Integer.valueOf(props.getProperty("jedis.pool.maxIdle")));
config.setMaxWaitMillis(Long.valueOf(props.getProperty("jedis.pool.maxWait")));
config.setTestOnBorrow(Boolean.valueOf(props.getProperty("jedis.pool.testOnBorrow")));
config.setTestOnReturn(Boolean.valueOf(props.getProperty("jedis.pool.testOnReturn")));*/
//根据配置实例化jedis池,暂时不需要
readPool = new JedisPool(config, "39.106.152.212", 6379);
} catch (Exception e) {
logger.info("redis连接池异常", e);
}
}
/**
* 获得jedis对象
*/
public static Jedis getReadJedisObject() {
return readPool.getResource();
}
/**
* 归还jedis对象
*/
public static void returnJedisOjbect(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
}
第二个类:
import redis.clients.jedis.Jedis;
import java.util.Set;
/**
* 封装后的api
* @author lawt
* @date 2018-07-27 10:14
**/
public class RedisUtils {
/**
* 获取hash表中所有key
*
* @param name
* @return
*/
public static Set<String> getHashAllKey(String name) {
Jedis jedis = null;
try {
jedis = MyJedisPool.getReadJedisObject();
return jedis.hkeys(name);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyJedisPool.returnJedisOjbect(jedis);
}
return null;
}
/**
* 从redis hash表中获取
*
* @param hashName
* @param key
* @return
*/
public static String getHashKV(String hashName, String key) {
Jedis jedis = null;
try {
jedis = MyJedisPool.getReadJedisObject();
return jedis.hget(hashName, key);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyJedisPool.returnJedisOjbect(jedis);
}
return null;
}
/**
* 根据key取value
*
* @param k
* @return
*/
public static String getKV(String k) {
Jedis jedis = null;
try {
jedis = MyJedisPool.getReadJedisObject();
return jedis.get(k);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyJedisPool.returnJedisOjbect(jedis);
}
return null;
}
public static void main(String[] args) {
String value = getKV("name");
System.out.println(value);
}
}
第三个类:redis分布式事务所(单机redis)
import redis.clients.jedis.Jedis;
import java.util.Collections;
/**
* redis分布式事务所(单机redis)
*
* @author lawt
* @date 2018-07-27 10:16
**/
public class RedisLock {
private static final String LOCK_SUCCESS = "OK";
//这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已
//经存在,则不做任何操作;
private static final String SET_IF_NOT_EXIST = "NX";
//这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时
//间由第五个参数决定。
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 尝试获取分布式锁
*
* @param jedis Redis客户端
* @param lockKey 锁 我们使用key来当锁,因为key是唯一的。
* @param requestId 请求标识 我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件
解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用
UUID.randomUUID().toString()方法生成。
* @param expireTime 具体超期时间 与第四个参数相呼应,代表key的过期时间。
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 释放分布式锁
*
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if jedis.call('get', KEYS[1]) == ARGV[1] then return jedis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
获取锁方法说明:
执行上面获取锁方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。
我们的加锁代码满足我们可靠性里描述的三个条件:
首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。
其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。
最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。
由于我们只考虑Redis单机部署的场景,所以容错性我们暂不考虑,得使用redisson来解决多机容错性?????后期会写相关文章,敬请期待
释放锁方法说明:
第一行代码,我们写了一个简单的Lua脚本代码,
第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。
这段Lua代码的功能:
首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。
为了要确保上述操作是原子性的。就使用在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令
借用网上的一张流程图:
标签:return,String,redis,jedis,key,static,分布式,正确 From: https://blog.51cto.com/u_11702014/6443675