ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

SpringBoot整合Redis+Redis缓存应用+Redis实现Session共享+...

2020-11-25 22:32:22  阅读:343  来源: 互联网

标签:+... return SpringBoot Redis param 缓存 key public redisTemplate


一、SpringBoot整合Redis

1.导入依赖

<!--存在Redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.application.yml

server:
  port: 8081
spring:
  session.store-type: redis
  redis:
    database: 0
    host: localhost
    port: 6379
    password: root
    timeout: 300
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0

3.使用方法

完成上述配置之后,会自动创建两个对象:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1WXTr721-1606313432296)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201111094810912.png)]

StringRedisTemplate:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RMFxzrHy-1606313432298)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201111094051072.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vd63ZUSQ-1606313432300)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201111094935637.png)]

RedisTemplate:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dZvEMS3F-1606313432302)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201111101857482.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TLeWvlhC-1606313432304)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201111101933208.png)]

4.Redis 配置类(直接用即可)

package com.sonnie.springbootredis_demo.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置类
 * @program: redisdemo
 * @Author: david
 * @Description:
 */
//RedisConfig中重写RedisTemplate类(why?)
@Configuration
@EnableCaching //开启注解
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * retemplate相关配置
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);

        // 值采用json序列化
        template.setValueSerializer(jacksonSeial);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();

        return template;
    }

    /**
     * 对hash类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    /**
     * 对redis字符串类型数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    /**
     * 对链表类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    /**
     * 对无序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    /**
     * 对有序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }
}

5.Redis工具类( 封装redisTemplate–有需要再加)

package com.sonnie.springbootredis_demo.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * redisTemplate封装
 *
 * @author david
 */
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    //============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }



    //===============================list=================================
    /**
     * 赋值操作,存在值返回true,不存在返回false;
     *
     * @param key   键
     * @param value 值
     * @param millisecond 有效期
     * @return 赋值结果
     */
    public boolean setIfAbsent(String key,String value, long millisecond) {
        Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value,
                millisecond, TimeUnit.MILLISECONDS);
        return success != null && success;
    }
    /**
     * 移除key对应的value。
     *
     * @param key   键
     *
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }
}

二、缓存应用

1.缓存

1.1什么是缓存?

缓存就是存在于计算机内存中的一段数据;

针对于我们的程序而言,缓存就是存在于JVM(JVM也存在于内存中)中的一段数据。

1.2缓存/内存中数据的特点

a、读写快

b、断电既失

1.3使用缓存的好处

a、提高网站响应速度,优化网站的运行

b、减轻访问数据库时给数据库带来的压力

1.4缓存的应用环境

缓存一般应用于查询较多,增删极少的业务领域

1.5项目中开发缓存模块

项目结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uWeFzfjW-1606313432306)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201111160631566.png)]

1.5.1SpringBoot+Mybatis

——使用Mybatis框架提供二级缓存技术

——只需要在使用缓存模块mapper配置中加入标签即可

<?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">

<!--sql映射文件-->
<mapper namespace="com.example.springbootredisdemo.dao.UserDao">
    <!--
            开启二级缓存;
            二级缓存作用域:namespace;
            Mybatis3中二级缓存默认开启
    -->
    <cache/>

    <select id="selectAll" resultType="com.example.springbootredisdemo.entity.User">
        select id,name,password from users
    </select>

    <select id="selectById" resultType="com.example.springbootredisdemo.entity.User" parameterType="String">
        select id,name,password from users where id = #{id}
    </select>

    <insert id="insert" parameterType="com.example.springbootredisdemo.entity.User">
        insert into users (id,name,password) values (#{id},#{name},#{password})
    </insert>

    <update id="updateById" parameterType="com.example.springbootredisdemo.entity.User">
        update users set name # {name},password = #{password} where id = #{id}
    </update>

    <delete id="deleteById" parameterType="com.example.springbootredisdemo.entity.User">
        delete from users where id = #{id}
    </delete>

</mapper>

——Mybatis自身缓存(也称作本地缓存)的缺点:

a、本地缓存存储在JVM内存中,如果数据过大,会影响JVM的性能

b、不能作为分布式缓存

1.5.2重写Mybatis cache实现Redis缓存
编写ApplicationContextUtil类
package com.example.springbootredisdemo.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;

//在自定义的类中获取工厂中已经创建好的某些对象
@Configuration
public class ApplicationContextUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    //根据bean id获取对象
    public static Object getBean(String id){
        return context.getBean(id);
    }

    //根据bean 类型获取对象
    public static Object getBean(Class clazz){
        return context.getBean(clazz);
    }

    //根据bean id和类型获取对象
    public static Object getBean(String id,Class clazz){
        return context.getBean(id,clazz);
    }

}

编写RedisCache类
package com.example.springbootredisdemo.config;

import com.example.springbootredisdemo.util.ApplicationContextUtil;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

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

/**
 * 重写Mybatis cache实现redis缓存
 * 不能交给工厂管理
 * */
