SpringBoot整合Redis(端口号7000)

Spring Boot Data Redis中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是

RedisTemplate的子类,两个类中成员方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate中的两个泛型都是Object,意味着存储的key和value都可以是一个对象,而

StringRedisTemplate的两个泛型都是String,意味着StringRedisTemplate的key和value都只能是字符串。注意:使用RedisTemplate默认是将对象序列化到Redis中,所以放入的对象必须实现序列化接口

环境准备

引入依赖(spring2.0以后不适用jedis了,使用lettuce)

 

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
 配置application.yml文件
spring.redis.host=192.168.80.100
spring.redis.port=6379
spring.redis.database=0

 

 


 

创建SpringBoot项目,把以下两个勾选上

redistemplate 出现类转换异常 redistemplate泛型_spring

redistemplate 出现类转换异常 redistemplate泛型_spring_02

 

配置依赖

测试连接

创建五个包(controller,entity,dao,service,utils),

入口类

1 package com.shujia.xiao;
 2 
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 
 6 @SpringBootApplication
 7 public class RedisDemoApplication {
 8 
 9     public static void main(String[] args) {
10         SpringApplication.run(RedisDemoApplication.class, args);
11     }
12 
13 }

练习类型代码

StringRedisTemplate中的key和value都是string类型

package com.shujia.xiao.controller;

import com.shujia.xiao.entity.MytypedTuple;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.time.Duration;
import java.util.*;

@Controller
@ResponseBody
@RequestMapping("redistest")//RequestMapping不仅可以在方法上,也可以在类上,在类上时属于联级定义,有分类的作用
public class RedisApiController {

    //springboot操作redis可以使用自己提供的redis依赖,父工程中已经写好了两个类供我们使用
    //RedisTemplate和StringRedisTemplate
    //通过对象注入的方式,使用该类的对象
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @RequestMapping("test")
    public String test(){
        return "测试模版springboot";
    }


    /**
     * 对redis中数据类型做操作
     * 1.字符类型(String)
     */
    @RequestMapping("testString")
    public String testString(){
        //链式调用
        //opsForValue操作值是String类型的
        //set方法是没有返回值的
        stringRedisTemplate.opsForValue().set("name","xiaoxiao");
        stringRedisTemplate.opsForValue().set("name1","panpan");
        stringRedisTemplate.opsForValue().set("name2","tangtang");
        //设置过期时间
        stringRedisTemplate.opsForValue().set("delect","sansan",Duration.ofSeconds(10L));
        //获取值,通过key获取
        String name = stringRedisTemplate.opsForValue().get("name");
        System.out.println(name);
        //获取多个值
        //将要获取的值的key组成一个集合
        ArrayList<String> arrayList=new ArrayList<>();
        arrayList.add("name");
        arrayList.add("name1");
        arrayList.add("name2");
        List<String> strings = stringRedisTemplate.opsForValue().multiGet(arrayList);
        for (String string : strings) {
            System.out.println(string);
        }
        //一次性插入多个键值对
        HashMap<String,String> map=new HashMap<>();
        map.put("age","18");
        map.put("age1","19");
        stringRedisTemplate.opsForValue().multiSet(map);

        Long l = stringRedisTemplate.opsForValue().size("name");
        System.out.println(l);

        //追加
        stringRedisTemplate.opsForValue().append("name","yu");
        return "测试String类型成功";
    }

    /**
     *   对List类型操作
     */

    @RequestMapping("testList")
    public String testList(){
        //插入
//        stringRedisTemplate.opsForList().leftPush("id","宋亚轩");
//        stringRedisTemplate.opsForList().leftPush("id","张极");
//        stringRedisTemplate.opsForList().leftPush("id","张泽禹");
//        stringRedisTemplate.opsForList().leftPush("id","马嘉祺");
//        stringRedisTemplate.opsForList().leftPush("id","刘耀文");
        //插入多个
//        ArrayList<String> arrayList = new ArrayList<>();
//        arrayList.add("xyg.九月");
//        arrayList.add("xyg.秀豆");
//        arrayList.add("xyg.灵梦");
//        arrayList.add("xyg.酷偕");
//        arrayList.add("xyg.羲和");
//        stringRedisTemplate.opsForList().leftPushAll("list1", arrayList);
        List<String> list1 = stringRedisTemplate.opsForList().range("list1", 0, -1);
        //断言   assert list1 !=null;
        if(list1!=null){
            for (String s : list1) {
                System.out.println(s);
            }
        }

        //在某个值的左边插入
//        stringRedisTemplate.opsForList().leftPush("list1","xyg.九月","xyg.破绽");
        //右边
//        stringRedisTemplate.opsForList().rightPush("id","宋亚轩","ss");

        //保留区间元素
        stringRedisTemplate.opsForList().trim("id",0,4);
        //返回和移除指定位置的值
//        List<String> list11 = stringRedisTemplate.opsForList().leftPop("list1", 1);
//        System.out.println(list11);

        //查看指定位置的索引
        Long s1 = stringRedisTemplate.opsForList().leftPushIfPresent("id", "宋亚轩");
        System.out.println(s1);

        //最后一次出现指定值的位置
        Long l1 = stringRedisTemplate.opsForList().lastIndexOf("id", "宋亚轩");
        System.out.println(l1);

        return "测试List类型";
    }

    /**
     *
     *    对set类型做操作
     */
//元素唯一并无序
    @RequestMapping("testSet")
    public String testSet(){
//        //添加数据
//        stringRedisTemplate.opsForSet().add("set1","ss","yy","xx","jiyu");
//        //读取数据
        Set<String> set1 = stringRedisTemplate.opsForSet().members("set1");
//        for (String s : set1) {
//            System.out.println(s);
//        }
        //是否存在
        Boolean member = stringRedisTemplate.opsForSet().isMember("set1", "ss");
        System.out.println(member);

        Set<String> difference = stringRedisTemplate.opsForSet().difference(set1);
        System.out.println(difference);

        return "测试set类型成功";
    }

    /**
     *
     *    对Zset类型做操作
     */
     //ZSet是可排序的
    @RequestMapping("testZSet")
    public String testZSet(){
        //添加元素
        HashSet<ZSetOperations.TypedTuple<String>> myTypedTuples = new HashSet<>();
        //封装自己的值和分数
        myTypedTuples.add(new MytypedTuple("11",100.0));
        myTypedTuples.add(new MytypedTuple("22",110.0));
        myTypedTuples.add(new MytypedTuple("33",120.0));
        myTypedTuples.add(new MytypedTuple("44",130.0));
        stringRedisTemplate.opsForZSet().add("zset1",myTypedTuples);

        //获取ZSet的数据
        Set<String> zset1 = stringRedisTemplate.opsForZSet().range("zset1", 0, -1);
        for (String s1 : zset1) {
            System.out.println(s1);
        }
        System.out.println("=======================");
        //获取元素的同时获取分数
        Set<ZSetOperations.TypedTuple<String>> zset11 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("zset1", 100, 120);
        for (ZSetOperations.TypedTuple<String> s2 : zset11) {
            String value = s2.getValue();
            Double score = s2.getScore();
            System.out.println(value+":"+score);
        }
        System.out.println("=======================");
        //倒序遍历
        Set<String> zset12 = stringRedisTemplate.opsForZSet().reverseRangeByScore("zset1", 100.0, 130.0);
        for (String s3 : zset12) {
            System.out.println(s3);
        }
        System.out.println("=======================");

        //倒序遍历并且输出分数
        Set<ZSetOperations.TypedTuple<String>> zset13 = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores("zset1", 0, 10000);
        for (ZSetOperations.TypedTuple<String> s4 : zset13) {
            String value = s4.getValue();
            Double score = s4.getScore();
            System.out.println(value+":"+score);
        }


        return "测试ZSet类型成功";
    }

    /**
     *
     *    对hash类型进行操作
     */
    //无序
    @RequestMapping("testHash")
    public String testHash(){
        //添加多个值   排序TreeMap  不排HashMap
//        HashMap<String,String> map=new HashMap<>();
////        map.put("area","37000");
//        map.put("area1","36000");
//        map.put("area2","35000");
//        stringRedisTemplate.opsForHash().putAll("map1",map);
//        System.out.println("======================");
//        //取值
//        ArrayList<Object> arrayList = new ArrayList<>();
//        arrayList.add("area");
//        arrayList.add("area1");
//        arrayList.add("area2");
//        List<Object> map1 = stringRedisTemplate.opsForHash().multiGet("map1", arrayList);
//        for (Object o : map1) {
//            System.out.println(o);
//        }

        //判断是否存在
        Boolean aBoolean = stringRedisTemplate.opsForHash().hasKey("map1", "area");
        System.out.println(aBoolean);


        return "测试hash类型成功";
    }


    @RequestMapping("testKey")
    public String testKey(){
        //获取当前库中所有的键
        Set<String> keys = stringRedisTemplate.keys("*");
        if(keys!=null){
            for (String key : keys) {
                System.out.println(key);
            }
        }else{
            System.out.println("该库中没有键存在");
        }
        //给键设置过期时间
        //设置分钟
        Boolean fr = stringRedisTemplate.expire("fr", Duration.ofMinutes(1L));
        System.out.println(fr);
        //设置秒级别
        Boolean a1 = stringRedisTemplate.expire("a1", Duration.ofSeconds(10L));
        System.out.println(a1);
        //判断key是否存在
        Boolean k1 = stringRedisTemplate.hasKey("name");
        System.out.println(k1);
        //移动
        stringRedisTemplate.move("name",1);
//        //删除一个键
//        stringRedisTemplate.delete("name");
//        //删除多个键
//        ArrayList<String> list1=new ArrayList<>();
//        list1.add("name");
//        list1.add("age");
//        stringRedisTemplate.delete(list1);

        //查看键对应的类型
        //自学的方法:
        //1、查看方法的名字
        //2、查看方法需要传的参数类型与个数
        //3、查看返回值类型
        //4、查看源码的实现(需要你掌握java的面向对象开发的基础)
        DataType set1 = stringRedisTemplate.type("zset1");
        DataType age1 = stringRedisTemplate.type("age");
        System.out.println(age1.code());


        return "测试与redis键相关的操作";
    }

}

 在entity创建MyTypedTuple实现接口类TypedTuple

 

package com.shujia.xiao.entity;

import org.springframework.data.redis.core.ZSetOperations;

public class MytypedTuple<String> implements ZSetOperations.TypedTuple<String> {
        private String value;
        private Double score;

    public MytypedTuple(String value, double score) {
        this.value=value;
        this.score=score;
    }

    @Override
    public String getValue() {
        return value;
    }

    @Override
    public Double getScore() {
        return score;
    }

    @Override
    public int compareTo(ZSetOperations.TypedTuple<String> o) {
        return 0;
    }
}

 

 练习RedisTemplate

* RedisTemplate:Key和Object类型都是Object类型,将key和value当作对象来看待, 既然将key和value当作对象来看待,那么存储的时候,就是存一个对象,对象存储到数据库中,是一个持久化的过程,对象持久化到数据库中,需要对对象进行序列化

  • 回忆一下java中的序列化,对象的类必须要实现serializable接口,其次生成一个序列化ID
  • 在java中String类实现了serializable接口,可能在使用RedisTemplate对象的时候能够偶读取到对应的数据
  •  一定是可以的嘛?

 由于StringRedisTemplate是RedisTemplate子类,大部分的方法,两个都差不多。

@Controller
@ResponseBody
public class RedisController {

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;



    @RequestMapping("test1")
    public String test1(){
        /**
         *  分析为什么是null?
         *  首先,name我们使用StringRedisTemplate,而我们现在用RedisTemplate去读,两个类都不一样,可能读不到
         *  我们回想一下刚刚提到的一点,RedisTemplate会将key和value当作对象来看待,所以需要对key和value做序列化
         *  那么RedisTemplate就应该把我们传进去的name做序列化,”name“ --->"namexxxx",然后拿着序列化后的值去redis中找,
         *  而redis中并没有namexxxx,所以结果为null
         *
         *  String类刚刚才看的,已经实现了serializable接口,为什么不行呢?
         *  所以这里,用的应该不是 jdk自己序列化,而是redis中特有的序列化方式
         *
         *  我们使用了redis特有的序列化方式后,结果却依旧没有出来。
         *  序列化ID,再次插入一次,让两次序列化ID保持一直就可以了。
         *
         */
        /*
        如果插入的数据是字符串,用StringRedisSerializer做序列化;如果插入的
        是对象,用JdkSerializationRedisSerializer做序列化
         */
//        Object name=redisTemplate.opsForValue().get("name");
//        System.out.println(name);

        //先设置序列化方式
//        redisTemplate.setKeySerializer(new JdkSerializationRedisSerializer());

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //再插入
//        redisTemplate.opsForValue().set("sname","xsee");
        //查询
//        Object name=redisTemplate.opsForValue().get("sname");
//        System.out.println(name);

        //创建学生对象,插入到redis中
//        Student s = new Student("ss", 18);
//        redisTemplate.opsForValue().set("1001",s);
        Object o = redisTemplate.opsForValue().get("1001");
        System.out.println(o);

        //对key序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //对value序列化
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.opsForHash().put("1002","cname","wqy");
        return "测试使用RedisTemplate成功";
    }



}

 

连接池

使用它是为了避免重复的创建和销毁链接对象而消耗资源

引入依赖
<dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
</dependency>

 

线程池代码
//构建redis资源连接池配置
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        //给定个数
        poolConfig.setMaxTotal(100);

        //根据这个配置来创建redis连接池
        JedisPool jedisPool = new JedisPool(poolConfig, "192.168.80.100", 6379);

       //在连接池中构建redis连接
        Jedis jedis = jedisPool.getResource();

       //拿着这个连接操作redis
        String name1 = jedis.get("name");
        System.out.println(name1);

       //操作完后将连接归还给连接池
        jedis.close();

 

 Redis学习高级部分(重要)

Redis主从复制

主从复制

主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据

不能解决master节点出现故障的自动故障转移

主从复制架构图

redistemplate 出现类转换异常 redistemplate泛型_spring_03

 

 master接受用户请求并存储数据,从节点只存储数据,所以主节点坏了之后数据不会丢

 

搭建主从配置

1.在/usr/local/soft/redis-install/下创建三个目录代表三台机器,master,node1,node2

redistemplate 出现类转换异常 redistemplate泛型_redis_04

 

2.把redis.conf文件分别拷贝到master,node1,node2中

[root@master redis-install]# cp redis-7.0.0/redis.conf ./master/
[root@master redis-install]# cp redis-7.0.0/redis.conf ./node1/
[root@master redis-install]# cp redis-7.0.0/redis.conf ./node2/

 

redistemplate 出现类转换异常 redistemplate泛型_System_05

 

 

# 1.准备3台机器并修改配置,修改端口号,开启远程连接,配置主节点是谁
- master
    port 6379
    protected-mode no
    
- node1
    port 6380
    protected-mode no
    replicaof <masterip> <masterport> 改成下面:
replicaof 192.168.80.100 6379

- node2
    port 6381
    protected-mode no
    replicaof <masterip> <masterport>    改成下面:
replicaof 192.168.80.100 6379
# 2.启动3台机器进行测试
- cd /usr/local/soft/bigdata19/redis-install
- redis-server ./master/redis.conf
- redis-server ./node1/redis.conf
- redis-server ./node2/redis.conf

 

node1和node2启动服务后,会在master中显示同步,在master中设置值,在node1和node2中也会接收到

查看运行进程 :ps aux|grep redis

redistemplate 出现类转换异常 redistemplate泛型_redis_06

 

杀死进程:kill -9 进程号(进程号就是第一个四字数字)

redistemplate 出现类转换异常 redistemplate泛型_redis_07

 

 杀死主进程后,其它子进程会停止连接

 

Redis哨兵机制

哨兵Sentine机制

Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。简单的说哨兵就是带有自动故障转移功能的主从架构

 不能解决    单节点并发压力问题和单节点内存和磁盘物理上限

哨兵架构原理

 

redistemplate 出现类转换异常 redistemplate泛型_redis_08

在主节点被恢复之后它就不是主节点了

 Redis集群(在开始之前拍摄快照)

Redis在3.0后开始支持Cluster(模式)模式,目前redis的集群支持节点的自动发现,支持slave-master选举和容错,支持在线分片(sharding shard )等特性。reshard

PING PONG协议(心跳机制)

集群架构

redistemplate 出现类转换异常 redistemplate泛型_redis_09

 

 

集群细节

- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
- 节点的fail是通过集群中超过半数的节点检测失效时才生效.(半数机制,后面hadoop也会说到 ) 
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value

  

redistemplate 出现类转换异常 redistemplate泛型_System_10

--如果key是一样的节点,则后面需要加一个符号来区分,加了不同符号之后的物理节点是不同的
例如:name*和mena@

集群搭建

判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建redis集群时建议节点数最好为奇数,搭建集群至少需要三个主节点,三个从节点,至少需要6个节点

