Java-Redis:入门篇

Redis

入门篇

初识Redis

认识NoSQL

认识Redis

Redis诞生于2009年,全称是Remote Dictionary Server,远程词典服务器,是一个基于内存键值型NoSQL数据库。

特征:

  • 键值(key-value)型,value支持多种不同数据结构,功能丰富。
  • 单线程,每个命令具备原子性。
  • 低延迟,速度快(基于内存、IO多路复用、良好的编码)。Redis速度快的最主要原因是基于内存
  • 支持数据持久化。
  • 支持主从集群、分片集群。
  • 支持多语言客户端。

Redis常见命令

Redis数据结构介绍

Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样。

Redis为了方便我们学习,将操作不同数据类型的命令也做了分组,在官网( https://redis.io/commands )可以查看到不同的命令。通过help [command]可以查看一个命令的具体用法,例如:help keys

Redis通用命令

通用指令是部分数据类型的,都可以使用的指令,常见的有:

  • KEYS:查看符合模板的所有key
  • DEL:删除一个指定的key
  • EXISTS:判断key是否存在。
  • EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除。
  • TTL:查看一个KEY的剩余有效期。

String类型

String类型,也就是字符串类型,是Redis中最简单的存储类型。

value是字符串,不过根据字符串的格式不同,又可以分为3类:

  • string:普通字符串。
  • int:整数类型,可以做自增、自减操作。
  • float:浮点类型,可以做自增、自减操作。

不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m

KEY VALUE
msg hello world
num 10
score 92.5
String的常见命令
  • SET:添加或者修改已经存在的一个String类型的键值对。
  • GET:根据key获取String类型的value
  • MSET:批量添加多个String类型的键值对。
  • MGET:根据多个key获取多个String类型的value
  • INCR:让一个整型的key自增1。
  • INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2num值自增2。
  • INCRBYFLOAT:让一个浮点类型的数字自增并指定步长。
  • SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行。
  • SETEX:添加一个String类型的键值对,并且指定有效期。
key的结构

Rediskey允许有多个单词形成层级结构,多个单词之间用:隔开,格式如下:项目名:业务名:类型:id。这个格式并非固定,也可以根据自己的需求来删除或添加词条。

例如我们的项目名称叫heima,有userproduct两种不同类型的数据,我们可以这样定义key

  • user相关的keyheima:user:1
  • product相关的keyheima:product:1

如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储:

KEY VALUE
heima:user:1 {“id”:1, “name”: “Jack”, “age”: 21}
heima:product:1 {“id”:1, “name”: “小米11”, “price”: 4999}

Hash类型

Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。

String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便:

KEY VALUE
heima:user:1 {name:”Jack”, age:21}
heima:user:2 {name:”Rose”, age:18}

Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD

KEY VALUE
field value
heima:user:1 name jack
age 21
heima:user:2 name rose
age 18
Hash的常见命令
  • HSET key field value:添加或者修改hash类型keyfield的值。
  • HGET key field:获取一个hash类型keyfield的值。
  • HMSET:批量添加多个hash类型keyfield的值。
  • HMGET:批量获取多个hash类型keyfield的值。
  • HGETALL:获取一个hash类型的key中的所有的fieldvalue
  • HKEYS:获取一个hash类型的key中的所有的field
  • HVALS:获取一个hash类型的key中的所有的value
  • HINCRBY:让一个hash类型key的字段值自增并指定步长。
  • HSETNX:添加一个hash类型的keyfield值,前提是这个field不存在,否则不执行。

List类型

Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索

特征也与LinkedList类似:

  • 有序。
  • 元素可以重复。
  • 插入和删除快。
  • 查询速度一般。

常用来存储一个有序数据,例如:朋友圈点赞列表评论列表等。

List的常见命令
  • LPUSH key element ... :向列表左侧插入一个或多个元素。
  • LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
  • RPUSH key element ... :向列表右侧插入一个或多个元素。
  • RPOP key:移除并返回列表右侧的第一个元素。
  • LRANGE key star end:返回一段角标范围内的所有元素。
  • BLPOPBRPOP:与LPOPRPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil
  1. 利用List结构模拟一个栈:入口和出口在同一边。
  2. 利用List结构模拟一个队列:入口和出口在不同边。
  3. 利用List结构模拟一个阻塞队列:入口和出口在不同边,出队时采用BLPOPBRPOP

Set类型

RedisSet结构与Java中的HashSet类似,可以看做是一个valuenullHashMap。因为也是一个hash表,因此具备与HashSet类似的特征:

  • 无序。
  • 元素不可重复。
  • 查找快。
  • 支持交集、并集、差集等功能。
Set的常见命令
  • SADD key member ... :向set中添加一个或多个元素。
  • SREM key member ... : 移除set中的指定元素。
  • SCARD key: 返回set中元素的个数。
  • SISMEMBER key member:判断一个元素是否存在于set中。
  • SMEMBERS:获取set中的所有元素。
  • SINTER key1 key2 ...:求key1key2的交集。
  • SDIFF key1 key2 ...:求key1key2的差集。
  • SUNION key1 key2 ...:求key1key2的并集。
案例

将下列数据用RedisSet集合来存储:

  • 张三的好友有:李四、王五、赵六
  • 李四的好友有:王五、麻子、二狗

利用Set的命令实现下列功能:

  • 计算张三的好友有几人
  • 计算张三和李四有哪些共同好友
  • 查询哪些人是张三的好友却不是李四的好友
  • 查询张三和李四的好友总共有哪些人
  • 判断李四是否是张三的好友
  • 判断张三是否是李四的好友
  • 将李四从张三的好友列表中移除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
127.0.0.1:6379> sadd zs lisi wangwu zhaoliu
(integer) 3
127.0.0.1:6379> sadd ls wangwu mazi ergou
(integer) 3
127.0.0.1:6379> scard zs
(integer) 3
127.0.0.1:6379> sinter zs ls
1) "wangwu"
127.0.0.1:6379> sdiff zs ls
1) "lisi"
2) "zhaoliu"
127.0.0.1:6379> sunion zs ls
1) "wangwu"
2) "ergou"
3) "zhaoliu"
4) "lisi"
5) "mazi"
127.0.0.1:6379> sismember zs lisi
(integer) 1
127.0.0.1:6379> sismember ls zhangsan
(integer) 0
127.0.0.1:6379> srem zs lisi
(integer) 1
127.0.0.1:6379> smembers zs
1) "zhaoliu"
2) "wangwu"

