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

一、SpringBoot整合Redis

1.导入依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
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配置中加入标签即可





<mapper namespace="com.example.springbootredisdemo.dao.UserDao">
    
    <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




<mapper namespace="com.example.springbootredisdemo.dao.UserDao">
    
    <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导入依赖


<dependency>
    	<groupId>org.springframework.sessiongroupId>
    	<artifactId>spring-session-data-redisartifactId>
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

<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.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.3.5.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>com.examplegroupId>
    <artifactId>springboot-redis-demoartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <packaging>warpackaging>
    <name>springboot-redis-demoname>
    <description>Demo project for Spring Bootdescription>

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

    <dependencies>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>

            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>org.springframework.sessiongroupId>
            <artifactId>spring-session-data-redisartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-tomcatartifactId>
            <scope>providedscope>
        dependency>

        <dependency>
            <groupId>org.apache.tomcat.embedgroupId>
            <artifactId>tomcat-embed-jasperartifactId>
            <scope>providedscope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettucegroupId>
                    <artifactId>lettuce-coreartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>org.springframework.datagroupId>
            <artifactId>spring-data-redisartifactId>
        dependency>
        <dependency>
            <groupId>redis.clientsgroupId>
            <artifactId>jedisartifactId>
        dependency>




        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>

        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>1.3.2version>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>

                <configuration>
                    <fork>truefork>

                    <jvmArguments>-Dfile.encoding=UTF-8jvmArguments>

                    <mainClass>com.example.springbootredisdemo.SpringbootRedisDemoApplicationmainClass>
                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详情可见另一

你可能感兴趣的:(SONNIE的学习之路,java,nosql,redis,分布式)