likes
comments
collection
share

快乐Redis学习之旅

作者站长头像
站长
· 阅读数 42

我是一个小菜鸡,若我的想法有所不对,请各位大佬们随意指正,持续更新中....

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可视化界面之后你就会发现

快乐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

相关代码:罗晨/redisDemo - 码云 - 开源中国 (gitee.com)

转载自:https://juejin.cn/post/7314546931862667283
评论
请登录