public class RedisCache implements Cache {
    //必须定义这个String类型的id,因为这个id表示当前加入缓存的namespace;
     private String id;

    public RedisCache(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    //放入缓存
    @Override
    public void putObject(Object key, Object value) {
        //获取redisTemplate对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        //存储缓存数据
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置HashKey序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //hash模型
        redisTemplate.opsForHash().put(id,key.toString(),value);
    }

    //从缓存中获取
    @Override
    public Object getObject(Object key) {
        //获取redisTemplate对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置HashKey序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate.opsForHash().get(id.toString(),key.toString());
    }

    //从缓存中移除
    //真正使用过程中,这个方法并不会被用到
    @Override
    public Boolean removeObject(Object key) {
        return null;
    }

    //清除缓存
    @Override
    public void clear() {
        //获取redisTemplate对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        redisTemplate.delete(id);
    }

    //缓存命中率计算
    @Override
    public int getSize() {
//      获取redisTemplate对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate.opsForHash().size(id.toString()).intValue();
    }

    /*
    * ReadWriteLock读写锁 表示:读写之间互斥,读读之间不互斥,写写之间不互斥
    * 区别于Synchronized  表示:读读之间互斥,写写之阿互斥,读写之间互斥
    * 因此ReadWriteLock效率比Synchronized高
    * 对于缓存,只有写操作,没有写操作
    * */
    @Override
    public ReadWriteLock getReadWriteLock() {
        return new ReentrantReadWriteLock();
    }
}
mapper.xml
<?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">

<!--sql映射文件-->
<mapper namespace="com.example.springbootredisdemo.dao.UserDao">
    <!--
            开启二级缓存;
            二级缓存作用域:namespace;
            Mybatis3中二级缓存默认开启
            参数:
                type:用来指定自定义cache的全限定名
    -->
    <cache type="com.example.springbootredisdemo.config.RedisCache"/>

    <select id="selectAll" resultType="com.example.springbootredisdemo.entity.User">
        select id,name,password from users
    </select>

    <select id="selectById" resultType="com.example.springbootredisdemo.entity.User" parameterType="String">
        select id,name,password from users where id = #{id}
    </select>

    <insert id="insert" parameterType="com.example.springbootredisdemo.entity.User">
        insert into users (id,name,password) values (#{id},#{name},#{password})
    </insert>

    <update id="updateById" parameterType="com.example.springbootredisdemo.entity.User">
        update users set name # {name},password = #{password} where id = #{id}
    </update>

    <delete id="deleteById" parameterType="com.example.springbootredisdemo.entity.User">
        delete from users where id = #{id}
    </delete>

</mapper>

1.6Redis集成Mybatis实现分布式缓存——总结

1.6.1为什么要这样做?

1.Mybatis本地缓存的缺点:

​ a、本地缓存存储在JVM内存中,如果数据过大,会影响JVM的性能

​ b、不能在分布式系统中实现共享

2.redis缓存的优点:

​ a、本身就是内存数据库,在内存中存储数据,读写块

​ b、可以在分布式系统中实现共享

1.6.2如何保证同一个查询中多次查询key始终一致?

MyBatis 中涉及到动态 SQL 的原因,缓存项的 key 不能仅仅通过一个 String 来表示,所以通过CacheKey 来封装缓存的 key 值,CacheKey 可以封装多个影响缓存项的因素

​ a、namespace.id

​ b、指定查询结果集的范围(分页信息)

​ c、查询所使用的 SQL 语句

​ d、用户传递给 SQL 语句的实际参数值

通过这种方式确保了cacheKey的唯一。

1.6.3Redis缓存模型如何设计?

a、初步设计:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-se6yHQX2-1606313432307)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201111174524613.png)]

