1. Redis 简介
Redis(Remote Dictionary Server)是一个开源的内存数据库,遵守 BSD 协议,它提供了一个高性能的键值(key-value)存储系统,常用于缓存、消息队列、会话存储等应用场景。
1.1 特征
丰富的数据类型:Redis 不仅仅支持简单的 key-value 类型的数据,还提供了 list、set、zset(有序集合)、hash 等数据结构的存储。这些数据类型可以更好地满足特定的业务需求,使得 Redis 可以用于更广泛的应用场景。
高性能的读写能力:Redis 能读的速度是 110000次/s,写的速度是 81000次/s。这种高性能主要得益于 Redis 将数据存储在内存中,从而显著提高了数据的访问速度。
原子性操作:Redis 的所有操作都是原子性的,这意味着操作要么完全执行,要么完全不执行。这种特性对于确保数据的一致性和完整性非常重要。
持久化机制:Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,以便在系统重启后能够再次加载使用。这为 Redis 提供了数据安全性,确保数据不会因为系统故障而丢失。
丰富的特性集:Redis 还支持 publish/subscribe(发布/订阅)模式、通知、key 过期等高级特性。这些特性使得 Redis 可以用于消息队列、实时数据分析等复杂的应用场景。
主从复制和高可用性:Redis 支持 master-slave 模式的数据备份,提供了数据的备份和主从复制功能,增强了数据的可用性和容错性。
支持 Lua 脚本:Redis 支持使用 Lua 脚本来编写复杂的操作,这些脚本可以在服务器端执行,提供了更多的灵活性和强大的功能。
单线程模型:尽管 Redis 是单线程的,但它通过高效的事件驱动模型来处理并发请求,确保了高性能和低延迟。
1.2 SQL 和 NOSQL 区别
Redis 属于 NOSQL 类型
- 结构化:数据库表的字段的类型即长度都是限定好的。
- SQL查询:SQL 查询语句格式是固定的无论 MySql 还是 Oracle
- ACID:原子性,一致性,隔离性,持久性
- BASE:不全部满足 ACID
- 关联:数据表中的数据可以通过外键关联在一起
2. Redis 命令行客户端
Redis 安装完成后就自带了命令行客户端:redis-cli,使用方式如下:
// 先输入
redis-cli -h 127.0.0.1 -p 6379
// 回车后输入
auth 123456
其中常见的 options 有:
-h 127.0.0.1
:指定要连接的 redis 节点的 IP 地址,默认是 127.0.0.1 (本机),也可以填写本机的 IP 地址-p 6379
:指定要连接的 redis 节点的端口,默认是 6379-a 123456
:指定 redis 的访问密码 (一般不这个,使用auth
命令来输入密码)
3. Redis 数据结构介绍
4. Redis 命令
对于这些命令 Redis 都提供了帮助文档只要在你要使用的命令前面加入 help 即可
4.1 通用命令
KEYS
查看符合模板的所有 key,不建议在生成环境设备上使用
KEYS pattern
DEL
删除一个指定的 Key
DEL key [key ...]
EXISTS
判断 Key 是否存在
EXISTS key [key ...]
EXPIRE
给一个 Key 设置有效期,有效期到了该 Key 自动删除
EXPIRE key seconds
TTL
查看一个 Key 剩余的有效期
TTL key
hlep
查看某个命令的使用方法
help del
4.2 String类型命令
4.2.1 String类型分类
- 字符串
- 整形(Int)
- 浮点型(Float)
4.2.2 常用命令
SET
添加或者修改,指定键不存在则新增,否则修改
set name jack
GET
根据 key 获取 String 类型的 Value
get name
MSET
批量添加 String 类型的键值对
mset key1 value1 key2 value2
MGET
根据多个 Key 获取多个 Value
mget key1 key2
INCR
让一个整形的 key 的值自增 1
incr age
INCRBY
让一个整形 key 自增并指定步长
incrby num 2
INCRBYFLOAT
让一个浮点类型的值自增并指定步长
incrbyfloat num 0.2
SETNX
添加一个 String 类型的键值对前提是这个 Key 不存在,否则不执行
setnx name jack
SETEX
添加一个键值对,并指定有效日期。
setex name 10 jack
4.3 Key的层级格式
在 Redis 没有类似于 MySQL 中 Table 的概念,我们如何区分不同类型 Key 呢?
比如我们需要存储商品信息和用户信息到 Redis 当中,此时他们的 Id 恰好都为 1。这样当我们要取数据时发现无法区分。此时我们可以给 Key 加上一个前缀加以区分。当然这前缀按规范要具有一定层次结构
4.4 Hash类型命令
该结构类似于 Java 的 HashMap
4.4.1 哈希类型结构
4.4.2 常见命令
HSET key field value
添加或者修改 Hash 类型,键为 key ,值的键为 field 的值为 Value
hset user name zhansan
HGET key field
根据 key 获取 Hash 类型,键为 key ,值的键为 field 的值
hget user name
HMSET
批量添加 Hash 类型 Key 的 field 的值
hset user name zhansan age 18
HMGET
批量获取 Hash 类型,键为 key 的多个 field 的值
hmget user name age
HGETALL
获取一个 Hash 类型,键为 key 所有的键值对
hgetall user
HKEYS
获取一个 Hash 类型,键为 key 所有的键
hkeys user
HVALS
获取一个 Hash 类型,键为 key 所有的值
hvals user
HINCRBY
让一个 Hash 类型的 key 的 field 的值自增长指定步长
hincrby user age 2
HSETNX
添加一个 Hash 类型的 key 的 field 的值,前提是这个 field 不存在,否则不执行
hsetnx user age 100
4.5 List类型
类似双端队列,查询慢,删除快,该数据类型可以根据方法,模拟栈,队列,阻塞队列的数据结构
4.5.1 常见命令
LPUSH key element
向列表左侧插入一个或者多个元素
lpush goods car food
RPUSH key element
向列表右侧插入一个或者多个元素
rpush goods car food
RPOP KEY [COUNT]
将列表左侧元素弹出一个或者多个,没有则返回 nil
lpop goods 2
RPOP KEY
将列表右侧元素弹出一个或者多个,没有则返回 nil
rpop goods 2
BLPOP 和 BRPOP [TimeOut]
和 LPOP 和 RPOP 类似,只不过在当列表中没有元素的时候会等待指定时间,而不是返回nil,后面该功能会被充当缓冲队列
Brpop user 10
LRANGE KEY STAR END
返回一段角标内的所有元素
4.6 Set类型
无序,不重复,查询快,类似于 C++ 的 Unordered_Set
4.6.1 常见命令
SADD key member
向指定 Key 的集合中插入一个或者多个元素
SREM key member
将指定 Key 的集合中指定元素删除
SCARD key
统计集合中的元素
SISMEMBER key member
查询某个元素是否在集合当中
SMEMBERS key:
获取指定Key的集合中的所有元素
SINTER key1 key2
获取键为 key1 和 key2 两个集合的交集
SDIFF key1 key2
获取键为 key1 和 key2 两个集合的差集
SUNION key1 key2
获取键为 key1 和 key2 两个集合的并集
4.7 SortedSet类型
可排序集合和 Java 中的 TreeSet 类似,但底层差距很大。SortedSet中的每个元素都带有一个score属性,可以基于 score 的值对集合进行排序,底层实现是 跳表 + hash表 。常被用于实现排行榜的业务。
4.7.1 常用命令
ZADD key score member
添加一个或者多个元素,score 是加入元素的分数(排序依据),member 表示要加入的元素
ZREM key member
删除指定元素
ZSCORE key member
获取指定元素的分数
ZRANK key member
获取指定元素的排名(默认升序排名,可以用 ZREVRANK 获取降序排名)
ZCARD key
获取集合中的元素数量
ZCOUNT key min max
统计 Score 值在 min 和 max 之间的元素个数
ZINCRBY key increment member
让集合指定元素 member 自增,步长为指定的 increment 的值
ZRANGE key min max
按照 Score 排序后,获取指定排名范围内的元素
ZRANGEBYSCORE key min max
按照 Score 排序后,获取指定分数范围内的元素
ZDIFF,ZINTER,ZUNION
求差集,求交集,求并集。
5. Java客户端
5.1 简介
Java的Redis客户端(如Jedis、Lettuce)通过封装Redis协议,允许Java应用与Redis服务器交互。它们提供直观的API执行数据操作,支持连接池、集群、哨兵等部署模式。其中Jedis轻量高效但线程不安全,需配合连接池使用;Lettuce基于Netty实现异步与线程安全;Redisson专注分布式服务。开发者可根据性能、线程模型和功能需求灵活选用。
5.2 Jedis
5.2.1 简介
Jedis 是一款轻量级、同步阻塞的 Java Redis 客户端,通过直连模式与 Redis 服务器通信。其 API 直接映射 Redis 原生命令(如
set()
对应SET
指令),学习成本低且稳定性强;但原生线程不安全(多线程需依赖连接池),适用于并发要求不高的传统项目或小型应用,是 Java 生态中最经典的 Redis 集成方案之一。
5.2.2 特点
优点:
Jedis 设计小巧,实现简洁,稳定性高。其最大优势在于 API 与 Redis 原生命令高度一致:方法名和参数直接对应官方文档指令。这使得开发者遇到不熟悉的操作时,无需额外学习客户端封装,直接查阅 Redis 命令文档即可快速上手,显著降低了学习成本。相比之下,某些客户端重命名 API 的做法虽意图简化,实则增加了额外的记忆负担。缺点:
Jedis 采用直连模式且非线程安全。其核心问题在于:单个Jedis
实例内的RedisOutputStream
(写流) 和RedisInputStream
(读流) 是共享的全局资源。当多个线程并发操作同一实例时,会争抢这些流资源,导致读写数据混乱(如数据错位、混合),而非 Redis 服务本身的数据安全问题。
5.2.3 Jedis 的使用
- 引用依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
- 建立连接
private Jedis jedis;
@Before // 该注解,表示在所有你创键的测试方法前先执行,这里用于建立连接
public void setUp() throws Exception {
jedis = new Jedis("192.168.195.132", 6379); //建立连接
jedis.auth("123456"); //密码
jedis.select(0); //选择库
}
- 使用 Jedis
@Test
public void testString() {
//命令名和 Redis 的一致
String s = jedis.set("name", "tom");
String name = jedis.get("name");
}
- 释放资源
@After // 该注解,表示在所有你创键的测试方法后执行,这里用于关闭连接
public void tearDown() throws Exception {
// 如果在建立连接的时候抛出了异常,那么运行到这的时就会有空指针异常的风险
if(jedis != null) {
jedis.close();
}
}
5.2.4 Jedis连接池
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗。所以建议使用 Jedis 的连接池来代替 Jedis的直连方式。
public class JedisConnectionFactory {
private static JedisPool jedisPool;
static {
//配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
//最大连接量,表示最多建立8个连接
poolConfig.setMaxTotal(8);
//最大空闲连接,表示即便没有线程访问我这个池子,也会最多预备8个连接
poolConfig.setMaxIdle(8);
//最小空闲连接,表示空闲连接存放一段时间后,会被自动释放,直到预备连接小于等于2个
poolConfig.setMinIdle(2);
//最长等待时间,表示当连接池内没有空闲连接时,线程对多等多久后放弃等待
poolConfig.setMaxWaitMillis(1000);
//建立连接
jedisPool = new JedisPool(poolConfig,
"192.168.195.132", 6379, 1000, "123456");
}
// 获取连接
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
5.3 SpringDataRedis
5.3.1 简介
- Spring Data Redis 是 Spring 生态的 Redis 集成框架,内部整合来 Jedis 和 Lettuce。
- 提供了
RedisTemplate
统一 API 来操作 Redis。- 简化数据操作(自动处理序列化/连接管理)
- 支持事务、发布订阅、集群与哨兵模式,并天然适配 Spring 事务管理,显著降低 Java 应用访问 Redis 的复杂度,适用于需与 Spring 深度整合的中大型项目。
5.3.2 SpringDataRedis的使用
**SpringBoot 已经整合 SpringDataRedis,并且实现了自动装配,使用起来非常方便,**所以下面我们基于SpringBoot 来使用 SpringDataRedis
- 引入依赖
<!--redis起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池依赖-->
<!--无论是 Jedis 还是 Luttce,都是基于 commons-pool 来实现连接池的效果 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- 连接和连接池配置
spring:
data:
redis:
host: 192.168.195.132 #连接的虚拟机的 IP 地址
port: 6379 #端口号
database: 0 #连接的数据库
password: 123456 #密码
lettuce:
pool: #连接池的配置
max-active: 8 #最大连接数
max-idle: 8 #最大空闲连接数
min-idle: 0 #最小空闲连接数
max-wait: 100ms #最大等待时间
#具体解释可以看 Jedis 配置连接池那一节
- 测试代码
@SpringBootTest
class RedisDemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void stringTest() {
redisTemplate.opsForValue().set("name", "虎哥");
Object name = redisTemplate.opsForValue().get("name");
System.out.println(name);
}
}
5.3.3 RedisTemplated的缺点
RedisTemplate 可以存储任意类型的数据,原因是在存储之前会先把存储的数据序列化字节形式,默认采用的 JDK 序列化。但是得到的结果,可读性差,并且占用了更多的内存
5.3.3 解决方式
RedisTemplate 的缺点产生原因在于他默认的序列化方式不太合适,所以我们可以更改他的序列化方式即可解决这个问题。
存在的小问题以及解决办法
- 但是这种方式还是存在一点点问题就是,他 Value 使用的是 Json 序列化器,当存入和获取的数据类型是对象是会自动序列化和反序列化,但是要实现自动反序列化,存入 Redis 的数据数据需要加一段额外的信息来记录该 Json 数据属于哪个对象,这样就造成了,内存的额外开销。
- 解决方式也很简单,就是再一次跟换 Value 的序列化方式,采用和 Key 一样的 String 序列化器,但是后果就是,存入数据类型只能是 String 类型。但是没关系,只要当我们要存入对象时,就先将他转为 Json 字符串格式
- 但是幸运的是我们不要自己再去修改,RedisTemplate 的 Value 的序列化方式了。这个别人已经写好了。Spring 提供了一个 StringRedisTemplate 类,他的 Key 和 Value 默认的序列化方式就是 String 序列化,省去了我们自己定义 RedisTemplate。
5.3.4 StringRedisTemplate
5.3.4.1 简介
为了节省空间我们并不会使用 Json 的序列化器来处理 value,而是统一的使用 String 的序列化器,要求只能存储String 类型的 key 和 value。当需要存储 Java 对象的时候,需手动完成对象序列化和反序列化。
5.3.4.2 使用步骤
至于序列化工具,使用自己喜欢的就好,我这里使用的 FastJson
- 导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
- 代码演示
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void objectTest() {
User user = new User("name", "虎哥");
//手动序列化(object -> Json)
String s = JSON.toJSONString(user);
stringRedisTemplate.opsForValue().set("user:100", s);
String s1 = stringRedisTemplate.opsForValue().get("user:100");
//手动反序列化(Json -> object)
User user1 = JSON.parseObject(s1, User.class);
System.out.println(user1);
}