成都網(wǎng)站建設公司興田德潤在哪兒個人發(fā)布信息的免費平臺
目錄
一、整合redis
1、介紹
1.1、redis(Remote Dictionary Server)
1.2、MySQL
1.3、區(qū)別
2、整合
2.1、配置
2.2、文件配置
2.3、key的生成規(guī)則方法
2.4、注意
二、redis注解式緩存
1、@Cacheable注解
2、@CachePut注解
3、@CacheEvict注解
4、應用場景
三、redis擊穿穿透雪崩
1、擊穿(Cache Miss)
2、穿透(Cache Penetration)
3、雪崩(Cache Avalanche)
一、整合redis
1、介紹
1.1、redis(Remote Dictionary Server)
- Redis是一種基于內(nèi)存的鍵值存儲系統(tǒng),它將數(shù)據(jù)存儲在內(nèi)存中,因此讀寫速度非常快。
- Redis支持多種數(shù)據(jù)結構,如字符串、哈希、列表、集合、有序集合等,這使得Redis適用于各種應用場景,如緩存、消息隊列、計數(shù)器等。
- Redis具有高可用性和可擴展性,支持主從復制和分片,以實現(xiàn)數(shù)據(jù)的備份和負載均衡。
- Redis的持久化方式有RDB(快照)和AOF(日志追加),可以將數(shù)據(jù)持久化到磁盤,保證數(shù)據(jù)的安全性。
1.2、MySQL
- MySQL是一種關系型數(shù)據(jù)庫管理系統(tǒng),使用標準的SQL語言進行數(shù)據(jù)操作。
- MySQL將數(shù)據(jù)存儲在磁盤上,因此相對于Redis來說,讀寫速度較慢。
- MySQL支持事務處理和復雜的查詢,適用于需要處理結構化數(shù)據(jù)的應用,如網(wǎng)站、電子商務等。
- MySQL具有較高的穩(wěn)定性和成熟度,支持ACID特性(原子性、一致性、隔離性、持久性),可以保證數(shù)據(jù)的完整性和一致性。
1.3、區(qū)別
redis是nosql數(shù)據(jù)庫
MySQL是sql數(shù)據(jù)庫
更一步的理解:
- 存儲方式:Redis將數(shù)據(jù)存儲在內(nèi)存中,而MySQL將數(shù)據(jù)存儲在磁盤中。
- 數(shù)據(jù)結構:Redis支持多種數(shù)據(jù)結構,MySQL使用表格和關系進行數(shù)據(jù)存儲。
- 讀寫性能:由于Redis使用內(nèi)存存儲,讀寫速度較快,而MySQL較慢。
- 功能特性:Redis適用于緩存和實時數(shù)據(jù)處理,MySQL適用于結構化數(shù)據(jù)的存儲和復雜查詢。
- ACID特性:MySQL支持事務處理和ACID特性,而Redis在默認情況下不支持事務處理。
- 持久化方式:Redis可以將數(shù)據(jù)持久化到磁盤,MySQL具有多種持久化方式,如日志文件和復制。
2、整合
2.1、配置
創(chuàng)建ssm的項目,在配置文件的pom文件添加
<redis.version>2.9.0</redis.version> <redis.spring.version>1.7.1.RELEASE</redis.spring.version><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>${redis.version}</version> </dependency> <dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>${redis.spring.version}</version> </dependency>
2.2、文件配置
編寫一個配置文件redis.properties,在我們的resources包里面。
redis.hostName:對應的IP地址
redis.port:對應的端口號
redis.password:對應的redis連接的密碼redis.hostName=localhsot redis.port=6379 redis.password=123456 redis.timeout=10000 redis.maxIdle=300 redis.maxTotal=1000 redis.maxWaitMillis=1000 redis.minEvictableIdleTimeMillis=300000 redis.numTestsPerEvictionRun=1024 redis.timeBetweenEvictionRunsMillis=30000 redis.testOnBorrow=true redis.testWhileIdle=true redis.expiration=3600
spring-redis.xml里面包含了
- 添加注冊
- redis的連接池配置:對應的value值是redis.properties里面的配置
- redis的的連接工廠:這里就用到了連接池的配置redis。
- 配置序列化:里面有string、json、hash的序列化器
- 配置key的生成
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:cache="http://www.springframework.org/schema/cache"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/cachehttp://www.springframework.org/schema/cache/spring-cache.xsd"><!-- 1. 引入properties配置文件 --><!--<context:property-placeholder location="classpath:redis.properties" />--><!-- 2. redis連接池配置--><bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"><!--最大空閑數(shù)--><property name="maxIdle" value="${redis.maxIdle}"/><!--連接池的最大數(shù)據(jù)庫連接數(shù) --><property name="maxTotal" value="${redis.maxTotal}"/><!--最大建立連接等待時間--><property name="maxWaitMillis" value="${redis.maxWaitMillis}"/><!--逐出連接的最小空閑時間 默認1800000毫秒(30分鐘)--><property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/><!--每次逐出檢查時 逐出的最大數(shù)目 如果為負數(shù)就是 : 1/abs(n), 默認3--><property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/><!--逐出掃描的時間間隔(毫秒) 如果為負數(shù),則不運行逐出線程, 默認-1--><property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/><!--是否在從池中取出連接前進行檢驗,如果檢驗失敗,則從池中去除連接并嘗試取出另一個--><property name="testOnBorrow" value="${redis.testOnBorrow}"/><!--在空閑時檢查有效性, 默認false --><property name="testWhileIdle" value="${redis.testWhileIdle}"/></bean><!-- 3. redis連接工廠 --><bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"destroy-method="destroy"><property name="poolConfig" ref="poolConfig"/><!--IP地址 --><property name="hostName" value="${redis.hostName}"/><!--端口號 --><property name="port" value="${redis.port}"/><!--如果Redis設置有密碼 --><property name="password" value="${redis.password}"/><!--客戶端超時時間單位是毫秒 --><property name="timeout" value="${redis.timeout}"/></bean><!-- 4. redis操作模板,使用該對象可以操作redishibernate課程中hibernatetemplete,相當于session,專門操作數(shù)據(jù)庫。--><bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"><property name="connectionFactory" ref="connectionFactory"/><!--如果不配置Serializer,那么存儲的時候缺省使用String,如果用User類型存儲,那么會提示錯誤User can't cast to String!! --><property name="keySerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property><property name="valueSerializer"><bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/></property><property name="hashKeySerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property><property name="hashValueSerializer"><bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/></property><!--開啟事務 --><property name="enableTransactionSupport" value="true"/></bean><!-- 5.配置緩存管理器 --><bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"><constructor-arg name="redisOperations" ref="redisTemplate"/><!--redis緩存數(shù)據(jù)過期時間單位秒--><property name="defaultExpiration" value="${redis.expiration}"/><!--是否使用緩存前綴,與cachePrefix相關--><property name="usePrefix" value="true"/><!--配置緩存前綴名稱--><property name="cachePrefix"><bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix"><constructor-arg index="0" value="-cache-"/></bean></property></bean><!--6.配置緩存生成鍵名的生成規(guī)則--><bean id="cacheKeyGenerator" class="com.tgq.ssm.redis.CacheKeyGenerator"></bean><!--7.啟用緩存注解功能--><cache:annotation-driven cache-manager="redisCacheManager" key-generator="cacheKeyGenerator"/> </beans>
redis.properties與jdbc.properties在與Spring做整合時會發(fā)生沖突;所以引入配置文件的地方要放到applicationContext.xml中
applicationContext.xml配置如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--1. 引入外部多文件方式 --><bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" /><property name="ignoreResourceNotFound" value="true" /><property name="locations"><list><value>classpath:jdbc.properties</value><value>classpath:redis.properties</value></list></property></bean><!-- 隨著后續(xù)學習,框架會越學越多,不能將所有的框架配置,放到同一個配制間,否者不便于管理 --><import resource="applicationContext-mybatis.xml"></import><import resource="spring-redis.xml"></import><import resource="applicationContext-shiro.xml"></import> </beans>
2.3、key的生成規(guī)則方法
spring-redis.xml的最后的配置就說明了鍵的配置規(guī)則。
package com.tgq.ssm.redis;import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.util.ClassUtils;import java.lang.reflect.Array;
import java.lang.reflect.Method;@Slf4j
public class CacheKeyGenerator implements KeyGenerator {// custom cache keypublic static final int NO_PARAM_KEY = 0;public static final int NULL_PARAM_KEY = 53;@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder key = new StringBuilder();key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append(":");if (params.length == 0) {key.append(NO_PARAM_KEY);} else {int count = 0;for (Object param : params) {if (0 != count) {//參數(shù)之間用,進行分隔key.append(',');}if (param == null) {key.append(NULL_PARAM_KEY);} else if (ClassUtils.isPrimitiveArray(param.getClass())) {int length = Array.getLength(param);for (int i = 0; i < length; i++) {key.append(Array.get(param, i));key.append(',');}} else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {key.append(param);} else {//Java一定要重寫hashCode和eqaulskey.append(param.hashCode());}count++;}}String finalKey = key.toString();
// IEDA要安裝lombok插件log.debug("using cache key={}", finalKey);return finalKey;}
}
2.4、注意
- 在applicationContext.xml中注冊多個.properties結尾配置,那么不能在spring-*.xml添加注冊。
- resources的配置必須要涵蓋讀取.properties結尾的文件。
- redisTemplate的使用可以參考jdbcTemplate、amqpTemplate、rabbitMQTemplate等。
二、redis注解式緩存
1、@Cacheable注解
????????配置在方法或類上,作用:本方法執(zhí)行后,先去緩存看有沒有數(shù)據(jù),如果沒有,從數(shù)據(jù)庫中查找出來,給緩存中存一份,返回結果,
????????下次本方法執(zhí)行,在緩存未過期情況下,先在緩存中查找,有的話直接返回,沒有的話從數(shù)據(jù)庫查找。value:緩存位置的一段名稱,不能為空。
key:緩存的key,默認為空,表示使用方法的參數(shù)類型及參數(shù)值作為key,支持SpEL。
condition:觸發(fā)條件,滿足條件就加入緩存,默認為空,表示全部都加入緩存,支持SpEL 。
- @Cacheable測試
但我們調(diào)用查詢的方法而沒有用@Cacheable注解的時候我們可以看到我們查詢出來的結果是兩個,而且查詢了數(shù)據(jù)庫兩次。
當我們用到@Cacheable注解的時候我們只查詢了數(shù)據(jù)庫一次,如果再次查詢就不會出現(xiàn)sql語句,而且已經(jīng)緩存到了redis里面。
測試結果:redis中有數(shù)據(jù),則訪問redis;如果沒有數(shù)據(jù),則訪問MySQL;
- 改變原有的key生成規(guī)則
改變原有的規(guī)則之后我們再次運行,我們的redis的鍵就不一樣了。
condition:當某個值大于或者小于某個值才進行緩存。
2、@CachePut注解
????????類似于更新操作,即每次不管緩存中有沒有結果,都從數(shù)據(jù)庫查找結果,并將結果更新到緩存,并返回結果。
value:緩存的名稱,在 spring 配置文件中定義,必須指定至少一個。
key:緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數(shù)進行組合。
condition:緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩存。
當我們使用它測試出來的結果則是:只存不取
- @Cacheable與@CachePut區(qū)別
@Cacheable
?注解表示方法的返回值可以被緩存,下次訪問相同的方法時,會直接從緩存中獲取結果,而不會再執(zhí)行方法內(nèi)部的邏輯。該注解標記在方法上,指定了緩存的名稱(也可以使用默認的緩存名稱),也可以通過參數(shù)指定緩存的Key。如果緩存中已有相應的Key存在,則會直接返回緩存中的值;若緩存中不存在對應的Key,則會執(zhí)行方法,并將方法的返回值添加到緩存中。
@CachePut
?注解表示無條件地執(zhí)行方法內(nèi)部的邏輯,并將方法的返回值添加到緩存中。該注解也標記在方法上,通常用于更新緩存的操作。它與?@Cacheable
?注解的不同之處在于,每次調(diào)用帶有?@CachePut
?注解的方法時,都會執(zhí)行方法內(nèi)部的邏輯,并將結果添加到緩存中,覆蓋原有的緩存值。
@CachePut
?注解常用于需要更新緩存中數(shù)據(jù)的場景,可以保證每次調(diào)用方法時都會執(zhí)行方法內(nèi)部的邏輯,并更新緩存的結果。Cacheable:會在redis中存儲數(shù)據(jù),同時也會讀取數(shù)據(jù)。
CachePut:會在redis中寫數(shù)據(jù),不會讀取數(shù)據(jù)。
3、@CacheEvict注解
????????用來清除用在本方法或者類上的緩存數(shù)據(jù)(用在哪里清除哪里)
value:緩存位置的一段名稱,不能為空
key:緩存的key,默認為空,表示使用方法的參數(shù)類型及參數(shù)值作為key,支持SpEL
condition:觸發(fā)條件,滿足條件就加入緩存,默認為空,表示全部都加入緩存,支持SpEL
allEntries:true表示清除value中的全部緩存,默認為false
測試結果:可以配置刪除指定緩存數(shù)據(jù),也可以刪除符合規(guī)則的所有緩存數(shù)據(jù);
4、應用場景
Redis注解式緩存是一種通過使用注解來簡化緩存操作的方法,它可以方便地在方法調(diào)用前后自動進行緩存的讀取和寫入。以下是Redis注解式緩存的常見應用場景:
-
方法結果緩存:對于一些計算成本較高的方法,可以使用注解將其結果緩存起來,避免重復計算。當下次調(diào)用相同的方法時,可以直接從緩存中獲取結果,提高性能并減輕服務器負載。
-
數(shù)據(jù)查詢緩存:對于頻繁讀取的數(shù)據(jù)查詢操作,可以使用注解將查詢結果緩存起來。這樣可以避免反復查詢數(shù)據(jù)庫,提高系統(tǒng)的響應速度。
-
限流和熔斷:通過使用注解實現(xiàn)限流,可以控制對某些資源或接口的并發(fā)請求量,避免系統(tǒng)過載。同時,可以設置特定的緩存時間,當資源或接口不可用時,返回緩存中的數(shù)據(jù),實現(xiàn)熔斷降級。
-
防止緩存穿透:通過使用注解和布隆過濾器等技術,可以在查詢之前攔截請求,并且判斷緩存中是否存在對應的數(shù)據(jù)。如果不存在,則直接返回空值,避免對數(shù)據(jù)庫的無效查詢。
-
異步刷新緩存:通過使用注解和異步任務,在數(shù)據(jù)更新時可以同時異步地更新相應的緩存,保持緩存和數(shù)據(jù)庫的一致性。
三、redis擊穿穿透雪崩
????????redis的簡單的作用:能夠極大的減輕MySQL的訪問壓力。
????????大部分的解決方案需要根據(jù)具體的場景和需求選擇合適的解決方案。同時,合理的緩存策略、數(shù)據(jù)預熱和監(jiān)控是緩解這些問題的關鍵,以確保系統(tǒng)的穩(wěn)定性和可靠性。
1、擊穿(Cache Miss)
什么事緩存擊穿?
????????緩存擊穿就是在處于集中式高并發(fā)訪問的情況下,當某個熱點 key 在失效的瞬間,大量的請求在緩存中獲取不到。瞬間擊穿了緩存,所有請求直接打到數(shù)據(jù)庫,就像是在一道屏障上擊穿了一個洞。
高并發(fā)量的同時key失效,導致請求直接到達數(shù)據(jù)庫;
- 解決方案之一:設置鎖
- 使用互斥鎖(Mutex Lock)或分布式鎖(Distributed Lock)來防止并發(fā)請求同時訪問數(shù)據(jù)庫。
- 獲取 Redis 鎖,如果沒有獲取到,則回到任務隊列繼續(xù)排隊
- 獲取到鎖,從數(shù)據(jù)庫拉取數(shù)據(jù)并放入緩存中
- 釋放鎖,其他請求從緩存中拿到數(shù)據(jù)
- 另一種方式:限流
- 在緩存中設置短暫的過期時間,并使用異步更新機制,即在緩存失效時,只有一個請求重新加載數(shù)據(jù)到緩存中,其他請求等待緩存數(shù)據(jù)更新完成后再讀取。
- 請求redis之前做流量削峰
2、穿透(Cache Penetration)
很多請求都在訪問數(shù)據(jù)庫一定不存在的數(shù)據(jù),造成請求將緩存和數(shù)據(jù)庫都穿透的情況。?
解決方案如下:
- 規(guī)則排除:
- 使用布隆過濾器(Bloom Filter)等數(shù)據(jù)結構來過濾不存在的請求,在查詢緩存之前先進行判斷,避免對數(shù)據(jù)庫進行不必要的查詢。
- 或者可以增加一些參數(shù)檢驗。例如數(shù)據(jù)庫數(shù)據(jù) id 一般都是遞增的,如果請求 id = -10 這種參數(shù),勢必繞過Redis。避免這種情況,可以對用戶真實性檢驗等操作。
- 緩存空值(Null Cache):
- 即將空結果緩存一段時間,以避免頻繁查詢不存在的數(shù)據(jù)。
- 可以理解為當緩存穿透時,redis存入一個類似null的值,下次訪問則直接緩存返回空,當數(shù)據(jù)庫中存在該數(shù)據(jù)的值則需要把redis存在的null值清除并載入新值,此方案不能解決頻繁隨機不規(guī)則的key請求。
3、雪崩(Cache Avalanche)
?雪崩和擊穿類似,不同的是擊穿是一個熱點 Key 某時刻失效,而雪崩是大量的熱點 Key 在一瞬間失效 。
方案如下:
- 設置合適的緩存失效時間,可以使用隨機值或添加一定的時間偏移量,避免多個鍵同時失效,導致數(shù)據(jù)庫負載過大。
- 使用多級緩存架構,將緩存分為多個層次,如本地緩存、分布式緩存等,提高緩存命中率和可用性。
- 實時監(jiān)控緩存健康狀態(tài),當緩存出現(xiàn)異常時,采取相應的措施,如自動降級、限流等,保護后端系統(tǒng)。
更快捷的解決方案是:給不同的熱點key設置不同的緩存策略