---随笔--Redis的学习以及在Spring Boot中的整合使用(RedisTemplate、Redisson分布式锁)

---随笔--Redis的学习以及在Spring Boot中的整合使用(RedisTemplate、Redisson分布式锁)

    • 引言
    • 1. 什么是Redis
    • 2. Redis的数据结构
    • 3. Redis其它常用命令
    • 4. 小插曲之Redis面试常考
    • 5. 正篇:Redis在Spring Boot中的使用!(着急的小伙伴可以直接跳这里看噢)
      • 5.1 在maven文件中导入Redis依赖
      • 5.2 配置Redis连接信息
      • 5.3 使用RedisTemplate进行操作(记得运行redis server)
        • 5.3.1 编写RedisTemplate配置类
        • 5.3.2 注入使用RedisTemplate
        • 5.3.3 Redis工具类完整代码(可自行添加方法)
      • 5.4 测试Redis操作
      • 5.5 Redisson分布式锁的使用
    • 总结

引言

在现代Web应用开发中,性能和可扩展性至关重要。Redis作为一个高性能的内存数据结构存储系统,因其卓越的速度和丰富的功能而广受欢迎。而将Redis集成到Spring Boot项目中,可以显著提升应用程序的响应速度和处理能力。

本文将简要介绍Redis的用法及如何在Spring Boot中配置和使用Redis,涵盖基础配置和常见操作。通过阅读本文,您将掌握Redis的使用及如何在Spring Boot项目中高效使用Redis,优化应用性能,提升用户体验。 (若想直接跳到代码部分可以直接点目录哈~)

关于Redis的安装可以在网上查阅相关资料,这里就不费时间写如何安装了,还是蛮简单的,可以参考下‘明金同学’的这篇文章:Window下Redis的安装和部署详细图文教程,或者参考‘阿里社区’的这篇文章:Redis安装与配置指南:适用于Windows、Mac和Linux系统的详细教程,也可以参考’JAVA白大仙’的这篇文章:Windows版Docker安装Redis教程(保姆级),适合开发环境快速提供Redis服务

1. 什么是Redis

Redis(Remote Dictionary Server)是一个开源(BSD许可)的内存数据结构存储系统。它可以用作数据库、缓存和消息代理。Redis支持多种数据结构,例如字符串(String)、哈希表(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)高级数据类型包括位图 (Bitmaps)、概率性数据结构 (HyperLogLog)、地理空间 (Geospatial)。它的主要特点和功能包括:

  1. 高性能:Redis通过将数据存储在内存中,实现了非常高的读写速度,读写操作可以在毫秒级别内完成。
  2. 持久化:尽管Redis的数据主要存储在内存中,但它支持将数据异步地保存到磁盘,以实现持久化。支持RDB(快照)和AOF(Append Only File)两种持久化方式。
  3. 丰富的数据类型:Redis不仅支持简单的键值对,还支持复杂的数据结构,如列表、集合、有序集合和哈希表,这使得它在处理复杂数据时更加灵活。
  4. 原子操作:Redis提供了一组丰富的原子操作,这些操作保证了数据的一致性和完整性。
  5. 发布/订阅:Redis支持发布/订阅消息模式,可以用于构建实时消息系统。
  6. 主从复制:Redis支持主从复制,允许数据在多个Redis实例之间进行同步,从而提高数据的可用性和可靠性。
    高可用性和分片:通过Redis Sentinel和Redis Cluster,可以实现高可用性和自动分片,从而支持更大规模的集群和更高的故障容忍度。
    Redis广泛应用于缓存、会话存储、实时分析、消息队列和排行榜等场景,是提升Web应用性能和可扩展性的强大工具。

2. Redis的数据结构

Redis是一种高效的内存数据结构存储系统,支持多种数据结构和丰富的操作。以下是Redis的几种主要数据结构及其常见用法:

  1. 字符串 (String)
    字符串是Redis中最基本的数据结构,每个键对应一个字符串值。
  • 用法示例【SET、GET、INCR、MGET 、MSET 】:
SET key "value"           # 设置字符串值
GET key                   # 获取字符串值value
INCR key                  # 将字符串值自增1(要求值是整数)
MGET key1 key2			  # 同时获取一个或多个 key 的值
MSET key1 key2			  # 同时设置一个或多个 key-value 对
  1. 哈希 (Hash)
    哈希是一种键值对集合,适用于存储对象。例如,用户信息。
  • 用法示例【HSET、HGET、HGETALL、HEXISTS、HKEYS、HVALS 】:
HSET user:1 name "Alice"  # 设置哈希字段
HGET user:1 name          # 获取哈希字段
HGETALL user:1            # 获取所有哈希字段和值
HEXISTS user:1 name		  # 检查指定字段是否存在于 Hash 中
HKEYS user:1			  # 获取 Hash 中所有字段的列表
HVALS user:1			  # 获取 Hash 中所有值的列表
  1. 列表 (List)
    列表是一个有序的字符串集合,可以添加元素到列表的两端(左端和右端)。
  • 用法示例【LPUSH、RPUSH、LPOP、RPOP、LRANGE、LINDEX 、LSET 、LTRIM 】:
LPUSH tasks "task1"       # 从左端插入元素
RPUSH tasks "task2"       # 从右端插入元素
LPOP tasks                # 从左端弹出元素
RPOP tasks                # 从右端弹出元素
LRANGE tasks 0 -1         # 获取列表中所有元素
LINDEX tasks index 	  # 通过索引获取列表中的元素
LSET tasks index value	  # 将列表中指定索引的元素设置为另一个值
LTRIM tasks start stop   # 修剪(缩减)一个列表,使其只包含指定范围的元素
  1. 集合 (Set)
    集合是一个无序的字符串集合,适用于存储不重复的数据。
  • 用法示例【SADD、SMEMBERS、SISMEMBER、SREM、SUNION、SINTER 、SDIFF 、SCARD、SRANDMEMBER、SPOP 】:
SADD myset "value1"       # 添加元素到集合
SADD myset "value2"
SMEMBERS myset            # 获取集合中的所有元素
SISMEMBER myset "value1"  # 判断指定元素是否存在于集合中
SREM myset "value1"       # 从集合中移除元素
SUNION set1 set2		  # 返回两个或多个集合的并集
SINTER set1 set2		  # 返回两个或多个集合的交集
SDIFF set1 set2			  # 返回两个或多个集合的差集
SCARD myset				  # 获取集合中元素的数量
SRANDMEMBER myset 2  	  # 返回两个随机元素
SPOP myset 1  			  # 随机移除并返回一个元素
  1. 有序集合 (Sorted Set)
    有序集合与集合类似,但每个元素都会关联一个评分(score),可以根据评分排序。
  • 用法示例【ZADD、ZRANGE(ZREVRANGE)、ZRANGEBYSCORE(ZREVRANGEBYSCORE)、ZREM、ZINCRBY 、ZSCORE 】:
ZADD myzset 1 "value1"    # 添加元素及其评分
ZADD myzset 2 "value2"
ZRANGE myzset 0 -1 WITHSCORES  # 获取所有元素及其评分,按分数升序排列
ZRANGEBYSCORE myzset min max WITHSCORES  # 获取分数在 min 和 max 之间的元素及其分数
ZREM myzset "value1"      # 移除元素
ZINCRBY myzset 2 "value2"  # 将元素 "value2" 的分数增加 2
ZSCORE myzset "value2"
  1. 位图 (Bitmaps)
    位图是一个字符串,在位级别上操作。可以将其视为一个大位数组。
  • 用法示例【SETBIT、GETBIT、BITCOUNT】:
SETBIT bitmap 10 1        # 将第10位置为1
GETBIT bitmap 10          # 获取第10位的值
BITCOUNT bitmap           # 统计位图中值为1的位数
  1. HyperLogLog
    HyperLogLog是一种概率性数据结构,用于基数统计(即不重复元素的近似计数)。
  • 用法示例【PFADD、PFCOUNT】:
PFADD hll "elem1" "elem2" # 添加元素到HyperLogLog
PFCOUNT hll               # 获取不重复元素的近似计数
  1. 地理空间 (Geospatial)
    Redis支持地理空间索引,允许存储地理位置及执行地理空间半径查询。
  • 用法示例【GEOADD、GEORADIUS】:
GEOADD locations 13.361389 38.115556 "Palermo"  # 添加地理位置
GEORADIUS locations 15 37 200 km                # 查询半径内的位置

3. Redis其它常用命令

除了上述数据结构常用的命令外,Redis还提供了许多特殊的操作命令:

  1. TTL
    TTL命令用于获取指定键的剩余生存时间(Time To Live),即距离过期还有多少秒。
  • 用法示例:
SET key "value"     # 设置键 key 的值为 value
EXPIRE key 60       # 设置键 key 的过期时间为 60 秒
TTL key             # 获取键 key 的剩余生存时间
  1. EXPIRE
    EXPIRE命令用于设置指定键的过期时间,单位为秒。当键过期后,将自动被删除
  • 用法示例:
SET session "token"   # 设置会话 session 的值为 token
EXPIRE session 3600   # 设置 session 的过期时间为 3600 秒(1小时)
  1. DEL
    DEL命令用于删除指定键及其对应的值
  • 用法示例:
SET key "value"     # 设置键 key 的值为 value
DEL key             # 删除键 key
  1. KEYS
    KEYS命令用于获取所有符合给定模式的键。
  • 用法示例:
KEYS *              # 获取所有键
KEYS user:*         # 获取以 user: 开头的键
  1. SETNX
    SETNX命令用于设置键的值,但仅在键不存在时才执行设置操作。如果键已经存在,则该命令不会执行任何操作。
  • 用法示例:
SETNX key "value"   # 当键 key 不存在时,设置其值为 value

这个命令通常用于设置分布式锁,确保同一时刻只有一个客户端能够获取锁。

  1. SETEX
    SETEX命令用于设置键的值,并同时设置该键的过期时间(单位为秒,原子操作)。
  • 用法示例:
SETEX key 60 "value"   # 设置键 key 的值为 value,并同时设置过期时间为60秒
  1. PUBLISH 、SUBSCRIBE、UNSUBSCRIBE
    使用 SUBSCRIBE 命令让客户端订阅一个或多个频道。当客户端订阅了频道后,它可以接收到所有发送到这些频道的消息。
SUBSCRIBE channel1

使用 PUBLISH 命令来发布消息到一个指定的频道。任何订阅了该频道的客户端都将收到这条消息

PUBLISH channel1 "Hello, World!"

订阅频道的客户端会实时接收到所有发布到这些频道的消息。消息的接收是自动的,一旦客户端订阅了频道,就会保持监听状态直到取消订阅

如果客户端不再希望接收某个频道的消息,可以使用 UNSUBSCRIBE 命令来取消订阅

UNSUBSCRIBE channel1
  1. FLUSHDB
    FLUSHDB命令用于清空当前数据库中的所有键,不影响其它数据库。
  • 用法示例:
FLUSHDB   # 清空当前数据库
  1. FLUSHALL
    FLUSHALL命令用于清空所有数据库的所有键。
  • 用法示例:
FLUSHALL  # 清空所有数据库
  1. RENAME
    RENAME命令用于修改键的名字。
  • 用法示例:
SET mykey "value"   # 设置键 mykey 的值
RENAME mykey newkey # 将 mykey 重命名为 newkey
  1. TYPE
    TYPE命令用于获取键的数据类型。
  • 用法示例:
TYPE mykey  # 获取 mykey 的数据类型
  1. DBSIZE
    DBSIZE命令用于获取当前数据库的键的数量。
  • 用法示例:
DBSIZE  # 获取当前数据库的键的数量
  1. PERSIST
    PERSIST命令用于移除键的过期时间,使其变为持久键
  • 用法示例:
SET mykey "value" EX 60   # 设置 mykey 的过期时间为60秒
PERSIST mykey             # 移除 mykey 的过期时间
  1. INFO
    INFO命令用于获取服务器的信息和统计数据。
  • 用法示例:
INFO   # 获取服务器的详细信息
INFO memory  # 获取关于内存的统计信息
  1. MULTI、EXEC、DISCARD、WATCH
    MULTI :通过该命令来开启一个事务,后续的操作命令统统填进该事务队列中,直到执行或取消
    EXEC:该命令来执行所有队列中的命令
    DISCARD: 在执行 EXEC 之前决定不再继续事务,可以使用命令来取消事务
    WATCH:在开启事务之前,可以使用 WATCH 命令来监控一个或多个键。如果在执行事务之前这些键的值被修改,那么事务将不会执行,EXEC 命令将返回 nil。
WATCH balance # 监控余额,防止在事务开启前值被更改
GET balance
# 假设余额检查后足够进行某项操作
MULTI # 开启事务
DECRBY balance 100  #命令入队
LPUSH changelog "100 deducted from balance"  #命令入队
#DISCARD 		# 取消上边事务队列中的事务
EXEC  			#将队列中的命令依次执行,保证事务

4. 小插曲之Redis面试常考

Redis也是后端开发技术面试的常考的一个知识,我们也是需要多了解了解的,有些理论知识也是挺有帮助的,有时可以很快地解决未知的bug。下面一起来看看Redis常考的面试题吧,浅浅过一下,查漏补缺。

  1. 基本概念和数据类型: 了解 Redis 是什么,它的特点和用途,为什么那么快。 掌握各种数据类型的使用和特点,如字符串(String),列表(List),集合(Set),有序集合(Sorted Set),哈希(Hash)。
  2. 命令熟悉度: 需要对常用的 Redis 命令有深入的了解,比如刚刚讨论的字符串和列表操作命令。 如何使用这些命令来实现具体的功能,比如实现队列、堆栈、消息发布/订阅等。
  3. 持久化机制: 理解 Redis 的持久化选项:RDB(快照)和 AOF(追加文件)。 知道它们的工作原理、优缺点以及如何配置。
  4. 事务处理: 如何在 Redis 中使用事务,了解 MULTI, EXEC, WATCH, 和 DISCARD 命令。 事务的特性,如原子性、隔离级别。
  5. 性能优化和监控: 熟悉如何优化 Redis 配置和性能。 了解监控工具和相关的性能指标。
  6. 高可用性与集群: Redis 的主从复制机制,故障转移和数据一致性。 Redis 集群的工作原理,如何设置和运维一个 Redis 集群。
  7. 特殊问题处理: 对缓存穿透、缓存雪崩和缓存击穿的理解和解决方案。
  8. 安全性: 如何安全地配置和使用 Redis,包括网络安全和数据安全。

Redis的面试题涵盖许多,更多的也可以参考下’涝山道士‘的【精选】30+Redis面试题整理(2024)附答案

5. 正篇:Redis在Spring Boot中的使用!(着急的小伙伴可以直接跳这里看噢)

5.1 在maven文件中导入Redis依赖

在pom.xml文件下的dependencies标签中添加以下标签:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

这里需要注意,添加了一个spring-boot-starter-web的依赖,因为这个依赖里包含了Redis所需的序列化Jackson依赖,所以需要添加进来,如果不添加spring-boot-starter-web依赖,则需要手动再添加jackson-databind的相关依赖

5.2 配置Redis连接信息

spring:
  application:
    name: myRedis
  data:
    redis:
      host: localhost
      port: 6379
      database: 0 # Redis默认提供16个数据库(索引0-15),一般从第0个使用
      password: <PASSWORD> # 如果有,没有则删除
      # 使用lettuce作为连接客户端,若需要使用jedis需要再导入依赖并配置
      lettuce:
        # 连接池配置
        pool:
          max-active: 8 #最大活跃连接数为8
          max-idle: 8   # 最大空闲连接数为8
          min-idle: 0   # 最小空闲连接数为0
          max-wait: -1ms # 最大等待时间为-1ms,即不限制等待时间

5.3 使用RedisTemplate进行操作(记得运行redis server)

5.3.1 编写RedisTemplate配置类
@Configuration
public class RedisConfig {