缺点:在Redis中散列了很多的key,他们都被存储在一起;当进行清除操作时,会将其他模块的缓存一起清除掉。

b、优化改进:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yZw8J53R-1606313432308)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201111174958554.png)]

1.6.4谈谈缓存穿透问题、缓存击穿问题以及缓存雪崩问题?

缓存雪崩

​ **概念:**是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。

​ 举个例子,目前电商首页以及热点数据都会去做缓存 ,一般缓存都是定时任务去刷新,或者是查不到之后 去更新的,定时任务刷新就有一个问题。如果所有首页的Key失效时间都是12小时,中午12点刷新的,我零点 有个秒杀活动大量用户涌入,假设当时每秒 6000 个请求,本来缓存在可以扛住每秒 5000 个请求,但是缓 存当时所有的Key都失效了。此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住,它会报一下警,真 实情况可能DBA都没反应过来就直接挂了。此时,如果没用什么特别的方案来处理这个故障,DBA 很着 急,重启数据库,但是数据库立马又被新的流量给打死了。这就是我理解的缓存雪崩。

如何应对:

​ a、在业务数据量不是很大的情况下,可以设置缓存数据为永久存储

​ b、基于不同的业务数据,设置不同的超时时间,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j6R33EBj-1606313432309)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201112092228645.png)]

缓存穿透

​ **概念:**是指缓存和数据库中都没有的数据,而用户不断发起请求,每次都能绕开Redis直接打到数据库,数据 库也查不到,每次都这样,并发高点就容易崩掉了。

​ 举个例子:我们数据库的 id 都是从1开始自增上去的,在不对参数做校验的情况下,如发起为id值为 -1 的 数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据 库。

如何处理:

​ a、其实,Redis集成Mybatis实现分布式缓存,已经解决了缓存穿透的问题。无论你查询的id在缓存和数 据库中是否存在,都会将其存入到缓存中,只不过value为null。当对上述的id值在数据库中做了添加操作 后,Redis缓存会将已经存在于缓存中id对应的数据清除。当再次发起对这个id的查询时,查询的自然也就是 刚存入数据库的数据。

​ b、在不使用Redis集成Mybatis实现分布式缓存的情况下,可以在接口层增加校验,比如用户鉴权校验, 参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截等。

​ c、Redis还有一个高级用法==布隆过滤器(Bloom Filter)==这个也能很好的防止缓存穿透的发生,他的原 理也很简单就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好 了,存在你就去查了DB刷新KV再return。

缓存击穿

​ **概念:**是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。

如何处理:设置热点数据永远不过期,或者加上互斥锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z7Sspax4-1606313432310)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201112085021200.png)]

1.7补充

标签作用:与指定dao共享同一个缓存

使用方法:

注意:和不能同时出现

三、使用Redis实现Session共享

1.memcache和Redis管理机制的区别:

a、memcache与tomcat整合,实现的是全局Session管理机制,也就是说整个服务器所有应用全部基于memcache管理。

b、Redis与应用整合,实现的是基于应用的Session管理,也就是说一个应用会话交给Redis管理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CP759K1K-1606313432312)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201112093439765.png)]

2.使用Redis实现Session共享

2.1导入依赖

<!--全局Session管理依赖-->
<dependency>
    	<groupId>org.springframework.session</groupId>
    	<artifactId>spring-session-data-redis</artifactId>
</dependency>

2.2application.yml

spring:
  session.store-type: redis

2.3自定义配置类来开启整个应用的会话交给Redis管理

package com.example.springbootredisdemo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@Configuration
@EnableRedisHttpSession
//@EnableRedisHttpSession开启Redis全局会话管理
public class RedisSessionConfig {
    
}

2.4编写controller

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@RestController
public class SessionController {
    @RequestMapping("/trs")
    public void testRedisSession(HttpServletRequest request, HttpServletResponse response) throws IOException {
        List<String> list = (List<String>) request.getSession().getAttribute("list");
        if(list==null){
            list = new ArrayList<>();
        }
        list.add("xxx");
        request.getSession().setAttribute("list",list);
        response.getWriter().println("Session:" + request.getSession().getId());
        response.getWriter().println("counts:" + list.size());
    }
}

