Redis基础篇

1. Redis简介

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

  • Redis的特征:
    • 键值(key-value)型,value支持多种不同数据结构,功能丰富;
    • 单线程,每个命令具备原子性;
    • 低延迟,速度快(基于内存、IO多路复用、良好的编码);
    • 支持数据持久化;
    • 支持主从集群、分片集群;
    • 支持多语言客户端。
  • 什么是NoSQL:
    • NoSQL最常见的解释是"non-relational", 很多人也说它是"Not Only SQL";
    • NoSQL仅仅是一个概念,泛指非关系型的数据库;
    • 区别于关系数据库,它们不保证关系数据的ACID特性(原子性 (Atomicity)、 一致性;(Consistency)、隔离性(Isolation) 和 持久性(Durability));
    • NoSQL是一项全新的数据库革命性运动,提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入;
    • 常见的NoSQL数据库有:RedisMemCacheMongoDB等。
  • NoSQL与SQL的差异:
属性 SQL NoSQL
数据结构 结构化 非结构化
数据关联 关联的 无关联的
查询方式 SQL查询 非SQL
事务特性 ACID BASE
存储方式 磁盘 内存
扩展性 垂直 水平
使用场景 (1)数据结构固定
(2)相关业务对数据安全性、一致性要求较高
(1)数据结构不固定
(2)对一致性、安全性要求不高
(3)对性能要求

2. Redis安装

2.1 ubuntu安装

  • ubunutu指令安装:
1
apt install redis-server
  • 检查安装:
1
2
3
4
5
service redis-server start

# 检查启动情况
ps -ef | grep redis # 进程检查
netstat -anp | grep redis # 端口检查
  • 启动redis服务:
1
service redis-server start
  • 连接redis客户端:
1
2
# 需要在客户端上对redis进行操作
redis-cli

2.2 linux源码安装

由于redis使用C语言编写,所以想要使用源码编译安装,必须先安装编译器再执行后续步骤。
apt install gcc:为了编译源代码
apt install make:为了自动化构建项目

  • 解压压缩包
1
tar -zxvf redis-6.0.6.tar.gz
  • 进入源码目录
1
cd redis-6.0.6
  • 执行编译
1
2
# 需要主机上有C语言的编译环境,即gcc 等编译工具链
make
  • 默认安装
1
2
# 默认地,相关程序会被安装到 /usr/local/bin 目录下,例如 /usr/local/bin/redis-server
make install
  • 或者安装到指定目录
1
2
export PREFIX=/opt/redis
make install
  • 运行
1
2
3
4
cd /usr/local/bin/
redis-server
# 运行时也可以指定配置文件路径
redis-server /path/of/redis/redis.conf

2.3 设置开机自启

Redis推荐开机自启

  • 首先,新建一个系统服务文件 :
1
vi /etc/systemd/system/redis.service
  • 粘贴一下内容:
1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target
  • 然后重载系统服务 :
1
systemctl daemon-reload
  • 之后,我们可以用下面这组命令来操作redis:
1
2
3
4
5
6
7
8
# 启动
systemctl start redis
# 停止
systemctl stop redis
# 重启
systemctl restart redis
# 查看状态
systemctl status redis
  • 执行下面的命令,可以让redis开机自启
1
systemctl enable redis

3. 修改配置文件

找到/etc/redis/下的redis.conf文件使用vim编辑:

  1. 绑定ip配置:当服务器存在多个网卡(IP) 时,让服务器监听哪个IP
1
2
3
4
bind 127.0.0.1 # 只能从本机访问
bind 192.168.43.128 # 只能从内网访问
bind 202.10.8.130 # 可以从外网访问
bind 0.0.0.0 # 可以从任意位置访问此服务器 (比较常用)
  1. 设置密码:
1
2
# 找到requirepass这一行 使用/requirepass + n 或者 ?requirepass + n 快速查找(vim知识点)
requirepass 1a2b3c # 密码尽量复杂些,避免被破解
  1. 重启redis服务:
1
2
# 不重启服务输入密码不通过
service redis-server restart
  1. 连接客户端:
1
2
3
4
5
redis-cli
# 验证密码
auth 1a2b3c
# 也可以将以上两步合为一步 redis-cli -a a1b2c3
ping # 服务端正常会返回pong

4. Redis命令

4.1 通用命令

