
redis
Redis基础篇
1. 初识Redis
1.1 认识NoSQL
Redis是一种键值型的NoSQL数据库。
键值型:Redis中存储的数据都是以key-value对的形式存储,而value的形式多种多样,可以是字符串、数值、甚至json。
NoSQL:相对于传统关系型数据库而言,有很大差异的一种特殊的数据库,因此也叫非关系型数据库。
SQL vs NoSQL 对比:
| SQL | NoSQL | |
|---|---|---|
| 数据结构 | 结构化(Structured) | 非结构化 |
| 数据关联 | 关联的(Relational) | 无关联的 |
| 查询方式 | SQL查询 | 非SQL |
| 事务特性 | ACID | BASE |
| 存储方式 | 磁盘 | 内存 |
| 扩展性 | 垂直 | 水平 |
| 使用场景 | 1) 数据结构固定 2) 相关业务对数据安全性、一致性要求较高 | 1) 数据结构不固定 2) 对一致性、安全性要求不高 3) 对性能要求较高 |
1.2 认识Redis
特征:
- 键值(key-value)型,value支持多种不同数据结构,功能丰富
- 单线程,每个命令具备原子性
- 低延迟,速度快(基于内存、IO多路复用、良好的编码)
- 支持数据持久化
- 支持主从集群、分片集群
- 支持多语言客户端
1.3 安装Redis
Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖:
yum install -y gcc tcl
或者用docker一键安装
启动Redis:
# 默认启动(前台运行)
redis-server
# 指定配置文件启动
redis-server /usr/local/src/redis-6.2.6/redis.conf
# 开机自启(配置为系统服务)
systemctl start redis
systemctl enable redis
停止Redis:
# 利用redis-cli来执行shutdown命令
redis-cli -u 密码 shutdown
1.4 Redis客户端
命令行客户端:
Redis安装完成后就自带了命令行客户端:redis-cli,使用方式如下:
redis-cli [options] [commonds]
常见options:
-h 127.0.0.1:指定要连接的redis节点的IP地址,默认是127.0.0.1-p 6379:指定要连接的redis节点的端口,默认是6379-a 密码:指定redis的访问密码
图形化桌面客户端:
GitHub上的 Another Redis Desktop Manager,地址:https://github.com/qishibo/AnotherRedisDesktopManager
2. Redis常见命令
2.1 Redis数据结构介绍
Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样:
| 类型 | 举例 |
|---|---|
| String | hello world |
| Hash | {name: "Jack", age: 21} |
| List | [A -> B -> C -> C] |
| Set | {A, B, C} |
| SortedSet | {A: 1, B: 2, C: 3} |
| GEO | {A: (120.3, 30.5)} |
| BitMap | 0110110101110101011 |
| HyperLog | 0110110101110101011 |
2.2 Redis通用命令
通用指令是部分数据类型的,都可以使用的指令,常见的有:
KEYS:查看符合模板的所有key,不建议在生产环境设备上使用DEL:删除一个指定的keyEXISTS:判断key是否存在EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除TTL:查看一个key的剩余有效期
# 示例
KEYS * # 查看所有key
KEYS a* # 查看以a开头的key
DEL key1 key2 # 删除多个key
EXISTS key # 判断key是否存在,返回1存在,0不存在
EXPIRE key 20 # 设置key的有效期为20秒
TTL key # 查看key的剩余有效期,-1表示永久有效,-2表示已过期
2.3 String类型
String类型,也就是字符串类型,是Redis中最简单的存储类型。
其value是字符串,不过根据字符串的格式不同,又可以分为3类:
string:普通字符串int:整数类型,可以做自增、自减操作float:浮点类型,可以做自增、自减操作
不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m。
String的常见命令:
| 命令 | 描述 |
|---|---|
| SET | 添加或者修改已经存在的一个String类型的键值对 |
| GET | 根据key获取String类型的value |
| MSET | 批量添加多个String类型的键值对 |
| MGET | 根据多个key获取多个String类型的value |
| INCR | 让一个整型的key自增1 |
| INCRBY | 让一个整型的key自增并指定步长 |
| INCRBYFLOAT | 让一个浮点类型的数字自增并指定步长 |
| SETNX | 添加一个String类型的键值对,前提是这个key不存在,否则不执行 |
| SETEX | 添加一个String类型的键值对,并且指定有效期 |
key的层级格式:
Redis没有类似MySQL中的Table的概念,通过给key添加前缀加以区分,格式:项目名:业务名:类型:id
例如:
heima:user:1→{"id":1, "name":"Jack", "age":21}heima:product:1→{"id":1, "name":"小米11", "price":4999}
2.4 Hash类型
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD。
Hash的常见命令:
| 命令 | 描述 |
|---|---|
| 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不存在,否则不执行 |
2.5 List类型
Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。
特征:有序、元素可以重复、插入和删除快、查询速度一般。
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
List的常见命令:
| 命令 | 描述 |
|---|---|
| LPUSH key element ... | 向列表左侧插入一个或多个元素 |
| LPOP key | 移除并返回列表左侧的第一个元素,没有则返回nil |
| RPUSH key element ... | 向列表右侧插入一个或多个元素 |
| RPOP key | 移除并返回列表右侧的第一个元素 |
| LRANGE key star end | 返回一段角标范围内的所有元素 |
| BLPOP和BRPOP | 与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil |
2.6 Set类型
Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。
特征:无序、元素不可重复、查找快、支持交集、并集、差集等功能。
Set的常见命令:
| 命令 | 描述 |
|---|---|
| SADD key member ... | 向set中添加一个或多个元素 |
| SREM key member ... | 移除set中的指定元素 |
| SCARD key | 返回set中元素的个数 |
| SISMEMBER key member | 判断一个元素是否存在于set中 |
| SMEMBERS | 获取set中的所有元素 |
| SINTER key1 key2 ... | 求key1与key2的交集 |
| SDIFF key1 key2 ... | 求key1与key2的差集 |
| SUNION key1 key2 ... | 求key1和key2的并集 |
2.7 SortedSet类型
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。
特征:可排序、元素不重复、查询速度快。
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。(天生适合排行榜)
SortedSet的常见命令:
| 命令 | 描述 |
|---|---|
| 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 | 求差集、交集、并集 |
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可,例如:ZREVRANK
2.8 Redis的Java客户端
前面学习的都是通过 redis-cli 来操作 Redis,但在实际开发中,我们更多是通过 Java客户端 来访问 Redis。
常见的 Java 客户端主要有:
- Jedis
- Lettuce
- Spring Data Redis
- Redisson
对于初学者来说,现阶段重点掌握两个就够了:
- Jedis:更加贴近 Redis 原生命令,适合入门理解 Redis 的 Java 操作方式
- Spring Data Redis:Spring 生态中最常用,企业开发里更常见
2.9 Jedis客户端
2.9.1 什么是Jedis
Jedis 是一个比较经典的 Redis Java 客户端,它对 Redis 的命令做了 Java API 封装。
比如 Redis 里的 set、get、hset、hgetAll 等命令,在 Jedis 中都能找到对应的方法来操作。
它的特点是:
- 使用简单
- 上手快
- 和 Redis 原生命令对应关系清晰
- 适合学习 Redis 的基本操作
不过需要注意的是:Jedis 本身是线程不安全的,因此在实际项目里一般不会频繁 new Jedis(),而是配合 连接池 来使用。
2.9.2 引入依赖
如果要在项目中使用 Jedis,需要先引入依赖:
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>/*看看就行了没必要记*/
2.9.3 Jedis快速入门
1)建立连接
private Jedis jedis;
@BeforeEach
void setUp() {
jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("123456");
jedis.select(0);
}
这里的含义分别是:
new Jedis("127.0.0.1", 6379):连接 Redis 服务auth("123456"):输入 Redis 密码select(0):选择 0 号库
2)操作String类型数据
@Test
void testString() {
jedis.set("name", "zhangsan");
String name = jedis.get("name");
System.out.println(name);
}
这段代码就对应 Redis 命令:
SET name zhangsan
GET name
也就是说,Jedis 本质上就是把 Redis 命令封装成了 Java 方法。
3)操作Hash类型数据
@Test
void testHash() {
jedis.hset("user:1", "name", "Jack");
jedis.hset("user:1", "age", "21");
Map<String, String> userMap = jedis.hgetAll("user:1");
System.out.println(userMap);
}
执行后,Redis 中会保存一个 Hash 结构的数据。
4)释放资源
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}
这里的 close() 很重要。
如果不及时释放连接,连接资源会被一直占用,项目并发一高就容易出问题。
2.10 Jedis连接池
2.10.1 为什么要用连接池
虽然 Jedis 简单好用,但它有两个明显问题:
- 线程不安全
- 频繁创建和销毁连接有性能损耗
所以,实际开发中通常会使用 JedisPool 来管理连接。 这和数据库连接池、线程池的思想是一样的: 提前创建好一批可复用的资源,需要时直接拿,用完后归还,提高性能。
2.10.2 创建连接池
可以写一个工具类来统一获取 Jedis 连接:
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8); // 最大连接数
poolConfig.setMaxIdle(8); // 最大空闲连接
poolConfig.setMinIdle(0); // 最小空闲连接
poolConfig.setMaxWaitMillis(1000); // 最大等待时间
jedisPool = new JedisPool(
poolConfig,
"127.0.0.1",
6379,
1000,
"123456"
);
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
这样做的好处是:
- 统一管理 Redis 连接
- 降低代码耦合
- 提高连接复用率
- 更适合项目开发
2.10.3 使用连接池改造代码
private Jedis jedis;
@BeforeEach
void setUp() {
jedis = JedisConnectionFactory.getJedis();
jedis.select(0);
}
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}
注意这里的 close() 不再是真正断开连接,而是把连接 归还给连接池。
2.11 Spring Data Redis
2.11.1 什么是Spring Data Redis
如果说 Jedis 更适合学习 Redis 命令,那么 Spring Data Redis 更适合企业开发。
它是 Spring 提供的数据访问模块之一,专门用来整合 Redis。 它的核心优点有:
- 对 Jedis 和 Lettuce 做了统一封装
- 提供了统一的
RedisTemplate - 与 Spring Boot 集成方便
- 更适合在 SpringBoot 项目中使用
在实际开发中,我们通常不会直接操作 Jedis,而是更多使用:
RedisTemplateStringRedisTemplate
2.11.2 引入依赖
<!-- redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
2.11.3 配置Redis连接信息
在 application.yml 中配置:
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 100ms
这里底层默认使用的是 Lettuce。
2.11.4 RedisTemplate基本使用
@SpringBootTest
class RedisTest {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
void testString() {
redisTemplate.opsForValue().set("name", "zhangsan");
Object name = redisTemplate.opsForValue().get("name");
System.out.println(name);
}
}
可以看到,RedisTemplate 已经把不同数据结构的操作都封装好了:
opsForValue():操作 StringopsForHash():操作 HashopsForList():操作 ListopsForSet():操作 SetopsForZSet():操作 SortedSet
这比直接写 Jedis 更符合 SpringBoot 项目的开发习惯。
2.12 RedisTemplate的序列化问题
2.12.1 默认序列化的问题
RedisTemplate<String, Object> 虽然很方便,但它默认会使用 JDK序列化 来处理 value。
这样会带来两个问题:
- 可读性差
- 占用内存更大
也就是说,你往 Redis 里存一个 Java 对象,看到的往往不是清晰的 JSON,而是一串不太直观的序列化结果。
2.12.2 自定义RedisTemplate序列化器
可以把 RedisTemplate 的序列化方式改成 JSON:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
GenericJackson2JsonRedisSerializer jsonRedisSerializer =
new GenericJackson2JsonRedisSerializer();
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
return template;
}
}
这样存到 Redis 里的数据可读性会更好,也更方便调试。
2.13 StringRedisTemplate
2.13.1 为什么推荐StringRedisTemplate
虽然 RedisTemplate 可以通过 JSON 序列化解决可读性问题,但它通常会额外保存类型信息,还是会多占一点内存。
因此在实际开发中,很多场景更推荐使用 StringRedisTemplate。
它的特点是:
- key 按 String 序列化
- value 也按 String 序列化
- 存储结果更直观
- 内存占用更低
简单理解就是: 对象自己转 JSON,再把 JSON 字符串存进去。
2.13.2 操作普通字符串
@SpringBootTest
class RedisStringTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void testString() {
stringRedisTemplate.opsForValue().set("code:phone:13800000000", "123456");
String code = stringRedisTemplate.opsForValue().get("code:phone:13800000000");
System.out.println(code);
}
}
2.13.3 存储Java对象
假设有一个 User 对象:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private Integer age;
}
使用 StringRedisTemplate 时,需要手动把对象转成 JSON:
@SpringBootTest
class RedisStringTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testSaveUser() throws Exception {
User user = new User("zhangsan", 20);
// 手动序列化
String json = mapper.writeValueAsString(user);
stringRedisTemplate.opsForValue().set("user:100", json);
// 读取
String userJson = stringRedisTemplate.opsForValue().get("user:100");
// 手动反序列化
User result = mapper.readValue(userJson, User.class);
System.out.println(result);
}
}
这种方式在项目里非常常见,因为它既保证了数据清晰,又减少了额外内存开销。
2.14总结
关于对Java客户端中redis的总结
- Jedis:帮助你快速理解“Java是如何操作Redis命令的”(理解就好没必要用,线程不安全)
- JedisPool:解决 Jedis 线程不安全和连接复用问题(解决了也用的少)
- Spring Data Redis:SpringBoot 项目里更常用的方案(要学)
- RedisTemplate:通用,但默认序列化不太友好(有坑不好使)
- StringRedisTemplate:更推荐,尤其适合实际开发(夯爆了)
后续将更新session与缓存