SortedSet类型

RedisSortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加hash表

SortedSet具备下列特性:

  • 可排序。
  • 元素不重复。
  • 查询速度快。

因为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:求差集、交集、并集。

注意:所有的排名默认都是升序序号从0开始,如果要降序则在命令的Z后面添加REV即可。

案例

将班级的下列学生得分存入RedisSortedSet中:

Jack 85, Lucy 89, Rose 82, Tom 95, Jerry 78, Amy 92, Miles 76

并实现下列功能:

  • 删除Tom同学
  • 获取Amy同学的分数
  • 获取Rose同学的排名
  • 查询80分以下有几个学生
  • 给Amy同学加2分
  • 查出成绩前3名的同学
  • 查出成绩80分以下的所有同学
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
127.0.0.1:6379> zadd stus 85 Jack 89 lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
(integer) 7
127.0.0.1:6379> zrem stus Tom
(integer) 1
127.0.0.1:6379> zrank stus Rose
(integer) 2
127.0.0.1:6379> zrevrank stus Rose
(integer) 3
127.0.0.1:6379> zcard stus
(integer) 6
127.0.0.1:6379> zcount stus 0 80
(integer) 2
127.0.0.1:6379> zincrby stus 2 Amy
"94"
127.0.0.1:6379> zrange stus 0 2
1) "Miles"
2) "Jerry"
3) "Rose"
127.0.0.1:6379> zrevrange stus 0 2
1) "Amy"
2) "lucy"
3) "Jack"
127.0.0.1:6379> zrangebyscore stus 0 80
1) "Miles"
2) "Jerry"
127.0.0.1:6379>

Redis的Java客户端

Redis官网中提供了各种语言的客户端,地址:https://redis.io/clients

Jedis

Jedis的官网地址: https://github.com/redis/jedis

快速入门

Jedis使用的基本步骤:

1.引入依赖。

1
2
3
4
5
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>

2.创建Jedis对象,建立连接。

使用Jedis,方法名与Redis命令一致。

释放资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.heima.test;

public class JedisTest {
private Jedis jedis;

@BeforeEach
void setUp() {
//1.建立连接
jedis = new Jedis("localhost", 6379);
//2.设置密码
// jedis.auth("");//没有设置密码,所以不需要这一步
//3.选择库
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);
}

@Test
void testHash(){
//插入hash数据
jedis.hset("user:1", "name", "jacketlove");
jedis.hset("user:1", "age", "18");

//获取
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
}

@AfterEach
void tearDown() {
if(jedis != null){
jedis.close();
}
}
}
Jedis连接池

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.heima.jedis.util;

