分布式锁可以采用数据库、zookeeper、redis 三种方式实现。
采用数据库实现方式,主要采用表字段的唯一索引特性。数据库是非常昂贵的资源,非常不推荐,最致命就是性能,不要去增加不必要的负担。
采用 zookeeper 的实现方式,主要使用其为客户端创建临时有序节点的特性,在我之前的博客有介绍。虽然使用 Apache Curator 客户端框架可以简化操作,但是其底层实现比较复杂,总体而言性能相对较差,因为需要维护大量的 Zookeeper 状态,引起大量网络 IO 的开销。
采用 redis 的实现方式,主要采用其单线程执行相关命令的特性。实现原理非常简单,性能也是最好,尤其采用 Redisson 实现方案。
Redisson 是基于 Redis 实现的一个框架。充分的利用了 Redis 键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包,获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度,简化了分布式环境中程序相互之间的协作。Redisson已经内置提供了基于Redis的分布式锁实现,此种方式是我们推荐的分布式锁使用方式。
本篇博客介绍 redis 的两种分布式锁的实现方式:自己编码实现方式和采用 redisson 框架的实现方式,在博客的最后会提供源代码下载。
Redisson 的官网访问地址为:https://redisson.org
一、搭建工程
新建一个 springboot 工程,取名为 springboot_redisson,工程结构如下图所示:
首先查看一下 pom 文件的内容,主要是引入了 redis 和 redisson 的 starter 依赖包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.jobs</groupId><artifactId>springboot_redisson</artifactId><version>1.0</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.5</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--引入 redis 的 starter 依赖包--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--引入 redisson 的 starter 依赖包--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.30.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.5</version></plugin></plugins></build>
</project>
然后查看一下 application.yml 的配置内容,主要是 redis 的连接信息配置
server:port: 8888
spring:redis:host: 192.168.136.128port: 6379password: rootjedis:pool:# 最大连接数max-active: 10# 最大空闲连接数max-idle: 5# 最小空闲min-idle: 1# 连接超时时间(毫秒)max-wait: 3000
二、代码细节
如果使用 RedisTemplate 操作 redis 的话,需要编写一个配置类,主要是改变一下 key 的序列化方式,方便明文查看
package com.jobs.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();//默认的Key序列化器为:JdkSerializationRedisSerializer//这里只是将 key 采用 string 序列化,方便查看redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setConnectionFactory(connectionFactory);redisTemplate.setEnableTransactionSupport(true);return redisTemplate;}
}
最后就是编写一个 controller 类,里面有 redis 和 redisson 两种实现分布式锁的方案
package com.jobs.controller;import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/lock")
public class LockController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;/*** 使用 redis 的两个命令实现分布式加锁和解锁* 加锁:set lock_key lock_value NX PX 3000* 解锁:del lock_key* 该分布式锁方案,是由我们自己编写代码实现,可以简单的实现分布式锁的特性,* 主要的不足时:这里是非阻塞锁,没有重试机制,获取不到锁后,直接返回失败,不太符合大多数应用场景*/@GetMapping("/redistest")public String stock1() {String result;//获取当前线程的idString threadId = String.valueOf(Thread.currentThread().getId());//尝试加锁,如果获取锁成功,则返回 true//为了防止死锁,获取到锁之后,给锁设置了一个 3 秒的有效期,过期自动释放锁//key 可以随便定义,value 是线程idBoolean locked = redisTemplate.opsForValue().setIfAbsent("mylock", threadId, 3, TimeUnit.SECONDS);if (locked) {try {//由于是 demo,这里就不从数据库中获取库存量了,以 redis 代替数据库获取库存量String temp = redisTemplate.opsForValue().get("stock");if (StringUtils.hasText(temp)) {int stock = Integer.parseInt(temp);if (stock > 0) {stock--;redisTemplate.opsForValue().set("stock", String.valueOf(stock));result = "库存量扣减成功,剩余库存量:" + stock;System.out.println(result);} else {result = "库存不足!!!";System.out.println(result);}} else {result = "请提前在 redis 中设置好 stock 库存量的值";System.out.println(result);}} catch (Exception ex) {result = ex.getMessage();System.out.println(result);} finally {//对比 redis 中的 value 值,如果是当前线程 id 才可以进行解锁String myValue = redisTemplate.opsForValue().get("mylock");if (threadId.equals(myValue)) {redisTemplate.delete("mylock");}}} else {result = "没有获取到锁,不能扣减库存量!!!";System.out.println(result);}return result;}//--------------------------------------------------@Autowiredprivate RedissonClient redissonClient;/*** 使用 Redission 框架实现分布式锁功能,具有阻塞重试的特性,非常适合绝大多数应用场景。*/@GetMapping("/redissontest")public String stock2() {String result;//获得分布式锁对象,这里还没有尝试去获取锁RLock lock = redissonClient.getLock("mylock");//尝试获取锁,如果获取成功,则后续程序继续执行;如果获取不成功则阻塞等待//如果获取锁成功,则锁的有效期是 3 秒,超时后自动解锁lock.lock(3, TimeUnit.SECONDS);try {//由于是 demo,这里就不从数据库中获取库存量了,以 redis 代替数据库获取库存量String temp = redisTemplate.opsForValue().get("stock");if (StringUtils.hasText(temp)) {int stock = Integer.parseInt(temp);if (stock > 0) {stock--;redisTemplate.opsForValue().set("stock", String.valueOf(stock));result = "库存量扣减成功,剩余库存量:" + stock;System.out.println(result);} else {result = "库存不足!!!";System.out.println(result);}} else {result = "请提前在 redis 中设置好 stock 库存量的值";System.out.println(result);}} catch (Exception ex) {result = ex.getMessage();System.out.println(result);} finally {//解锁lock.unlock();}return result;}
}
代码已经编写完毕,注释也比较详细,应该很容易理解。可以打包后部署多份,采用 nginx 进行负载均衡转发,然后采用 Jmeter 等压力测试工具,模拟出多线程进行访问请求,测试分布式锁的实现结果。这里就不进行展示了。
本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springboot_redisson.zip