基础:

(windows版本)

基于内存存储key-value的一个数据库

Nosql:非关系型数据库 sql:关系型数据库

占用默认端口:6379

redis服务启动命令:redis-serve.exe redis.windows.conf

redis客户端连接命令:redis-cli.exe -h 连接地址 -p 端口号

redis没有用户概念。默认无密码,可以在配置文件中修改redis.windows.conf

特征:

1. Redis 基本介绍

  • 诞生时间:2009年
  • 全称Remote Dictionary Server(远程字典服务器)
  • 定义:一个基于内存的键值型 NoSQL 数据库。

2. 核心特征

  • 数据类型丰富:属于键值(key-value)型数据库,但 value 支持多种不同的数据结构,功能强大。

  • 单线程模型:采用单线程设计,每个命令具备原子性(即操作不会被中断)。

  • 高性能与低延迟

    :速度极快,主要得益于以下三点:

    • 基于内存存储
    • IO多路复用技术
    • 良好的编码实现
  • 数据持久化:虽然是内存数据库,但也支持将数据保存到磁盘(持久化)。

  • 高可用与扩展性:支持主从集群和分片集群模式。

  • 多语言支持:提供多种编程语言的客户端支持。

redis常用的数据类型:

key-value形式存储: key为字符串形式,value由以下组成

字符串 string 常用

哈希 hash 存储对象

列表 list

集合 set

有序集合sorted set

由图可见关系:

image-20260514192546950 image-20260514193729488

Key 的结构设计(很重要)

  • 层级结构:Redis 的 key 允许由多个单词组成层级结构。

  • 分隔符:多个单词之间使用冒号 : 隔开。

  • 推荐格式

    1
    项目名:业务名:类型:id
    • :该格式并非强制固定,可根据实际需求灵活删除或添加词条。

命名示例

假设项目名称为 heima,包含 userproduct 两种数据类型,定义方式如下:

  • User 相关的 keyheima:user:1
  • Product 相关的 keyheima:product:1

复杂对象的存储(Java 对象)

如果 Value 是一个 Java 对象(例如 User 对象),通常的处理方式是将其序列化为 JSON 字符串后进行存储。

存储示例表

KEY VALUE (JSON格式)
heima:user:1 {"id":1, "name": "Jack", "age": 21}
heima:product:1 {"id":1, "name": "小米11", "price": 4999}

Redis常用命令:

String字符

String类型的三种格式:

int float 字符串

常见命令

  • SET:添加或者修改已经存在的一个 String 类型的键值对。
  • GET:根据 key 获取 String 类型的 value。
  • MSET:批量添加多个 String 类型的键值对。
  • MGET:根据多个 key 获取多个 String 类型的 value。
  • INCR:让一个整型的 key 自增 1。
  • INCRBY:让一个整型的 key 自增并指定步长。
    • 示例incrby num 2 让 num 值自增 2。
  • INCRBYFLOAT:让一个浮点类型的数字自增并指定步长。
  • SETNX:添加一个 String 类型的键值对,前提是这个 key 不存在,否则不执行(Set if Not eXists)。
  • SETEX:添加一个 String 类型的键值对,并且指定有效期。

哈希操作命令:

  • HSET key field value:添加或者修改 hash 类型 key 的 field 的值。
  • HGET key field:获取一个 hash 类型 key 的 field 的值。
  • HMSET:批量添加多个 hash 类型 key 的 field 的值。
  • HMGET:批量获取多个 hash 类型 key 的 field 的值。
  • HGETALL:获取一个 hash 类型的 key 中的所有的 field 和 value。
  • HKEYS:获取一个 hash 类型的 key 中的所有的 field。
  • HVALS:获取一个 hash 类型的 key 中的所有的 value。
  • HINCRBY:让一个 hash 类型 key 的字段值自增并指定步长。
  • HSETNX:添加一个 hash 类型的 key 的 field 值,前提是这个 field 不存在,否则不执行。

以上即为 Redis Hash 数据类型的常用操作命令及功能说明。

