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、單節點併發壓力和物理上限的問題。