redis在SpringBoot中的使用

以下部分内容由AI生成,再添加自己的理解,仅供参考与了解记录

一、redis简单介绍

Redis是一个开源的高性能键值对数据库,支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)等。

核心原理

1. 单线程模型
redis使用单线程处理命令(核心逻辑),避免了多线程竞争问题。通过非阻塞I/O多路复用监听多个客户端连接,高效处理请求。所有操作原子性执行,无需加锁。

2. 持久化机制

  • RDB(Redis DataBase)
    原理:RDB是redis的默认持久化方式,会将redis在内存中的数据生成快照文件保存到硬盘上。快照文件是一个二进制文件,包含了某个时间点内redis的所有数据。
    优点: RDB文件是一个紧凑的二进制文件,体积小,便于备份和恢复;生成RDB文件时,redis主进程可以继续处理命令请求,不会长时间阻塞主进程。
    缺点:由于是定期生成快照,如果redis意外宕机,可能会丢失最近一次快照生成后的数据;对于大型数据集,fork子进程可能会比较耗时,影响性能。

  • AOF(Append Only File)
    原理:AOF日志以追加的方式记录redis执行的每条写命令,当redis重启时会顺序执行一遍AOF文件中的命令来恢复原始的数据。
    开启方式:默认是关闭的,需要通过修改redis.config配置文件中的appendonly参数为yes来开启。
    配置项
    appendonly:控制AOF日志同步的频率,有三个合法值always、everysec、no。always表示每个写命令执行完,立马同步磁盘,性能较低但数据安全性高;everysec表示每秒同步一次,性能和安全都比较中庸,是推荐的方式;no表示让操作系统来决定何时同步磁盘,性能较好但不安全。
    auto-aof-rewrite-min-size:设置AOF重写的最小文件尺寸,只有大于此尺寸时才会触发重写。
    auto-aof-rewrite-percentage:设置当前AOF文件增长到上一次rewrite之后大小的百分比,达到该比例时触发重写。
    优点
    可以提供更高的数据安全性,即使Redis意外宕机,最多只会丢失一秒内的数据。
    AOF日志文件是可读的文本文件,方便进行数据恢复和分析。
    缺点
    AOF日志文件会持续增大,需要定期进行AOF重写来对日志进行瘦身。
    对于变更操作比较密集的场景,可能会导致磁盘IO的负荷加重。

  • 混合持久化
    原理:结合了RDB和AOF的优点,在重启时优先加载AOF文件来恢复原始的数据,当AOF文件过大时,会采用类似RDB快照的方式基于Copy On Write全量遍历内存中数据,然后逐个序列到AOF文件中。
    优点
    既具有RDB的快速加载特性,又能保证数据的持久性和安全性。
    在AOF重写期间,可以利用RDB文件快速恢复数据。
    缺点:实现相对复杂,需要同时维护RDB和AOF两种持久化机制。

3. 数据结构与内存管理
所有数据存储在内存中,通过哈希表(全局字典)组织。支持多种数据结构,针对不同厂家优化内存分配(如ziplist、intset等紧凑结构)。

4. 过期策略与内存淘汰
惰性删除:访问时检查过期时间,过期则删除。
定期删除:随机抽查部分key,清理过期数据。
内存不足时触发淘汰策略(如LRU、LFU、随机删除等)。

5. 高可用与扩展
主从复制:主节点异步同步数据到从节点,实现读写分离和故障恢复。
哨兵模式:监控节点状态,自动故障转移。
Cluster模式:分片存储,支持水平扩展,数据分散在多个节点。

说了一大堆,都是用来面试造飞机的。。。
关于redis的数据类型,来看些例子吧,基于Spring boot的示例。

二、在Spring Boot中使用redis

前置配置

1. 依赖pom.xml添加依赖:我用的Spring Boot版本是2.3.4.RELEASE,JDK11

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

如果你是Spring boot 2.2以后,@Test需要使用org.junit.jupiter.api.Test的,pom.xml文件中引入

<dependency>
    <groupId>org.junit.jupitergroupId>
    <artifactId>junit-jupiter-apiartifactId>
    <version>5.7.1version>
dependency>

不要加test,加了之后还是会无法引入@Test,具体原因还没搞明白。这样在测试类中使用@Autowired注解引入的类就不会为null
完整pom.xml内容

<dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-tomcatartifactId>
            <scope>providedscope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-testartifactId>
        dependency>
        <dependency>
            <groupId>org.junit.jupitergroupId>
            <artifactId>junit-jupiter-apiartifactId>
            <version>5.7.1version>
        dependency>
    dependencies>

2. 配置application.properties 配置 Redis 连接:记得启动redis服务哦

## spring boot 2.x及以上使用spring.data.redis;1.x版本使用spring.redis

spring.data.redis.host=localhost
spring.data.redis.port=6379

## 默认没有密码,有密码的话自己填上即可
spring.data.redis.password=