List 的常见命令

  • LPUSH key element …
    • 功能:向列表 左侧 插入一个或多个元素。
    • 图示对应:图中左上角的绿色箭头指向列表左端,表示从左侧推入数据。
  • LPOP key
    • 功能:移除并返回列表 左侧 的第一个元素;如果列表为空,则返回 nil。
    • 图示对应:图中左下角的红色箭头从列表左端指出,表示从左侧弹出数据。
  • RPUSH key element …
    • 功能:向列表 右侧 插入一个或多个元素。
    • 图示对应:图中右上角的绿色箭头指向列表右端,表示从右侧推入数据。
  • RPOP key
    • 功能:移除并返回列表 右侧 的第一个元素。
    • 图示对应:图中右下角的红色箭头从列表右端指出,表示从右侧弹出数据。
  • LRANGE key start end
    • 功能:返回一段角标范围内的所有元素。
    • 图示对应:图下方的花括号标注了 AB 两个元素,下方文字说明为 LRANGE key 1, 2,意指获取索引 1 到 2 的元素。
  • BLPOP 和 BRPOP
    • 功能:与 LPOP 和 RPOP 类似,区别在于当列表中没有元素时,它们会 等待指定时间,而不是直接返回 nil。这通常用于实现阻塞队列。

图示解析

中间的链表结构展示了数据的存储形态:

  • C <-> A <-> B:这是一个双向链表结构。
  • LPUSH/LPOP:操作链表的头部(Left)。
  • RPUSH/RPOP:操作链表的尾部(Right)。
  • LRANGE:通过索引位置截取链表中的一段数据。
image-20260609141355649

1. 模拟栈 (Stack)

  • 核心特征:入口和出口在同一边。
  • 实现方式:通常使用 LPUSH + LPOP (或 RPUSH + RPOP),即从同一端进行推入和弹出操作,遵循“后进先出”原则。

2. 模拟队列 (Queue)

  • 核心特征:入口和出口在不同边。
  • 实现方式:通常使用 LPUSH + RPOP (或 RPUSH + LPOP),即一端进另一端出,遵循“先进先出”原则。

3. 模拟阻塞队列 (Blocking Queue)

  • 核心特征
    • 入口和出口在不同边。
    • 出队时采用 BLPOPBRPOP
  • 说明:这是普通队列的升级版。当队列为空时,消费者线程会阻塞等待,直到有新数据进入或超时,常用于消息队列场景。

左头右尾

集合操作命令(set):

Set 类型概述

  • 定义与类比:Redis 的 Set 结构与 Java 中的 HashSet 类似,可以将其理解为一个 value 为 null 的 HashMap
  • 底层原理:由于底层也是一个 hash 表,因此它具备与 HashSet 相似的特征。

核心特征

  • 无序:集合中的元素没有固定的排列顺序。
  • 元素不可重复:集合内不允许存在相同的元素(自动去重)。
  • 查找快:基于 Hash 表实现,查询效率高。
  • 支持集合运算:支持交集、并集、差集等数学集合操作功能。

常见命令:

说明:添加时先添加的是一个集合名称,后面跟上成员

  • SADD key member …

    • 功能:向 set 中添加一个或多个元素。
  • SREM key member …

    • 功能:移除 set 中的指定元素。
  • SCARD key

    • 功能:返回 set 中元素的个数(基数)。
  • SISMEMBER key member

    • 功能:判断一个元素是否存在于 set 中(返回 1 或 0)。
  • SMEMBERS key

    • 功能:获取 set 中的所有元素。
  • SINTER key1 key2 …

    • 功能:求 key1 与 key2 等多个集合的 交集
  • SDIFF key1 key2 …

    • 功能:求 key1 与 key2 等多个集合的 差集(即属于 key1 但不属于其他集合的元素)。
  • SUNION key1 key2 …

    • 功能:求 key1 和 key2(以及更多集合)的 并集

有序操作集合(SortedSet):

特征:

