学习视频:
2022最新版Redis入门到精通 - 网易云课堂
学习笔记:
目录
一、初识Redis-认识NoSQL
1.认识NoSQL
2.认识Redis
3.安装Redis
二、Redis常见命令
2.1 Redis数据结构介绍
2.2 通用命令
2.3 不同数据结构的操作命令
2.3.1 String类型(值是String字符串)
2.3.2 Hash类型(值是Hash表)
2.3.3 List类型(值是List集合)
问题:如何利用List结构模拟一个阻塞队列?
2.3.4 Set类型(底层是Hash表)
2.3.5 SortedSet类型(可排序的集合,底层是一个跳表(SkipList)加hash表)
三、Redis的Java客户端Clients | Redis
3.1 Radis的Java客户端-Jedis客户端
3.1.1Jedis客户端快速入门
3.1.2Jedis客户端-Jedis的连接池
3.2 Radis的Java客户端-SpringDataRedis客户端
3.2.1认识SpringDataRedis
3.2.1SpringDataRedis快速入门
3.2.2Redis的Java客户端-Redis Template的RedisSerializer序列化方式
3.3 Radis的Java客户端-StringRedisTemplate
总结:
3.4 Radis的Java客户端-RedisTemplate操作Hash类型
SQL语言,是结构化查询语言(Structured Query Language)的简称。
结构化不建议去修改,最好在表建立之初,就建立好,不然后续如果要修改可能会在成锁表。
关系型数据库,就是表和表之间是有联系的:
#2关联的(Relational): 无关联的#2:通过json文档的嵌套的一种形式去记录数据:
#3SQL查询:无论什么SQL数据库(MySQL、Orecal),查询的语法是固定的,都能用一种语法查询。
非SQL#3:查询语法是不固定的,不统一的。如下(Redis:命令、MongoDB:函数、elasticsearch:http请求),优点就是简单、没有复杂的语法需要学习。缺点:不统一,各不相同。
#4事务ACID:所有的关系型数据库都是满足A、C、I、D。ACID为原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)的总称。(对安全性要求较高,就应该优先选择SQL关系型数据库。)
SQL数据库的ACID特性
事务BASE#4:BASE:基本可用(BA)、软状态(S)、最终一致性(E)。NoSQL中的“BASE”特性
总结:
Redis6.0仅仅针对网络请求处理这一部分是多线程的,其他核心的命令的执行部分依然是单线程的。
Redis是基于C语言编写的,所以具备良好的编码。
速度快、性能高的核心的原因是基于内存,所以速度快。
基于内存的缺点是,一旦断电,数据无法保存。所以加了数据持久化,一段时间保存一次数据。
先安装虚拟机:
(43条消息) 虚拟机安装:“VMware-workstation-full-16.2.4”和“镜像”下载安装_时时师师的博客-CSDN博客
再安装Redis:
(53条消息) Redis安装说明_时时师师的博客-CSDN博客
先学会怎么用这些数据类型,然后学习什么时候使用这些数据类型。
要学习命令行,肯定要先学习帮助文档。(学习的时候不是死记硬背,而是参考文档来学习。边学,边查询。)
在官网https://redis.io/commands可以查看到不同的命令。可以看到在redis中的这些命令都是分组去学习的。一组一组的来,就能看到了。
除了在官方网站去看,在虚拟机的终端上用命令行(没有文档介绍的详细。最好不用),也能看到这些帮助文档。
#查看通用的命令
192.168.122.1:6379> help @generic
#分组单独查询
192.168.122.1:6379> help @String
192.168.122.1:6379> help @List
等
通用命令是任何数据类型都可以使用的命令,常见的有:
到官方文档去查看,通用的是放在Generic。
也可以通过命令行:#查看通用的命令192.168.122.1:6379> help @generic
常用的几个命令:
(虚拟机的终端上)写到一半的时候按Tab键是可以自动补全的。
(模糊查询:*代表多个字符位或所有。精准查询:?代表一个字符位。)
返回值代表的是删除的key的数量,因为已经删除过看k1、k2了,所以不存在,删除0个返回值就是0。
MSET 批量插入键值。
(Set a key's time to live in seconds)(默认key的有效期是永久有效。)
(建议存储数据时,都设置有效期。)
(到期自动删除,基于内存存储的redis就可以节省redis的有效空间,例如验证码,只有效60s,就可以删除。)
(当一个key的有效期变成-2的时候,代表这个key已经被移除了。)
(当一个key的有效期变成-1的时候,代表这个key永久有效。)
即直接存储,一个key和值,有效期就是-1。永久有效。
redis是键值型的数据库,只不过它的值的类型五花八门。最简单的一种数据类型String。
String类型的常见命令:
增、查、批量增加、批量查找;
自增、步长自增、浮点步长自增;
新增、新增指定有效期。
执行结果:
192.168.1.107:6379> set name ai
OK
192.168.1.107:6379> get name
"ai"
192.168.1.107:6379> set name ci
OK
192.168.1.107:6379> get name
"ci"
192.168.1.107:6379> MSET k1 v1 k2 v2 k3 v3
OK
192.168.1.107:6379> MGET name age k1 k2 k3
1) "ci"
2) "20"
3) "v1"
4) "v2"
5) "v3"
192.168.1.107:6379> INCR age
(integer) 21
192.168.1.107:6379> get age
"21"
192.168.1.107:6379> INCRBY age 3
(integer) 24
192.168.1.107:6379> get age
"24"
192.168.1.107:6379> INCRBY age -2
(integer) 22
192.168.1.107:6379> INCRBY age -2
(integer) 20
192.168.1.107:6379> DECRBY age 3
(integer) 17
192.168.1.107:6379> set score 10.1
OK
192.168.1.107:6379> INCRBYFLOAT score 2.6
"12.7"
192.168.1.107:6379> help setnxSETNX key value
summary: Set the value of a key, only if the key does not exist
since: 1.0.0
group: string192.168.1.107:6379> key *
(error) ERR unknown command 'key', with args beginning with: '*'
192.168.1.107:6379> keys *
1) "k3"
2) "age"
3) "k2"
4) "k1"
5) "name"
6) "score"
192.168.1.107:6379> setnx name lisi
(integer) 0
192.168.1.107:6379> get name
"ci"
192.168.1.107:6379> setnx name2 lisi
(integer) 1
192.168.1.107:6379> get name2
"lisi"
192.168.1.107:6379> setex name 10 jack
OK
192.168.1.107:6379> get name
"jack"
192.168.1.107:6379> ttl name
(integer) -2
192.168.1.107:6379> setex name3 sam 10
(error) ERR value is not an integer or out of range
192.168.1.107:6379> setex name3 10 sam
OK
192.168.1.107:6379> ttl name3
(integer) 4
192.168.1.107:6379>
执行结果:
192.168.1.107:6379> set heima:user:1 '{"id":1,"name":"Jack","age":21}'
OK
192.168.1.107:6379> set heima:user:2 '{"id":2,"name":"Rose","age":18}'192.168.1.107:6379> set heima:product:1 '{"id":1,"name":"小米11","price":4999}'
OK192.168.1.107:6379> set heima:product:2 '{"id":2,"name":"荣耀6","price":2999}'
OK192.168.1.107:6379> keys *
1) "k3"
2) "heima:product:1"
3) "score"
4) "heima:user:2"
5) "name2"
6) "heima:user:1"
7) "age"
8) "k2"
9) "k1"
10) "heima:product:2"
192.168.1.107:6379>
图形化界面的客户端里:
执行结果:
user:3没有sex这个属性,所以可以使用HSETNX 添加sex。
执行结果:
LPOP、RPOP:左侧、 右侧取得并移除:
栈:先进后出。(既是入口也是出口<-框子——》框底)
队列:先进先出。(入口<-框子——>出口)
执行结果:
案例练习: 执行结果:
多了一个score元素。
还有很多命令,可以去官网看看:
去控制台看看:help @sorted-set 或help @Sorted-Set
案例:
执行结果:ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
默认升序:
在Redis官网中提供了各种语言的客户端, 下图中的地址作废,变成Clients | Redis
官网上面的文字说推荐我们使用带红心和⚡的客户端。
虽然SpringDataRedis客户端会整合Jedis和lettuce,但是有一些企业依然喜欢分开用,所以,我们也要分开学Jedis和lettuce,然后也要学SpringDataRedis客户端。
Jedis的官网地址:https://github.com/redis/jedis,我们先来个快速入门:
创建一个maven工程:
3.1.1.1引入依赖
redis.clients
jedis
4.3.1
junit
junit
4.13.2
test
org.junit.jupiter
junit-jupiter
5.9.0
test
3.1.1.2创建Jedis对象,建立连接
3.1.1.3使用Jedis,方法名名与redis命令名称一致!!!!)
3.1.1.4释放资源
package com.jedis.test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
public class JedisTest {
private Jedis jedis;
@BeforeEach
void setUP(){
//建立链接
jedis =new Jedis("192.168.1.107",6379);
//设置密码
jedis.auth("123456");
//选择库
jedis.select(0);
}
@Test
void testString(){
//存入数据,方法名名称就是redis命令名称!!!!
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();
}
}
}
值得注意的是:释放资源这里的close()也已经不是关闭了,而是归还。
执行结果:
那么查询Hash类型的呢?
@Test
void testHash(){
//插入hash数据
jedis.hset("user:1","name","Jack");
jedis.hset("user:1","age","23");
//获取
Map map = jedis.hgetAll("user:1");
System.out.println("map = "+map);
}
Jedis本身是线程不安全的,如果说在多线程的环境下并发的使用Jedis是有可能出现问题的。
并且频繁的创建和销毁Jedis对象会有性能损耗。
因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。
具体如下:
建立Jedis工具类JedisConnectionFactory、新建Jedis的静态成员变量JedisPool、编写静态代码块(配置参数、新建JedisPool对象)、编写静态方法(从JedisPool资源中获取Jedis对象)。
package com.jedis.until;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static {
//配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
//最大连接数 (连接池里最多允许创建八个连接)
poolConfig.setMaxTotal(8);
//最大空闲连接(没有人来访问,也会预备8个连接)
poolConfig.setMaxIdle(8);
//最小空闲连接 (如果一直没人访问,这些连接就会被释放)
poolConfig.setMinIdle(0);
//等待时长(等待一段时间这些连接才会被释放)
//poolConfig.setMaxWaitMillis(1000);
poolConfig.setMaxWait(Duration.ofMillis(1000));
//poolConfig.setMaxWait(Duration.ofDays(1000));
//poolConfig.setMaxWait(Duration.ofHours(1000));
//创建连接对象 timeout代表传输时间
jedisPool = new JedisPool(poolConfig,
"192.168.1.107",6379,1000,"123456");
}
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
poolConfig.setMaxWaitMillis(1000);过时了,他的底层代码:
所以,改成poolConfig.setMaxWait(Duration.ofMillis(1000));仔细查看,可以发现,现在可以写:秒、小时、天数等。
修改:
其他内容不变。执行结果也不变,是正确的。
值得注意的是:释放资源这里的close()也已经不是关闭了,而是归还。
Spring Data Redis https://spring.io/projects/spring-data-redis
SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单:
3.2.1.1创建一个SpringBoot新项目:
3.2.1.2引入依赖
org.springframework.boot
spring-boot-starter-data-redis
2.7.4
org.apache.commons
commons-pool2
2.11.1
3.2.1.3编写配置文件,配置redis。
spring:
redis:
host: 192.168.1.107
port: 6379
password: 123456
lettuce: #这里可以选择lettuce也可以选择Jedis,但是spring默认使用lettuce。要使用Jedis需要额外引入依赖并配置。
pool:
max-active: 8 #最大连接
max-idle: 8 #最大空闲连接
min-idle: 0 #最小空闲连接
max-wait: 100 #连接等待时间
3.2.1.4注入RedisTemplate
@Autowired private RedisTemplate redisTemplate;
3.2.1.5编写测试
package com.redis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class RedisDemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void testString() {
//插入一条string类型的数据
redisTemplate.opsForValue().set("name","李四");
//读取一条string类型的数据
Object name = redisTemplate.opsForValue().get("name");
System.out.println("name = "+ name);
//插入一条string类型的数据
redisTemplate.opsForValue().set("nameS","李小四");
//读取一条string类型的数据
Object nameS = redisTemplate.opsForValue().get("nameS");
System.out.println("nameS = "+ nameS);
}
}
3.2.1.6执行结果:
这是因为,在redisTemplate.opsForValue().set("name","李四");这个方法中,接收的参数并不是字符串,而是Object。SpringDataRedis的特殊功能,它可以接收任何类型的对象,然后帮我们转成redis可以处理的字节。所以我们存进去的"name"、"李四"都被当成了Java对象,而Redis Template底层,默认对这些对象的处理方式就是利用JDK的序列化工具ObjectOutputStream,把redis对象转成字节。
从下面可以看出以上论断:
可以看到上面的序列化器都没有定义,而是等于null。这时候,就会启动默认的序列化器this.defaultSerializer:
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}
具体代码:
public void afterPropertiesSet() { super.afterPropertiesSet(); boolean defaultUsed = false; if (this.defaultSerializer == null) { this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader()); } if (this.enableDefaultSerializer) { if (this.keySerializer == null) { this.keySerializer = this.defaultSerializer; defaultUsed = true; } if (this.valueSerializer == null) { this.valueSerializer = this.defaultSerializer; defaultUsed = true; } if (this.hashKeySerializer == null) { this.hashKeySerializer = this.defaultSerializer; defaultUsed = true; } if (this.hashValueSerializer == null) { this.hashValueSerializer = this.defaultSerializer; defaultUsed = true; } } if (this.enableDefaultSerializer && defaultUsed) { Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized"); } if (this.scriptExecutor == null) { this.scriptExecutor = new DefaultScriptExecutor(this); } this.initialized = true; }
还可以更清晰地看,追本溯源:
Redis Template可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认采用JDK序列化,得到的结果是:
缺点:
通过上面可知使用默认的序列化,就会变成这样的字节形式,所以我们换一种方式:
我们不希望使用JdkSerializationRedisSerializer(classLoader);,可以看到下面还有好多序列化方式:
其中StringRedisSerializer.UTF_8;是专门用来处理字符串的,底层是getBytes(this.charset);。所以处理key值(字符串)一般用这个。
那么vlaue值有可能是对象,那么建议使用GenericJackson2JsonRedisSerializer();转json字符串的序列化。
那我们产生一个猜想,是不是把这里的null修改成一个确定的值就可以了呢?
我们可以自定义RedisTemplate的序列化方式,代码如下:
创建一个新的类RedisConfig.class:
定义泛型RedisTemplate
package com.redis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
//创建RedisTemplate对象
RedisTemplate redisTemplate = new RedisTemplate<>();
//创建连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer
= new GenericJackson2JsonRedisSerializer();
//key和HashKey采用String序列化
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
//value和HashValue采用JSON序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
return redisTemplate;
}
}
修改测试类:里面的RedisTemplate为RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
执行测试类:报错“类没有被找到”,缺少JACKSON的依赖:Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder
at com.redis.config.RedisConfig.redisTemplate(RedisConfig.java:22)
引入依赖:
com.fasterxml.jackson.core
jackson-databind
2.13.4.2
重新运行程序,执行结果正常了:
再尝试一下,添加一个对象:
新建一个类:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//get、set方法;无参、有参构造。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Integer age;
}
编写单元测试:
package com.redis;
import com.redis.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class RedisDemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void testSaveUser(){
//写入数据
redisTemplate.opsForValue().set("user:100",new User("立夏",21));
//获取数据
User user = (User) redisTemplate.opsForValue().get("user:100");
System.out.println("user: "+ user);
}
}
执行结果:自动化的:存User转JSON,取JSON转User。自动序列化。
存的时候自动帮我们存入了"@class": "com.redis.pojo.User",所以读取的时候可以自动反序列化从JSON变成User。
为了节省内存空间,我们并不会使用JSON序列化器来处理Value,
而是统一使用String序列化器,要求只能存储String类型的Key和Value。
当需要存储Java对象时,手动完成对象的序列化和反序列化。
又因为Spring默认提供了一个StringRedisTemplate类,它的Key和Value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:
以为已经有Spring默认的Key和Value的序列化方式:String方式,所以不再需要RedisConfig!!
package com.redis;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.redis.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
@SpringBootTest
class RedisStringTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//JSON工具:ObjectMapper是SpringMVC里面默认的JSON处理工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testString() {
//插入一条string类型的数据
stringRedisTemplate.opsForValue().set("name","李四");
//读取一条string类型的数据
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name = "+ name);
//插入一条string类型的数据
stringRedisTemplate.opsForValue().set("nameS","李小四");
//读取一条string类型的数据
Object nameS = stringRedisTemplate.opsForValue().get("nameS");
System.out.println("nameS = "+ nameS);
}
@Test
void testStringTemplate() throws JsonProcessingException {
//准备对象
User user = new User("夏至",18);
//手动序列化,转JSON,变成字符串
String json = mapper.writeValueAsString(user);
//插入一条数据到redis
stringRedisTemplate.opsForValue().set("user:200",json);
//读取数据,是一个JSON字符串
String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
//反序列化,把字符串转成User对象。
User user1 = mapper.readValue(jsonUser,User.class);
System.out.println("user1 = "+ user1);
}
}
@SpringBootTest class RedisStringTests { @Autowired private StringRedisTemplate stringRedisTemplate; //JSON工具:ObjectMapper是SpringMVC里面默认的JSON处理工具 private static final ObjectMapper mapper = new ObjectMapper(); @Test void testStringTemplate() throws JsonProcessingException { //准备对象 User user = new User("夏至",18); //手动序列化,转JSON,变成字符串 String json = mapper.writeValueAsString(user); //插入一条数据到redis stringRedisTemplate.opsForValue().set("user:200",json); //读取数据,是一个JSON字符串 String jsonUser = stringRedisTemplate.opsForValue().get("user:200"); //反序列化,把字符串转成User对象。 User user1 = mapper.readValue(jsonUser,User.class); System.out.println("user1 = "+ user1); } }
执行结果:已经没有了"@class": "com.redis.pojo.User"。
在stringRedisTemplate里面,不是以命令行HSet的形式存储Hash值的,而是像java中HashMapper中一样如上使用put。还有很多:
@SpringBootTest
class RedisStringTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//JSON工具:ObjectMapper是SpringMVC里面默认的JSON处理工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testHash(){
//存
stringRedisTemplate.opsForHash().put("user:400","name","冬至");
stringRedisTemplate.opsForHash().put("user:400","age","21");
//取
Map
执行结果:
其他的形式可以自己摸索或者参考官方文档,还是很容易掌握的!!!可以自己试一下。