• 【编程不良人】Redis后端实战学习笔记02---持久化机制(RDB/AOF)、Java操作Redis


    1. 持久化机制

    视频链接:8.Redis的持久化机制之快照持久化(一)_哔哩哔哩_bilibili

    client redis[内存] -----> 内存数据---数据持久化-->磁盘,即:内存 to 硬盘

    Redis官方提供了两种不同的持久化方法来将数据存储到硬盘里面分别是:

    • 快照(Snapshot) / RDB

    • AOF (Append Only File) 只追加日志文件

    1.1 快照(Snapshot)/RDB

    1.1.1 特点

            这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是redis默认开启的持久化方式,保存的文件是以.rdb形式结尾的文件,因此这种方式也称之为RDB方式

    1.1.2 快照生成方式

    • 客户端方式:BGSAVE 和 SAVE指令

    • 服务器配置自动触发

     # 1.客户端方式之BGSAVE
     - a.客户端可以使用BGSAVE命令来创建一个快照,当接收到客户端的BGSAVE命令时,redis会调用fork来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求。
         
        fork:当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类unix系统中创建子进程的操作会进行优化:在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务(不会阻塞Redis服务,同时还能保证快照的创建)。

     # 2.客户端方式之SAVE
     - b.客户端还可以使用SAVE命令来创建一个快照,接收到SAVE命令的redis服务器在快照创建完毕之前,将不再响应任何其他的命令

           注意:SAVE命令并不常用,使用SAVE命令在快照创建完毕之前,redis处于阻塞状态,无法对外服务。

     # 3.服务器配置方式之满足配置自动触发
     - 如果用户在redis.conf中设置了save配置选项,redis会在save选项条件满足之后自动触发一次BGSAVE命令,如果设置多个save配置选项,当任意一个save配置选项条件满足,redis也会触发一次BGSAVE命令。

     # 4.服务器接收客户端shutdown指令
     - 当redis通过shutdown指令接收到关闭服务器的请求时,会执行一个save命令,阻塞所有的客户端,不再执行客户端执行发送的任何命令,并且在save命令执行完毕之后关闭服务器。

    1.1.3 配置生成快照名称和位置

     # 1.修改生成快照名称
     - dbfilename dump.rdb
     ​
     # 2.修改生成位置
     - dir ./

           总结:RDB持久化(记录数据)只能保存某一时刻的数据,如果在下一次保存时间还未到来前出现宕机等情况,就会导致该时刻后的数据丢失,为此提出了AOF持久化。

    1.2 只追加日志文件(AOF)

    视频链接:9.Redis的持久化机制之AOF持久化(一)_哔哩哔哩_bilibili

    1.2.1 特点

           这种方式可以将所有客户端执行的写命令记录到日志文件中,AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化,因此只要redis从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集(可做到丢失1秒数据甚至不丢失数据,记录写命令,以日志的形式保存到aof文件中,数据状态为二进制)。

    1.2.2 开启AOF持久化

    在redis的默认配置中AOF持久化机制是没有开启的,需要在配置中开启:

     # 1.开启AOF持久化
     - a.修改 appendonly yes 开启持久化(默认为no)
     - b.修改 appendfilename "appendonly.aof" 指定生成文件名称(默认名称为appendonly.aof)

    1.2.3 日志同步频率修改

     # 1.修改日志同步频率(默认每秒写入)
     - 修改appendfsync everysec|always|no 指定

     # 1.always 【谨慎使用】
     - 说明: 每个redis写命令都要同步写入硬盘,严重降低redis速度
     - 解释: 如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制;
     - 注意: 转盘式硬盘在这种频率下200~500左右个命令/s ; 固态硬盘(SSD)几百万个命令/s;
     - 警告: 使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的写入放大问题,导致将固态硬盘的寿命从原来的几年降低为几个月。
     ​
     # 2.everysec 【推荐】
     - 说明: 每秒执行一次同步显式的将多个写命令同步到磁盘
     - 解释:为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据。
     ​
     # 3.no  【不推荐】
     - 说明: 由操作系统决定何时同步 
     - 解释:最后使用no选项,将完全由操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响,但是系统崩溃时,会丢失不定数量的数据,甚至全部数据;另外,如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时(一次需要大量写入aof,磁盘处理不过来),redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢。

           总结:持久化机制默认优先执行AOF(数据丢失少,可通过不执行SAVE或BGSAVE来停止RDB持久化),且AOF和RDB可同时生效。

    1.2.4 AOF文件的重写

    视频链接:10.Redis的持久化机制之AOF文件的重写(三)_哔哩哔哩_bilibili

    1. AOF带来的问题

           AOF的方式也同时带来了另一个问题:持久化文件会变的越来越大,例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的,因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件Redis提供了AOF重写(ReWriter)机制。

    2. AOF重写

           用来在一定程度上减小AOF文件的体积

    3. 触发重写方式

     # 1.客户端方式触发重写
     - 执行BGREWRITEAOF命令  不会阻塞redis的服务
     ​
     # 2.服务器配置方式自动触发
     - 配置redis.conf中的auto-aof-rewrite-percentage选项 参加下图↓↓↓
     - 如果设置auto-aof-rewrite-percentage值为100和auto-aof-rewrite-min-size 64mb,并且启用的AOF持久化时,那么当AOF文件体积大于64M,并且AOF文件的体积比上一次重写之后体积大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将auto-aof-rewrite-percentage设置为更大(100:1倍、200:2倍......)。
     ​
     64MB---》20MB---》40MB---》18MB---》36MB---》26MB---》52MB

    4. 重写原理(文件的替换)

           注意:重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件这点和快照有点类似。

     # 重写流程
     - 1. redis调用fork,现在有父子两个进程,子进程根据内存中的数据状态写入数据库快照,往临时文件中写入重建数据库状态的命令。
     - 2. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来,这样就能保证如果子进程重写失败的话并不会出问题。
     - 3. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程,然后父进程把缓存的写命令也写入到临时文件。
     - 4. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。

    1.3 持久化总结

           两种持久化方案既可以同时使用(aof),又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定。

           无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)。

    2. Java操作Redis

    视频链接:11.Java操作Redis服务_哔哩哔哩_bilibili

    2.1 环境准备

          新建普通的java maven项目,archetype选择quickstart,main下新建resources目录。

    2.1.1 引入依赖

     
     
       redis.clients
       jedis
       2.9.0
     

    2.1.2 创建jedis对象测试redis连接

     package com.study.test;
     ​
     import redis.clients.jedis.Jedis;
     ​
     import java.util.Set;
     ​
     /**
      * @ClassName TestRedis
      * @Description 测试Redis连接
      * @Author Jiangnan Cui
      * @Date 2022/9/11 13:31
      * @Version 1.0
      */
     public class TestRedis {
         public static void main(String[] args) {
             // 创建jedis客户端对象
             Jedis jedis = new Jedis();// 不指定时使用本机地址,端口号默认为6379
             //Jedis jedis = new Jedis("192.168.1.7",6379);
     ​
             // 选择库,默认使用0号库
             jedis.select(0);
     ​
             // 获取redis中所有key信息
             Set keys = jedis.keys("*");
             keys.forEach(key-> System.out.println("key = " + key));
     ​
             // 操作相关
             // 1.清空当前库
             jedis.flushDB();
             // 2.清空所有库
             jedis.flushAll();
     ​
             // 释放资源
             jedis.close();
         }
     }
     ​

    注意:

    • redis服务必须关闭防火墙

    • redis服务必须开启远程连接

    未启动redis-server.exe(开启远程连接)时报错:

     Exception in thread "main" redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
         at redis.clients.jedis.Connection.connect(Connection.java:207)
         at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:93)
         at redis.clients.jedis.Connection.sendCommand(Connection.java:126)
         at redis.clients.jedis.BinaryClient.select(BinaryClient.java:176)
         at redis.clients.jedis.BinaryJedis.select(BinaryJedis.java:522)
         at com.study.test.TestRedis.main(TestRedis.java:21)
     Caused by: java.net.SocketTimeoutException: connect timed out
         at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
         at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:81)
         at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:476)
         at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:218)
         at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:200)
         at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:162)
         at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:394)
         at java.net.Socket.connect(Socket.java:606)
         at redis.clients.jedis.Connection.connect(Connection.java:184)
         ... 5 more

    正常启动后,idea控制台输出0号库的所有key信息。

    2.2 操作key相关API

     package com.study.test;
     ​
     import org.junit.After;
     import org.junit.Before;
     import org.junit.Test;
     import redis.clients.jedis.Jedis;
     ​
     /**
      * @ClassName TestKey
      * @Description TODO
      * @Author Jiangnan Cui
      * @Date 2022/9/11 13:52
      * @Version 1.0
      */
     public class TestKey {
         private Jedis jedis;
     ​
         @Before
         public void before(){
             this.jedis = new Jedis("192.168.1.7",6379);
         }
     ​
         @After
         public void after(){
             jedis.close();
         }
     ​
         /**
          * 测试key相关
          */
         @Test
         public void testKeys(){
             // 删除一个key
             jedis.del("name");
             // 删除多个key
             //jedis.del("name","age");
             
             // 判断一个key是否存在
             Boolean name = jedis.exists("name");
             System.out.println("name = " + name);
             
             //设置key的超时时间
             Long bir = jedis.expire("bir", 100);
             System.out.println("bir = " + bir);// 返回1表示未超时
     ​
             // 查看超时时间
             Long bir1 = jedis.ttl("bir");
             System.out.println("bir1 = " + bir1);
     ​
             // 获取一个随机key
             String s = jedis.randomKey();
             System.out.println("s = " + s);
     ​
             // 修改key名称
             String rename = jedis.rename("age", "newAge");
             System.out.println("rename = " + rename);
     ​
             // 查看key对应值的类型
             String newAge = jedis.type("newAge");
             System.out.println("newAge = " + newAge);
             
             // ...
         }
     }

    2.3 操作String相关API

     //测试String相关
         @Test
         public void testString(){
             //set
             jedis.set("name","小陈");
             //get
             String s = jedis.get("name");
             System.out.println(s);
             //mset
             jedis.mset("content","好人","address","海淀区");
             //mget
             List mget = jedis.mget("name", "content", "address");
             mget.forEach(v-> System.out.println("v = " + v));
             //getset
             String set = jedis.getSet("name", "小明");
             System.out.println(set);
     ​
             //............
         }

    2.4 操作List相关API

     @Test
     public void testList(){
     ​
         //lpush
         jedis.lpush("names1","张三","王五","赵柳","win7");
     ​
         //rpush
         jedis.rpush("names1","xiaomingming");
     ​
         //lrange
     ​
         List names1 = jedis.lrange("names1", 0, -1);
         names1.forEach(name-> System.out.println("name = " + name));
     ​
         //lpop rpop
         String names11 = jedis.lpop("names1");
         System.out.println(names11);
     ​
         //llen
         jedis.linsert("lists", BinaryClient.LIST_POSITION.BEFORE,"xiaohei","xiaobai");
     ​
         //........
     }

    2.5 操作Set的相关API

     @Test
     public void testSet(){
     ​
       //sadd
       jedis.sadd("names","zhangsan","lisi");
     ​
       //smembers
       jedis.smembers("names");
     ​
       //sismember
       jedis.sismember("names","xiaochen");
     ​
       //...
     }

    2.6 操作ZSet相关API

     @Test
     public void testZset(){
     ​
       //zadd
       jedis.zadd("names",10,"张三");
     ​
       //zrange
       jedis.zrange("names",0,-1);
     ​
       //zcard
       jedis.zcard("names");
     ​
       //zrangeByScore
       jedis.zrangeByScore("names","0","100",0,5);
     ​
       //..
     }

    2.7 操作Hash相关API

     @Test
     public void testHash(){
       //hset
       jedis.hset("maps","name","zhangsan");
       //hget
       jedis.hget("maps","name");
       //hgetall
       jedis.hgetAll("mps");
       //hkeys
       jedis.hkeys("maps");
       //hvals
       jedis.hvals("maps");
       //....
     }

    2.8 项目目录结构

    3. SpringBoot整合Redis

    视频链接:12.springboot操作redis(一)_哔哩哔哩_bilibili

    补充:

           Spring Boot Data(Spring Data,操作数据框架) Redis中提供了RedisTemplate和StringRedisTemplate,其中,StringRedisTemplate是RedisTemplate的子类,两个方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate中的两个泛型都是Object,意味着存储的key和value都可以是一个对象,而StringRedisTemplate的两个泛型都是String,意味着StringRedisTemplate的key和value都只能是字符串。

    RedisTemplate (Object,Object)  自动序列化  自动反序列化

    StringRedisTemplate (String,String)

    注意: 使用RedisTemplate默认是将对象序列化到Redis中,所以放入的对象必须实现对象序列化接口

    3.1 环境准备

           新建Spring Initializr项目redis-spring-boot,引入spring-boot-devtools、lombok、spring-boot-starter-test、spring-boot-starter-web对应依赖。

    3.1.1 pom.xml中引入依赖

     
     
         org.springframework.boot
         spring-boot-starter-data-redis
     

    刷新Maven!

    3.1.2 application.properties配置redis

     # redis配置
     spring.redis.host=localhost
     spring.redis.port=6379
     spring.redis.database=0

    3.2 使用StringRedisTemplate和RedisTemplate

    视频链接:13.springboot操作redis(二)_哔哩哔哩_bilibili

    3.2.1 测试StringRedisTemplate

     package com.study.test;
     ​
     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.DefaultTypedTuple;
     import org.springframework.data.redis.core.StringRedisTemplate;
     import org.springframework.data.redis.core.ZSetOperations;
     ​
     import java.util.*;
     import java.util.concurrent.TimeUnit;
     ​
     ​
     /**
      * @ClassName TestStringRedisTemplate
      * @Description TODO
      * @Author Jiangnan Cui
      * @Date 2022/9/12 11:32
      * @Version 1.0
      */
     @SpringBootTest// 此处不需要引入@RunWith注解了
     // 针对@RunWith注解找不到的问题:https://blog.csdn.net/u014641168/article/details/125405225
     public class TestStringRedisTemplate {
         // 注入StringRedisTemplate
         @Autowired
         private StringRedisTemplate stringRedisTemplate;
     ​
         /**
          * 操作redis中的字符串:opsForValue()
          */
         @Test
         public void testString(){
             stringRedisTemplate.opsForValue().set("name","zhangsan");
             String name = stringRedisTemplate.opsForValue().get("name");
             System.out.println("name = " + name);
     ​
             stringRedisTemplate.opsForValue().set("code","1234",60, TimeUnit.SECONDS);
             Long code = stringRedisTemplate.getExpire("code");
             System.out.println("code = " + code);
     ​
             Integer append = stringRedisTemplate.opsForValue().append("code", "OneTwoThreeFour");
             System.out.println("append = " + append);
         }
     ​
         /**
          * 操作redis中key相关
          */
         @Test
         public void testKey(){
             // 删除一个key
     //        Boolean name = stringRedisTemplate.delete("name");
     //        System.out.println("name = " + name);
     ​
             // 判断key是否存在
             Boolean name1 = stringRedisTemplate.hasKey("name");
             System.out.println("name1 = " + name1);
     ​
             // 查看所有key
             Set keys = stringRedisTemplate.keys("*");
             keys.forEach(key-> System.out.println("key = " + key));
     ​
             // 获得超时时间
             Long name2 = stringRedisTemplate.getExpire("name");
             System.out.println("name2 = " + name2);
         }
     ​
         /**
          * 操作redis中list类型:opsForList()
          */
         @Test
         public void testList(){
             // 添加元素
     //        stringRedisTemplate.opsForList().leftPush("names","zhangsan");
             // 添加多个元素
     //        stringRedisTemplate.opsForList().leftPushAll("names","lisi","wangwu");
             // 以集合形式进行添加元素
     //        List names = new ArrayList<>();
     //        names.add("xiaoming");
     //        names.add("xiaohua");
     //        stringRedisTemplate.opsForList().leftPushAll("names",names);
             
             // 遍历list
             List names1 = stringRedisTemplate.opsForList().range("names", 0, -1);
             for (String s : names1) {
                 System.out.println("s = " + s);
             }
             
             // 截取指定区间的list,无返回值
             stringRedisTemplate.opsForList().trim("names",1,3);
             List names = stringRedisTemplate.opsForList().range("names", 0, -1);
             names.forEach(name-> System.out.println("name = " + name));
         }
     ​
         /**
          * 操作redis中set类型:opsForSet()
          */
         @Test
         public void testSet(){
             // 添加元素
             Long add = stringRedisTemplate.opsForSet().add("sets", "zhangsan", "zhangsan", "lisi", "wangwu");
             System.out.println("add = " + add);
             // 遍历元素
             for (String set : stringRedisTemplate.opsForSet().members("sets")) {
                 System.out.println("set = " + set);
             }
     ​
             // 获得元素个数
             Long sets = stringRedisTemplate.opsForSet().size("sets");
             System.out.println("sets = " + sets);
         }
     ​
         /**
          * 操作redis中Zset类型: opsForZSet()
          */
         @Test
         public void testZset(){
             // 添加单个元素
             stringRedisTemplate.opsForZSet().add("zsets","小明",80);
     ​
             //以集合形式添加元素
             Set> tuples = new HashSet<>();
             for (int i = 0; i < 5; i++) {
                 DefaultTypedTuple tuple = new DefaultTypedTuple<>("张三" + i, 1D + i);
                 tuples.add(tuple);
             }
             stringRedisTemplate.opsForZSet().add("zsets",tuples);
     ​
             // 遍历value
             Set zsets = stringRedisTemplate.opsForZSet().range("zsets", 0, -1);
             zsets.forEach(value-> System.out.println("value = " + value));
     ​
             System.out.println("---");
     ​
             // 遍历value-score
             Set> zsets1 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("zsets", 0, 100);
             for (ZSetOperations.TypedTuple stringTypedTuple : zsets1) {
                 System.out.println("stringTypedTuple = " + stringTypedTuple);
                 System.out.println(stringTypedTuple.getValue());
                 System.out.println(stringTypedTuple.getScore());
             }
         }
     ​
         /**
          * 操作redis中Hash类型:
          */
         @Test
         public void testHash(){
             // 存入一个元素
             stringRedisTemplate.opsForHash().put("hashs","name","张三");
     ​
             // 以集合形式存入元素
             Map map = new HashMap<>();
             map.put("age","20");
             map.put("bir","2012-12-12");
             stringRedisTemplate.opsForHash().putAll("hashs",map);
     ​
             // 获取一个值
             String val = (String) stringRedisTemplate.opsForHash().get("hashs", "name");
             System.out.println("val = " + val);
     ​
             // 获取多个值
             List objects = stringRedisTemplate.opsForHash().multiGet("hashs", Arrays.asList("name", "age"));
             objects.forEach(vals-> System.out.println("vals = " + vals));
     ​
             // 获取所有值
             List hashs = stringRedisTemplate.opsForHash().values("hashs");
             hashs.forEach(hash-> System.out.println("hash = " + hash));
     ​
             // 获取所有key
             Set hashs1 = stringRedisTemplate.opsForHash().keys("hashs");
             hashs1.forEach(hash-> System.out.println("hash = " + hash));
         }
     ​
     }
     ​ 
    

    3.2.2 测试RedisTemplate

    视频链接:14.springboot操作redis(三)_哔哩哔哩_bilibili

    • 新建对象User

     package com.study.entity;
     ​
     import lombok.AllArgsConstructor;
     import lombok.Data;
     import lombok.NoArgsConstructor;
     import lombok.experimental.Accessors;
     ​
     import java.io.Serializable;
     import java.util.Date;
     ​
     /**
      * @ClassName User
      * @Description TODO
      * @Author Jiangnan Cui
      * @Date 2022/9/12 15:37
      * @Version 1.0
      */
     @Data
     @NoArgsConstructor
     @AllArgsConstructor
     @Accessors(chain = true)
     public class User implements Serializable {
         private String id;
         private String name;
         private Integer age;
         private Date bir;
     }
     ​
    • 测试RedisTemplatekey、value均会被序列化

     package com.study.test;
     ​
     import com.study.entity.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;
     import org.springframework.data.redis.serializer.RedisSerializer;
     import org.springframework.data.redis.serializer.StringRedisSerializer;
     ​
     import java.util.Date;
     import java.util.UUID;
     ​
     /**
      * @ClassName TestRedisTemplate
      * @Description TODO
      * @Author Jiangnan Cui
      * @Date 2022/9/12 15:38
      * @Version 1.0
      */
     @SpringBootTest
     public class TestRedisTemplate {
         // 注入RedisTemplate
         @Autowired
         private RedisTemplate redisTemplate;
     ​
         /**
          * 测试RedisTemplate
          */
         @Test
         public void testRedisTemplate(){
             // 获取redis序列化
             RedisSerializer defaultSerializer = redisTemplate.getDefaultSerializer();
             System.out.println("defaultSerializer = " + defaultSerializer);
             //defaultSerializer = org.springframework.data.redis.serializer.JdkSerializationRedisSerializer@4c731956
     ​
             /**
              * redisTemplate对象中 key 和 value 的序列化都是 JdkSerializationRedisSerializer
              *      key: string
              *      value: object
              *      修改默认key序列化方案 :  key  StringRedisSerializer
              */
             // 修改key采用string序列化方式
             redisTemplate.setKeySerializer(new StringRedisSerializer());
             // 修改hash key采用string序列化方式
             redisTemplate.setHashKeySerializer(new StringRedisSerializer());
     ​
     ​
             // User必须序列化
             User user = new User()
                     .setId(UUID.randomUUID().toString())
                         .setName("小崔")
                             .setAge(18)
                                 .setBir(new Date());
             redisTemplate.opsForValue().set("user",user);
             Object user1 = redisTemplate.opsForValue().get("user");
             System.out.println("user1 = " + user1);
     ​
             redisTemplate.opsForList().leftPush("list",user);
     ​
             redisTemplate.opsForSet().add("set",user);
     ​
             redisTemplate.opsForZSet().add("zset",user,100);
     ​
             // hash有两个key,默认均为jdk序列化方式
             redisTemplate.opsForHash().put("hash","name",user);
         }
     }
     ​

    key采用jdk序列化:

    key采用string序列化:

    添加后获取key结果:

    3.2.3 Spring Data提供的Bound API

    视频链接:15.springboot操作redis(四)_哔哩哔哩_bilibili

     package com.study.test;
     ​
     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.*;
     import org.springframework.data.redis.serializer.StringRedisSerializer;
     ​
     import javax.security.auth.callback.NameCallback;
     import java.util.List;
     ​
     /**
      * @ClassName TestBoundAPI
      * @Description TODO
      * @Author Jiangnan Cui
      * @Date 2022/9/12 16:42
      * @Version 1.0
      */
     @SpringBootTest
     public class TestBoundAPI {
         @Autowired
         private RedisTemplate redisTemplate;
     ​
         @Autowired
         private StringRedisTemplate stringRedisTemplate;
     ​
         /**
          * Spring Data为了方便我们对redis进行更加友好的操作,提供了Bound API简化操作
          *   1.针对于日后处理key value 都是 String 使用 StringRedisTemplate
          *   2.针对于日后处理的key value 存在对象 使用 RedisTemplate
          *   3.针对于同一个key多次操作可以使用boundXXxOps() Value List Set Zset Hash的api 简化书写
          */
         @Test
         public void testBound(){
             redisTemplate.setKeySerializer(new StringRedisSerializer());
             redisTemplate.setHashKeySerializer(new StringRedisSerializer());
     ​
             // 通常都是将一个key进行多次绑定后操作,操作比较麻烦
             stringRedisTemplate.opsForValue().set("name","小崔");
             stringRedisTemplate.opsForValue().append("name","加油!");
             String name = stringRedisTemplate.opsForValue().get("name");
             System.out.println("name = " + name);
     ​
             // 对string类型key进行绑定,后续所有操作都是基于这个key的操作
             BoundValueOperations nameValueOperation = stringRedisTemplate.boundValueOps("name");
             nameValueOperation.set("小猪猪");
             nameValueOperation.append("棒棒哒!");
             String s = nameValueOperation.get();
             System.out.println("s = " + s);
     ​
             // 对list类型key进行绑定
             BoundListOperations boundListOperations = stringRedisTemplate.boundListOps("list");
             boundListOperations.leftPushAll("熊大","熊二","光头强");
             List lists = boundListOperations.range(0, -1);
             lists.forEach(list -> System.out.println("list = " + list));
     ​
             // 对set类型key进行绑定
             BoundZSetOperations set = stringRedisTemplate.boundZSetOps("set");
     ​
             // 对zset类型进行绑定
             BoundZSetOperations stringBoundZSetOperations = stringRedisTemplate.boundZSetOps("zset");
     ​
             // 对hash类型key进行绑定
             BoundHashOperations boundHashOperations = stringRedisTemplate.boundHashOps("hash");
         }
     }

    附视频中测试代码:

     @Autowired
     private StringRedisTemplate stringRedisTemplate;  //对字符串支持比较友好,不能存储对象
     ​
     @Autowired
     private RedisTemplate redisTemplate;  //存储对象
     ​
     @Test
     public void testRedisTemplate(){
         System.out.println(redisTemplate);
         //设置redistemplate值使用对象序列化策略
         redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());//指定值使用对象序列化
         //redisTemplate.opsForValue().set("user",new User("21","小黑",23,new Date()));
         User user = (User) redisTemplate.opsForValue().get("user");
         System.out.println(user);
     //      Set keys = redisTemplate.keys("*");
     //      keys.forEach(key -> System.out.println(key));
         /*Object name = redisTemplate.opsForValue().get("name");
         System.out.println(name);*/
     ​
         //Object xiaohei = redisTemplate.opsForValue().get("xiaohei");
         //System.out.println(xiaohei);
         /*redisTemplate.opsForValue().set("name","xxxx");
         Object name = redisTemplate.opsForValue().get("name");
         System.out.println(name);*/
         /*redisTemplate.opsForList().leftPushAll("lists","xxxx","1111");
         List lists = redisTemplate.opsForList().range("lists", 0, -1);
         lists.forEach(list-> System.out.println(list));*/
     }
     ​
     ​
     //key的绑定操作 如果日后对某一个key的操作及其频繁,可以将这个key绑定到对应redistemplate中,日后基于绑定操作都是操作这个key
     //boundValueOps 用来对String值绑定key
     //boundListOps 用来对List值绑定key
     //boundSetOps 用来对Set值绑定key
     //boundZsetOps 用来对Zset值绑定key
     //boundHashOps 用来对Hash值绑定key
     ​
     @Test
     public void testBoundKey(){
         BoundValueOperations nameValueOperations = stringRedisTemplate.boundValueOps("name");
         nameValueOperations.set("1");
         //yuew
         nameValueOperations.set("2");
         String s = nameValueOperations.get();
         System.out.println(s);
     }
     ​
     //hash相关操作 opsForHash
     @Test
     public void testHash(){
         stringRedisTemplate.opsForHash().put("maps","name","小黑");
         Object o = stringRedisTemplate.opsForHash().get("maps", "name");
         System.out.println(o);
     }
     ​
     //zset相关操作 opsForZSet
     @Test
     public void testZSet(){
         stringRedisTemplate.opsForZSet().add("zsets","小黑",10);
         Set zsets = stringRedisTemplate.opsForZSet().range("zsets", 0, -1);
         zsets.forEach(value-> System.out.println(value));
     }
     ​
     //set相关操作 opsForSet
     @Test
     public void testSet(){
         stringRedisTemplate.opsForSet().add("sets","xiaosan","xiaosi","xiaowu");
         Set sets = stringRedisTemplate.opsForSet().members("sets");
         sets.forEach(value-> System.out.println(value));
     }
     ​
     //list相关的操作opsForList
     @Test
     public void testList(){
         // stringRedisTemplate.opsForList().leftPushAll("lists","张三","李四","王五");
         List lists = stringRedisTemplate.opsForList().range("lists", 0, -1);
         lists.forEach(key -> System.out.println(key));
     }
     ​
     //String相关的操作 opsForValue
     @Test
     public void testString(){
         //stringRedisTemplate.opsForValue().set("166","好同学");
         String s = stringRedisTemplate.opsForValue().get("166");
         System.out.println(s);
         Long size = stringRedisTemplate.opsForValue().size("166");
         System.out.println(size);
     }
     ​
     //key相关的操作
     @Test
     public void test(){
         Set keys = stringRedisTemplate.keys("*");//查看所有key
         Boolean name = stringRedisTemplate.hasKey("name");//判断某个key是否存在
         stringRedisTemplate.delete("age");//根据指定key删除
         stringRedisTemplate.rename("","");//修改key的名称
         stringRedisTemplate.expire("key",10, TimeUnit.HOURS);
         //设置key超时时间 参数1:设置key名 参数2:时间 参数3:时间的单位
         stringRedisTemplate.move("",1);//移动key
     }

    3.3 项目最终目录结构

    4. Redis中应用场景说明

    视频链接:16.redis中应用场景说明_哔哩哔哩_bilibili

     1.利用redis中字符串类型完成:项目中手机验证码存储的实现(有效时间)
     ​
     2.利用redis中字符串类型完成:具有失效性业务功能,如:12306、淘宝等,离订单支付关闭还有:40分钟
     ​
     3.利用redis实现分布式集群系统中Session共享问题
       memcache:内存、数据存储上限、数据类型比较简单
       redis:内存、解决数据上限、数据类型丰富
     ​
     4.利用redis的zset类型(可排序set类型):元素 分数
       实现如:排行榜之类的功能   
              dangdang 销量排行:sales(zset) [商品id,商品销量] ......
                                             唯一标识  分数   可以取前多少区间内的数据,分页显示等
     ​
     5.利用redis实现分布式缓存(提高查询效率)
     ​
     6.利用redis存储认证之后token信息(超时特性)
       如:微信小程序、微信公众号 
          用户 openid(用户唯一标识)---> 令牌(token,解决安全问题) redis设置超时时间,时间过后失效
       
     7.利用redis解决分布式集群系统中分布式锁问题   
       jvm  1进程开启多个线程 synchronize int n=20
       jvm  1进程开启多个线程 synchronize int n=20   集群
       .....  LRA脚本
       redis 单进程 单线程 n 20 定义
                         incr -1
                         decr  1

    附:可搜索LUA脚本 redis分布式锁 进行查找

  • 相关阅读:
    无涯教程-JavaScript - EVEN函数
    IDEA这样配置Maven:让你一遍就能学会!
    实战演练 | 使用 Navicat Premium 自动运行数据库复制
    函数调用的几种方式:__cdecl、__stdcall、__fastcall、__thiscall、__clrcall、__vectorcall
    关于富文本编辑器wangeditor在vue3中的使用
    【考研数学】七. 曲线积分
    requests库的使用(一篇就够了)
    mysql练习1
    【教3妹学算法-每日1题】按字典序排在最后的子串
    小啊呜产品读书笔记001:《邱岳的产品手记-05》第9讲 产品案例分析:Hopper的“人工智能” & 第10讲 产品被抄袭了怎么办?
  • 原文地址:https://blog.csdn.net/xiaocui1995/article/details/126804429