Redis快速入门

  • 数据结构特点:Redis采用键值对(key-value)存储结构,与传统关系型数据库的表结构有本质区别
  • 存储方式:
    • 基础存储:将对象字段拆分为多个键值对(如用户ID、name、age分别存储)
    • 优化存储:可将多个字段组合成JSON字符串作为value,以ID为key存储
  • 值类型多样性:value不仅支持字符串,还支持集合(有序/无序)、哈希表等复杂数据结构
  • NoSQL特性:属于非关系型数据库,没有表结构和约束的概念

关系型与非关系型的差异

数据结构

  • 关系型数据库:
    • 结构化特征: 数据必须遵循预定义的表结构和约束条件
    • 约束示例: 主键约束(ID)、唯一约束(name)、无符号约束(age)、数据类型约束(bigint(20), varchar(32), int(3))
    • 修改限制: 数据量大时修改表结构可能导致锁表,影响业务逻辑
  • NoSQL数据库:
    • 非结构化特征: 数据结构约束松散,常见四种类型:
      • 键值型(如Redis):key可自定义,value类型灵活
      • 文档型(如MongoDB):JSON格式存储,字段可任意增减
      • 图型(如Neo4j):数据作为节点,维护节点间关系
      • 列型(如HBase):宽列存储
    • 修改灵活性: 数据结构可随时调整,影响较小

数据关联

  • 关系型数据库:
    • 关联机制: 通过外键建立表间关系(如订单表关联用户表和商品表)
    • 优势: 节省存储空间,数据库自动维护引用完整性
    • 限制: 删除被引用数据时会受到约束
  • NoSQL数据库:
    • 无关联特性: 数据间无直接关联维护
    • 替代方案: 使用JSON嵌套方式描述关系(如用户文档内嵌订单数组)
    • 缺点: 可能导致数据冗余,关联需程序员手动维护

查询方式

  • 关系型数据库:
    • 标准语法: 统一SQL语法(如SELECT id, name, age FROM tb_user WHERE id = 1)
    • 优势: 语法通用,适用于所有关系型数据库
  • NoSQL数据库:
    • 多样性:
    • 特点: 语法简单但不统一,不同数据库需学习不同语法

事务

  • 关系型数据库:
    • ACID特性: 原子性、一致性、隔离性、持久性
    • 实现: 所有关系型数据库都支持完整ACID
  • NoSQL数据库:
    • BASE理论: 基本可用、软状态、最终一致性
    • 限制: 无法完全保证ACID,适合对一致性要求不高的场景

数据库选择建议

  • 关系型适用场景:
    • 数据结构固定
    • 对数据安全性和一致性要求高(如订单系统)
  • NoSQL适用场景:
    • 数据结构灵活多变
    • 高性能查询需求
    • 对一致性要求不高
  • 最佳实践: 两者结合使用,关键数据用关系型,高频查询数据用NoSQL缓存

Redis的特征

1)键值型

  • 数据结构:类似词典的键值对存储方式,通过key查找value
  • 数据类型:支持字符串、集合(有序/无序)、哈希等近十种数据结构
  • 操作特点:简单易用,功能丰富能满足多样化业务需求

2)单线程

  • 执行机制:命令串行执行(Redis 6.0+仅网络请求处理部分多线程)
  • 线程安全:命令具备原子性,不会出现执行中途被其他命令打断的情况
  • 性能表现:单线程架构下仍保持低延迟和高速度

3)低延迟和速度快

  • 核心原因:
    • 基于内存:数据存储在内存而非磁盘,读写速度比磁盘高多个数量级
    • IO多路复用:采用高效网络通信模型提升吞吐能力
    • 优秀编码:C语言编写,代码质量获业界公认(标杆级开源项目)
  • 性能对比:内存存储是Redis性能优于MySQL的最关键因素

4)数据的持久化

  • 必要性:内存数据断电易失
  • 解决方案:定期将数据从内存持久化到磁盘
  • 优势:兼顾内存的高性能和磁盘的数据安全性

5)支持主从集群和分片集群

  • 主从集群:
    • 数据备份:从节点复制主节点数据
    • 高可用:节点宕机时可快速恢复
    • 读写分离:提升查询效率
  • 分片集群:
    • 水平扩展:将数据拆分存储到不同节点
    • 容量突破:突破单机存储限制(如1TB数据分n份存储)

多语言客户端

  • 支持语言:Java、Python、C等主流编程语言
  • 生态优势:降低使用门槛,方便不同技术栈团队接入

Redis客户端