指令 描述
KEYS 查看符合模板的所有key,不建议在生产环境设备上使用
DEL 删除一个指定的key
EXISTS 判断key是否存在
EXPIRE 给一个key设置有效期,有效期到期时该key会被自动删除
TTL 查看一个KEY的剩余有效期

可以通过help [command]查看一个命令的具体用法!

4.2 各个数据类型命令

4.2.1 String

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类型的键值对,并且指定有效期

注意:

  • 以上命令除了INCRBYFLOAT都是常用命令
  • SETGET:如果key不存在则是新增,如果存在则是修改
1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> set name Rose  //原来不存在
OK

127.0.0.1:6379> get name
"Rose"

127.0.0.1:6379> set name Jack //原来存在,就是修改
OK

127.0.0.1:6379> get name
"Jack"

4.2.2 Hash

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
  • HINCRBY:让一个hash类型key的字段值自增并指定步长
  • HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行

4.2.3 List

List的常见命令:

  • LPUSH key element ... :向列表左侧插入一个或多个元素
  • LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
  • RPUSH key element ...:向列表右侧插入一个或多个元素
  • RPOP key:移除并返回列表右侧的第一个元素
  • LRANGE key star end:返回一段角标范围内的所有元素
  • BLPOPBRPOP:与LPOPRPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil

image.png

4.2.4 Set

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的并集

image.png

4.2.5 SortedSet

SortedSet的常见命令:

  • ZADD key score member:添加一个或多个元素到SortedSet ,如果已经存在则更新其score值
  • ZREM key member:删除SortedSet中的一个指定元素
  • ZSCORE key member : 获取SortedSet中的指定元素的score值
  • ZRANK key member:获取SortedSet 中的指定元素的排名
  • ZCARD key:获取SortedSet中的元素个数
  • ZCOUNT key min max:统计score值在给定范围内的所有元素的个数
  • ZINCRBY key increment member:让SortedSet中的指定元素自增,步长为指定的increment值
  • ZRANGE key min max:按照score排序后,获取指定排名范围内的元素
  • ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素
  • ZDIFF.ZINTER.ZUNION:求差集、交集、并集

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

  • 升序获取SortedSet中的指定元素的排名:ZRANK key member
  • 降序获取SortedSet中的指定元素的排名:ZREVRANK key memeber

5. Redis的Java客户端

5.1 目前主流的Redis的Java客户端:

  • Jedis和Lettuce: 这两个主要是提供了Redis命令对应的API,方便我们操作Redis,而SpringDataRedis又对这两种做了抽象和封装,因此我们后期会直接以SpringDataRedis来学习。
  • Redisson: 是在Redis基础上实现了分布式的可伸缩的java数据结构,例如Map、Queue等,而且支持跨进程的同步机制:Lock、Semaphore等待,比较适合用来实现特殊的功能需求。

5.2 Jedis快速入门

不使用连接池:

  • 引入相关依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>

<!--单元测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
  • 建立测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Jedis jedis;

// 建立连接
@BeforeEach
void setUp() {
// 建立连接(ip + port)
jedis = new Jedis("127.0.0.1", 6379);
// 设置密码
jedis.auth("1a2b3c");
// 选择库(总共16个库)
jedis.select(0);
}

// 释放资源
@AfterEach
void tearDown(){
if (jedis != null){
jedis.close();
}
}
  • 在测试类中进行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 测试String
@Test
void testString(){
jedis.set("name","SmallBoat");
String name = jedis.get("name");
System.out.println("name: " + name);
}

// 测试Hash
@Test
void testHash(){
jedis.hset("user:1","name","Jack");
jedis.hset("user:2","name","Rose");
jedis.hset("user:1","age","21");
jedis.hset("user:2","age","18");
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
}

使用连接池:

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

  • 创建JedisConnectionFactory工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class JedisConnectionFactory {

private static JedisPool jedisPool;

static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
poolConfig.setMinIdle(0);
poolConfig.setMaxWaitMillis(1000);
// 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码
jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 1000, "1a2b3c");
}