2.5执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7rLfWoNo-1606313432313)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201112111019093.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V6EZvR7R-1606313432314)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201112111055270.png)]

2.6全部代码

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-redis-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>springboot-redis-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
<!--        web支持-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
<!--            排除传导依赖-->
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
<!--        redis-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
<!--        去掉内嵌的Tomcat-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
<!--引入JSP支持-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>



<!--        MySQL-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
<!--        Mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
<!--                使用热部署出现中文乱码解决方法-->
                <configuration>
                    <fork>true</fork>
<!--                    增加JVM参数-->
                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
<!--                    指定入口类-->
                    <mainClass>com.example.springbootredisdemo.SpringbootRedisDemoApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
application.yml
spring:
  datasource:
    username: root
    password:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    mode: LEGACYHTML5
    encoding: UTF-8
    cache: false
  session.store-type: redis
  redis:
    database: 0
    host: localhost
    port: 6379
    password:
    timeout: 300
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0
mybatis:
  mapper-locations: classpath:mapping/*Mapper.xml
  type-aliases-package: com.example.entity
#showSql
logging:
  level:
    com:
      example:
        mapper : debug
cache.RedisCache.java
package com.example.springbootredisdemo.cache;

import com.example.springbootredisdemo.util.ApplicationContextUtil;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

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

/**
 * 重写Mybatis cache实现redis缓存
 * 不能交给工厂管理
 * */
public class RedisCache implements Cache {
    //必须定义这个String类型的id,因为这个id表示当前加入缓存的namespace;
     private String id;