命令行客户端

  • 基本语法:redis-cli [options] [commands]
  • 常用options:
    • -h 127.0.0.1:指定连接IP(默认本机)
    • -p 6379:指定端口(默认6379)
    • -a 密码:指定访问密码
  • 交互模式:不指定commands时进入交互控制台
  • 连接测试:
    • ping命令返回pong表示连接正常
    • 密码验证两种方式:
      • 连接时直接加-a参数
      • 连接后使用auth 密码命令验证

图形化客户端

开源

Redis命令

笔记内容5中存在基本的命令介绍

SortedSet

  • 数据结构特性:
    • 可排序的set集合,类似Java中的TreeSet但底层实现不同
    • 每个元素带有score属性用于排序
    • 底层实现:跳表(SkipList)+哈希表
  • 核心特点:
    • 可排序性:基于score值自动排序
    • 元素唯一性:保证元素不重复
    • 高效查询:哈希表保证O(1)查询复杂度
  • 典型应用:排行榜功能实现(如topN查询)

常用命令

  • 基础操作:
    • ZADD key score member:添加/更新元素(需指定分数)
    • ZREM key member:删除指定元素
    • ZCARD key:获取元素总数
  • 分数相关:
    • ZSCORE key member:查询元素分数
    • ZINCRBY key increment member:分数自增/减
  • 范围查询:
    • ZCOUNT key min max:统计分数区间元素数量
    • ZRANGE key min max:按排名范围查询(默认升序)
    • ZRANGEBYSCORE key min max:按分数范围查询
  • 集合运算:
    • ZDIFF/ZINTER/ZUNION:差集/交集/并集运算
  • 排序控制:
    • 默认升序排列,添加REV后缀可改为降序(如ZREVRANGE)

Redis的Java客户端

  • 官网资源: Redis官网提供多种语言客户端支持,地址为https://redis.io/clients
  • 语言覆盖: 包含ActionScript、C#、Java、Python等40+编程语言支持
  • 主流选择: Java领域推荐使用Jedis、Lettuce、Redisson三个客户端

1)Jedis

  • 命名特点: 方法名称与Redis命令完全一致(如set/get/mset等)
  • 学习成本: 命令即方法,学习成本极低
  • 线程安全: 实例线程不安全,多线程环境需使用连接池
  • 使用场景: 早期广泛使用,适合简单业务场景

2)Lettuce

  • 底层实现: 基于Netty高性能网络框架
  • 编程模式: 支持同步/异步及响应式编程
  • 线程安全: 原生支持线程安全
  • 集群支持: 完善支持哨兵模式和集群模式
  • Spring整合: Spring官方默认兼容的Redis客户端

3)Redisson

  • 核心功能: 基于Redis实现分布式Java工具
  • 数据结构: 提供分布式Map、队列、锁、信号量等
  • 应用场景: 专为解决分布式环境下的数据共享问题
  • 典型应用: 分布式锁实现的首选方案

Spring Data Redis

在 Java 生态中,Spring Data Redis 是 Spring 官方提供的一个用于简化 Redis 操作的框架,而 LettuceJedisRedisson 等是具体的 Redis 客户端实现。它们之间的关系可以总结为:


1. Spring Data Redis 的作用

Spring Data Redis 是一个抽象层,它封装了底层 Redis 客户端的操作,提供统一的 API(如 RedisTemplateReactiveRedisTemplate),开发者无需直接调用客户端的具体方法。它的核心功能包括:

  • 统一操作接口:通过 RedisTemplate 简化数据序列化、连接管理和命令调用。
  • Repository 支持:类似 Spring Data JPA,支持通过接口定义 Redis 数据访问逻辑。
  • 自动化配置:与 Spring Boot 深度集成,通过配置文件(application.yml)即可管理 Redis 连接。
  • 异常处理:将 Redis 客户端的异常转换为 Spring 的统一异常体系(如 DataAccessException)。

2. 与 Redis 客户端的关系

  • 依赖关系: Spring Data Redis 底层依赖具体的 Redis 客户端(如 Lettuce 或 Jedis)来与 Redis 服务器通信。

    • 默认客户端:Spring Boot 2.x+ 默认集成 Lettuce(因其异步和非阻塞特性)。
    • 切换客户端:可以通过排除 Lettuce 并引入 Jedis 依赖,切换为 Jedis(如旧项目需要同步阻塞模型)。
  • 抽象与实现: Spring Data Redis 定义了一套通用操作(如 ValueOperationsHashOperations),而具体命令的执行由底层客户端(Lettuce/Jedis)实现。 例如:当你调用 redisTemplate.opsForValue().set("key", "value"),底层实际是通过 Lettuce 或 Jedis 发送 SET 命令。

