Redis
基础:
(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
由图可见关系:
Key 的结构设计(很重要)
层级结构:Redis 的 key 允许由多个单词组成层级结构。
分隔符:多个单词之间使用冒号
:隔开。推荐格式
1
项目名:业务名:类型:id
- 注:该格式并非强制固定,可根据实际需求灵活删除或添加词条。
命名示例
假设项目名称为 heima,包含 user 和 product 两种数据类型,定义方式如下:
- User 相关的 key:
heima:user:1 - Product 相关的 key:
heima: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
- 功能:返回一段角标范围内的所有元素。
- 图示对应:图下方的花括号标注了
A和B两个元素,下方文字说明为LRANGE key 1, 2,意指获取索引 1 到 2 的元素。
- BLPOP 和 BRPOP
- 功能:与 LPOP 和 RPOP 类似,区别在于当列表中没有元素时,它们会 等待指定时间,而不是直接返回 nil。这通常用于实现阻塞队列。
图示解析
中间的链表结构展示了数据的存储形态:
- C <-> A <-> B:这是一个双向链表结构。
- LPUSH/LPOP:操作链表的头部(Left)。
- RPUSH/RPOP:操作链表的尾部(Right)。
- LRANGE:通过索引位置截取链表中的一段数据。
1. 模拟栈 (Stack)
- 核心特征:入口和出口在同一边。
- 实现方式:通常使用
LPUSH+LPOP(或RPUSH+RPOP),即从同一端进行推入和弹出操作,遵循“后进先出”原则。
2. 模拟队列 (Queue)
- 核心特征:入口和出口在不同边。
- 实现方式:通常使用
LPUSH+RPOP(或RPUSH+LPOP),即一端进另一端出,遵循“先进先出”原则。
3. 模拟阻塞队列 (Blocking Queue)
- 核心特征
- 入口和出口在不同边。
- 出队时采用
BLPOP或BRPOP。
- 说明:这是普通队列的升级版。当队列为空时,消费者线程会阻塞等待,直到有新数据进入或超时,常用于消息队列场景。
左头右尾
集合操作命令(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 | public class JedisDemo { |
Lettuce
Spring Data Redis
1.导入Spring Data Redis的坐标。
2.配置Redis的数据源
3.编写配置类,创建RedisTemplate对象(可以直接在创建项目时引入这个依赖)
4.在配置文件中配置Redis的信息
5.通过对象操作Redis
RedisTemplate序列化的方案:
方案一:自定义 RedisTemplate
- 自定义 RedisTemplate
- 创建一个配置类,声明并返回一个自定义的
RedisTemplateBean。
- 创建一个配置类,声明并返回一个自定义的
- 修改序列化器为 GenericJackson2JsonRedisSerializer
- 将
RedisTemplate的 Key 和 Value 的序列化器设置为GenericJackson2JsonRedisSerializer。这样在存取数据时,Spring 会自动处理 Java 对象与 JSON 之间的转换。
- 将
方案二:使用 StringRedisTemplate
- 使用 StringRedisTemplate
- 直接注入或使用 Spring Boot 默认提供的
StringRedisTemplate,它专门用于处理字符串类型的键值对。
- 直接注入或使用 Spring Boot 默认提供的
- 写入 Redis 时,手动把对象序列化为 JSON
- 在调用
set方法前,使用 JSON 工具(如 Jackson、Fastjson)将 Java 对象转换为 JSON 字符串。
- 在调用
- 读取 Redis 时,手动把读取到的 JSON 反序列化为对象
- 从 Redis 获取到 JSON 字符串后,再使用 JSON 工具将其解析还原为对应的 Java 对象。
这两种方案的核心区别在于:方案一由框架自动完成序列化/反序列化,代码更简洁;方案二则需要开发者手动控制序列化和反序列化过程,灵活性更高但代码量稍多且第二种方案的所占用内存较小。
补充小知识点:
序列化:将各种类型的内存中的对象转化为可传输可存储的格式如JSON,XML等
反序列化就上将序列化的过程逆转
下图包含了序列化的步骤!!!!!!
步骤如下图
测试用例:String类型
其他类型类比就行不做演示
1 |
|
通用方法演示:
进阶:
SQL和NoSQL对比
核心对比表
| 维度 | SQL (关系型) | NoSQL (非关系型) |
|---|---|---|
| 数据结构 | 结构化 (Structured) | 非结构化 |
| 数据关联 | 关联的 (Relational) | 无关联的 |
| 查询方式 | SQL查询 | 非SQL |
| 事务特性 | ACID | BASE |
| 存储方式 | 磁盘 | 内存 |
| 扩展性 | 垂直 | 水平 |
| 使用场景 | 1. 数据结构固定2. 相关业务对数据安全性、一致性要求较高 | 1. 数据结构不固定2. 对一致性、安全性要求不高3. 对性能要求高 |
NoSQL 的四种主要类型
图片右侧的气泡框中列出了 NoSQL 数据库的四大分类及其代表产品:
- 键值类型:Redis
- 文档类型:MongoDB
- 列类型:HBase
- Graph (图) 类型:Neo4j
💡 补充说明
- ACID vs BASE:这是两者在事务处理上的根本差异。SQL 遵循 ACID 原则(原子性、一致性、隔离性、持久性),强调强一致性;而 NoSQL 通常遵循 BASE 理论(基本可用、软状态、最终一致性),为了高性能和扩展性牺牲了部分强一致性。
- 存储方式:虽然图中将 NoSQL 归纳为“内存”,但这是一种概括性的说法。实际上,像 MongoDB 和 HBase 也是基于磁盘存储的,只有 Redis 是典型的纯内存数据库。
- 扩展性:SQL 通常通过升级单机硬件(垂直扩展)来提升性能,有上限;NoSQL 设计之初就支持分布式,可以通过增加服务器数量(水平扩展)来线性提升性能。
基于Redis实现共享Session登录
一些使用细节:
- 注意选择合适的数据结构
- 选择合适的key
- 选择合适的存储粒度
拦截器的优化
问题:在原有设计上若访问的不是登录的请求,则无法刷新token。
解决方案:再加一层拦截器,拦截一切请求,每次拦截放行后都刷新一次token,在第二层拦截器中拦截的登录请求做校验登录操作
缓存
定义:缓存就是数据交换的缓冲区(称作 Cache [kæʃ]),是存贮数据的临时地方,一般读写性能较高。
缓存的作用
- 降低后端负载
- 提高读写效率,降低响应时间
缓存的成本
- 数据一致性成本
- 代码维护成本
- 运维成本
以上内容概括了缓存技术的基本概念及其在实际应用中的优缺点。
案例示例:
商户查询缓存:
缓存更新策略:
| 内存淘汰 | 超时剔除 | 主动更新 | |
|---|---|---|---|
| 说明 | 不用自己维护,利用Redis的内存淘汰机制,当内存不足时自动淘汰部分数据。下次查询时更新缓存。 | 给缓存数据添加TTL时间,到期后自动删除缓存。下次查询时更新缓存。 | 编写业务逻辑,在修改数据库的同时,更新缓存。 |
| 一致性 | 差 | 一般 | 好 |
| 维护成本 | 无 | 低 | 高 |
业务场景:
- 低一致性需求: 使用内存淘汰机制。例如店铺类型的查询缓存
- 高一致性需求: 主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存
一般选用主动更新
线程安全问题:
如图所示,右侧线程安全问题概率低,所以一般选用先操作数据库,再删缓存
读操作中:命中缓存直接返回,没有命中则查数据库后写入缓存,设定超时时间
写操作中:先更新数据库后删除缓存。
要确保数据库与缓存操作的原子性
重难点:
缓存穿透
缓存穿透是客户端请求的数据在缓存和数据库中都不存在,这样缓存永远不会生效,请求全部打在了数据库上。
解决方案:
缓存空对象
- 优点: 实现简单,维护方便
- 缺点:
- 额外的内存消耗
- 可能造成短期的不一致
布隆过滤
- 优点: 内存占用较少,没有多余key
- 缺点:
- 实现复杂
- 存在误判可能
增强id的复杂度,避免被猜测id规律
做好数据的基础格式校验
加强用户权限校验
案例:解决商铺查询中缓存穿透问题(缓存null值)
缓存雪崩:
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
- 给不同的Key的TTL添加随机值(较为简单)
- 利用Redis集群提高服务的可用性
- 给缓存业务添加降级限流策略
- 给业务添加多级缓存
缓存击穿:
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
解决方案:
- 互斥锁
- 逻辑过期
两者优劣势:
| 解决方案 | 优点 | 缺点 |
|---|---|---|
| 互斥锁 | • 没有额外的内存消耗• 保证一致性• 实现简单 | • 线程需要等待,性能受影响• 可能有死锁风险 |
| 逻辑过期 | • 线程无需等待,性能较好 | • 不保证一致性• 有额外内存消耗• 实现复杂 |
全局唯一id生成策略:
- UUID
- Redis自增
- snowflake算法
- 数据库自增(这里的数据库自增指的是单独在数据库中有一张表放的是唯一id,其他表对他进行关联)
超卖问题解决方案:
加锁:
悲观锁
- 核心思想:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。
- 典型示例:
Synchronized、Lock都属于悲观锁。
乐观锁
- 核心思想:认为线程安全问题不一定会发生,因此不加锁,只是在更新数据时去判断有没有其它线程对数据做了修改。
- 处理逻辑
- 如果没有修改则认为是安全的,自己才更新数据。
- 如果已经被其它线程修改说明发生了安全问题,此时可以重试或异常。
这两种机制分别适用于不同的业务场景:悲观锁适合写操作多、竞争激烈的环境;而乐观锁(通常基于 CAS 或版本号机制)适合读多写少、冲突较少的场景。
cas方法
核心就是查询时比较之前的数据有没有修改过常见有两种,一个是比较版本号,另一个是直接比较要扣减的对象是否相等
分布式锁:
分布式锁: 满足分布式系统或集群模式下多进程可见并且互斥的锁。
| MySQL | Redis | Zookeeper | |
|---|---|---|---|
| 互斥 | 利用mysql本身的互斥锁机制 | 利用setnx这样的互斥命令 | 利用节点的唯一性和有序性实现互斥 |
| 高可用 | 好 | 好 | 好 |
| 高性能 | 一般 | 好 | 一般 |
| 安全性 | 断开连接,自动释放锁 | 利用锁超时时间,到期释放 | 临时节点,断开连接自动释放 |
实现分布式锁时需要实现的两个基本方法
- 获取锁
核心要求:互斥(确保只有一个线程能获取锁)
技术实现示例
(以 Redis 命令为例):
1
2# 添加锁,NX是互斥、EX是设置超时时间
SET lock thread1 NX EX 10
- 释放锁
两种方式
- 手动释放;
- 超时释放(获取锁时添加一个超时时间,避免锁长期占用)。
技术实现示例
(以 Redis 命令为例):
1
2# 释放锁,删除即可
DEL key
特性:
- 利用 set nx 满足互斥性
- 确保同一时刻只有一个客户端能持有锁。
- 利用 set ex 保证故障时锁依然能释放,避免死锁,提高安全性
- 设置过期时间(expire),防止因客户端崩溃等原因导致锁无法释放。
- 利用 Redis 集群保证高可用和高并发特性
- 通过集群部署提升系统的稳定性和处理能力。
Redis Lua 脚本功能与语法
核心概念
- 原子性保证: Redis 提供了 Lua 脚本功能,允许在一个脚本中编写多条 Redis 命令,从而确保这些命令在执行时的原子性。
- 语言基础: Lua 是一种编程语言,基本语法可参考官方教程(如 runoob.com)。
调用函数
Redis 提供了专门的调用函数来执行命令。
语法格式:
1
redis.call('命令名称', 'key', '其它参数', ...)
代码示例
单条命令执行
目标: 执行
set name jackLua 脚本:
1
2# 执行 set name jack
redis.call('set', 'name', 'jack')
多条命令组合执行
目标: 先执行
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 脚本实现 的信息如下:
释放锁的业务流程
为了确保安全释放锁(防止误删其他线程的锁),需要遵循以下逻辑步骤:
- 获取标示: 获取当前锁中存储的线程标示。
- 判断一致性: 将获取到的标示与指定的标示(即当前持有锁的线程标示)进行比对。
- 执行释放:
- 如果两者 一致,则执行释放操作(删除 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 |
|
1 | // 定义一个静态常量,用于封装 Redis Lua 脚本对象。 |