首页 > 数据库 >如何正确使用redis实现分布式锁?

如何正确使用redis实现分布式锁?

时间:2023-06-08 22:00:51浏览次数:39  
标签:return String redis jedis key static 分布式 正确


分布式锁三种实现方式:

  • 数据库乐观锁
  • 基于redis的分布式锁
  • 基于zookeeper的分布式锁

为了保证分布式锁的高可用,我们至少要确保所得实现同时满足一下几个条件:

  •      互斥性,即就是在任意时刻只有一个客户端能持有锁
  •      不会发生死锁,即就是说计算有一个客户端持有锁期间崩了但是锁没有主动释放,也需要能保证后续其他客户端能获取到锁
  •      具有容错性,只要大部分redis节点正常运行,任何客户端来都可以做加锁或者解锁
  •      加锁和解锁必须是同一个客户端,别人不能把你的锁给释放了,你也不可能释放别人的锁

 redis官网地址:http://www.redis.io/   

我的版本是:2.8.3

安装步骤:

  1. 下载源码:wget http://download.redis.io/releases/redis-2.8.3.tar.gz
  2. 下载完毕后 解压源码:tar xzf redis-2.8.3.tar.gz
  3. 安装redis:进入cd redis-2.8.3   然后编译安装  make
  4. 编译完成后,在src目录下有四个可执行文件:
  5. redis-server,redis-benchmark,redis-cli,redis.config
  6. 新建文件目录:mkdir /usr/redis
  7. 把上面的四个文件都拷贝到这个新建的文件目录下:cp redis-server /usr/redis ,cp redis-benchmark /usr/redis ,cp redis-cli /usr/redis ,cp redis.conf /usr/redis
  8. 进入这个:cd /usr/redis
  9. 修改部分配置信息:vi redis.config  修改 daemonize no改为yes后台方式启动,
  10. 修改bind为127.0.0.1就可以外部访问了
  11. 启动redis需要到redis节点目录下执行:redis-server /etc/redis.conf  
  12. 查看是否已经启动了redis:   ps -ef|grep redis
  13. 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才会执行其他命令

借用网上的一张流程图:

如何正确使用redis实现分布式锁?_分布式锁

标签:return,String,redis,jedis,key,static,分布式,正确
From: https://blog.51cto.com/u_11702014/6443675

相关文章

  • 源码安装redis-migrate-tool(redis迁移工具)部署安装
    源码安装redis-migrate-toolredis-migrate-toolunzipredis-migrate-tool-master.zipcdredis-migrate-tool-masteryum-yinstallautomakelibtoolautoconfbzip2autoreconf-fvi./configuremake./src/redis-migrate-toolrmt.conf配置项修改[source]typ......
  • 墨天轮国产关系型分布式数据库榜单解读
    分布式关系型数据库概述作为数据库演进方向之一,分布式能力受到更多用户的关注。从技术架构演进来看,数据库正走过了从单机式、集中式到分布式的发展过程,目前是多种架构并存的阶段。分布式架构以其更好的存储与计算扩展能力,受到更多的关注。在墨天轮社区的中国数据库流行度排行榜上......
  • RedisTemplate常用方法总结
    很多公司都将redisTemplate进行了封装,封装成业务所需要的RedisUtil工具类方便进行调用,本篇文章总结了redisTemplate常用的一些方法。Redis常用的数据类型:•String•Hash•List•Set•zSet•SortedsetString类型判断是否有key所对应的值,有则返回true,没有则返回false......
  • 关联:Redis I/O模式
    Redis使用的是I/O多路复用首先,Redis是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以I/O操作在一般情况下往往不能直接返回,这会导致某一文件的I/O阻塞导致整个进程无法对其它客户提供服务,而I/O多路复用就是为了解决这......
  • Redis 面试题
    1.Redis是什么?Redis是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此读写速度非常快,常用于缓存,消息队列、分布式锁等场景。2.Redis有哪些数据类型?5种基础数据结构:String(字符串)、List(列表)、Set(集合)、Hash(哈希)、Zset(有序集合)。3种特殊数据结构:HyperLogLogs(......
  • redis 安装fatal error: jemalloc/jemalloc.h: No such file or directory 错误
    转自;https://www.cnblogs.com/oxspirt/p/11392437.html 问题现象: 我第一次安装redis时,没有安装gcc,报错了,然后安装好gcc,后再次执行make命令,安装redis就出现了如上的错误 网上错误解决办法网上大部分解决办法都是错误的,如下文:(错误解决办法)makeMALLOC=libc正确解决......
  • Redis系列15:使用Stream实现消息队列(精讲)
    Redis系列1:深刻理解高性能Redis的本质Redis系列2:数据持久化提高可用性Redis系列3:高可用之主从架构Redis系列4:高可用之Sentinel(哨兵模式)Redis系列5:深入分析Cluster集群模式追求性能极致:Redis6.0的多线程模型追求性能极致:客户端缓存带来的革命Redis系列8:Bitmap实现亿万级......
  • 十、Redis主从复制
    读操作:主库、从库都可以接收;写操作:首先到主库执行,然后,主库将写操作同步给从库。主从第一次同步第一阶段,主从库间建立连接、协商同步的过程,主要是为全量复制做准备。从库和主库建立起连接,主库确认回复后,就可以开始同步了。具体来说,从库给主库发送psync命令,psync命令包含了......
  • 十一、Redis扩容如何保证哈希一致性
    横向扩容,保证哈希一致性一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形)下一步将各个服务器使用Hash进行一个哈希,具体可以选择服务器的ip或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置......
  • 十三、Redis并发竞争问题
    多客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了。或者是多客户端同时获取一个key,修改值之后再写回去,只要顺序错了,数据就错了首先使用分布式锁,确保同一时间,只能有一个系统实例在操作某个key然后修改key的值时,要先判断这值的时间戳是否比缓存里的值的时......