Jedis

快速入门

1)引入依赖:

<!--jedis-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.0</version>
</dependency>
<!--单元测试-->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>

2)建立连接

新建一个单元测试类,内容如下:

private Jedis jedis;
 
@BeforeEach
void setUp() {
    // 1.建立连接
    // jedis = new Jedis("192.168.150.101", 6379);
    jedis = JedisConnectionFactory.getJedis();
    // 2.设置密码
    jedis.auth("123321");
    // 3.选择库
    jedis.select(0);
}

3)测试:

@Test
void testString() {
    // 存入数据
    String result = jedis.set("name", "虎哥");
    System.out.println("result = " + result);
    // 获取数据
    String name = jedis.get("name");
    System.out.println("name = " + name);
}
 
@Test
void testHash() {
    // 插入hash数据
    jedis.hset("user:1", "name", "Jack");
    jedis.hset("user:1", "age", "21");
 
    // 获取
    Map<String, String> map = jedis.hgetAll("user:1");
    System.out.println(map);
}

4)释放资源

@AfterEach
void tearDown() {
    if (jedis != null) {
        jedis.close();
    }
}

连接池

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此推荐使用Jedis连接池代替Jedis的直连方式。

package com.heima.jedis.util;
 
import redis.clients.jedis.*;
 
public class JedisConnectionFactory {
 
    private static JedisPool jedisPool;
 
    static {
        // 配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(8);
        poolConfig.setMaxIdle(8);
        poolConfig.setMinIdle(0);
        poolConfig.setMaxWaitMillis(1000);
        // 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码
        jedisPool = new JedisPool(poolConfig, "192.168.150.101", 6379, 1000, "123321");
    }
 
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

SpringDataRedis

  • 模块定位: SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。
  • 官网地址: https://spring.io/projects/spring-data-redis
  • 核心特性:
    • 提供了对不同Redis客户端的整合(Lettuce和Jedis)
    • 提供了RedisTemplate统一API来操作Redis
    • 支持多种序列化方式(JDK、JSON、字符串等)
    • 支持发布订阅模型、哨兵、集群等高级特性
    • 支持响应式编程(基于Lettuce实现)
    • 实现了分布式版本的JDK集合
    • 版本迭代: 目前最新版本为2.6.0(发布于2021年12月11日),支持到2022年11月12日
    • 企业实践: 企业通常使用稍旧版本(如2.3.x或2.4.x),但各版本间差异不大

Jedis的不足

  • 数据类型限制: Jedis仅支持字符串和字节类型的数据存储,无法直接存储Java对象
  • 序列化问题: 需要手动对复杂对象进行序列化才能存储
    • 示例代码中jedis.set(“name”,“虎哥”)和jedis.hset(“user:1”,“name”,“Jack”)都只能操作字符串
  • 连接管理: 需要手动处理连接建立、认证和数据库选择
  • API组织方式: 按照Redis命令分组封装,避免方法臃肿
    • opsForValue(): 返回ValueOperations对象,封装字符串操作
    • opsForHash(): 返回HashOperations对象,封装哈希操作
    • opsForList(): 返回ListOperations对象,封装列表操作
    • opsForSet(): 返回SetOperations对象,封装集合操作
    • opsForZSet(): 返回ZSetOperations对象,封装有序集合操作
  • 设计优势: 各数据类型操作分离,职责单一,降低学习成本
  • 通用命令: RedisTemplate类本身封装了一些通用或特殊命令

快速入门

首先,新建一个maven项目,然后按照下面步骤执行:

1)引入依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.heima</groupId>
    <artifactId>redis-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--common-pool-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!--Jackson依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
</project>

2)配置Redis

spring:
  redis:
    host: 192.168.150.101
    port: 6379
    password: 123321
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 100ms

3)注入RedisTemplate

因为有了SpringBoot的自动装配,我们可以拿来就用:

@SpringBootTest
class RedisStringTests {
 
    @Autowired
    private RedisTemplate redisTemplate;
}

4)编写测试

@SpringBootTest
class RedisStringTests {
 
    @Autowired
    private RedisTemplate edisTemplate;
 
    @Test
    void testString() {
        // 写入一条String数据
        redisTemplate.opsForValue().set("name", "虎哥");
        // 获取string数据
        Object name = stringRedisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
    }
}

