一、Redis 的数据结构
Redis是一个key-value的数据库,的key一般是String类型,不过value类型多种多样:
二、通用命令
使用命令登录: redis-cli -h IP -p redis端口 -a “redis登录密码”
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中的一个指定元素
- ZSCOREkey member :获取sorted set中的指定元素的score值
- ZRANK key member:获取sorted set中的指定元素的排名
- ZCARDkey:获取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 */ @BeforeEach void setUp() { // 建立连接 jedis = new Jedis("192.168.30.130"); // 设置密码 jedis.auth("123456"); // 选择库 jedis.select(0); }- 使用 Jedis 操作数据
@Test void testString() { // 插入数据 String result = jedis.set("name", "张三"); System.out.println("result = " + result ); // 获取数据 String name = jedis.get("name"); System.out.println("name = " + name); }- 释放连接
/* 释放资源 */ @AfterEach void tearDown() { if (jedis != null) { jedis.close(); } }整体代码如下:
@SpringBootTest class JedisDemoApplicationTests { private Jedis jedis; /* 初始化 jedis */ @BeforeEach void setUp() { // 建立连接 jedis = new Jedis("192.168.30.130"); // 设置密码 jedis.auth("123456"); // 选择库 jedis.select(0); } @Test void testString() { // 插入数据 String result = jedis.set("name", "张三"); System.out.println("result = " + result ); // 获取数据 String name = jedis.get("name"); System.out.println("name = " + name); } /* 释放资源 */ @AfterEach void 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 */ @BeforeEach void setUp() { /* // 建立连接 jedis = new Jedis("192.168.30.130"); // 设置密码 jedis.auth("123456");*/ jedis = JedisConnectPool.getJedis(); // 选择库 jedis.select(0); } @Test void testString() { // 插入数据 String result = jedis.set("name", "张三"); System.out.println("result = " + result ); // 获取数据 String name = jedis.get("name"); System.out.println("name = " + name); } /* 释放资源 */ @AfterEach void 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 # 服务器IP password: 123456 # 密码 jedis: pool: max-active: 8 # 最大连接数量 max-idle: 8 # 最大空闲连接数 min-idle: 0 #最小空闲连接数 max-wait: 100 #等待连接时间3.注入 redisTemplate并使用
@SpringBootTest class RedisTemplateDempApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void 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 { @Bean public 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 { @Autowired private RedisTemplate<String, Object> redisTemplate; @Test void testString() { // 插入数据 redisTemplate.opsForValue().set("name", "赵六"); // 读取数据 Object name = redisTemplate.opsForValue().get("name"); System.out.println("name = " + name); } @Test void 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 { @Autowired private StringRedisTemplate StringredisTemplate; // JSON工具 private static final ObjectMapper mapper = new ObjectMapper(); @Test void 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反序列化为对象