首页 > 数据库 >使用 Redisson 框架基于 Redis 实现分布式锁

使用 Redisson 框架基于 Redis 实现分布式锁

时间:2024-05-11 19:41:35浏览次数:13  
标签:Redisson Redis redis springframework result org stock redisTemplate 分布式

分布式锁可以采用数据库、zookeeper、redis 三种方式实现。

采用数据库实现方式,主要采用表字段的唯一索引特性。数据库是非常昂贵的资源,非常不推荐,最致命就是性能,不要去增加不必要的负担。

采用 zookeeper 的实现方式,主要使用其为客户端创建临时有序节点的特性,在我之前的博客有介绍。虽然使用 Apache Curator 客户端框架可以简化操作,但是其底层实现比较复杂,总体而言性能相对较差,因为需要维护大量的 Zookeeper 状态,引起大量网络 IO 的开销。

采用 redis 的实现方式,主要采用其单线程执行相关命令的特性。实现原理非常简单,性能也是最好,尤其采用 Redisson 实现方案。

Redisson 是基于 Redis 实现的一个框架。充分的利用了 Redis 键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包,获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度,简化了分布式环境中程序相互之间的协作。Redisson已经内置提供了基于Redis的分布式锁实现,此种方式是我们推荐的分布式锁使用方式。

本篇博客介绍 redis 的两种分布式锁的实现方式:自己编码实现方式和采用 redisson 框架的实现方式,在博客的最后会提供源代码下载。

Redisson 的官网访问地址为:https://redisson.org


一、搭建工程

新建一个 springboot 工程,取名为 springboot_redisson,工程结构如下图所示:

image

首先查看一下 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.0
         http://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.128
    port: 6379
    password: root
    jedis:
      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 {

    @Bean
    public 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 {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 使用 redis 的两个命令实现分布式加锁和解锁
     * 加锁:set lock_key lock_value NX PX 3000
     * 解锁:del lock_key
     * 该分布式锁方案,是由我们自己编写代码实现,可以简单的实现分布式锁的特性,
     * 主要的不足时:这里是非阻塞锁,没有重试机制,获取不到锁后,直接返回失败,不太符合大多数应用场景
     */
    @GetMapping("/redistest")
    public String stock1() {
        String result;
        //获取当前线程的id
        String threadId = String.valueOf(Thread.currentThread().getId());
        //尝试加锁,如果获取锁成功,则返回 true
        //为了防止死锁,获取到锁之后,给锁设置了一个 3 秒的有效期,过期自动释放锁
        //key 可以随便定义,value 是线程id
        Boolean 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;
    }

    //--------------------------------------------------

    @Autowired
    private 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

标签:Redisson,Redis,redis,springframework,result,org,stock,redisTemplate,分布式
From: https://www.cnblogs.com/studyjobs/p/18187085

相关文章

  • 利用pycharm对分布式命令的设备进行debug(Vision mamba)
    背景介绍接着上次的visionmamba初步跑通,想进一步了解内部代码运行的过程,模型的工作机理,因此打算利用pycharm进行断点调试(即debug),花了半天时间终于学会了如何将控制台命令的形式传入pycharm中进行传参。在此,感谢大佬的博客,这里只是具体结合本机安装wsl2对大佬的博客进行......
  • centos7下redis集群部署
    1、环境准备安装redis所需依赖环境#在线:yum-yinstallepel-releasegcc #离线:#下载yuminstall--downloadonly--downloaddir=/home/filesepel-releasegcc#将依赖包放到离线服务器,进入目录cd/home/files#执行安装rpm-Uvh./*.rpm--node......
  • 速度围观|使用分布式企业级任务调度平台,到底有多香?
    任务调度平台是关键的软件基础设施,专门设计用于自动化、高效和可靠地安排及执行预定的后台任务。谷歌云首席决策工程师KasimKhan曾提到:“在云计算环境中,自动化和效率是关键。”任务调度平台通过优化资源使用和集中管理功能,提供了一系列强大的调度策略、执行管理、监控报警和开发......
  • docker redis
    1.创建redis的Docker容器时,容器处于Exited(1)或Restarting(0)状态原因:在配置文件/etc/redis/redis.conf中'daemonize'设置为yes时,即为后台运行,也就是Redis服务器会以守护进程的方式在后台默默地运行。在这种情况下,Redis服务器会脱离终端地控制,并在后台持续运行,不会输出日志信息......
  • 【redis学习】Redis-IO多路复用
    为什么要有IO多路复用大家印象中的redis都是单线程的,没有加锁的操作,因此才会是redis这么快的原因其中之一。先暂且不说redis究竟是不是单线程,即便是单线程的,作为服务提供方,面对成百上千的客户端连接请求,读写操作,单线程是怎么做到高效的处理这些请求?单线程处理socket连接,面对客户......
  • Redis高可用架构
    redis架构的演进单机、主从、集群特性/配置Redis主从复制Redis哨兵Redis集群主要目的数据备份与读写分离高可用性和故障自动切换高并发和数据分散处理架构一个主节点和多个从节点监控主从结构并自动切换多个主节点,数据分片数据复制主节点到从节点监控并管......
  • 谈谈分布式事务原理
    前言分布式系统中,不同服务之间的交互可能会出现各种问题,如网络、异常等,可能会导致服务间的数据产生不一致的情况,如何避免?本文将详细讲述分布式事务的原理和解决方案。为什么需要分布式事务目前大多是互联网公司都选择的是分布式系统架构,随之而来暴露本地事务出现的问题。所......
  • 分布式 raft 可以同步日志为何还要gossip同步日志?
    Raft可以同步日志通信协议用的是Gossip 分布式系统中,Raft是一种常用的一致性算法,用于保证多个节点之间的数据一致性。Raft通过选举leader节点,并在leader节点上复制日志来确保数据的一致性。然而,尽管Raft提供了可靠的一致性保证,但在某些情况下,仍然需要一些额外的机制......
  • redis-sentinel
    首先搭建1主两从的redis主从服务mkdir-pv/etc/redis/mkdir-pv/redis/db{2,3}cd/etc/redis/redis主配置不变vim/usr/local/redis-6.2.6/bin/redis.confrequirepass"lzjasdqq"appendonlyyesdaemonizeyespidfile"/var/run/redis_6380.pid"logfile"......
  • redis持久化
    redis持久化rdbaofvimredis.confprotected-modeyesport6379tcp-backlog511timeout0tcp-keepalive300daemonizeyespidfile/var/run/redis_6379.pidloglevelnoticelogfile"/var/log/redis/redis.log"databases16always-show-logonoset-p......