由于这一部分是讲解 Redis,并没有涉及到分布式架构方面的东西,所以我很纠结要不要写这篇文章,最终我还是决定在这里说一下 Redis 实现分布式锁,在后续文章中还是会涉及到分布式架构的相关知识,这里就当一个引子,主要还是讲解如何使用 Redis 来实现,我会将写的方法封装成一个工具类,对代码进行讲解,由于没有分布式环境,所有没有办法进行测试。
一、程序客户端之 Java 客户端 Jedis
既然到了代码层级,那么我们就需要先讲解一下程序客户端之 Java 客户端 Jedis,我们需要使用 Java 代码来连接 Redis,那么我们先来看一下 Jedis 如何使用吧。
1、程序客户端之 Java 客户端 Jedis
1)添加依赖包
redis.clients
jedis
2.9.0
2)单实例连接
这里我们先创建 RedisUtils 类,并给创两个常量:HOST 和 PORT,就是 Redis 服务地址
public class RedisUtils {
public static String HOST = "192.168.1.216";
public static int PORT = 6379;
public static void main(String[] args) {
}
}
然后我们来编写单连接实例,代码很简单,获取到 jedis 对象后,就可以使用对应方法操作命令了
public static void singleConnect() {
Jedis jedis = new Jedis(HOST, PORT);
String result = jedis.get("k2");
System.out.println(result);
jedis.close();
}
public static void main(String[] args) {
singleConnect();
}
但是我们在 main() 方法中执行的时候,是会报错的,错误信息如下:(连接失败)
造成这种错误的常见的原因无非两种:
(1)没有配置防火墙
为了方便,我直接关闭了防火墙,没有去配置出入站规则,练习这样做是没有问题的,但是正是环境中切记不要关闭防火墙。使用以下命令关闭防火墙:(注意:CentOS 6 中是这个命令,而在 CentOS 7 中有所差异,请自行百度使用)
service iptables stop
(2)redis.conf 配置文件中的 bind 127.0.0.1 没有修改
我们进入 Redis 的 bin 目录下,打开 redis.conf 配置文件,使用 :set nu 命令,在 vim 中显示行号,找到第61行,并进行修改为 bind 192.168.1.216:然后在执行 main() 方法,就 OK 了。
3)连接池连接
Redis 的连接池与关系型数据库的连接池具有相同的作用,这不是我们讲解的重点,直接给出代码。
public static void poolConnect() {
JedisPool pool = new JedisPool(HOST, PORT);
Jedis jedis = pool.getResource();
String result = jedis.get("k2");
System.out.println(result);
jedis.close();
pool.close();
}
二、Redis 实现分布式锁
1、锁的处理
单应用中使用锁:单进程多线程(synchronize、Lock)
分布式应用中使用锁:多进程
2、分布式锁的实现方式
数据库的乐观锁
基于 Zookeeper 的分布式锁
基于 Redis 的分布式锁
分布式锁有多种实现方式,本章节我们只讲解 Redis 如何实现分布式锁。
3、分布式锁的注意事项
互斥性:在任意时刻,只有一个客户端能持有锁。
同一性:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
避免死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
4、实现分布式锁
初始化操作
public static String HOST = "192.168.1.216";
public static int PORT = 6379;
private static JedisPool pool;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMinIdle(5);
pool = new JedisPool(config, HOST, PORT);
}
public static Jedis getJedis() {
return pool.getResource();
}
1)获取锁
获取锁有两种方式,一种是使用 set 命令,另一种是使用 setnx 命令,下面我们会对这两种方式分别进行说明。
(1)使用 set 命令实现
/**
* 使用redis的set命令实现获取分布式锁
*
* @param lockKey 锁
* @param requestId 请求ID,保证同一性
* @param timeout 过期时间,避免死锁
* @return
*/
public static boolean getLockBySet(String lockKey, String requestId, int timeout) {
// 获取jedis对象,负责与远程redis服务器进行链接
Jedis jedis = getJedis();
String result = jedis.set(lockKey, requestId, "NX", "EX", timeout);
if (result == "OK") {
return true;
}
return false;
}
(2)使用 setnx 命令实现
/**
* 使用redis的setnx命令实现获取分布式锁
*
* @param lockKey 锁
* @param requestId 请求ID,保证同一性
* @param timeout 过期时间,避免死锁
* @return
*/
public static synchronized boolean getLockBySetnx(String lockKey, String requestId, int timeout) {
// 获取jedis对象,负责与远程redis服务器进行链接
Jedis jedis = getJedis();
Long result = jedis.setnx(lockKey, requestId);
if (result == 1) {
// 设置有效期,防止死锁
jedis.expire(lockKey, timeout);
return true;
}
return false;
}
2)释放锁
(1)使用 del 命令实现
/**
* 使用del命令释放锁
*
* @param lockKey 锁
* @param requestId 请求ID,保证同一性
*/
public static void releaseLockByDel(String lockKey, String requestId) {
// 获取jedis对象,负责与远程redis服务器进行链接
Jedis jedis = getJedis();
// 保证同一性
if (requestId.equals(jedis.get(lockKey))) {
jedis.del(lockKey);
}
小结
本章节主要是对 Redis 如何实现分布式锁进行了讲解,虽然给出了代码,但由于环境限制,并没有进行演示,在后面分布式架构的章节中,我们会重新对这一部分进行演示,本章节主要是为了让读者了解 Redis 实现分布式锁的方式,了解 Redis 是可以用来实现分布式锁的。本章节所用代码可在下方码云地址中查看。