    /***
     * 这段代码的主要功能是创建和配置一个RedisTemplate bean,
     * 在Redis数据库中设置键、值、哈希键和哈希值的序列化器,以与Redis数据库交互。
     * 该代码配置了RedisTemplate与连接工厂和特定的序列化器,用于处理Redis中的键值对和哈希结构,允许以JSON格式存储和检索Redis数据库中的对象。
     * @param factory 接受一个RedisConnectionFactory类型的参数factory,这个参数是由Spring自动注入的(yaml文件里配置了),用于连接到Redis数据库。
     * @return
     */
    @Bean
    // 在控制台输出信息,表示正在使用自定义配置的 RedisTemplate
    System.out.println("使用自定义的RedisTemplate配置");

    // 创建一个 RedisTemplate<String, Object> 实例
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    // 设置 Redis 连接工厂,这是必须的,以便模板能够访问 Redis 服务器
    template.setConnectionFactory(factory);

    // 创建一个用于序列化字符串的序列化器
    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    
    // 设置键(key)(value)的序列化方式。这里使用字符串序列化器,可以确保 key和value 的存储和读取不会出现乱码等问题
    template.setKeySerializer(stringRedisSerializer);
    template.setValueSerializer(stringRedisSerializer);
    template.setHashKeySerializer(stringRedisSerializer);
    template.setHashValueSerializer(stringRedisSerializer);

    // 初始化 RedisTemplate 实例的各项属性,确保它在返回前是完全可用的
    template.afterPropertiesSet();

    // 返回配置好的 RedisTemplate 实例
    return template;
}

一开始我使用’GenericJackson2JsonRedisSerializer’来对值进行序列化,但是后面测试的时候发现,在存储字符串的时候会把双引号""也存储进去,虽然读取的时候读出来的数据没有双引号,但是在客户端上get或者通过可视化查看值时还是不是很好看的,所以都使用‘StringRedisSerializer’ 来序列化了,可能这也会带来一些问题,需要再研究了。

5.3.2 注入使用RedisTemplate
@Component
public class MyRedisUtils{

    private final RedisTemplate<String, Object> redisTemplate;
    
    //构造器注入
    @Autowired
    public MyRedisUtils(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

}

这里我使用了Spring框架推荐的构造器注入方式,主要是这几点原因:

  1. 不变性和线程安全: 通过构造器注入,可以确保依赖对象在创建时就完全初始化。这样,redisTemplate 字段可以被声明为 final,意味着一旦赋值后就不可更改。这种不变性有助于保证线程安全,因为这个对象状态不会再发生变化。

  2. 依赖关系的清晰性: 构造器注入强制性地要求提供所有必需的依赖,否则对象无法被创建。这种方式使得依赖关系更加明显,不会隐藏在类的内部,有助于维护和理解代码。当查看构造函数时,可以清楚地看到类的所有依赖项,这比在类中间或底部查找注入点要直观得多。

  3. 与Spring框架的兼容性: 在现在的Spring主流版本上,如果类只有一个构造函数,Spring会默认使用这个构造函数进行依赖注入,即使没有显式地标注
    @Autowired。这表明Spring团队推荐使用构造器注入,而且这种方式与Spring的自动装配机制非常兼容。

  4. 测试的便利性: 使用构造器注入可以更容易地编写单元测试,因为可以在不启动Spring容器的情况下,通过直接传递构造参数来创建对象的实例。这使得测试更快、更独立,不依赖于Spring的配置或环境。

