一、Redis 的数据结构
Redis是一个key-value的数据库,的key一般是String类型,不过value类型多种多样:
二、通用命令
Redis为了方便我们学习,将操作不同数据类型的命令也做了分组,在官网 https://redis.io/commands 可以查看到不同的命令:
- 查看通用的命令:
help @generic
查看单个命令的帮助,如 help keys
查看符合模板的key, 不建议在生产设备使用。因为redis是单线程的,在搜索符合的模板是会阻塞其他的命令,在集群中应避免在主节点使用该命令。
- 批量插入key,value值
MSET key1 value1 …… key n value n
- 删除一个指定的key
DEL key
- 判断key是否存在,存在返回1,不存在返回0
EXISTS key
- 给一个key设置有效期(单位是秒),有效期到期时该key会被自动删除
EXPIRE key 时间数
- 查看一个KEY的剩余有效期
TTL key
如果值为 -1 代表永久有效
三 、String 类型
String类型,也就是字符串类型,是Redis中最简单的存储类型。其value是字符串,不过根据字符串的格式不同,又可以分为3类:
- string:普通字符串
- int:整数类型,可以做自增、自减操作
- float:浮点类型,可以做自增、自减操作
String 类型常用命令
- 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不存在,否则不执行
- SETEX:添加一个String类型的键值对,并且指定有效期
插入key 为 teacher, value 为 zhangsan的键值对,10秒过后再次取值已经失效了。
扩展
Redis没有类似MySQL中的Table的概念,我们该如何区分不同类型的key呢?例如,需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1
redis 的 key 允许多个单词用 : 隔开,格式可以是 项目名:业务名:类型:id。这个格式并非固定,可以按照实际需求。
四、Hash 类型
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
String 结构是将对象序列化为JSON后存储,需要修改某个字段时不方便。
Hash 结构可以将对象中的每个字段单独存储,可以对单个字段进行CRUD
常见命令
- HSET key field value:添加或者修改hash类型key的field的值
添加
修改年龄
- HGET key field:获取一个hash类型key的field的值
- HMSET:批量添加多个hash类型key的field的值
添加一个key为 it:user:4 的 hash数据,插入了name = LiLei,age = 20 , sex = man 三个字段。
- HMGET:获取指定key的多个字段值
获取key为 it:user:4 的 name、age 、sex字段。
- HGETALL:获取指定key的所有字段和值
- HKEYS:获取指定key的所有字段
- HVALS:获取指定key的所有值
- HINCRBY: 指定key的某个字段值自增并指定步长
- HSETNX:根据指定key值添加新字段,前提是这个field不存在,否则不执行
五、List类型
Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。
特征和LinkeList相似:有序、可重复、插入和删除快、查询效率一般。
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
List常见命令:
- LPUSH key element ... :向列表左侧插入一个或多个元素,返回列表总个数
- LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
- RPUSH key element ... :向列表右侧插入一个或多个元素,返回列表总个数
- RPOP key [count]:移除并返回列表右侧的第一个元素
可以跟取出的个数,如 rpop user 5,表示在key为user的列表中取出5个元素
- LRANGE key star end:返回一段下标范围内的所有元素
如取出下标为 0 到 2 范围内列表的元素
- BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间(单位 秒),而不是直接返回nil。
L 表示左边,R表示右边。
思考:
如何利用List结构模拟一个栈?
- 入口和出口在同一边
如何利用List结构模拟一个队列?
- 入口和出口在不同边
如何利用List结构模拟一个阻塞队列?
- 入口和出口在不同边
- 出队时采用BLPOP或BRPOP
六、Set类型
- Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:无序、不可重复、查找快、支持交集、并集。
- SADD key member ... :向set中添加一个或多个元素
在队列s2中添加值 1,3,4,6
- SREM key member ... : 移除set中的指定元素
移除队列名为s1的,且值为4的元素
- SCARD key: 返回set中元素的个数
- SISMEMBER key member:判断一个元素是否存在于set中
存在返回1, 不存在返回0
- SMEMBERS:获取set中的所有元素
- SINTER key1 key2 ... :求key1与key2的交集
显示s1 和 s2 的所有元素,并取 s1 和 s2 的交集
- SDIFF key1 key2 ... :求key1与key2的差集
- SUNION key1 key2 ..:求key1和key2的并集
七、SortedSet类型
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。
- 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即可
八、Redis的JAVA客户端
1. Jedis
1.1 Jedis 使用步骤
- 引入 Jedis 依赖
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.7.0</version>
</dependency>
- 注入模板,建立连接
/* 初始化 jedis */@BeforeEachvoid setUp() {// 建立连接jedis = new Jedis("192.168.30.130");// 设置密码jedis.auth("123456");// 选择库jedis.select(0);}
- 使用 Jedis 操作数据
@Testvoid testString() {// 插入数据String result = jedis.set("name", "张三");System.out.println("result = " + result );// 获取数据String name = jedis.get("name");System.out.println("name = " + name);}
- 释放连接
/* 释放资源 */@AfterEachvoid tearDown() {if (jedis != null) {jedis.close();}}
整体代码如下:
@SpringBootTest
class JedisDemoApplicationTests {private Jedis jedis;/* 初始化 jedis */@BeforeEachvoid setUp() {// 建立连接jedis = new Jedis("192.168.30.130");// 设置密码jedis.auth("123456");// 选择库jedis.select(0);}@Testvoid testString() {// 插入数据String result = jedis.set("name", "张三");System.out.println("result = " + result );// 获取数据String name = jedis.get("name");System.out.println("name = " + name);}/* 释放资源 */@AfterEachvoid tearDown() {if (jedis != null) {jedis.close();}}
}
1.2 Jedis 连接池
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。
1. 创建一个名为JedisConnectPool的连接池工具类。
package com.example.jedis_demo.util;import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;/*** Jedis 连接池* */
public class JedisConnectPool {// 定义一个常量private static final JedisPool JEDIS_POOL;static {// 配置连接池JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();// 最大连接数量jedisPoolConfig.setMaxTotal(8);// 最大空闲连接数量jedisPoolConfig.setMaxIdle(8);// 最小空闲连接数量jedisPoolConfig.setMinIdle(0);// 创建连接, 1000ms 表示等待时间JEDIS_POOL = new JedisPool(jedisPoolConfig, "192.168.30.130", 6379, 1000, "123456");}// 获取 Jedis 对象public static Jedis getJedis() {return JEDIS_POOL.getResource();}}
2. 使用连接池
@SpringBootTest
class JedisDemoApplicationTests {private Jedis jedis;/* 初始化 jedis */@BeforeEachvoid setUp() {/* // 建立连接jedis = new Jedis("192.168.30.130");// 设置密码jedis.auth("123456");*/jedis = JedisConnectPool.getJedis();// 选择库jedis.select(0);}@Testvoid testString() {// 插入数据String result = jedis.set("name", "张三");System.out.println("result = " + result );// 获取数据String name = jedis.get("name");System.out.println("name = " + name);}/* 释放资源 */@AfterEachvoid tearDown() {if (jedis != null) {jedis.close();}}
}
2. SpringDataRedis
现在多数的项目都是基于SpringBoot的,如何在SpringBoot中整合 Jedis 呢?
SpringData 是 Spring 中数据操作的模块,包含了各种数据库的集成,其中对redis数据库的继承模块叫做SpringDataRedis。官网地址:Spring Data Redis
- 提供了对不同Redis客户端的整合(Lettuce和Jedis)
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型
- 支持Redis哨兵和Redis集群
- 支持基于Lettuce的响应式编程
- 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
- 支持基于Redis的JDKCollection实现
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
2.1 使用步骤
- 引入spring-boot-starter-data-redis 依赖
- 编写yml配置文件, 配置 Redis
- 注入 redisTemplate并使用
1. 引入依赖
<!-- 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>
2. 编写yml配置文件, 配置 Redis
spring:redis:port: 6379 # redis 端口host: 192.168.30.130 # 服务器IPpassword: 123456 # 密码jedis:pool:max-active: 8 # 最大连接数量max-idle: 8 # 最大空闲连接数min-idle: 0 #最小空闲连接数max-wait: 100 #等待连接时间
3. 注入 redisTemplate并使用
@SpringBootTest
class RedisTemplateDempApplicationTests {@Autowiredprivate RedisTemplate redisTemplate;@Testvoid testString() {// 插入数据redisTemplate.opsForValue().set("name", "赵六");// 读取数据Object name = redisTemplate.opsForValue().get("name");System.out.println("name = " + name);}}
2.2 SpringDataRedis的序列化方式
RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果如下图所示,缺点是可读性差、占用内存大。
可以使用自定义RedisTemplate 序列化方式。
1. 添加jackson依赖
<!-- jackson 序列化--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency>
2. 新建Redis配置类,设置key、value 的序列化方式。
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {// 1. 创建 redisTemplate 对象RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();// 2. 设置连接工厂redisTemplate.setConnectionFactory(redisConnectionFactory);// 3. 设置序列化工具GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();// 4. 设置key、hashKey 为String 序列化redisTemplate.setKeySerializer(RedisSerializer.string());redisTemplate.setHashKeySerializer(RedisSerializer.string());// 5. 设置 value、hashValue 为 Json 序列化redisTemplate.setValueSerializer(jsonRedisSerializer);redisTemplate.setHashKeySerializer(jsonRedisSerializer);return redisTemplate;}
}
3. 测试。插入一条Object类型数据,查看是否序列化成功
创建一个实体对象
public class User {private String user;private Integer age;public User() {}public User(String user, Integer age) {this.user = user;this.age = age;}// 省略 get、set方法
}
引入Template 模板
@SpringBootTest
class RedisTemplateDempApplicationTests {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Testvoid testString() {// 插入数据redisTemplate.opsForValue().set("name", "赵六");// 读取数据Object name = redisTemplate.opsForValue().get("name");System.out.println("name = " + name);}@Testvoid testSavaUser() {// 写入数据redisTemplate.opsForValue().set("user:1", new User("张无忌", 20));// 获取数据User u = (User) redisTemplate.opsForValue().get("user:1");System.out.println(u);}}
2.3 手动序列化
尽管JSON的序列化方式可以满足我们的需求。但是Json 序列化器为了知道对象的类型,还会把实体类的类型添加上去,额外增加了内存开销。
为了节省内存空间,不采用Json序列化器处理value,而是通过采用String序列化器,但它只能存储String类型的key、value,如果需要存储Obect类型的value,需要手动序列化和反序列化。
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:
// JSON工具
private static final ObjectMapper mapper = new ObjectMapper();
// 序列化,序列化成json对象
String json = mapper.writeValueAsString(实体对象);
// 反序列化, 由json变成 实体对象
User user1 = mapper.readValue(val, User.class);
1. 引入序列化器依赖
<!-- jackson 序列化--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency>
2. 注入 StringRedisTemplate, 定义Json工具类
3. 定义实体对象,这里定义了一个 User 对象。
public class User {private String user;private Integer age;public User() {}public User(String user, Integer age) {this.user = user;this.age = age;}// 省略 get、set方法
}
4. 测试
@SpringBootTest
class RedisTemplateDempApplicationTests {@Autowiredprivate StringRedisTemplate StringredisTemplate;// JSON工具private static final ObjectMapper mapper = new ObjectMapper();@Testvoid testStringTemplate() throws JsonProcessingException {User user = new User("张无忌1", 20);// 手动序列化String json = mapper.writeValueAsString(user);// 写入数据StringredisTemplate.opsForValue().set("user:2", json);// 获取数据String val = StringredisTemplate.opsForValue().get("user:2");// 反序列化User user1 = mapper.readValue(val, User.class);System.out.println(user1);}}
运行结果
3 序列化两种方式总结
方案一:自定义序列化
1. 自定义RedisTemplate
2. 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer
方案二:手动序列化
1. 使用StringRedisTemplate
2. 写入Redis时,手动把对象序列化为JSON
3. 读取Redis时,手动把读取到的JSON反序列化为对象