public class JedisConnectionFactory {
private static final JedisPool jedisPool;//Jedis官方提供的连接池对象

static{//初始化
//配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);//最大连接数
poolConfig.setMaxIdle(8);//最大空闲连接
poolConfig.setMinIdle(0);//最小空闲连接:一段时间后一致没有连接,则空闲连接被释放直到为0
poolConfig.setMaxWaitMillis(1000);//设置最长等待时间(ms);没有连接时等待一段时间报错
//创建连接池对象
jedisPool = new JedisPool(poolConfig, "localhost", 6379, 1000);
}

public static Jedis getJedis(){
return jedisPool.getResource();//拿到连接池对象
}
}
1
2
3
4
5
6
7
8
9
10
@BeforeEach
void setUp() {
//1.建立连接
//jedis = new Jedis("localhost", 6379);
jedis = JedisConnectionFactory.getJedis();//从连接池获取连接
//2.设置密码
//jedis.auth("");//没有设置密码,所以不需要这一步
//3.选择库
jedis.select(0);
}

SpringDataRedis

SpringDataSpring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis

  • 提供了对不同Redis客户端的整合(LettuceJedis)。
  • 提供了RedisTemplate统一API来操作Redis
  • 支持Redis的发布订阅模型。
  • 支持Redis哨兵和Redis集群。
  • 支持基于Lettuce的响应式编程。
  • 支持基于JDKJSON、字符串、Spring对象的数据序列化及反序列化。
  • 支持基于RedisJDKCollection实现。
SpringDataRedis快速入门

SpringDataRedis中提供了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 通用的命令

SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单:

1.引入依赖。

1
2
3
4
5
6
7
8
9
10
<!--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.配置文件。

1
2
3
4
5
6
7
8
9
10
11
spring:
redis:
host: localhost
port: 6379
password:
lettuce:
pool:
max-active: 8 # 最大连接
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: 100 # 连接等待时间

3.注入RedisTemplate

1
2
@Autowired
private RedisTemplate redisTemplate;

4.编写测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.itheima;

@SpringBootTest
class RedisDemoApplicationTests {

@Autowired
private RedisTemplate redisTemplate;

@Test
void testString(){
redisTemplate.opsForValue().set("name","surround");
Object name = redisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
}
}
SpringDataRedis的序列化方式

RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的:

缺点:

  • 可读性差。
  • 内存占用较大。

自定义RedisTemplate的序列化方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.itheima.redis.config;

@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
//创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();

//设置连接工厂
template.setConnectionFactory(connectionFactory);

//创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

//设置key的序列化:String
template.setKeySerializer(RedisSerializer.string());//RedisSerializer.string()返回StringRedisSerializer.UTF_8,是一个常量
template.setHashKeySerializer(RedisSerializer.string());

//设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
return template;
}
}

1
2
3
4
5
6
7
8
9
package com.itheima.redis.pojo;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private int age;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.itheima;

@SpringBootTest
class RedisDemoApplicationTests {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
void testString(){
redisTemplate.opsForValue().set("name","surround");
Object name = redisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
}

@Test
void testSaveUser(){
//写入数据
redisTemplate.opsForValue().set("user:3", new User("柔姐", 21));
//获取数据
User user = (User) redisTemplate.opsForValue().get("user:3");
System.out.println("user = " + user);
}

}

【注意】需要添加Jackon依赖。

1
2
3
4
5
<!--Jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
StringRedisTemplate

尽管JSON的序列化方式可以满足我们的需求,但依然存在一些问题,如图:

为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销

为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的keyvalue。当需要存储Java对象时,手动完成对象的序列化和反序列化。

Spring默认提供了一个StringRedisTemplate类,它的keyvalue的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.itheima;

@SpringBootTest
class RedisStringTests {

@Autowired
private StringRedisTemplate stringRedisTemplate;

@Test
void testString(){
stringRedisTemplate.opsForValue().set("name","surround");
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
}

private static final ObjectMapper objectMapper = new ObjectMapper();//SpringMVC默认使用的JSON处理工具
@Test
void testSaveUser() throws JsonProcessingException {
//创建对象
User user = new User("柔姐", 21);
//手动序列化
String json = objectMapper.writeValueAsString(user);
//写入数据
stringRedisTemplate.opsForValue().set("user:4", json);
//获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:4");
//手动反序列化
User user1 = objectMapper.readValue(jsonUser, User.class);
System.out.println("user = " + user1);
}

@Test
void testHash(){
stringRedisTemplate.opsForHash().put("user:5","name","surround");
stringRedisTemplate.opsForHash().put("user:5","age","18");

Map<Object,Object> entries = stringRedisTemplate.opsForHash().entries("user:5");
System.out.println("entries = " + entries);
}
}
RedisTemplate的两种序列化实践方案

方案一:

  • 自定义RedisTemplate
  • 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer

方案二:

  • 使用StringRedisTemplate
  • 写入Redis时,手动把对象序列化为JSON
  • 读取Redis时,手动把读取到的JSON反序列化为对象。

Java-Redis:入门篇
http://surourou8.github.io/2024/12/19/Java-Redis:入门篇/
作者
Su Rourou
发布于
2024年12月19日
许可协议