public static Jedis getJedis(){
return jedisPool.getResource();
}
}
  • 创建测试类
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
@SpringBootTest
class RedisTestApplicationTests {

private Jedis jedis = JedisConnectionFactory.getJedis();

// 测试String
@Test
void testString(){
jedis.set("name","SmallBoat");
String name = jedis.get("name");
System.out.println("name: " + name);
}

// 测试Hash
@Test
void testHash(){
jedis.hset("user:1","name","Jack");
jedis.hset("user:2","name","Rose");
jedis.hset("user:3","name","SmallBoat");
jedis.hset("user:1","age","21");
jedis.hset("user:2","age","18");
jedis.hset("user:3","age","18");
Map<String, String> map = jedis.hgetAll("user:3");
System.out.println(map);
}

// 释放资源
@AfterEach
void tearDown(){
if (jedis != null){
jedis.close();
}
}
}

5.3 SpringDataRedis快速入门

5.3.1 SpringDataRedis简介

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。
特性:

  • 提供了对不同Redis的Java客户端的整合(包括Jedis和Lettuce);
  • 提供了RedisTemplate统一API来操作Redis;
  • 支持Redis的发布订阅模型;
  • 支持Redis哨兵和Redis集群;
  • 支持基于Lettuce的响应式编程;
  • 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化;
  • 支持基于Redis的JDKCollection实现。

redisTemplate的各种API:

API 返回值类型 说明
redisTemplate.opsForValue() ValueOperations 操作String类型数据
redisTemplate.opsForHash() HashOperations 操作Hash类型数据
redisTemplate.opsForList() ListOperations 操作List类型数据
redisTemplate.opsForSet() SetOperations 操作Set类型数据
redisTemplate.opsForzSet() ZSetOperations 操作SortedSet类型数据

5.3.2 示例

  • 引入相关依赖
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
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--common-pool-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--Jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
  • 在yaml文件中配置redis
1
2
3
4
5
6
7
8
9
10
11
spring:
redis:
host: 127.0.0.1
port: 6379
password: 1a2b3c
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 100ms
  • 测试
1
2
3
4
5
6
7
8
9
10
// 因为有了SpringBoot自动装配的加持,在使用的时候直接注入即可
@Autowired
private RedisTemplate redisTemplate;

@Test
void stringTest(){
redisTemplate.opsForValue().set("username", "SmallBoat");
String username = (String) redisTemplate.opsForValue().get("username");
System.out.println(username);
}

5.3.3 序列化

RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果类似于:\xAC\xED\x00\x05t\x00\x06\xE5\xBC\xA0\xE4\xB8\x89
我们可以看到,如果使用默认的序列化工具,不仅可读性差,而且还浪费内存。于是我们需要自定义RedisTemplate的序列化方式。
自定义序列化:

  • 编写Redis配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}
  • 得到的存储结果
1
2
3
4
5
{
"@class": "com.cxc.pojo.User",
"name": "SmallBoat",
"age": 18
}

由于我们使用了Json序列化代替jdk序列化,当我们使用自定义序列化后,整体可读性得到提升,且能将Java对象自动的序列化为JSON字符串,并且查询时还能自动把JSON反序列化为Java对象。但是,在存数据时会记录序列化时对应的Class名称(为了查询时实现自动反序列化),这会带来额外的内存开销。因此,再介绍一种方案:StringRedisTemplate

5.3.4 StringRedisTemplate

为了节省内存空间,我们可以不使用JSON序列化器来处理value,而是统一使用String序列化器,这样就只能存储String类型的key和value。所以,当需要存储Java对象时,需要手动完成对象的序列化和反序列化。因为存入和读取时的序列化及反序列化都是我们自己实现的,SpringDataRedis就不会将class信息写入Redis。
由于这种用法比较普遍,于是乎SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。
StringRedisTemplate源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
this.setKeySerializer(RedisSerializer.string());
this.setValueSerializer(RedisSerializer.string());
this.setHashKeySerializer(RedisSerializer.string());
this.setHashValueSerializer(RedisSerializer.string());
}

public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
this.setConnectionFactory(connectionFactory);
this.afterPropertiesSet();
}

protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return new DefaultStringRedisConnection(connection);
}
}

有了StringRedisTemplate,我们就不需要再进行自定义序列化了,而是直接进行使用。

  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用StringRedisTemplate测试String
@Test
void stringTest() throws JsonProcessingException {
// 创建对象
User user = new User("SmallBoat", 18);
// 手动序列化
String userjson = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("userjson", userjson);
// 获取数据
String getUserjson = stringRedisTemplate.opsForValue().get("userjson");
// 手动反序列化
User getUser = mapper.readValue(getUserjson, User.class);
System.out.println(getUser);
}
  • 存储结果
1
2
3
4
{
"name": "SmallBoat",
"age": 18
}