Redis基础篇
初始Redis
认识NoSQL
SQL
- 结构化(Structured)
- 关联的(Relational)
- SQL查询
- SELECT id,name,age FROM tb_user WHERE id = 1
- 事务
- 满足ACID(原子性、一致性、隔离性、持久性)
NoSQL
- 非结构化
- 无关联的
- 非SQL
- Redis get user:1
- 事务
- BASE
- 要么没有事务,要么无法全部满足ACID
Redis基本特性
- 键值(key-value)型,value支持多种不同数据结构,功能丰富
- 单线程,每个命令具备原子性,中途不会执行其他命令(指命令处理始终是单线程的,自6.x起改为多线程接收网络请求)
- 低延迟,速度快(基于内存、IO多路复用、良好的编码)
- 支持数据持久化
- 支持主从、分片集群
- 支持多语言客户端
安装Redis
- 安装依赖
yum install -y gcc tcl
- 找到 redis 官网下载压缩包
进入/user/local/src/,并上传压缩包
cd /usr/local/src/
- 进行解压
tar zxvf redis-7.0.11.tar.gz
- 进入 redis 目录
cd redis-7.0.11
- 编译 和 运行
make && make install
- 进入 /usr/local/src/查看文件
- 启动 redis
# 前台启动
redis-server
- 设置后台启动
- 备份 redis 的配置文件
cp redis.conf redis.conf.bck
- 修改配置文件
# 监听的地址,默认是127.0.0.1,会导致只能在本地访问,修改为0.0.0.0则可以在任意Ip访问,生产环境不要设置0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes则可以在后台运行
demonize yes
# 密码,设置后访问Redis必须输入密码
requirepass 123321
- 启动 redis
# 进入 redis 安装目录
cd /usr/local/src /redis-7.0.11
# 启动
redis-server redis.conf
- 停止服务
# 利用redis-cli来执行 shutdown 命令,即可停止 redis 服务
# 因为之前配置了密码需要-u来指定密码
redis-cli -u 123321 shutdown
#或者使用kill
kill -9 【进程id】
- 设置开机自启
- 新建 redis.service
vim lib/systemd/system/redis.service
- 修改文件
[Unit]
Documentation=https://redis.io/
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-7.0.11/redis.conf
PrivateTmp=true
[Install]
WantedBy=multi-user.target
- 重载系统服务
systemctl daemon-reload
- 服务指令
设置开机启动,启动redis
设置开机自启动:systemctl enable redis
关闭开机自动启动:systemctl disable redis
启动redis服务:systemctl start redis
停止服务:systemctl stop redis
重新加载redis配置文件:systemctl reload redis
查看所有已启动的服务:systemctl list-units --type=service
查看服务当前状态:systemctl status redis
Redis常见命令
Redis的数据结构
Redis 提供了丰富的数据类型,常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。
查看帮助
- 查看官方文档
https://redis.io/commands/?group=string - 在命令行使用 help 命令
如:help @string
通用命令
不同数据结构和操作命令
String类型
- set: set name zhangsan
- get: get name
- mset: mset name zhangsan age 18
批量增加 - mget: mget name age
- 批量获取
- incr: incr age
- 执行一次自增1
- incrby: incrby age 2
- 自己设置自增整型
- incrbyfloat: incrbyfloat age 0.8
- 自己设置浮点类型数字自增
- setnx: setnx name lisi
- key 不存在执行,否则不执行
- setex: setex name 20 lisi
key的结构
解决使用 Redis 时 key冲突:添加前缀
127.0.0.1:6379> set allen:sites:1 '{ "name":"菜鸟教程" , "url":"www.runoob.com" }'
127.0.0.1:6379> set allen:sites:2 '{ "name":"google" , "url":"www.google.com" }'
- 效果(在图形化界面)
- 弊端:无法直接修改字符串中的字段值
Hash类型
Hash类型常见的命令
- HSET
# 添加单个
HSET heima:user:1 name allen
(integer) 1
HSET heima:user:1 age 19
(integer) 1
- HGET
# 获取单个
127.0.0.1:6379> HGET heima:user:1 name
"allen"
127.0.0.1:6379> HGET heima:user:1 age
"19"
- HMSET
# 批量添加数据
127.0.0.1:6379> HMSET heima:user2 name Mikasa age 19 gender 0
OK
- HMGET
# 批量获取数据
127.0.0.1:6379> HMGET heima:user:2 name age gender
1) "Mikasa"
2) "19"
3) "0"
- HGETALL
# 获取所有key - value 相当于 Java的 entry
127.0.0.1:6379> HGETALL heima:user:2
1) "name"
2) "Mikasa"
3) "age"
4) "19"
5) "gender"
6) "0"
- HKEYS
# 获取所有key值 相当于Java的 getKeys
127.0.0.1:6379> HKEYS heima:user:1
1) "name"
2) "age"
- HVALS
# 获取所有value 相当于 Java的 getValues
127.0.0.1:6379> HVALS heima:user:2
1) "Mikasa"
2) "19"
3) "0"
- HINCRBY
# 让一个key 的字段指定整型自增
127.0.0.1:6379> HINCRBY heima:user:1 age 1
(integer) 20
127.0.0.1:6379> HINCRBY heima:user:1 age 1
(integer) 21
127.0.0.1:6379> HINCRBY heima:user:1 age 1
(integer) 22
- HSETNX
# 有gender字段,不执行,未修改
127.0.0.1:6379> HSETNX heima:user:2 gender 1
(integer) 0
127.0.0.1:6379> HGET heima:user:2 gender
"0"
# 无gender字段,执行
127.0.0.1:6379> HSETNX heima:user:1 gender 1
(integer) 1
127.0.0.1:6379> HGET heima:user:1 gender
"1"
List类型
Redis中的LIst类型和Java中的LinkedList类似,可以看做双向链表,实际比双向链表更加复杂。支持正向检索和反向检索
特征:
- 有序
- 元素可重复
- 插入删除快
- 查询速度一般
List类型常见命令
LRANGE key 1,2 取1-2范围的元素
- LPUSH
# 在左侧插入
127.0.0.1:6379> LPUSH users 1 2 3
(integer) 3
- RPUSH
# 在右侧插入
127.0.0.1:6379> RPUSH users 4 5 6
(integer) 6
- LPOP
# 从左侧出
127.0.0.1:6379> LPOP users
"3"
127.0.0.1:6379> LPOP users
"2"
- RPOP
# 从右侧出
127.0.0.1:6379> RPOP users
"6"
127.0.0.1:6379> RPOP users
"5"
- LRANGE
# 获取范围内的元素 下标从 0 开始
127.0.0.1:6379> LRANGE users 0 1
1) "1"
2) "4"
- BLPOP和BRPOP
# user2 不存在 进入等待,阻塞时间无限
127.0.0.1:6379> BLPOP user2 0
#user2 中下标为1的元素不存在 进入阻塞,阻塞时间为100s
127.0.0.1:6379> BRPOP user2 1 100
思考:
如何利用List结构模拟一个栈?
- 入口和出口在同一边(喝酒喝吐)
如何利用List结构模拟一个队列?
- 入口和出口在不同边(喝酒喝饱上厕所)
如何利用List结构模拟一个阻塞队列?
- 入口和出口在不同边
- 出队时采用BLPOP或BRPOP
Set类型
Redis和Set结构与Java中的HashSet类似,可以看作是一个value为null的HashMap。因为也是一个hash表,因此具备HashSet类似的特性:
- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集等功能
适用于交友型、社交型的应用(好友列表、共同列表、关注)
Set类型常见命令
- 单个集合操作
- 多个集合直接操作
- SINTER:交集
上图中的B C - SDIFF:差集
- s1 DIFF s2 为 A
- s2 DIFF s1 为 D
- SUNION :并集
上图中的A B C D,如果并集过程中有相同的会合并为一个
练习:
# 创建张三的好友
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) "zhaoliu"
2) "lisi"
# 查询张三和李四的好友共有哪些人
127.0.0.1:6379> SUNION zs ls
1) "zhaoliu"
2) "wangwu"
3) "lisi"
4) "ergou"
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
SortedSet类型
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加hash表。
SortedSet具备下列特征:
- 可排序
- 元素不重复
- 查询速度快
因为SortedSet的可排序特征,经常被用来实现排行榜这些功能。
案例练习:
将班级的下列学生得分存入Redis的SortedSet中:
Jack 85,Lucy 89,Rose 82,Tom 95,Jerry 78,Amy 92,Miles 76,实现下列功能
- 添加学生得分
127.0.0.1:6379> ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
(integer) 7
- 删除Tom同学
127.0.0.1:6379> ZREM stus Tom
(integer) 1
- 获取Amy同学的分数
127.0.0.1:6379> ZSCORE stus Amy
"92"
- 获取Rose同学的排名
127.0.0.1:6379> ZRANK stus Rose
(integer) 2
- 查询80分以下有几个学生
# 此处范围时包含79的
127.0.0.1:6379> ZCOUNT stus 0 79
(integer) 2
- 给Amy同学加2分
127.0.0.1:6379> ZINCRBY stus 2 Amy
"94"
- 查出成绩前3名的同学
127.0.0.1:6379> ZREVRANGE stus 0 2
1) "Amy"
2) "Lucy"
3) "Jack"
- 查出成绩80分以下的所有同学
127.0.0.1:6379> ZRANGEBYSCORE stus 0 79
1) "Miles"
2) "Jerry"
Redis的Java客户端
Redis的Java客户端官网:https://redis.io/resources/clients/#java
Jedis客户端
Jedis官网:https://github.com/redis/jedis
使用:
- 导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.0</version>
</dependency>
- 建立连接
@BeforeEach
void setUp() {
// 1. 建立连接 指定ip地址和端口号
jedis = new Jedis("192.168.6.100",6379);
// 2. 输入密码
jedis.auth("password");
// 3. 选择库
jedis.select(0);
}
- 进行测试
@Test
void testString() {
String result = jedis.set("name", "Allen");
System.out.println("result = " + result); // result = OK
String name = jedis.get("name");
System.out.println("name = " + name); // name = Allen
}
@Test
void testHash() {
jedis.hset("user:allen:1","name","TomCat");
jedis.hset("user:allen:1","color","Write");
Map<String, String> map = jedis.hgetAll("user:allen:1");
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + ": " + entry.getValue() );
}
}
- 关闭连接
@AfterEach
void close() {
if(jedis != null){
jedis.close();
}
}
Jedis连接池
public class JedisConnectionFactory {
private static final JedisPool jedisPoll;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接
jedisPoolConfig.setMaxTotal(8);
// 最大空闲连接
jedisPoolConfig.setMaxIdle(8);
// 最小空闲连接
jedisPoolConfig.setMinIdle(0);
// 最长等待时间
jedisPoolConfig.setMaxWait(Duration.ofSeconds(1));
jedisPoll = new JedisPool(jedisPoolConfig,
"192.168.6.100", 6379, 1000, "password");
}
public static Jedis getJedis() {
return jedisPoll.getResource();
}
}
SpringDataRedis客户端
SpringData时Spring中数据操作的模块,包含对各种数据库的继承,其中Redis的继承模块就叫做SpringDataRedis。
官网地址:https://spring.io/projects/spring-data-redis
SpringDataRedis快速入门
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同类型中。
SpringDataRedis基于SpringBoot,默认引入lettuce依赖,如果有jedis需求需要引入jedis依赖
- 引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
- 在application.yaml中写redis相关配置信息
spring:
redis:
host: 192.168.6.100
port: 6379
password: password
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 1000ms
- 测试
@SpringBootTest
class RedisDemoApplicationTests {
@Resource
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("name","ShowMaker");
Object name = redisTemplate.opsForValue().get("name");
System.out.println("name = " + name); // name = ShowMaker
}
}
SrpingDataRedis的序列化方式
自定义RedisTemplate
我们可以自定义RedisTemplate的序列号方式,代码如下:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 创建RedisTemplate
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(redisConnectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
template.setConnectionFactory(redisConnectionFactory);
// 设置key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置value 的序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
// 返回
return template;
}
}
测试:
@Test
void objTest() {
User user = new User("张三",19);
redisTemplate.opsForValue().set("USER",user);
User res = (User) redisTemplate.opsForValue().get("USER");
System.out.println("res = " + res);
}
- 写入对象序列化转为JSON,读出反序列化。
- 存在问题:为了反序列化时指定对象类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,对带来额外的开销。
手动序列化和反序列化(推荐使用)
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
@SpringBootTest
public class RedisStringTest {
@Resource
private StringRedisTemplate stringRedisTemplate;
// SpringMVC默认使用的字符串序列化工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void redisStringTest() throws JsonProcessingException {
User user = new User("王五", 18);
// 进行序列化
String json = mapper.writeValueAsString(user);
// 写入
stringRedisTemplate.opsForValue().set("UserObj",json);
// 读取
String userObj = stringRedisTemplate.opsForValue().get("UserObj");
//反序列化
User res = mapper.readValue(userObj, User.class);
System.out.println("res = " + res);
}
@Test
void hashTest() {
stringRedisTemplate.opsForHash().put("user:200","name","SouthWind");
stringRedisTemplate.opsForHash().get("user:200","name");
stringRedisTemplate.opsForHash().put("user:200","age","18");
Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:200");
System.out.println("entries = " + entries);
}
}