    public RedisCache(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    //放入缓存
    @Override
    public void putObject(Object key, Object value) {
        //获取redisTemplate对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        //存储缓存数据
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置HashKey序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //hash模型
        redisTemplate.opsForHash().put(id,key.toString(),value);
    }

    //从缓存中获取
    @Override
    public Object getObject(Object key) {
        //获取redisTemplate对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置HashKey序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate.opsForHash().get(id.toString(),key.toString());
    }

    //从缓存中移除
    //真正使用过程中,这个方法并不会被用到
    @Override
    public Boolean removeObject(Object key) {
        return null;
    }

    //清除缓存
    @Override
    public void clear() {
        //获取redisTemplate对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        redisTemplate.delete(id);
    }

    //缓存命中率计算
    @Override
    public int getSize() {
//      获取redisTemplate对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate.opsForHash().size(id.toString()).intValue();
    }

    /*
    * ReadWriteLock读写锁 表示:读写之间互斥,读读之间不互斥,写写之间不互斥
    * 区别于Synchronized  表示:读读之间互斥,写写之阿互斥,读写之间互斥
    * 因此ReadWriteLock效率比Synchronized高
    * 对于缓存,只有写操作,没有写操作
    * */
    @Override
    public ReadWriteLock getReadWriteLock() {
        return new ReentrantReadWriteLock();
    }
}
config.RedisConfig.java
package com.example.springbootredisdemo.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置类
 * @program: redisdemo
 * @Author: david
 * @Description:
 */
//RedisConfig中重写RedisTemplate类(why?)
@Configuration
@EnableCaching //开启注解
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * retemplate相关配置
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);

        // 值采用json序列化
        template.setValueSerializer(jacksonSeial);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();

        return template;
    }

    /**
     * 对hash类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    /**
     * 对redis字符串类型数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    /**
     * 对链表类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    /**
     * 对无序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    /**
     * 对有序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }

}
config.RedisSessionConfig.java
package com.example.springbootredisdemo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@Configuration
@EnableRedisHttpSession
//@EnableRedisHttpSession开启Redis全局会话管理
public class RedisSessionConfig {

}
Util.ApplicationContextUtil.java
package com.example.springbootredisdemo.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;

//在自定义的类中获取工厂中已经创建好的某些对象
@Configuration
public class ApplicationContextUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    //根据bean id获取对象
    public static Object getBean(String id){
        return context.getBean(id);
    }

    //根据bean 类型获取对象
    public static Object getBean(Class clazz){
        return context.getBean(clazz);
    }

    //根据bean id和类型获取对象
    public static Object getBean(String id,Class clazz){
        return context.getBean(id,clazz);
    }

}
controller.RedisSessionController.java
package com.example.springbootredisdemo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@RestController
public class RedisSessionController {
    @RequestMapping("/trs")
    public void testRedisSession(HttpServletRequest request, HttpServletResponse response) throws IOException {
        List<String> list = (List<String>) request.getSession().getAttribute("list");
        if(list==null){
            list = new ArrayList<>();
        }
        list.add("xxx");
        request.getSession().setAttribute("list",list);
        response.getWriter().println("Session:" + request.getSession().getId());
        response.getWriter().println("counts:" + list.size());
    }
}

四、主从复制架构(master-slave)、哨兵机制(sentinal)、Redis Cluster

1.主从复制(master-slave)

1.1概念

主从复制,是指将一台Redis服务器的数据(主服务器master)复制到其他的Redis服务器(从服务器slave)。数据的复制是单向的,只能由主节点到从节点。默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。(主从之间是1 : n关系,n >= 0)

1.2解决的问题

数据冗余备份

但不能解决故障的自动转移。

1.3搭建

(1)开启主从复制

​ 主从复制的开启,完全是在从节点发起的,主节点被动接收从节点的请求并做出相应处理就好。从节点开启主从复制,有3种方式:

  • 配置文件:在从服务器的配置文件中加入:slaveof
  • 启动命令:redis-server启动命令后加入 --slaveof
  • 客户端命令:Redis服务器(记为A)启动后,直接通过客户端(连接到A服务器的客户端)执行命令:slaveof ,则该Redis实例成为从节点。

(2)关闭主从复制

​ 通过slaveof 命令建立主从复制关系以后,可以通过****slaveof no one****断开。从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化。

1.4原理

主从复制包括三个阶段:

a、建立连接阶段:保存主节点信息、建立socket连接、发送ping命令、身份验证、发送从节点端口信息

b、数据同步阶段:全量复制和部分复制

c、命令传播阶段:发送写命令、维持心跳机制

2.哨兵机制(sentinel)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zW7Dh594-1606313432316)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201112135250873.png)]

2.1概念

Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务:
a、监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
b、提醒(Notification):当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。

​ c、自动故障迁移(Automatic failover):当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。

哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。
每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown).
若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。
虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel)。

2.2解决的问题

哨兵机制其实就是带有故障自动转移功能的主从式架构;

主要解决的是:

a、数据冗余备份;

b、故障自动转移

但不能解决现有系统单节点并发压力和物理上限的问题。

2.3搭建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xW39Kt9f-1606313432317)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201112140207946.png)]

2.4修改application.yml

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SAEyZYvj-1606313432318)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201112140052873.png)]

2.5工作方式

1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。

2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。

3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。

4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。

5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。

6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。

7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。

​ 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。

3.Redis集群(cluster)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JoAAndvH-1606313432319)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20201112141856941.png)]

3.1解决的问题

a、数据冗余备份;

b、故障自动转移

c、单节点并发压力和物理上限的问题

3.2详情可见另一篇文章——《RedisCluster》

失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。

哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。
每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown).
若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。
虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel)。

2.2解决的问题

哨兵机制其实就是带有故障自动转移功能的主从式架构;

主要解决的是:

a、数据冗余备份;

b、故障自动转移

但不能解决现有系统单节点并发压力和物理上限的问题。

2.3搭建

[外链图片转存中…(img-xW39Kt9f-1606313432317)]

2.4修改application.yml

[外链图片转存中…(img-SAEyZYvj-1606313432318)]

2.5工作方式

1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。

2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。

3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。

4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。

5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。

6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。

7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。

​ 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。

3.Redis集群(cluster)

[外链图片转存中…(img-JoAAndvH-1606313432319)]

3.1解决的问题

a、数据冗余备份;

b、故障自动转移

c、单节点并发压力和物理上限的问题

3.2详情可见另一

标签:+...,return,SpringBoot,Redis,param,缓存,key,public,redisTemplate
来源: https://blog.csdn.net/qq_44469202/article/details/110148873

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有