# redis-基础篇
Redis
- 键值数据库
- NoSQL数据库
目录:
初识 Redis
- 认识 NoSQL
- 认识 Redis
- 安装 Redis
Redis 常见命令
- 5 种常见数据结构
- 通用命令
- 不同数据结构的操作命令
Redis 的 Java 客户端
- Jedis 客户端
- SpringDataRedis 客户端
# 1. 初识 Redis
# 1.1. 认识 NoSQL
| 类型 | SQL | NoSQL |
|---|---|---|
| 数据结构 | 结构化 | 非结构化(松散) |
| 数据关联 | 关联的(外键) | 无关联的 |
| 查询方式 | SQL查询(统一的语法) | 非SQL(五花八门) |
| 事务特性 | 事务 ACID | BASE |
| 存储方式 | 磁盘 | 内存 |
| 扩展性 | 垂直 | 水平 |
| 使用场景 | 1)数据结构固定 2)数据安全性、一致性要求较高 | 1)数据结构不固定 2)数据安全性、一致性要求不高 3)对性能要求高 |
# 1.2. 认识 Redis
Redis 诞生于 2009 年,是一个基于内存的键值型 NoSQL 数据库
全称:
- Remote Dictionary Server
- 远程词典服务器
特征:
- 键值(key-value)型,value 支持多种不同数据结构,功能丰富
- 单线程,每个命令具备原子性
- 低延迟、速度快(基于内存、IO多路复用、良好的编码)
- 支持数据持久化
- 支持主动集群、分片集群
- 支持多语言客户端
# 1.3. 安装 Redis
下载: (windows版本)
- 地址: https://github.com/tporadowski/redis/releases
- 版本: Redis for Windows 5.0.14.1
- 文件: Redis-x64-5.0.14.1.zip
启动:
D:
cd D:\soft\redis-5.0.14.1
redis-server.exe redis.windows.conf
配置: (redis.windows.conf)
# 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。
# 修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes后即可后台运行
daemonize yes
# 密码,设置后访问Redis必须输入密码
requirepass 123321
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
参考:
# 1.4. 命令行客户端
在 Redis 安装目录存在 redis-cli.exe 客户端
D:\install\redis-5.0.14.1>redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> get name
"zhangsan"
# 1.5. 图形化桌面客户端
GitHub上的大神编写了Redis的图形化桌面客户端,地址:https://github.com/uglide/RedisDesktopManager
不过该仓库提供的是RedisDesktopManager的源码,并未提供 windows 安装包。
在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases
# 2. Redis 常见命令
# 2.1. Redis 数据结构介绍
Redis 是一个 key-value 的数据库, key 一般是 String 类型,value 的类型多种多样:
基本类型:
- String
- Hash
- List
- Set
- SortedSet
特殊类型:
- GEO
- BitMap
- HyperLog
帮助文档:
- https://redis.io/docs/latest/commands/
# 2.2. Redis 通用命令
常见指定:
- KEYS: 查看符合模板的所有 key,不建议在生产环境使用
- DEL: 删除指定的 key
- EXISTS: key 是否存在
- EXPIRE: 设置有效期,到期自动删除
- TTL: 查看剩余有效期;
-1永久有效,-2已删除
KEYS 的模式:
Supported glob-style patterns:
h?llo matches hello, hallo and hxllo
h*llo matches hllo and heeeello
h[ae]llo matches hello and hallo, but not hillo
h[^e]llo matches hallo, hbllo, ... but not hello
h[a-b]llo matches hallo and hbllo
示例:
127.0.0.1:6379> MSET name ZhangSan age 18
127.0.0.1:6379> help KEYS
KEYS pattern
summary: Find all keys matching the given pattern
since: 1.0.0
group: generic
127.0.0.1:6379> KEYS *
1) "age"
2) "name"
127.0.0.1:6379> DEL age
(integer) 1
127.0.0.1:6379> KEYS *
1) "name"
127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> EXISTS age
(integer) 0
127.0.0.1:6379> EXPIRE name 10
(integer) 1
127.0.0.1:6379> TTL name
(integer) 1
127.0.0.1:6379> TTL name
(integer) -2
127.0.0.1:6379> KEYS *
(empty list or set)
# 2.3. String 类型
说明:
- 字符串类型,最简单的存储类型
value 是字符串,可以分为三类:
- string
- int
- float
常见命令:
SET: 增 或 改
GET: 查
MSET: 批量增
MGET: 批量查
INCR: int 自增1
INCRBY: int 自增指定步长
INCRBYFLOAT: float 自增指定步长
SETNX: 增。不存在时新增
SETEX: 增或改。设置有效期
示例:
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> set name zhangsan2
OK
127.0.0.1:6379> get name
"zhangsan2"
127.0.0.1:6379> mset k1 v1 k2 v2
OK
127.0.0.1:6379> keys *
1) "name"
2) "k1"
3) "k2"
127.0.0.1:6379> mget k1 k2
1) "v1"
2) "v2"
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> incr age
(integer) 19
127.0.0.1:6379> incrby age 2
(integer) 21
127.0.0.1:6379> incrby age -2
(integer) 19
127.0.0.1:6379> set score 82.5
OK
127.0.0.1:6379> incrbyfloat score 0.5
"83"
127.0.0.1:6379> incrbyfloat score 0.5
"83.5"
127.0.0.1:6379> setnx name zhangsan2
(integer) 0
127.0.0.1:6379> setnx name2 zhangsan2
(integer) 1
# 等价于如下命令
127.0.0.1:6379> set name2 zhangsan2 nx
(nil)
127.0.0.1:6379> set name3 lisi
OK
127.0.0.1:6379> setex name3 10 lisi2
OK
127.0.0.1:6379> get name3
"lisi2"
127.0.0.1:6379> ttl name3
(integer) -2
127.0.0.1:6379> set name3 lisi
OK
# 等价于如下命令
127.0.0.1:6379> set name3 lisi ex 10
OK
127.0.0.1:6379> ttl name3
(integer) 8
# 2.4. key 的格式
建议格式:
项目名:业务名:类型:id
说明:
- 通过
:分隔的 key 在图形界面中会形成层级结构
注意:
- windows cmd 要设置 UTF-8 编码,否则会导致乱码
- 在 CMD 执行:
chcp 65001
示例:
set user:1 '{"id":1,"name":"zhangsan","age":18}'
set user:2 '{"id":2,"name":"lisi","age":20}'
set product:1 '{"id":1,"name":"小米11","price":1888}'
set product:2 '{"id":2,"name":"荣耀100","price":1999}'
keys *
# 1) "product:1"
# 2) "product:2"
# 3) "user:2"
# 4) "user:1"
# 2.5. Hash 类型
说明:
- 散列,无序字典,类似 Java 中的 HashMap
常见命令:
- HSET key field value: 增或改 key 的字段
- HGET key field: 查 key 的字段的值
- HMSET: 批量 增或改 key 的字段
- HMGET: 批量 查 key 的字段
- HGETALL: 查 key 的所有字段和值
- HKEYS: 查 key 的所有字段
- HVALS: 查 key 的所有值
- HINCRBY: key 的字段 自增
- HSETNX: 增。存在则增
示例:
# 增 字段
HSET user:3 name wangwu
# (integer) 1
# 查 字段
HGET user:3 name
# "wangwu"
# 批量增 字段
HMSET user:3 age 18 gender man
# OK
# 批量查 字段
HMGET user:3 name age gender
# 1) "wangwu"
# 2) "18"
# 3) "man"
# 查所有 字段-值 对
HGETALL user:3
# 1) "name"
# 2) "wangwu"
# 3) "age"
# 4) "18"
# 5) "gender"
# 6) "man"
# 查所有 字段
HKEYS user:3
# 1) "name"
# 2) "age"
# 3) "gender"
# 查所有 值
HVALS user:3
# 1) "wangwu"
# 2) "18"
# 3) "man"
# 字段自增长
HINCRBY user:3 age 2
# (integer) 20
# 不存在则新增
HSETNX user:3 name wangwu2
# (integer) 0
# 2.6. List 类型
说明:
- 类似 Java 中的 LinkedList
- 双向链表,支持 正向、反向 检索
特征:
- 有序
- 元素可重复
- 插、删 快
- 查 慢
应用:
- 通用用于存储有序数据,比如 XX排行榜
常用命令:
LPUSH key element ...: 左侧(队首)插入 一个或多个 元素
LPOP key: 左侧(队首)移除并返回第一个元素,没有则返回 nil
RPUSH key element ...: 右侧(队尾)插入 一个或多个 元素
RPOP key: 右侧(队尾)移除并返回第一个元素,没有则返回 nil
LRANGE key start end: 返回范围内所有元素,索引从 0 开始
BLPOP / BRPOP: 与 LPOP/RPOP 类似,没有元素时阻塞指定时间
示例:
LPUSH users u1 u2 u3
#=> u3 u2 u1
RPUSH users u4 u5 u6
#=> u3 u2 u1 u4 u5 u6
LPOP users
# u3
#=> u2 u1 u4 u5 u6
RPOP users
# u6
#=> u2 u1 u4 u5
LRANGE users 1 2
# 1) "u1"
# 2) "u4"
BLPOP users2 10
# (nil)
# (10.03s)
思考:
如何利用 List 结构模拟一个栈?
- 入口 和 出口 在同一边
- 左入 左出
- 右入 右出
如何利用 List 结构模拟一个队列?
- 入口 和 出口 在不同边
- 左入 右出
- 右入 左出
如何利用 List 结构模拟一个阻塞队列?
- 入口 和 出口 在不同边
- 出队时使用 BLPOP 或 BRPOP
# 2.7. Set 类型
说明:
- 与 Java 中的 HashSet 类似
- 可以看作一个 HashMap (value 都为 null)
特征:
- 无序
- 元素不可重复
- 查 快
- 支持 交集、并集、差集
常用命令:
- SADD key member ...: 增加元素
- SREM key member ...: 删除元素
- SCARD key: 元素个数
- SISMEMBER key member: 是否包含
- SMEMBERS: 返回所有元素
- SINTER key1 key2 ...: 交集
- SDIFF key1 key2 ...: 差集,key1 有 而 key2 无
- SUNION key1 key2 ...: 并集
示例:
SADD s1 a b c
# (integer) 3
SCARD s1
# (integer) 3
SMEMBERS s1
# 1) "c"
# 2) "b"
# 3) "a"
SREM s1 a
# (integer) 1
SMEMBERS s1
# 1) "c"
# 2) "b"
SADD s2 a b c
# (integer) 3
SADD s3 b c d
# (integer) 3
SINTER s2 s3
# 1) "c"
# 2) "b"
SDIFF s2 s3
# 1) "a"
SUNION s2 s3
# 1) "b"
# 2) "a"
# 3) "d"
# 4) "c"
# 2.8. SortedSet 类型
说明:
- 可排序的 Set 集合
- 每个元素都有一个 score 属性,元素基于 score 顺序排序
特征:
- 可排序
- 元素不可重复
- 查 快
应用:
- 排行榜
常用命令:
- ZADD key score member: 增或改
- ZREM key member: 删
- ZSCORE key member: 查 元素的 score
- ZRANK key member: 查 元素的排名,从 0 开始
- ZCARD key: 查 元素个数
- ZCOUNT key min max: score 在
[min, max]的元素个数 - ZINCRBY key increment member: 增加 元素 的 score
- ZRANGE key min max: 排序后,排名 在
[min, max]的元素 - ZRANGEBYSCORE key min max: 排序后,score 在
[min, max]的元素 - ZDIFF / ZINTER / ZUNION
注意:
- 排名是升序,降序可以使用
ZREV前缀
示例:
ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
# (integer) 7
# 删除 Tom
ZREM stus Tom
# (integer) 1
# Amy 的分数
ZSCORE stus Amy
# "92"
# Rose 的排名
ZRANK stus Rose
# (integer) 2
# 80 以下的元素个数
ZCOUNT stus 0 80
# (integer) 2
# Amy 加 2 分
ZINCRBY stus 2 Amy
# "94"
# 分数前 3 的同学
ZREVRANGE stus 0 2
# 1) "Amy"
# 2) "Lucy"
# 3) "Jack"
# 80 以下的元素
ZRANGEBYSCORE stus 0 80
# 1) "Miles"
# 2) "Jerry"
# 3. Redis 的 Java 客户端
Connect with Redis client API libraries: https://redis.io/docs/latest/develop/clients/
常用客户端:
- Jedis
- lettuce
- Redisson
Jedis:
- 以 Redis 命令作为方法名称
- 学习成本低,简单实用
- 线程不安全。一个线程 一个 Jedis 实例,需要配合线程池来使用。
Lettuce:
- 基于 Netty 实现
- 支持 同步、异步、响应式编程,线程安全
- 支持 Redis 的哨兵模式、集群模式、管道模式
Redisson:
- 基于 Redis 实现了一套 分布式、可伸缩的数据结构 集合,
- 比如 Map、Queue、Lock、Semaphore、AtomicLong
Spring Data Redis:
- 提供了一套通用的 API 接口
- 接口实现 可以是 Jedis 也可以是 Lettuce
# 3.1. Jedis 客户端
# 3.1.1. Jedis 快速入门
使用步骤:
- 引入依赖
- 建立连接
- 使用
- 释放资源
官网:
- https://github.com/redis/jedis
依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>6.2.0</version>
</dependency>
示例:
public class TestJedis {
private Jedis jedis;
@BeforeEach
void setup() {
// 建立连接
jedis = new Jedis("127.0.0.1", 6379);
// 密码
// jedis.auth("");
// 选择库(默认 0 号库)
jedis.select(0);
}
@Test
void testString() {
String result = jedis.set("name", "张三");
System.out.println("result = " + result); //=> OK
String name = jedis.get("name");
System.out.println("name = " + name); //=> 张三
}
@Test
void testHash() {
jedis.hset("user:1", "name", "李四");
jedis.hset("user:1", "age", "18");
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println("map = " + map); //=> {name=李四, age=18}
}
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}
}
# 3.1.2. Jedis 连接池
说明:
- Jedis 是线程不安全的
- 频繁 创建、销毁 连接,会有性能损耗
- 因此,推荐使用 Jedis 连接池
示例:
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接数
jedisPoolConfig.setMaxTotal(8);
// 最大空闲连接
jedisPoolConfig.setMaxIdle(8);
// 最小空闲连接 (常驻连接数)
jedisPoolConfig.setMinIdle(0);
Duration duration = Duration.of(1, ChronoUnit.SECONDS);
// (从连接池获取连接,如果没有连接)最长等待时间
jedisPoolConfig.setMaxWait(duration);
jedisPool = new JedisPool(
jedisPoolConfig,
"127.0.0.1",
6379,
// 超时时间
1000
);
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
# 3.2. SpringDataRedis 客户端
# 3.2.1. 介绍
说明:
- SpringData 是 Spring 中 操作数据库 的模块
- 对 Redis 的集成模块叫做 SpringDataRedis
- 官网: https://spring.io/projects/spring-data-redis
特点:
- 对不同 Redis 客户端的整合(Lettuce 和 Jedis)
- 使用 RedisTemplate API 来操作 Redis
支持:
- Redis 的发布订阅模式
- Redis 哨兵、Redis 集群
- Lettuce 的响应式编程
- 基于 JDK、JSON、字符串、Spring对象 的序列化和反序列化
- 基于 Redis 的 JDKConnection 实现
RedisTemplate 工具类,封装了对 Redis 的操作,对不同 数据类型 的 API 进行分类,如下:
| API | 返回值类型 | 说明 |
|---|---|---|
| redisTemplate.opsForValue() | ValueOperations | 操作 String 类型数据 |
| redisTemplate.opsForHash() | HashOperations | 操作 Hash 类型数据 |
| redisTemplate.opsForList() | ListOperations | 操作 List 类型数据 |
| redisTemplate.opsForSet() | SetOperations | 操作 Set 类型数据 |
| redisTemplate.opsForZSet() | ZSetOperations | 操作 SortedSet 类型数据 |
| redisTemplate | - | 通用的命令 |
# 3.2.2. 快速开始
步骤:
- 依赖
- 配置
- 使用
依赖:
<!-- 默认 引入 io.lettuce:lettuce-core -->
<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>
配置:
spring:
data:
redis:
host: localhost
port: 6379
# 指定使用 哪个 Redis 客户端实现
lettuce:
# 配置线程池后,线程池才会生效
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 1000ms
使用:
@SpringBootTest
public class TestSpringDataRedis {
@Autowired
private RedisTemplate redisTemplate;
@Test
void testString() {
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("name", "张三");
Object name = valueOperations.get("name");
System.out.println("name = " + name);
}
}
# 3.2.3. 序列化: 自定义 RedisTemplate
说明:
- 默认情况下, RedisTemplate 存入 Redis 的 键和值 都被序列化为字节了
- 因此,需要自定义 键和值 的序列化器
序列化:
- key 使用 StringRedisSerializer, 只能是字符串
- value 使用 Jackson2JsonRedisSerializer, 则可以是任意类型
- 如果未引入 SpringMVC,则需要显式引入 jackson-databind 依赖
自定义 RedisTemplate 对象:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置 连接工厂
redisTemplate.setConnectionFactory(connectionFactory);
// 创建 JSON 序列化器
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置 key 的序列器
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
// 设置 value 的序列器
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
// 返回
return redisTemplate;
}
}
使用:
@SpringBootTest
public class TestSpringDataRedis {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
void testString() {
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
valueOperations.set("name", "张三");
Object name = valueOperations.get("name");
System.out.println("name = " + name);
}
@Test
void testObject() {
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
// 存: value 为 Pojo
valueOperations.set("user:100", new User("张三", 18));
/* redis =>
{
"@class": "org.example.pojo.User",
"name": "张三",
"age": 18
}
*/
// 取
User user = (User) valueOperations.get("user:100");
System.out.println("user = " + user);
//=> User(name=张三, age=18)
// 存: value 为 List
List<User> userList = new ArrayList<>();
userList.add(new User("李四", 21));
userList.add(new User("王五", 28));
valueOperations.set("userList:1", userList);
/* redis =>
[
"java.util.ArrayList",
[
{
"@class": "org.example.pojo.User",
"name": "李四",
"age": 21
},
{
"@class": "org.example.pojo.User",
"name": "王五",
"age": 28
}
]
]
*/
// 取
List<User> list = (List<User>) valueOperations.get("userList:1");
System.out.println("list = " + list);
//=> [User(name=李四, age=21), User(name=王五, age=28)]
}
}
# 3.2.4. 序列化: StringRedisTemplate
说明:
- SpringDataRedis 提供了一个 StringRedisTemplate
- key、value 都是要字符串序列化器
- value 如果是对象,则需要手动 序列化、反序列化
示例:
@SpringBootTest
public class TestStringRedisTemplate {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void testString() {
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
valueOperations.set("name", "张三");
Object name = valueOperations.get("name");
System.out.println("name = " + name);
}
@Test
void testObject() throws JsonProcessingException {
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
ObjectMapper objectMapper = new ObjectMapper();
User user = new User("张三", 18);
// 手动序列化
String userJsonStr = objectMapper.writeValueAsString(user);
valueOperations.set("user:20", userJsonStr);
String userJsonStrFromRedis = valueOperations.get("user:20");
// 手动反序列化
User userFromRedis = objectMapper.readValue(userJsonStrFromRedis, User.class);
System.out.println("userFromRedis = " + userFromRedis); //=> User(name=张三, age=18)
}
}
# 3.2.5. 序列化: 总结
RedisTemplate 的两种序列化方案
1)自定义 RedisTemplate
- 说明: 修改序列化器,如下
- key: StringRedisSerializer
- value: GenericJackson2JsonRedisSerializer
- 优点: 自动 序列化、反序列化 对象类型
- 缺点: 序列化对象类型,会将该对象的 类的字节码 也存入,占用大量存储空间
2)StringRedisTemplate
- 说明: StringDataRedis 提供的对象,key 和 value 都使用 StringRedisSerializer
- 优点: 干净
- 缺点: 手动 序列化、反序列化 对象类型
# 3.2.6. 操作 Hash 类型
@SpringBootTest
public class TestHash {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void testHash() {
HashOperations<String, String, Object> hashOperations = stringRedisTemplate.opsForHash();
// 写 key 的单个属性
hashOperations.put("user:30", "name", "小明");
hashOperations.put("user:30", "age", "18"); // value 不能是 数字
// 写 key 的所有属性
Map<String, Object> map = new HashMap<>();
map.put("name", "小红");
map.put("age", "28");
hashOperations.putAll("user:31", map);
// 读 key 的单个属性
Object name = hashOperations.get("user:30", "name");
System.out.println("name = " + name);
// 读 key 的所有条目
Map<String, Object> entries = hashOperations.entries("user:31");
System.out.println("entries = " + entries);
}
}
上一篇: 下一篇: