一、springboot+mybatis整合redis cluster整合流程图

spring连接redis失败 springboot连接redis cluster_spring连接redis失败

二、springboot+mybatis整合redis cluster具体实现

2.1   创建springboot+mybatis项目,引入jar

<!--springboot整合redis jar  开始-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<!--springboot整合redis jar  结束-->

spring连接redis失败 springboot连接redis cluster_mybatis_02

2.2 配置连接redis数据源

        在resources文件夹下 添加一个配置 project.properties配置文件

# redis 数据源配置   springboot+mybatis+redis cluster

# 最大能够保持空闲状态的连接数
redis.maxIdle=2000

#最大连接数
redis.maxTotal=20000

#最大的等待是时长  毫秒
redis.maxWaitMillis=20000

# 当调用borrow object方法时,是否进行有效性检查
redis.testOnBorrow=false

# 集群节点配置    对应redis服务器创建的集群模式中的IP和端口
redis.nodes=192.168.140.41:8001,192.168.140.41:8002,192.168.140.42:8003,192.168.140.43:8004,192.168.140.43:8005,192.168.140.43:8006

 2.3 springboot加载自定义配置文件

        创建一个property文件夹存放自定义配置文件

spring连接redis失败 springboot连接redis cluster_redis_03

       配置文件

package com.aaa.springboot.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * @author :caicai
 * @date :Created in 2022/8/12 13:30
 * @description: redis配置属性类(springboot加载自定义配置文件)
 * @modified By:
 * @version:
 */
@Data
//不在3层之内就使用组件注解 交给ioc容器管理
@Component
//spring 指定本类要加载的配置文件路径
@PropertySource("classpath:project.properties")
//加载到配置文件后,读取配置文件中以redis开头的配置 和本类中属性一致
@ConfigurationProperties(prefix = "redis")
public class RedisProperty {
    // 最大能够保持空闲状态的链接数
    private int maxIdle;
    // 最大连接数
    private int maxTotal;
    // 最大等待时长   毫秒
    private int maxWaitMillis;
    // 当调用borrow object方法时,是否进行有效性检查
    private boolean testOnBorrow;
    // 集群节点配置
    private String nodes;
}

2.4 配置rediscluster-config 使用配置连接配置数据源

        使用spring-boot-starter-data-redis中提供的JedisConnectionFactory 连接redis集群

package com.aaa.springboot.config;
import com.aaa.springboot.property.RedisProperty;
import com.aaa.springboot.util.MyCustomCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPoolConfig;

import javax.annotation.Resource;

@Configuration  // 相当于 redis-cluster-config.xml   <bean></bean>
public class RedisClusterConfig {
    // 依赖注入
    @Resource
    private RedisProperty redisProperty;


    /**
     *  配置JedisConnectionFactory redis连接工厂
     * @return
     */
    @Bean   //<bean id=jedisConnectionFactory class =org.springframework.data.redis.connection.jedis.JedisConnectionFactory>
    public JedisConnectionFactory jedisConnectionFactory(){
        // 实例化对象  使用redis 集群 + redis连接池
        // RedisClusterConfiguration clusterConfig, JedisPoolConfig poolConfig
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(clusterConfig(),poolConfig());

        // 返回对象
        return jedisConnectionFactory;
    }


    /**
     * 初始化 RedisClusterConfiguration   redis 集群配置
     * @return
     */
    @Bean
    public RedisClusterConfiguration clusterConfig() {
        // 实例化
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();

        // 192.168.140.41:8001,192.168.140.41:8002,192.168.140.42:8003,192.168.140.43:8004,192.168.140.43:8005,192.168.140.43:8006
        String nodes = redisProperty.getNodes();
        // nodesArray=["192.168.140.41:8001","192.168.140.41:8002","192.168.140.42:8003",....]
        String[] nodesArray = nodes.split(",");

        // 对节点集合进行遍历
        for (String node : nodesArray) {
            // node: 第一次:"192.168.140.41:8001"  第二次  "192.168.140.41:8002"
            // 在次分割  获取ip 和 端口
            String[] nodeip = node.split(":");
            // 实例化节点
            RedisNode redisNode = new RedisNode(nodeip[0], Integer.valueOf(nodeip[1]));
            // 添加节点到集群配置类中
            redisClusterConfiguration.addClusterNode(redisNode);
        }
        return redisClusterConfiguration;
    }


    /**
     *  初始化JedisPoolConfig  redis连接池配置
     * @return
     */
    @Bean
    public JedisPoolConfig poolConfig() {
        // 实例化连接池配置
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

        jedisPoolConfig.setMaxIdle(redisProperty.getMaxIdle());
        jedisPoolConfig.setMaxTotal(redisProperty.getMaxTotal());
        jedisPoolConfig.setTestOnBorrow(redisProperty.isTestOnBorrow());
        jedisPoolConfig.setMaxWaitMillis(redisProperty.getMaxWaitMillis());
        return jedisPoolConfig;
    }


    /**
     *   特殊的bean配置  在springboot启动时也会被执行
     *   没有返回值,目的是调用静态注入方法,把当前配置完成的jedisConnectionFactory
     *   赋值给MyCustomCache中的属性
     */
    @Bean
    public void setJCF(){
        // 调用MyCustomCache 静态方法,把jedisConnectionFactory注入进去
        MyCustomCache.setJedisConnectionFactory(jedisConnectionFactory());
    }

}

2.5 编写mybatis自定义缓存类

        通过mybatis调用redis集群缓存。 实际使用的mybatis二级缓存,调用第三方缓存。

        https://mybatis.net.cn/sqlmap-xml.html#cache

        MyBatis 内置了一个强大的事务性一旦进行CUD,缓存就会被清空)查询(只有查询有缓存)缓存机制,它可以非常方便地配置和定制。默认情况下,只启用了本地的会话缓存(只开启了一级缓存,无论想不想用,都是开启),它仅仅对一个会话中的数据进行缓存(一级缓存是基于SqlSession)。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行(二级缓存需要自己开启,二级缓存是语句XxxMapper.xml <cache>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aaa.springboot.dao.DeptDao">
    <!--    开启二级缓存-->
    <cache type="com.aaa.springboot.util.MyCustomCache"></cache>

    <select id="queryInfo" resultType="com.aaa.springboot.entity.DeptEntity">
        select * from tb_dept
        <where>
            <if test="dname!=null and dname!=''">
                and dname like concat("%",#{dname},"%")
            </if>
            <if test="loc!=null and loc!=''">
                and loc like concat("%",#{loc},"%")
            </if>
        </where>
    </select>


    <insert id="addInfo">
        insert into tb_dept(dname,loc) values (#{dname},#{loc})
    </insert>


    <update id="updateInfo">
        update tb_dept set dname=#{dname},loc=#{loc} where dept_no = #{deptNo}
    </update>


    <delete id="delInfo">
        delete from tb_dept where dept_no = #{deptNo}
    </delete>
</mapper>

mybatis 二级缓存详解:

        1、所有 select 语句的结果将会被缓存  除非自己配置去掉缓存useCache="false"

        2、所有 insert、update 和 delete 语句会刷新缓存。(增删改是改变数据库数据,改变之后一定要重新缓存)除非自己配置不刷新 flushCache="false"

        3、缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。(当1025个热点数据准备缓存时,先把缓存最近最少使用的踢出去)

       默认策略可以通过 eviction="FIFO"  改为先进先出

       4、缓存不会定时进行刷新(也就是说,没有刷新间隔)。如果想定时刷新(定时把所有缓存内容清空)配置flushInterval=""

       5、缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用(一共有多少个被缓存)。

       6、缓存会被视为读/写缓存 (readOnly="false"),这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

       如果配置的是readOnly="false"  每次请求获取缓存数据时,缓存会把当前缓存的数据复制一份返回给调用或者线程,修改复制品,对原来缓存没有任何影响,

       速度慢,但是安全性好。。。。 浪费内存

      如果配置的是readOnly="true"  每一个请求获取缓存数据时,都是同一份数据,如果有请求改了,其他人拿到的也会被修改,速度快,因为没有复制品

      但是安全性不好。。。 节省内存

编写缓存类

package com.aaa.springboot.util;

import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author :caicai
 * @date :Created in 2022/8/12 14:18
 * @description:自定义的缓存类 用于调用第三方缓存(我们在这调用的是redis cluster 缓存)
 *              必须要实现 Cache接口
 * @version:
 */
public class MyCustomCache implements Cache {
    // 定义缓存对象的唯一识别  mybatis官网要求必须提供一个接收String参数作为id的构造器
    private String id;

    // 使用在springboot 启动时,加载配置文件
    // 实例化 jedisConnectionFactory 对象,来进行数据存入和获取及删除相关操作
    private static JedisConnectionFactory jedisConnectionFactory;

    // 实例话读写锁对象  多个请求同时对缓存中某一个操作时的策略
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 无法使用正常注入,使用该对象 所以使用一个静态方法注入 给当前jedisConnectionFactory对象来赋值
    public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory){
        MyCustomCache.jedisConnectionFactory=jedisConnectionFactory;
    }


    /**
     * 提供一个接收有String参数 作为id的构造器(官网要求)
     * @return
     */
    public MyCustomCache(String id) {
        if (null == id){
            throw new RuntimeException("id不能为空,必须传递!!");
        }
        this.id = id;
    }


    /**
     *  获取缓存对象id
     * @return
     */
    @Override
    public String getId() {
        return this.id;
    }

    /**
     * 向redis集群中写入缓存对象
     * @param key
     * @param value
     */
    @Override
    public void putObject(Object key, Object value) {
        // 通过实例化连接redis集群的工厂类  获取一个redis连接  静态属性在非静态方法中可以直接使用  静态属性在类加载时就被放入到所有内存共享区域-方法区  所有对象都是共享
        RedisConnection connection = jedisConnectionFactory.getConnection();

        // 因为存储时存储的时字节数组类型  所以key 和 value 都要经过序列化
        // 使用springboot整合redis提供的序列化方式
        JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();

        // 使用该类,序列化 key 和 value  a=1 变为:aserialize =  1serialize
        byte[] serializeKey = jdkSerializationRedisSerializer.serialize(key);
        byte[] serializeValue = jdkSerializationRedisSerializer.serialize(value);

        // 通过RedisConnection 可以执行redis所有命令
        // 在此使用spring类型操作,向集群中存入数据  存入的key 和value都是序列化过的字节数组
        connection.set(serializeKey,serializeValue);

        // 这里面不要手动关闭连接,因为配置的有连接池,连接的使用和关闭由连接池来完成
        // connection.close();

    }

    /**
     *  通过 key 获取缓存对象
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        // 通过实例化连接 redis 集群的工厂类 获取一个redis 连接静态属性在非静态方法中可以直接使用  静态属性在类加载时就被放入到所有内存共享区域-方法区  所有对象都是共享
        RedisConnection connection = jedisConnectionFactory.getConnection();

        // 因为存储时存储的是自己数组类型  所以key 和 value 都要经过序列化
        // 使用springboot整合redis提供的序列化方式
        JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();

        // 序列化变字节数组   serializekey = a
        byte[] serializeKey = jdkSerializationRedisSerializer.serialize(key);

        //通过序列化的key获取到序列化后的值
        byte[] serializeValue = connection.get(serializeKey);

        // 反序列化后并返回   1serialize --> 1
        return jdkSerializationRedisSerializer.deserialize(serializeValue);
    }


    /**
     *  根据 key 删除缓存对象
     * @param key
     * @return
     */
    @Override
    public Object removeObject(Object key) {
        // 通过实例化连接 redis 集群的工厂类  获取一个redis 连接
        RedisConnection connection = jedisConnectionFactory.getConnection();


        // 使用springboot整合redis提供的序列化方式
        JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();

        // 序列 key 变为字节数组
        byte[] serializeKey = jdkSerializationRedisSerializer.serialize(key);

        // 删除时也要使用序列化后的key  这种写法当在大量缓存进行删除时,效率会低    每次都会执行删除
        // connection.del(serializeKey);

        // 设置该 key 失效  0为立马失效  立马失效,就获取不到,等同于删除    底层redis使用惰性删除  定期批量清理失效的key 批量操作,而不是一个一个操纵,提高效率
        return connection.expireAt(serializeKey,0);
    }


    /**
     *   清空所有缓存书库
     */
    @Override
    public void clear() {
        // 通过实例化连接redis集群的工厂类,获取一个redis连接   静态属性在非静态方法中可以直接使用  静态属性在类加载时就被放入到所有内存共享区域-方法区  所有对象都是共享
        RedisConnection connection = jedisConnectionFactory.getConnection();

        // flushDb redis 每个实例对象默认分为16库  清空当前库
        // connection.flushDb();

        // flushAll 清空所有库
        connection.flushAll();
    }

    /**
     *  获取当前缓存中对象数量
     * @return
     */
    @Override
    public int getSize() {
        // 通过实例化连接redis集群的工厂类,获取一个redis连接
        RedisConnection connection = jedisConnectionFactory.getConnection();

        // 使用 dbSize
        Long aLong = connection.dbSize();

        // 转为 int 类型,并返回
        return Integer.valueOf(aLong.toString());
    }

    /**
     *  缓存读写锁  (缓存读写策略)
     * @return
     */
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
}

2.6 整合测试

1、注意所有的实体类必须序列化 (只要使用了mybatis二级缓存,就需要这么做)

spring连接redis失败 springboot连接redis cluster_spring_04

 2、 启动redis集群,使用客户端命令测试是否正常

        使用脚本启动集群:

        ./shelldir/redis-start-stop.sh

        使用客户端命令测试:

        /usr/redis/bin/redis-cli -c  -h 192.168.140.43 -p 8005

        192.168.140.43:8005> cluster info         发现正常

spring连接redis失败 springboot连接redis cluster_spring boot_05

 3、启动sbm项目,使用swagger做查询 测试

      http://localhost:16666/swagger-ui/index.html#/

第一次请求时,在idea控制台可以看到执行语句(sql语句),说明是从数据获取

spring连接redis失败 springboot连接redis cluster_spring_06

第二次: 清空控制台,再次请求,发现控制台就不再执行语句,而是看到cache hit Ratio 命中率

请求两次,从缓存中取了一次 1/2

spring连接redis失败 springboot连接redis cluster_spring boot_07

 第三次:2次从缓存中获取 2/3

spring连接redis失败 springboot连接redis cluster_spring连接redis失败_08

 第四次:3次从缓存中获取 3/4

spring连接redis失败 springboot连接redis cluster_mybatis_09

。。。。。

4、查看缓存是否存在

        /usr/redis/bin/redis-cli -c  -h 192.168.140.41 -p 8001

        缓存到那个节点不确定 要一个一个试 

spring连接redis失败 springboot连接redis cluster_spring boot_10

5、清空缓存,再次请求,再观察控制台

        /usr/redis/bin/redis-cli -c  -h 192.168.140.41 -p 8001

        192.168.140.41:8001> flushdb

        /usr/redis/bin/redis-cli -c  -h 192.168.140.43 -p 8005

        192.168.140.43:8005> flushdb

spring连接redis失败 springboot连接redis cluster_mybatis_11

         说明清空缓存后,会再次从数据获取

        继续刷新请求

spring连接redis失败 springboot连接redis cluster_spring boot_12

 

增删改后,再次观察控制台(增删改后,一定会自动清空缓存,再次从数据库查询,再次缓存)

先执行添加操作

spring连接redis失败 springboot连接redis cluster_spring boot_13

 在此查询

spring连接redis失败 springboot连接redis cluster_spring连接redis失败_14

 查询方法和CUD方法必须同一个mapper才会CUD后,清空缓存,重新缓存