  5. 代码整洁与组织性: 构造器注入通常可以促使开发者编写更整洁、更有组织的代码。通过构造器集中管理依赖,可以避免在类中散布多个注入点,使类的结构更加清晰和模块化。

5.3.3 Redis工具类完整代码(可自行添加方法)
@Component
public class MyRedisUtils {

    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public MyRedisUtils(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 设置指定key的值
     *
     * @param key   键
     * @param value 值
     */
    public void set(String key, Object value) {
        System.out.println("set key: " + key + " value: " + value);
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 获取指定key的值
     *
     * @param key 键
     * @return 对应的值
     */
    public Object get(String key) {
        Object val = redisTemplate.opsForValue().get(key);
        System.out.println("get key's value: " + val);
        return val;
    }

    /**
     * 删除指定key
     *
     * @param key 键
     */
    public void delete(String key) {
        System.out.println("delete key: " + key);
        redisTemplate.delete(key);
    }

    /**
     * 判断指定key是否存在
     *
     * @param key 键
     * @return 存在则返回true,否则返回false
     */
    public boolean exists(String key) {
        Boolean hasKey = redisTemplate.hasKey(key);
        System.out.println("whether exists key->" + key + ":" + hasKey);
        return Boolean.TRUE.equals(hasKey);
    }

    /**
     * 为指定key设置过期时间
     *
     * @param key     键
     * @param timeout 过期时间(以秒为单位)
     */
    public void expire(String key, long timeout) {
        System.out.println("expire key: " + key + " timeout: " + timeout);
        redisTemplate.expire(key, Duration.ofSeconds(timeout));
    }
}

5.4 测试Redis操作

编写测试类:

@SpringBootTest
class MyRedisApplicationTests {

    @Autowired
    private MyRedisUtils myRedisUtils;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testMyRedisUtilsIsNotNull() {
        assertNotNull(myRedisUtils);
    }

    @Test
    public void testSetAndGet() {
        String key = "testKey";
        String value = "testValue";

        myRedisUtils.set(key, value);
        assertEquals(value, myRedisUtils.get(key));

        // 清理
        myRedisUtils.delete(key);
    }

    @Test
    public void testDelete() {
        String key = "testKey";
        myRedisUtils.set(key, "value");
        assertTrue(myRedisUtils.exists(key));

        myRedisUtils.delete(key);
        assertFalse(myRedisUtils.exists(key));
    }

    @Test
    public void testExpire() throws InterruptedException {
        String key = "testKey";
        myRedisUtils.set(key, "value");
        myRedisUtils.expire(key, 1);  // 设置1秒后过期

        Thread.sleep(1500);  // 等待超过1秒确保键已过期
        assertNull(myRedisUtils.get(key));
    }
}

执行结果:
执行结果

5.5 Redisson分布式锁的使用

除了RedisTemplate能满足我们的一些日常需求外,Redisson还提供了分布式锁的解决方案,接下来我会使用RedissonClient来实现一个分布式锁来进行测试:

@Service
public class RedissonService {
    private final RedissonClient redissonClient;
    private volatile int count = 0; // 用于计数的变量
    private static final Logger logger = LoggerFactory.getLogger(RedissonService.class);
    @Autowired
    public RedissonService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
    public void performTest() throws InterruptedException {
        int threadNums = 100;
        CountDownLatch countDownLatch = new CountDownLatch(threadNums);

        for (int i = 0; i < threadNums; i++) {
            new Thread(() -> {
                // 使用RLock,这是Redisson的可重入锁
                RLock lock = redissonClient.getLock("counterLock");

                try {
                    // 尝试获取锁,等待获取锁最多10秒,超过10秒当前线程自动放弃;锁持有最长1秒,超时自动释放
                    if (lock.tryLock(10, 1, TimeUnit.SECONDS)) {
                        try {
                            // 计数器自增 1,通过redis锁来保证每次+1操作只有一个线程能进行
                            count += 1;
                            logger.info("计数器+1为:{}", count);
                        } finally {
                            // 释放锁
                            lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 设置中断标志
                } finally {
                    // 计数器减1,直到计数器为0时
                    countDownLatch.countDown();
                }
            }).start();
        }

        countDownLatch.await(); // 等待所有线程完成,即当countDownLatch为0时完成,继续往下执行

        logger.info("count = {}", count);
    }
}

上述代码中同样使用了构造器注入的方式,将RedissonClient注入到方法中,主要需要理解下performTest()方法。

  1. 使用CountDownLatch:是为了让这100个线程全部执行完毕之后,才打印最终的计数count,不然循环中新创建的线程还没全部执行完就打印日志了,那结果肯定是不准的。
  2. 使用RLock:使用RLock而不是JUC包下的Lock,是因为RLock 是专门为使用 Redis 作为后端存储的环境设计的,用法也与Lock大差不差,但是RLock可以使用tryLock()尝试获取锁,并在规定时间内获取锁后,又会在指定时间后释放掉锁,从而避免锁得不到释放的情况。
  3. count的类型:之所以可以不用AtomicInteger来进行自增,是因为AtomicInteger更消耗时间和资源,并且最主要的是在这里不需要保证count的原子性,因为每次count的修改都只有1个线程才能对它进行修改,是通过redis的锁来实现的,当执行当前线程后释放锁,下一个线程又可以操作count了,

测试结果:

	@Test
    public void testRedisson() throws InterruptedException {
        redissonService.performTest();
    }

测试结果
当然,实现redis分布式锁的方式还有挺多,例如lua脚本: if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end、基于Setnx的原始实现等等,就不细聊了,有兴趣的可以多去了解下^^

总结

Spring Boot对于Redis和Redis的整合还是蛮简单方便的,总的老说就是创建了Spring Boot项目后,导入redis数据库的依赖,同时也别忘记了导入spring-boot-web的依赖,因为里面包含了redis序列话需要用到的jackson库,之后配置RedisTemplate的配置文件,后面就可以注入RedisClient进行使用啦;Redisson的话也是同理,添加redisson依赖,注入RedissonClient就可以使用啦,还是很方便的!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/639224.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NE555+74ls160+74LS20数电数字钟Multisim仿真设计

设计框图 数字钟电路的基本组成框架图如图一所示&#xff0c;它主要由多谐振荡器、计数器、译码器和数码显示器4个部分组成。 图一 数字钟电路的基本组成&#xff08;方框图&#xff09; 2、设计方案 数字钟是一个将“时”&#xff0c;“分”&#xff0c;“秒”显示于人的视…

初始MyBatis ,详细步骤运行第一个MyBatis程序,同时对应步骤MyBatis底层剖析

1. 初始MyBatis &#xff0c;详细步骤运行第一个MyBatis程序&#xff0c;同时对应步骤MyBatis底层剖析 文章目录 1. 初始MyBatis &#xff0c;详细步骤运行第一个MyBatis程序&#xff0c;同时对应步骤MyBatis底层剖析每博一文案2. 前沿知识2.1 框架&#xff08;framework&#…

jdk17安装教程详细(jdk17安装超详细图文)

2021年9月14日JDK17 发布&#xff0c;其中不仅包含很多新语言功能&#xff0c;而且与旧版 JDK 相比&#xff0c;性能提升也非常明显。与之前 LTS 版本的 JDK 8 和 JDK 11 相比&#xff0c;JDK17 的性能提升尤为明显&#xff0c;本文将教你如何安装 相比于JDK1.8&#xff0c;JD…

【Crypto】看我回旋踢

文章目录 一、看我回旋踢二、知识点什么是ROT13&#xff1f;工作原理分析字符串格式 解题感悟 一、看我回旋踢 关键词回旋&#xff0c;盲猜ROT13 因为以 synt{ 开头&#xff0c;并以 } 结束&#xff0c;基本可以判断是ROT13 小小flag&#xff0c;拿下&#xff01; 二、知识点 …

Python中动态调用C#的dll动态链接库中方法

在Python中调用C#的dll库_哔哩哔哩_bilibili 环境准备&#xff1a; 安装 pythonnet pip install pythonnet在Python中调用C#动态链接库&#xff08;DLL&#xff09;&#xff0c;可以使用pythonnet库&#xff0c;它允许直接使用 .NET 的程序集。以下是一个示例&#xff0c;…

1+x(Java)中级题库易混淆理论题

<ALL表示小于最小 小于最高等同于小于ANY 使用USING子句&#xff0c;在使用连接字段时&#xff0c;都不能在前面加上表的前缀&#xff0c;因为此时这个字段已经是连接字段&#xff0c;不再属于某个单独的表。 数据库提供的自动将提供的数据类型数据转换为期望的数据类…

go select 原理

编译器会使用如下的流程处理 select 语句&#xff1a; 将所有的 case 转换成包含 channel 以及类型等信息的 runtime.scase 结构体。调用运行时函数 runtime.selectgo 从多个准备就绪的 channel 中选择一个可执行的 runtime.scase 结构体。通过 for 循环生成一组 if 语句&…

AIGC:AI整活!万物皆可建筑设计

在过去的一年里 AI设计爆火 各行业纷纷将之用于工作中 同时不少网友也在借助它整活 万物皆可设计 甲方骂我方案像屎一样 于是我就回馈他屎一样的方案 他有点惊喜&#xff0c;但是没话 不是吧&#xff0c;随便找了个充电头图片 也能生成建筑设计&#xff01;这都能行 鸟…

人工智能应用-实验7-胶囊网络分类minst手写数据集

文章目录 &#x1f9e1;&#x1f9e1;实验内容&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;代码&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;分析结果&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;实验总结&#x1f9e1;&#x1f9e1; &#x1f9…

微信小程序的自定义组件

一、创建自定义组件 &#xff08;1&#xff09;定义&#xff1a; 把页面重复的代码部分封装成为一个自定义组件&#xff0c;以便在不同的页面中重复使用&#xff0c;有助于代码的维护。 &#xff08;2&#xff09;组成&#xff1a; 自定义组件的组成&#xff1a;json文件&a…

Flutter 中如何优雅地使用弹框

日常开发中&#xff0c;Flutter 弹框&#xff08;Dialog&#xff09;是我们使用频率非常高的控件。无论是提示用户信息、确认用户操作&#xff0c;还是表单填写&#xff0c;弹框都能派上用场。然而&#xff0c;看似简单的弹框&#xff0c;实际使用起来却有不少坑和使用的技巧。…

el-select可选择可搜索可输入新内容

需求&#xff1a;el-form-item添加el-select&#xff0c;并且el-select可选择可搜索可输入新内容&#xff0c;并且和其他的el-input做联动&#xff0c;如果是选择&#xff0c;那么el-input自动回填数据并且不可编辑&#xff0c;如果el-select输入新的内容&#xff0c;那么el-in…

webpack5 splitChunks分割代码

首先明确webpack 自身的打包行为 当splitChunks为false时&#xff0c;此时不启用任何打包设置 可以看到&#xff0c;静态引入全都打到一个chunk里&#xff0c;动态引入会拆分出来一个chunk,这是纯webpack无配置的打包&#xff0c; webpack会给每个模块打上标记 ,如下 { m…

【HCIP学习】RSTP和MSTP

一、RSTP&#xff08;Rapid Spanning Tree Protocol&#xff0c;快速生成树&#xff09; 1、背景&#xff1a;RSTP从STP发展而来&#xff0c;具备STP的所有功能&#xff0c;可以兼容stp运行 2、RSTP与STP不同点 &#xff08;1&#xff09;减少端口状态 STP:disabled\blockin…

抵御风险——漫谈运维核心价值和方法论

要明晰什么是运维的核心价值&#xff0c;也就是要弄明白&#xff0c;从整个软件行业运维的角色定位来看&#xff0c;运维的核心价值在哪里&#xff1f;怎样增强自己实现核心价值的能力的问题。 软件产业本质其实还是工业&#xff0c;它的产品和传统的工业产品形态虽然有巨大差…

汇舟问卷:海外问卷调查如何闭坑?

大家好&#xff0c;我是汇舟问卷。有很多人问我这行有什么骗局吗&#xff1f;怎么说呢&#xff1f;其实每个行业都是真实存在的&#xff0c;也都有赚到钱的和赚不到钱的&#xff0c;那区别在哪里呢&#xff1f; 在你的源头&#xff0c;也就是教你或者说是代理加盟的上家&#…

机器学习实验 --- 逻辑回归

第1关:逻辑回归核心思想 任务描述 本关任务:根据本节课所学知识完成本关所设置的编程题 #encoding=utf8 import numpy as npdef sigmoid(t):完成sigmoid函数计算:param t: 负无穷到正无穷的实数:return: 转换后的概率值:可以考虑使用np.exp()函数#********** Begin *******…

景源畅信数字:抖音小店新手该怎么做?

在数字化时代的浪潮中&#xff0c;抖音不仅仅是一个分享短视频的平台&#xff0c;更是一个充满潜力的电商平台。对于想要进入这个领域的朋友们来说&#xff0c;开设一家抖音小店无疑是一个既激动又迷茫的起点。那么&#xff0c;作为新手&#xff0c;该如何在这个全新的舞台上立…

笔记 | 《css权威指南》

网络安全色 URL text-indent line-height & vertical-align 字体 font-weight 400 normal 700 bold background-attachment