自定义序列化

  • 问题现象:通过RedisTemplate写入”name”:“虎哥”后,在控制台查询却显示”Jack”,修改为”rose”后查询结果变为”rose”,与预期不符。
  • 原因分析:RedisTemplate默认使用JDK序列化,会将key和value都序列化为字节形式,导致实际存储的key是序列化后的形式,而非原始字符串。
  • 缺点:
    • 可读性差
    • 内存占用较大

  • 核心组件:RedisTemplate内部有四个序列化器:
    • keySerializer:普通key的序列化
    • valueSerializer:普通value的序列化
    • hashKeySerializer:哈希key的序列化
    • hashValueSerializer:哈希value的序列化
  • 默认行为:当未显式设置时,会使用JDK的ObjectOutputStream进行序列化。

代码实现

  • 最佳实践:
    • key/hashKey使用StringRedisSerializer
    • value/hashValue使用GenericJackson2JsonRedisSerializer
  • 配置方法:通过RedisTemplate的setXXXSerializer()方法设置

我们可以自定义RedisTemplate的序列化方式,代码如下:

@Configuration
public class RedisConfig {
 
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        // 创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置连接工厂
        template.setConnectionFactory(connectionFactory);
        // 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = 
            							new GenericJackson2JsonRedisSerializer();
        // 设置Key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置Value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        // 返回
        return template;
    }
}

这里采用了JSON序列化来代替默认的JDK序列化方式。最终结果如图:

image-2

整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销

String RedisSerializer

  • 序列化方式:统一使用String序列化器,而非JSON序列化器处理value
  • 存储限制:只能存储String类型的key和value,不能直接存储Java对象
  • 内存优化:相比JSON序列化器,节省了存储类字节码的内存空间
  • 手动处理:存储Java对象时需要手动完成序列化和反序列化

image-1

示例代码

  • 默认配置:Spring提供的StringRedisTemplate类默认使用String序列化方式
  • 代码简化:省去了自定义RedisTemplate的复杂配置过程
  • 核心工具:使用ObjectMapper进行JSON序列化和反序列化
  • 操作流程:
    • 准备Java对象
    • 手动序列化为JSON字符串
    • 使用set方法写入Redis
    • 读取时获取JSON字符串
    • 手动反序列化为Java对象
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();
 
@Test
void testSaveUser() throws JsonProcessingException {
    // 创建对象
    User user = new User("虎哥", 21);
    // 手动序列化
    String json = mapper.writeValueAsString(user);
    // 写入数据
    stringRedisTemplate.opsForValue().set("user:200", json);
 
    // 获取数据
    String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
    // 手动反序列化
    User user1 = mapper.readValue(jsonUser, User.class);
    System.out.println("user1 = " + user1);
}
 

总结

  • 方案对比:
    • 自定义RedisTemplate:
      • 优点:自动处理序列化/反序列化
      • 缺点:占用额外内存存储类字节码
    • StringRedisTemplate:
      • 优点:节省内存,无需复杂配置
      • 缺点:需手动处理序列化/反序列化
  • 推荐方案:StringRedisTemplate配合工具类封装JSON处理
  • 内存优化:String序列化方案存储的数据更简洁,不包含类元信息

Hash操作

  • 操作获取方式:在RedisTemplate中,所有操作都通过opsFor前缀方法获取,如opsForValue()获取字符串操作,opsForHash()获取哈希操作。
  • 方法命名规则:Spring中不以Redis命令名作为方法名,而是采用更贴近Java习惯的命名,如哈希操作使用put而非hset,类似于Java的HashMap操作。
  • 基本操作:
    • 单个字段操作:使用put(key, field, value)方法存储单个字段值,如存储用户信息:put(“user:400”, “name”, “虎哥”)。
    • 批量操作:使用putAll(key, map)方法存储多个字段值,类似于Redis的hmset命令。
    • 取值操作:
      • get(key, field)获取单个字段值
      • entries(key)获取所有字段值对,返回Map
      • keys(key)获取所有字段名
      • values(key)获取所有字段值
  • 其他API:
    • delete(key, fields):删除指定字段
    • hasKey(key, field):判断字段是否存在
    • increment(key, field, delta):对数值字段进行增减操作
    • size(key):获取字段数量

序列化

  • 对象存储:
    • 使用ObjectMapper手动序列化对象为JSON字符串
    • 通过stringRedisTemplate.opsForValue().set()存储序列化后的字符串
  • 对象读取:
    • 从Redis获取JSON字符串
    • 使用ObjectMapper反序列化为Java对象