Sorted Set 概述
  • 定义:Redis 的 Sorted Set 是一个可排序的 set 集合。
  • 类比与区别:虽然与 Java 中的 TreeSet 有些类似,但两者的底层数据结构差别很大。
  • 核心机制:Sorted Set 中的每一个元素都带有一个 score(分数)属性,系统可以基于 score 属性对元素进行排序。
  • 底层实现:采用 跳表(SkipList) + Hash 表 的组合结构。
Sorted Set 的特性
  • 可排序:能够根据 score 值进行有序排列。
  • 元素不重复:集合内的成员(member)是唯一的。
  • 查询速度快:得益于底层的高效数据结构。

典型应用场景

由于具备可排序的特性,Sorted Set 经常被用来实现 排行榜 等功能。

命令:

  • ZADD key score member
    • 功能:添加一个或多个元素到 sorted set,如果已经存在则更新其 score 值。
  • ZREM key member
    • 功能:删除 sorted set 中的一个指定元素。
  • ZSCORE key member
    • 功能:获取 sorted set 中的指定元素的 score 值。
  • ZRANK key member
    • 功能:获取 sorted set 中的指定元素的排名(从低到高)。
  • ZCARD key
    • 功能:获取 sorted set 中的元素个数。
  • ZCOUNT key min max
    • 功能:统计 score 值在给定范围内的所有元素的个数。
  • ZINCRBY key increment member
    • 功能:让 sorted set 中的指定元素自增,步长为指定的 increment 值。
  • ZRANGE key min max
    • 功能:按照 score 排序后,获取指定排名范围内的元素。
  • ZRANGEBYSCORE key min max
    • 功能:按照 score 排序后,获取指定 score 范围内的元素。
  • ZDIFF、ZINTER、ZUNION
    • 功能:求差集、交集、并集。

通用命令:

定义:Redis 的通用命令是不分数据类型的,都可以使用的命令。

具体命令列表

命令 说明
KEYS pattern 查找所有符合给定模式 (pattern) 的 key
EXISTS key 检查给定 key 是否存在
TYPE key 返回 key 所储存的值的类型
DEL key 该命令用于在 key 存在时删除 key
EXPIRE key 给一个key设置有效期,有效期到期时会自动删除key
TTL 查看key的有效时期,无则返回-2

可通过help command来查看一个命令的具体用法

Redis的java客户端