3. RedisTemplate配置(解决key/value序列化问题):

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // Key 使用字符串序列化
        template.setKeySerializer(new StringRedisSerializer());
        // Value 使用 JSON 序列化
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

代码示例

1. 字符串(String)
场景:缓存数据、计数器

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;

@SpringBootTest
public class StringExampleTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testStringOperations() {
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        
        // 设置值
        ops.set("user:1:name", "Alice");
        // 设置值并指定过期时间
        ops.set("temp:session", "token123", 60, TimeUnit.SECONDS);
        
        // 获取值
        String name = (String) ops.get("user:1:name");
        System.out.println("获取用户名: " + name); // 输出: Alice
        
        // 自增操作
        Long count = ops.increment("article:1001:views");
        System.out.println("文章浏览量: " + count); // 输出: 1 (首次调用返回1)
    }
}

2. 哈希(Hash)
场景:存储对象(如用户信息)

@SpringBootTest
public class HashExampleTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testHashOperations() {
        HashOperations<String, String, Object> ops = redisTemplate.opsForHash();
        
        // 存储用户对象
        Map<String, Object> user = new HashMap<>();
        user.put("name", "Bob");
        user.put("age", 25);
        ops.putAll("user:2", user);
        
        // 获取单个字段
        String name = (String) ops.get("user:2", "name");
        System.out.println("用户姓名: " + name); // 输出: Bob
        
        // 修改字段值
        ops.put("user:2", "age", 26);
        
        // 获取所有字段
        Map<String, Object> userData = ops.entries("user:2");
        System.out.println("完整用户信息: " + userData); 
        // 输出: {name=Bob, age=26}
    }
}

3. 列表(List)
场景:消息队列、最新动态

@SpringBootTest
public class ListExampleTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testListOperations() {
        ListOperations<String, Object> ops = redisTemplate.opsForList();
        
        // 左推元素(插入队列头部)
        ops.leftPushAll("queue:tasks", "task1", "task2");
        
        // 右推元素(插入队列尾部)
        ops.rightPush("queue:tasks", "task3");
        
        // 获取列表长度
        Long size = ops.size("queue:tasks");
        System.out.println("队列长度: " + size); // 输出: 3
        
        // 右弹元素(从尾部取出)
        Object task = ops.rightPop("queue:tasks");
        System.out.println("处理任务: " + task); // 输出: task3
        
        // 获取范围元素
        List<Object> tasks = ops.range("queue:tasks", 0, -1);
        System.out.println("剩余任务: " + tasks); // 输出: [task2, task1]
    }
}

4. 集合(Set)
场景:标签系统、共同好友

@SpringBootTest
public class SetExampleTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testSetOperations() {
        SetOperations<String, Object> ops = redisTemplate.opsForSet();
        
        // 添加元素
        ops.add("user:1:tags", "java", "redis", "database");
        ops.add("user:2:tags", "python", "redis", "AI");
        
        // 获取所有元素
        Set<Object> tags = ops.members("user:1:tags");
        System.out.println("用户1的标签: " + tags); 
        // 输出: [java, redis, database]
        
        // 求交集(共同标签)
        Set<Object> commonTags = ops.intersect("user:1:tags", "user:2:tags");
        System.out.println("共同标签: " + commonTags); // 输出: [redis]
        
        // 删除元素
        ops.remove("user:1:tags", "database");
    }
}

5. 有序集合(Sorted Set)
场景:排行榜、优先级队列

@SpringBootTest
public class SortedSetExampleTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testSortedSetOperations() {
        ZSetOperations<String, Object> ops = redisTemplate.opsForZSet();
        
        // 添加元素(分数用于排序)
        ops.add("leaderboard", "player1", 100);
        ops.add("leaderboard", "player2", 90);
        ops.add("leaderboard", "player3", 95);
        
        // 获取前两名(按分数降序)reverseRange
        Set<Object> topPlayers = ops.reverseRange("leaderboard", 0, 1);
        System.out.println("排行榜前两名: " + topPlayers); 
        // 输出: [player1, player3]
        
		// 获取后两名(按分数升序)range
        Set<Object> lastPlayers = ops.range("leaderboard", 0, 1);
        System.out.println("排行榜后两名: " + lastPlayers);
        // 输出: [player2, player3]
        
        // 获取某个玩家的分数
        Double score = ops.score("leaderboard", "player3");
        System.out.println("player3的分数: " + score); // 输出: 95.0
        
        // 根据分数范围查询
        Set<ZSetOperations.TypedTuple<Object>> players = ops.rangeByScoreWithScores(
            "leaderboard", 90, 95
        );
        players.forEach(tuple -> 
            System.out.println(tuple.getValue() + ": " + tuple.getScore())
        );
        // 输出: player2: 90.0 和 player3: 95.0
    }
}

大致就是这样,多加练习哦,实践是检验真理的唯一标准。多动手才能有收获。

你可能感兴趣的:(redis,spring,boot,数据库)