1. 程式人生 > 資料庫 >SpringBoot整合Redis+Redis快取應用+Redis實現Session共享+...

SpringBoot整合Redis+Redis快取應用+Redis實現Session共享+...

一、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詳情可見另一