第三十四章:Redis实现分布式锁 - 知乎专栏·「JavaEE企业级分布式架构核心技术」

324 阅读5分钟
原文链接: zhuanlan.zhihu.com

一、前言

由于这一部分是讲解 Redis,并没有涉及到分布式架构方面的东西,所以我很纠结要不要写这篇文章,最终我还是决定在这里说一下 Redis 实现分布式锁,在后续文章中还是会涉及到分布式架构的相关知识,这里就当一个引子,主要还是讲解如何使用 Redis 来实现,我会将写的方法封装成一个工具类,对代码进行讲解,由于没有分布式环境,所有没有办法进行测试。


二、程序客户端之 Java 客户端 Jedis

既然到了代码层级,那么我们就需要先讲解一下程序客户端之 Java 客户端 Jedis,我们需要使用 Java 代码来连接 Redis,那么我们先来看一下 Jedis 如何使用吧。

1、程序客户端之 Java 客户端 Jedis

1)添加依赖包

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>2.9.0</version>
</dependency>

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 是可以用来实现分布式锁的。本章节所用代码可在下方码云地址中查看。

本章代码已经上传到码云:

JavaEE企业级分布式架构核心技术配套源码地址:AlanShelby/Course-Chapter