快乐Redis学习之旅
我是一个小菜鸡,若我的想法有所不对,请各位大佬们随意指正,持续更新中....
Redis是什么?
redis是一个存在内存中的key-value键值队(就一个字快)
为什么用redis,能不能不用redis?
我可以确切的告诉你,看情况使用redis,并不是非要用,看你的业务场景
例如:你网站的数据量访问量不是特别多的情况下,可以没必要使用redis,因为在我看来他最牛的就是缓存数据,想象一下你的用户量特别多的情况下,你从mysql中查出来数据是非常满的,此时你想极大的提升性能,只能依靠缓存了,这时候使用redis你会感觉这是飞一样的感觉(还有类似的很多情况,比如计数器,分布式session等等,都可以使用redis)
准备阶段
首先我们需要映入相关依赖(本人使用的是springboot2.7)
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redis连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
是不是和我们之前使用mysql很想,都是需要客户端和连接池,而我们redis的客户端(springboot默认使用lettuce客户端,基于netty实现 线程安全支持集群模式,哨兵模式,管道模式)我们就可以当成是spring-boot-starter-data-redis,springboot帮我集成好了的,连接池就是下面这个,当然如果你不在yml里面配置如下是不会启用的
spring:
redis:
host: 127.0.0.1
# 在redis中没有设置密码可以不填
password: root
# 使用的是第几个redis存储空间
database: 0
# 默认使用lettuce客户端
lettuce:
pool:
# 最大连接数
max-idle: 16
# 并发量
max-active: 32
# 最小连接数
min-idle: 8
可以看到我们这些配置与mysql有略微的不同,那就是多了一个database,还有可以不输入密码(如果你自己的服务器上的一定要设置密码,小心被攻击),当你拥有一个windows的redis可视化界面之后你就会发现
他其实是一共有15个的存储空间,每一个相互隔离(分别代表0-15,我没有截全)
接下来我们就可以测试一下啦
首先我们应该如何使用呢?
在mysql中我们需要写语句,然而redis完全不一样,我们可以使用springboot默认给我们的RedisTemplate(就像springboot给我们对于mysql也有一个template一样)里面有基础的操作
redis有最基本的5个类型分别是:String,List,set,sorted set,哈希,我们先从String开始
String类型
其实我们在使用String类型的时候可以把他想象成java中的hashmap都是由key和value的(可以说所有的类型基本都是这样)
如何使用String类型的api呢?
如何穿key和value呢?
如何取呢?
代码如下
@SpringBootTest
public class StringType {
@Resource
private RedisTemplate redisTemplate;
@Test
public void testInsert(){
//存
redisTemplate.opsForValue().set("hello","redis");
//取
Object hello = redisTemplate.opsForValue().get("hello");
System.out.println(hello);
}
}
最后运行的结果就是"redis",但是我们取客户端里看会发现一个很明显的问题,那就是看不懂里面的东西,那可怎么办呢?这下这个客户端还不如没有呢!!!
所以我们就需要配置一下这个模板的序列化了
当然我们使用string类型的时候传入会有序列化问题,那么其他类型的肯定也会有问题所以直接一起配置了吧
@Configuration
public class RedisConfig {
@Resource
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 连接工厂
redisTemplate.setConnectionFactory(this.factory);
// 序列化配置
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// value值的序列化采用FastJsonRedisSerializer
redisTemplate.setValueSerializer(objectJackson2JsonRedisSerializer);
// hash值的序列化采用FastJsonRedisSerializer的方式
redisTemplate.setHashValueSerializer(objectJackson2JsonRedisSerializer);
// key的序列化采用StringRedisSerializer
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
// hash的key的序列化采用StringRedisSerializer的方式
redisTemplate.setHashKeySerializer(objectJackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(RedisSerializer.string());
return redisTemplate;
}
}
配置完毕之后运行可能会有如下的一个坑:
Failed to instantiate [org.springframework.data.redis.core.RedisTemplate]: Factory method 'redisTemplate' threw exception; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/util/JacksonFeature
与
java: 无法访问com.fasterxml.jackson.databind.JavaType
找不到com.fasterxml.jackson.databind.JavaType的类文件
这个时候我们只需要再映入一个相关的jar包即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
就可以运行了,最后我们就可以看到我们客户端显示出来了正常的json串
其实我们直接这样存进去是永久的,他会永远存在我们的redis中,但是我们一般会用他来缓存数据(或者一些有限制时间的数据),内从占用过多不久得不偿失了吗?
答:所以他就有一个非常关键的东西,那就是这个数据在内存中的存活时间(以后往redis里面存数据的时候千万不要忘了设置存活时间,不然会有意向不到的后果,redis中数据到一定的容量后可是会随机清数据的哦)
如下操作:
/**
* 设置过期时间
*/
@Test
public void testEXP(){
redisTemplate.opsForValue().set("hello1","redis", 1,TimeUnit.MINUTES);
Object hello1 = redisTemplate.opsForValue().get("hello1");
System.out.println(hello1);
}
还是这个api,其实它后面还是可以加时间的,这样他就会覆盖之前的key成为新的有存活时间的数据了(其实我们就可以用他的这个特性,做一些例如短信验证的东西,到达时间验证码就没用了)
Hash类型
当你是用这个list类型的时候,可以这么向,把key就当成HashMap的key,然后value呢就是一个新的Hashmap就如下面举个例子
HashMap<String, HashMap<String, Object>> stringHashMapHashMap = new HashMap<>();
使用起来呢就是这样的如下(我们新建一个user来放入其中)
User user = new User();
user.setName("ll");
user.setSex("男");
user.setAge(18);
//可以把这个HashMap当作是存入redis中的数据(List类型)
HashMap<String, HashMap<String, Object>> stringHashMapHashMap = new HashMap<>();
//这个可以当作是这个redis中的值
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
//我们新建了一个user,然后将其放入到一个HashMap中(现在就把他当作是最后存入redis中的value)
stringObjectHashMap.put("user",user);
//最后我们组成了类似redis中的List类型
stringHashMapHashMap.put("hello1",stringObjectHashMap);
如上的操作我们新建了一个user,然后放入一个key为user的HashMap中,
存(第一种)
之后我们将将其翻译成redis中的存储操作
@SpringBootTest
public class ListType {
@Resource
private RedisTemplate redisTemplate;
@Test
public void testSave(){
User user = new User();
user.setName("ll");
user.setSex("男");
user.setAge(18);
redisTemplate.opsForHash().put("hello","user",user);
Object o = redisTemplate.opsForHash().get("hello", "user");
System.out.println(o);
}
}
可以看到这里取值的时候就跟String有很大的差距了,居然要放俩个参数进去,为什么要这样呢?
我们可以看到我们放进去的时候是先放key 也就是 hello ,然后放value就是 "user" 与 user,假如说我们就使用hello这个可以取查,别看现在我们只有一个马上能确定,但是实际上,里面可以还会存很多的key不一样的value比如value换成”user1“值是另一个user,所以我们需要再加上一个对应的key才能准确找到他
取(第一种)
最后在取出来的时候踩了一个坑就是我直接将这个来转成User其实是不能这样操作的
User o = (User)redisTemplate.opsForHash().get("hello", "user");
会报如下错误
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.lc.redis.entity.User
我们应该使用一个json转换的工具包将其转换出来这边我使用了Gson
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
最后存取的代码如下:
@SpringBootTest
public class ListType {
@Resource
private RedisTemplate redisTemplate;
@Test
public void testSave(){
User user = new User();
user.setName("ll");
user.setSex("男");
user.setAge(18);
// HashMap<String, HashMap<String, Object>> stringHashMapHashMap = new HashMap<>();
// HashMap<String, Object> stringObjectHashMap = new HashMap<>();
// stringObjectHashMap.put("user",user);
// stringHashMapHashMap.put("hello1",stringObjectHashMap);
redisTemplate.opsForHash().put("user:1","user",user);
Object o = redisTemplate.opsForHash().get("user:1", "user");
Gson gson = new Gson();
String json = gson.toJson(o);
User user1 = gson.fromJson(json, User.class);
System.out.println(user1.getSex());
}
}
那么问题来了,他怎么设置过期时间呢?
我看了一下api发现并没有,所以我只能根据key来设置他的时间,果然不出我所料加上如下操作即可(我姑且猜测一下,估计所有的key都可以用这个方法来设置时间,或者重新更新时间)
redisTemplate.expire("hello",1, TimeUnit.MINUTES);
存取(第二种)
就是直接扔一个map进去,取得时候我们可以直接根据key来取这个map
@Test
public void testSave2(){
User user = new User();
user.setName("ll");
user.setSex("男");
user.setAge(18);
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("name","xw");
stringObjectHashMap.put("age",18);
stringObjectHashMap.put("sex","男");
redisTemplate.opsForHash().putAll("user:2",stringObjectHashMap);
Object o = redisTemplate.opsForHash().entries("user:2");
System.out.println(o);
}
他取的时候密度更大一些,可以取得整个对象(可以用来存登录的用户信息,比Stirng类型存储用户数据,使用的空间更少一点,而且更灵活)。
List类型
你可以完全把他存取的时候想象成一个隧道,可以从左边存入,右边取,从右边存入,从左边取,所以他也可以当消息队列来操作,当然跟真正做消息队列的第三方软件还是有差距的
如果你真的把他当消息队列来使用,你必须自己规定好他的使用规则(存取),取的时候你需要一直使用死循环来操作,才能存入数据之后立即取,如下就是最简单的使用方式
@SpringBootTest
public class ListType {
@Resource
private RedisTemplate redisTemplate;
@Test
public void testList(){
redisTemplate.opsForList().leftPush("user:3",1);
Object o = redisTemplate.opsForList().rightPop("user:3");
System.out.println(o);
}
}
当使用取的方法的时候,他会将值取出来并且删除。
Set类型
set的特性就是,唯一,无序,可以做交集
根据这几个特性,我们可以看得出,它可以做到对文章进行关注的业务(可以看到这个文章被谁关注过,也可以看自己是否关注过这个文章),可以做一写共共同关注。
关注,查看被那些人关注,共同关注 的demo
@SpringBootTest
public class SetType {
@Resource
private RedisTemplate redisTemplate;
/**
* 关注
*/
@Test
public void like(){
//可以根据如下api查询当前用户是否点赞,如果点赞了就可以取消点赞,如果没有就添加点赞
Boolean member = redisTemplate.opsForSet().isMember("article:1", 1);
if(member){
redisTemplate.opsForSet().remove("article:1",1);
}else {
redisTemplate.opsForSet().add("article:1", 1);
}
System.out.println(member);
}
/**
* 查看被那些人关注
*/
@Test
public void MutualFriends(){
//假设value的用户id
redisTemplate.opsForSet().add("article:1", 1);
redisTemplate.opsForSet().add("article:1",2);
Set members = redisTemplate.opsForSet().members("article:1");
System.out.println(members);
}
/**
* 共同关注
*/
@Test
public void look(){
redisTemplate.opsForSet().add("article:2", 1);
redisTemplate.opsForSet().add("article:2",3);
Set intersect = redisTemplate.opsForSet().intersect("article:2", "article:1");
System.out.println(intersect);
}
}
Zset类型
他其实跟Set很像,只不过他是多加了一个值就是分数(score),可以跟据分数来排序(一般我们可以用他来做排行榜)
@SpringBootTest
public class ZSetType {
@Resource
private RedisTemplate redisTemplate;
/**
* 排名
*/
@Test
public void testSearch(){
//将其之前的关注数量放入ZSet里面当score
redisTemplate.opsForZSet().add("user:lc:ranking","arcticle:1",redisTemplate.opsForSet().members("article:1").size());
redisTemplate.opsForZSet().add("user:lc:ranking","arcticle:2",3);
redisTemplate.opsForZSet().add("user:lc:ranking","arcticle:3",6);
redisTemplate.opsForZSet().add("user:lc:ranking","arcticle:4",5);
Set range = redisTemplate.opsForZSet().range("user:lc:ranking", 0, 5);
System.out.println(range);
}
/**
* 添加点赞次数
*/
@Test
public void testAdd(){
redisTemplate.opsForZSet().incrementScore("user:lc:ranking","arcticle:3",1);
Set range = redisTemplate.opsForZSet().rangeWithScores("user:lc:ranking", 0, 5);
System.out.println(range);
}
}
redisTemplate.opsForZSet().add("?","?",?);
上面三位参数分别是key,value,socore
Set range = redisTemplate.opsForZSet().rangeWithScores("user:lc:ranking", 0, 5);
最后range出的就是0到5的分数排数(这边分数可以想象成带点赞数量等...)
redisTemplate.opsForZSet().incrementScore("user:lc:ranking","arcticle:3",1);
这个就是给对应的key,value增加一个点赞量
特别鸣谢小林coding
转载自:https://juejin.cn/post/7314546931862667283