Jedis

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class JedisDemo {
private Jedis jedis;
@BeforeEach
void set(){
// 创建jedis实例
jedis = new Jedis("192.168.100.128", 6379);
//设置密码
jedis.auth("123456");
//选择库
jedis.select(0);
}

@Test
void test(){
String result = jedis.set("name", "icecat");
System.out.println(result);
System.out.println(jedis.get("name"));
}

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

Lettuce

Spring Data Redis

1.导入Spring Data Redis的坐标。

2.配置Redis的数据源

3.编写配置类,创建RedisTemplate对象(可以直接在创建项目时引入这个依赖)

4.在配置文件中配置Redis的信息

5.通过对象操作Redis

RedisTemplate序列化的方案:

方案一:自定义 RedisTemplate
  1. 自定义 RedisTemplate
    • 创建一个配置类,声明并返回一个自定义的 RedisTemplate Bean。
  2. 修改序列化器为 GenericJackson2JsonRedisSerializer
    • RedisTemplate 的 Key 和 Value 的序列化器设置为 GenericJackson2JsonRedisSerializer。这样在存取数据时,Spring 会自动处理 Java 对象与 JSON 之间的转换。
方案二:使用 StringRedisTemplate
  1. 使用 StringRedisTemplate
    • 直接注入或使用 Spring Boot 默认提供的 StringRedisTemplate,它专门用于处理字符串类型的键值对。
  2. 写入 Redis 时,手动把对象序列化为 JSON
    • 在调用 set 方法前,使用 JSON 工具(如 Jackson、Fastjson)将 Java 对象转换为 JSON 字符串。
  3. 读取 Redis 时,手动把读取到的 JSON 反序列化为对象
    • 从 Redis 获取到 JSON 字符串后,再使用 JSON 工具将其解析还原为对应的 Java 对象。

这两种方案的核心区别在于:方案一由框架自动完成序列化/反序列化,代码更简洁;方案二则需要开发者手动控制序列化和反序列化过程,灵活性更高但代码量稍多且第二种方案的所占用内存较小。

补充小知识点:

序列化:将各种类型的内存中的对象转化为可传输可存储的格式如JSON,XML等

​ 反序列化就上将序列化的过程逆转

下图包含了序列化的步骤!!!!!!

步骤如下图

image-20260516170159952 image-20260516170235535

测试用例:String类型

其他类型类比就行不做演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootTest
public class SpringDataRedisTest {

@Autowired
private RedisTemplate redisTemplate;

@Test
public void test1(){
System.out.println(redisTemplate);
ValueOperations valueOperations = redisTemplate.opsForValue();//获取操作字符串的value对象
HashOperations hashOperations = redisTemplate.opsForHash();//获取操作hash的value对象
ListOperations listOperations = redisTemplate.opsForList();//获取操作list的value对象
SetOperations setOperations = redisTemplate.opsForSet();//获取操作set的value对象
ZSetOperations zSetOperations = redisTemplate.opsForZSet();//获取操作zset(有序集合)的value对象
}

通用方法演示:

image-20260516175656715

进阶:

SQL和NoSQL对比

核心对比表

维度 SQL (关系型) NoSQL (非关系型)
数据结构 结构化 (Structured) 非结构化
数据关联 关联的 (Relational) 无关联的
查询方式 SQL查询 非SQL
事务特性 ACID BASE
存储方式 磁盘 内存
扩展性 垂直 水平
使用场景 1. 数据结构固定2. 相关业务对数据安全性、一致性要求较高 1. 数据结构不固定2. 对一致性、安全性要求不高3. 对性能要求高

NoSQL 的四种主要类型

图片右侧的气泡框中列出了 NoSQL 数据库的四大分类及其代表产品:

  1. 键值类型:Redis
  2. 文档类型:MongoDB
  3. 列类型:HBase
  4. Graph (图) 类型:Neo4j

💡 补充说明

  • ACID vs BASE:这是两者在事务处理上的根本差异。SQL 遵循 ACID 原则(原子性、一致性、隔离性、持久性),强调强一致性;而 NoSQL 通常遵循 BASE 理论(基本可用、软状态、最终一致性),为了高性能和扩展性牺牲了部分强一致性。
  • 存储方式:虽然图中将 NoSQL 归纳为“内存”,但这是一种概括性的说法。实际上,像 MongoDB 和 HBase 也是基于磁盘存储的,只有 Redis 是典型的纯内存数据库。
  • 扩展性:SQL 通常通过升级单机硬件(垂直扩展)来提升性能,有上限;NoSQL 设计之初就支持分布式,可以通过增加服务器数量(水平扩展)来线性提升性能。

基于Redis实现共享Session登录

image-20260610202710440

一些使用细节:

  1. 注意选择合适的数据结构
  2. 选择合适的key
  3. 选择合适的存储粒度

拦截器的优化

问题:在原有设计上若访问的不是登录的请求,则无法刷新token。

解决方案:再加一层拦截器,拦截一切请求,每次拦截放行后都刷新一次token,在第二层拦截器中拦截的登录请求做校验登录操作

image-20260610204320021

缓存

定义:缓存就是数据交换的缓冲区(称作 Cache [kæʃ]),是存贮数据的临时地方,一般读写性能较高。

缓存的作用

  • 降低后端负载
  • 提高读写效率,降低响应时间

缓存的成本

  • 数据一致性成本
  • 代码维护成本
  • 运维成本

以上内容概括了缓存技术的基本概念及其在实际应用中的优缺点。

案例示例:

商户查询缓存:

缓存更新策略:

内存淘汰 超时剔除 主动更新
说明 不用自己维护,利用Redis的内存淘汰机制,当内存不足时自动淘汰部分数据。下次查询时更新缓存。 给缓存数据添加TTL时间,到期后自动删除缓存。下次查询时更新缓存。 编写业务逻辑,在修改数据库的同时,更新缓存。
一致性 一般
维护成本

业务场景:

  • 低一致性需求: 使用内存淘汰机制。例如店铺类型的查询缓存
  • 高一致性需求: 主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存

一般选用主动更新

线程安全问题:

如图所示,右侧线程安全问题概率低,所以一般选用先操作数据库,再删缓存

image-20260611130319651

读操作中:命中缓存直接返回,没有命中则查数据库后写入缓存,设定超时时间

写操作中:先更新数据库后删除缓存。

​ 要确保数据库与缓存操作的原子性

重难点:

缓存穿透

缓存穿透是客户端请求的数据在缓存和数据库中都不存在,这样缓存永远不会生效,请求全部打在了数据库上。

解决方案:

缓存空对象

  • 优点: 实现简单,维护方便
  • 缺点:
    • 额外的内存消耗
    • 可能造成短期的不一致

布隆过滤

  • 优点: 内存占用较少,没有多余key
  • 缺点:
    • 实现复杂
    • 存在误判可能

增强id的复杂度,避免被猜测id规律

做好数据的基础格式校验

加强用户权限校验

案例:解决商铺查询中缓存穿透问题(缓存null值)

image-20260611152056269

缓存雪崩:

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

解决方案:

  • 给不同的Key的TTL添加随机值(较为简单)
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

缓存击穿:

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

解决方案:

  • 互斥锁
image-20260611155436254
  • 逻辑过期
image-20260611160818788

两者优劣势:

解决方案 优点 缺点
互斥锁 • 没有额外的内存消耗• 保证一致性• 实现简单 • 线程需要等待,性能受影响• 可能有死锁风险
逻辑过期 • 线程无需等待,性能较好 • 不保证一致性• 有额外内存消耗• 实现复杂

全局唯一id生成策略:

  • UUID
  • Redis自增
  • snowflake算法
  • 数据库自增(这里的数据库自增指的是单独在数据库中有一张表放的是唯一id,其他表对他进行关联)

超卖问题解决方案:

加锁:

悲观锁

  • 核心思想:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。
  • 典型示例SynchronizedLock 都属于悲观锁。

乐观锁

  • 核心思想:认为线程安全问题不一定会发生,因此不加锁,只是在更新数据时去判断有没有其它线程对数据做了修改。
  • 处理逻辑
    • 如果没有修改则认为是安全的,自己才更新数据。
    • 如果已经被其它线程修改说明发生了安全问题,此时可以重试或异常。

这两种机制分别适用于不同的业务场景:悲观锁适合写操作多、竞争激烈的环境;而乐观锁(通常基于 CAS 或版本号机制)适合读多写少、冲突较少的场景。

cas方法

核心就是查询时比较之前的数据有没有修改过常见有两种,一个是比较版本号,另一个是直接比较要扣减的对象是否相等

image-20260611194849074

分布式锁:

分布式锁: 满足分布式系统或集群模式下多进程可见并且互斥的锁。

MySQL Redis Zookeeper
互斥 利用mysql本身的互斥锁机制 利用setnx这样的互斥命令 利用节点的唯一性和有序性实现互斥
高可用
高性能 一般 一般
安全性 断开连接,自动释放锁 利用锁超时时间,到期释放 临时节点,断开连接自动释放

实现分布式锁时需要实现的两个基本方法

  1. 获取锁
  • 核心要求:互斥(确保只有一个线程能获取锁)

  • 技术实现示例

    (以 Redis 命令为例):

    1
    2
    # 添加锁,NX是互斥、EX是设置超时时间
    SET lock thread1 NX EX 10
  1. 释放锁
  • 两种方式

    • 手动释放;
    • 超时释放(获取锁时添加一个超时时间,避免锁长期占用)。
  • 技术实现示例

    (以 Redis 命令为例):

    1
    2
    # 释放锁,删除即可
    DEL key

特性:

  • 利用 set nx 满足互斥性
    • 确保同一时刻只有一个客户端能持有锁。
  • 利用 set ex 保证故障时锁依然能释放,避免死锁,提高安全性
    • 设置过期时间(expire),防止因客户端崩溃等原因导致锁无法释放。
  • 利用 Redis 集群保证高可用和高并发特性
    • 通过集群部署提升系统的稳定性和处理能力。

Redis Lua 脚本功能与语法

核心概念

  • 原子性保证: Redis 提供了 Lua 脚本功能,允许在一个脚本中编写多条 Redis 命令,从而确保这些命令在执行时的原子性
  • 语言基础: Lua 是一种编程语言,基本语法可参考官方教程(如 runoob.com)。

调用函数

Redis 提供了专门的调用函数来执行命令。

  • 语法格式:

    1
    redis.call('命令名称', 'key', '其它参数', ...)

代码示例

  1. 单条命令执行

    • 目标: 执行 set name jack

    • Lua 脚本:

      1
      2
      # 执行 set name jack
      redis.call('set', 'name', 'jack')
  2. 多条命令组合执行

    • 目标: 先执行 set name Rose(注:图片注释写的是 Jack,但文字描述是 Rose,此处以代码逻辑为准),再执行 get name 并返回结果。

    • Lua 脚本:

      1
      2
      3
      4
      5
      6
      # 先执行 set name jack
      redis.call('set', 'name', 'jack')
      # 再执行 get name
      local name = redis.call('get', 'name')
      # 返回
      return name

根据图片内容,提取关于 Redis 分布式锁释放流程及 Lua 脚本实现 的信息如下:

释放锁的业务流程

为了确保安全释放锁(防止误删其他线程的锁),需要遵循以下逻辑步骤:

  1. 获取标示: 获取当前锁中存储的线程标示。
  2. 判断一致性: 将获取到的标示与指定的标示(即当前持有锁的线程标示)进行比对。
  3. 执行释放:
    • 如果两者 一致,则执行释放操作(删除 Key)。
    • 如果两者 不一致,则什么都不做(避免误删)。

如何实现释放锁的业务流程:

Lua 脚本实现

为了保证上述“判断”和“删除”操作的原子性,通常使用 Lua 脚本来实现。

  • 参数说明:

    • KEYS[1]:代表锁的 Key。
    • ARGV[1]:代表当前线程的标示(Value)。
  • 脚本代码:

    1
    2
    3
    4
    5
    6
    7
    8
    -- 这里的 KEYS[1] 就是锁的key,这里的ARGV[1] 就是当前线程标示
    -- 获取锁中的标示,判断是否与当前线程标示一致
    if (redis.call('GET', KEYS[1]) == ARGV[1]) then
    -- 一致,则删除锁
    return redis.call('DEL', KEYS[1])
    end
    -- 不一致,则直接返回
    return 0

该脚本通过原子性地比较并删除键值,有效防止了因网络延迟或业务超时导致的锁被错误释放的问题。

java中如何执行lua脚本

1
2
3
4
5
6
7
8
@Override
public void unlock() {
// 调用lua脚本
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义一个静态常量,用于封装 Redis Lua 脚本对象。
// DefaultRedisScript<Long> 表示该脚本执行后返回的结果类型为 Long(通常对应 Redis 的 DEL 命令返回值)
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

// 使用静态代码块进行初始化,确保在类加载时完成配置,且只执行一次
static {
// 实例化 DefaultRedisScript 对象
UNLOCK_SCRIPT = new DefaultRedisScript<>();

// 设置 Lua 脚本文件的位置
// new ClassPathResource("unlock.lua") 表示从 classpath(通常是 resources 目录)下加载名为 unlock.lua 的文件
// 这种将脚本独立为文件的方式便于维护和修改,避免硬编码在 Java 代码中
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));

// 设置脚本执行结果的预期返回类型
// 这里设置为 Long.class,因为解锁脚本通常返回删除键的数量(0 或 1),Spring Data Redis 会据此进行类型转换
UNLOCK_SCRIPT.setResultType(Long.class);
}