# 1.准备环境安装ruby以及redis集群依赖
- yum install -y ruby rubygems
# https://rubygems.org/gems/redis/versions
- gem install redis-3.3.5.gem(在d:soft/redis中)

 

#2.在、usr/local/soft/redis-install目录下创建redis-cluster文件
#在redis-cluster文件下创建7个目录

  

redistemplate 出现类转换异常 redistemplate泛型_System_11

# 3.每个目录复制一份配置文件(把/usr/local/soft/redis.install/redis-7.0.0下的redis.conf文件复制给七个目录)
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7000/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7001/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7002/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7003/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7004/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7005/
[root@master redis-cluster]# cp ../redis-7.0.0/redis.conf ./7006/

 

# 4.修改不同目录配置文件
- port     7000 .....                         //修改端口
- # bind 127.0.0.1 -::1                //开启远程连接
- protected-mode no
- daemonize yes                         //开启守护进程
- dbfilename dump-7000.rdb              //每台机器的文件不能一样
- cluster-enabled  yes                          //开启集群模式
- cluster-config-file  nodes-7000.conf //集群节点配置文件
- cluster-node-timeout  5000             //集群节点超时时间
- appendonly  yes                          //开启AOF持久化
- appendfilename "appendonly-7000.aof"       //修改aof文件名
- appenddirname "appendonlydir-7000"
# 5.指定不同目录配置文件启动七个节点(7003,7004的忘记修改了守护进程)(在d盘soft中的redis里面有redis-cluster文件上传到redis-install目录下)
[root@master redis-cluster]# redis-server 7000/redis.conf 
[root@master redis-cluster]# redis-server 7001/redis.conf 
[root@master redis-cluster]# redis-server 7002/redis.conf 
[root@master redis-cluster]# redis-server 7003/redis.conf 
[root@master redis-cluster]# redis-server 7004/redis.conf 
[root@master redis-cluster]# redis-server 7005/redis.conf 
[root@master redis-cluster]# redis-server 7006/redis.conf

 

在启动之前保证没有其它的redis服务进程

redistemplate 出现类转换异常 redistemplate泛型_spring_12

 

此时集群并没有创建成功

创建集群

# 1.复制集群操作脚本到bin目录中(可以不复制)
[root@master redis-cluster]# cp /usr/local/soft/redis-install/redis-7.0.0/src/redis-trib.rb /usr/local/soft/redis/bin/

 

redistemplate 出现类转换异常 redistemplate泛型_spring_13

# 2.创建集群

redis7.0.0之后的命令:redis-cli --cluster create 192.168.80.100:7000 192.168.80.100:7001 192.168.80.100:7002 192.168.80.100:7003 192.168.80.100:7004 192.168.80.100:7005 --cluster-replicas 1

 

redistemplate 出现类转换异常 redistemplate泛型_spring_14

 

查看集群状态

# 1.查看集群状态 check [原始集群中任意节点] [无]
redis-cli --cluster check 192.168.80.100:7000

# 2.集群节点状态说明
- 主节点
主节点存在hash slots,且主节点的hash slots 没有交叉
主节点不能删除
一个主节点可以有多个从节点
主节点宕机时多个副本之间自动选举主节点

- 从节点
从节点没有hash slots
从节点可以删除
从节点不负责数据的写,只负责数据的同步

 

演示其中一个主节点宕机的状态,然后由从节点接管

kill -9 进程号

redistemplate 出现类转换异常 redistemplate泛型_redis_15

 

五秒之后,现在的7005变成了主节点,再次启动7000这个节点就是从节点

redistemplate 出现类转换异常 redistemplate泛型_spring_16

 

设置的值要在对应的槽里存储

添加主节点

redis-cli --cluster add-node 192.168.80.100:7006 192.168.80.100:7001

- 注意:
1.该节点必须以集群模式启动
2.默认情况下该节点就是以master节点形式添加

 

添加从节点

redis-cli --cluster add-node 192.168.80.100:7006 192.168.80.100:7001 --cluster-slave

- 注意:
当添加副本节点时没有指定主节点,redis会随机给副本节点较少的主节点添加当前副本节点

 

删除副本节点

# 1.删除节点 del-node [集群中任意节点] [删除节点id]
redis-cli --cluster del-node

 redis需要连接可视化工具