一,使用緩存的必要性
二,redis作為緩存的優(yōu)勢
三移盆,redis作為緩存的一般邏輯圖
四,redis整合SpringBoot使用的兩種方式
4.1伤为,純代碼手工實現(xiàn)
4.1.1咒循,創(chuàng)建maven項目并導入相關(guān)的mysql,redis依賴
4.1.2绞愚,配置yml文件
4.1.3叙甸,配置RedisTemplate模板
4.1.4,根據(jù)數(shù)據(jù)庫構(gòu)建相對應的實體類位衩,在service層上編寫相關(guān)業(yè)務裆蒸。
4.1.5,測試
4.2糖驴,注解實現(xiàn)緩存
4.2.1光戈,構(gòu)建數(shù)據(jù)庫并配置yml文件
4.2.2哪痰,配置緩存管理器
4.2.3遂赠,開啟注解支持
4.2.4久妆,SpringCache的一些相關(guān)注解說明
4.2.5,創(chuàng)建pojo類跷睦,編寫dao與mapper.xml
4.2.6筷弦,編寫業(yè)務接口及其實現(xiàn)類
4.2.7,編寫controller層并進行測試
五抑诸,緩存中常見問題
5.1烂琴,緩存穿透
5.2,緩存擊穿
5.3蜕乡,緩存雪崩
一奸绷,使用緩存的必要性
當服務器端收到客戶端請求量變多時,某些數(shù)據(jù)請求量大也會隨之變大层玲,這些熱點數(shù)據(jù)要頻繁的從數(shù)據(jù)庫中讀取号醉,給數(shù)據(jù)庫造成壓力椎工,自然會導致服務器響應客戶端變慢佩厚。因此碴卧,在一些不考慮實時性的數(shù)據(jù)中盈咳,我們通常會將這些數(shù)據(jù)臨時存儲存在內(nèi)存中菱属,當請求時候孝治,我們就能夠直接讀取內(nèi)存中的數(shù)據(jù)及時響應衔彻。這就是使用緩存的初衷纽窟。
緩存主要用于解決高性能與高并發(fā)以時減少數(shù)據(jù)庫壓力的作用尘盼。它的本質(zhì)就是將數(shù)據(jù)存儲在內(nèi)存中憨愉,當數(shù)據(jù)沒有發(fā)生本質(zhì)變化的時候,應盡量避免直接連接數(shù)據(jù)庫進行查詢卿捎,因為并發(fā)高時很可能會將數(shù)據(jù)庫壓塌配紫,而是應去緩存中讀取數(shù)據(jù),只有緩存中未查找到時再去數(shù)據(jù)庫中查詢娇澎,這樣就大大降低了數(shù)據(jù)庫的讀寫次數(shù)笨蚁,增加系統(tǒng)的性能和能提供的并發(fā)量。
二趟庄,redis作為緩存的優(yōu)勢
Redis提供了高性能的數(shù)據(jù)存儲功能括细。單個Redis server對請求的處理是基于單線程工作模型的,但由于是純內(nèi)存操作戚啥,并且單線程的工作模式避免了線程上下文切換帶來的額外開銷奋单,同時使用NIO多路復用機制(單線程維護多個I/O socket的狀態(tài),socket event handler統(tǒng)一進行event分發(fā)猫十,并通知到各個event listener)览濒,所以即使是單臺Redis server的性能也是非常的快呆盖,可支持11萬次/秒的SET操作,8.1萬次/秒的GET操作贷笛。這樣可以減少網(wǎng)絡(luò)的IO次數(shù)和數(shù)據(jù)體積应又。
Redis將其數(shù)據(jù)完全保存在內(nèi)存中,僅使用磁盤進行持久化乏苦。與其它鍵值數(shù)據(jù)存儲相比株扛,Redis有一組相對豐富的數(shù)據(jù)類型。支持string汇荐,list洞就,set,sorted set掀淘,hash
Redis支持數(shù)據(jù)持久化和數(shù)據(jù)恢復旬蟋,支持master/slave主/從機制,sentinal哨兵模式以及cluster集群模式革娄,允許單點故障倾贰,雖然同時也會付出性能的代價,但是相比于MemCached稠腊,其穩(wěn)定性還是有保證的(MemCached不支持數(shù)據(jù)持久化躁染,斷電或重啟后數(shù)據(jù)消失)
Redis支持事務,操作具有原子性 - 所有Redis操作都是原子操作架忌,這確保如果兩個客戶端并發(fā)訪問吞彤,Redis服務器能接收更新的值。
三叹放,redis作為緩存的一般邏輯圖
如下:
采用redis作為Mysql數(shù)據(jù)庫的緩存饰恕,在查找的時候,首先查找redis緩存井仰,如果找到則返回結(jié)果埋嵌;如果在redis中沒有找到,那么查找Mysql數(shù)據(jù)庫俱恶,找到的花則返回結(jié)果并且更新redis雹嗦;如果沒有找到則返回空。對于寫入的情況合是,直接寫入mysql數(shù)據(jù)庫了罪,mysql數(shù)據(jù)庫通過觸發(fā)器及UDF機制自動把變更的內(nèi)容更新到redis中。
四聪全,redis整合SpringBoot使用的兩種方式
4.1泊藕,純代碼手工實現(xiàn)
由于這種方法筆者比較復雜,代碼量也多难礼,因此并不推薦娃圆,只做簡單實現(xiàn)玫锋,供大家了解一下即可。
4.1.1讼呢,創(chuàng)建maven項目并導入相關(guān)的mysql撩鹿,redis依賴
<?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.6.4</version>
<relativePath/>
</parent>
<groupId>com.yy</groupId>
<artifactId>springboot-08-jedis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-08-jedis</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.1.2,配置yml文件
spring:
datasource:
username: root
password: 123456
#時區(qū)報錯吝岭,需要添加時區(qū)
url: jdbc:mysql://localhost:3306/school?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
springboot-redis配置
spring:
redis:
host: redis地址
port: 6379
password: 123456
database: 0
4.1.3三痰,配置RedisTemplate模板
從依賴項中我們可以看到,SpringBoot集成redis的依賴中為我們提供了redisTamplate類來對redis進行操作窜管。
其中他為我們提供了默認的序列化方式 :JdkSerializationRedisSerializer,作為JDK提供的序列化功能稚机。 這個序列化方式優(yōu)點是反序列化時不需要提供類型信息(class)幕帆,但缺點是需要實現(xiàn)Serializable接口,還有序列化后的結(jié)果非常龐大赖条,是JSON格式的5倍左右失乾,這樣就會消耗redis服務器的大量內(nèi)存。并且這種默認序列化方式在RDM工具中查看k-v值時會出現(xiàn)“亂碼”纬乍,不方便查看碱茁。
因此我們需要自己去自定義序列化方式來更好的方便我們操作。在此仿贬,我更推薦去配置Jackson2JsonRedisSerializer序列化方式纽竣,因為它是使用Jackson庫將對象序列化為JSON字符串。優(yōu)點是速度快茧泪,序列化后的字符串短小精悍蜓氨,不需要實現(xiàn)Serializable接口。但缺點也非常致命队伟,那就是此類的構(gòu)造函數(shù)中有一個類型參數(shù)穴吹,必須提供要序列化對象的類型信息(.class對象)。 通過查看源代碼嗜侮,發(fā)現(xiàn)其只在反序列化過程中用到了類型信息港令。
在redis配置類中配置自定義的序列化方式
package com.yy.myconfig;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
-
自定義一個redis template模板
*/
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
//為了使開發(fā)方便,直接使用<String, Object>
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);//Json序列化配置 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); //string的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); //key采用string的序列化方式 template.setKeySerializer(stringRedisSerializer); //hash的key也是用string的序列化方式 template.setHashKeySerializer(stringRedisSerializer); //value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); //hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template;
}
}
為了方便我們對reids不同的數(shù)據(jù)類型的操作锈颗,有需要的同學也可以將redisTamplate封裝為一個工具類顷霹,以便后面直接調(diào)用。
package com.yy.utils;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.sql.Date;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtils {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public RedisTemplate<String, Object> getRedisTemplate() {
return this.redisTemplate;
}
/** -------------------key相關(guān)操作--------------------- */
/**
* 刪除key
*
* @param key
*/
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* 批量刪除key
*
* @param keys
*/
public void delete(Collection<String> keys) {
redisTemplate.delete(keys);
}
/**
* 序列化key
*
* @param key
* @return
*/
public byte[] dump(String key) {
return redisTemplate.dump(key);
}
/**
* 是否存在key
*
* @param key
* @return
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 設(shè)置過期時間
*
* @param key
* @param timeout
* @param unit
* @return
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 設(shè)置過期時間
*
* @param key
* @param date
* @return
*/
public Boolean expireAt(String key, Date date) {
return redisTemplate.expireAt(key, date);
}
/**
* 查找匹配的key
*
* @param pattern
* @return
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 將當前數(shù)據(jù)庫的 key 移動到給定的數(shù)據(jù)庫 db 當中
*
* @param key
* @param dbIndex
* @return
*/
public Boolean move(String key, int dbIndex) {
return redisTemplate.move(key, dbIndex);
}
/**
* 移除 key 的過期時間宜猜,key 將持久保持
*
* @param key
* @return
*/
public Boolean persist(String key) {
return redisTemplate.persist(key);
}
/**
* 返回 key 的剩余的過期時間
*
* @param key
* @param unit
* @return
*/
public Long getExpire(String key, TimeUnit unit) {
return redisTemplate.getExpire(key, unit);
}
/**
* 返回 key 的剩余的過期時間
*
* @param key
* @return
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
/**
* 從當前數(shù)據(jù)庫中隨機返回一個 key
*
* @return
*/
public String randomKey() {
return (String) redisTemplate.randomKey();
}
/**
* 修改 key 的名稱
*
* @param oldKey
* @param newKey
*/
public void rename(String oldKey, String newKey) {
redisTemplate.rename(oldKey, newKey);
}
/**
* 僅當 newkey 不存在時泼返,將 oldKey 改名為 newkey
*
* @param oldKey
* @param newKey
* @return
*/
public Boolean renameIfAbsent(String oldKey, String newKey) {
return redisTemplate.renameIfAbsent(oldKey, newKey);
}
/**
* 返回 key 所儲存的值的類型
*
* @param key
* @return
*/
public DataType type(String key) {
return redisTemplate.type(key);
}
/** -------------------string相關(guān)操作--------------------- */
/**
* 設(shè)置指定 key 的值
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 獲取指定 key 的值
*
* @param key
* @return
*/
public String get(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
/**
* 返回 key 中字符串值的子字符
*
* @param key
* @param start
* @param end
* @return
*/
public String getRange(String key, long start, long end) {
return redisTemplate.opsForValue().get(key, start, end);
}
/**
* 將給定 key 的值設(shè)為 value ,并返回 key 的舊值(old value)
*
* @param key
* @param value
* @return
*/
public String getAndSet(String key, String value) {
return (String) redisTemplate.opsForValue().getAndSet(key, value);
}
/**
* 對 key 所儲存的字符串值姨拥,獲取指定偏移量上的位(bit)
*
* @param key
* @param offset
* @return
*/
public Boolean getBit(String key, long offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
/**
* 批量獲取
*
* @param keys
* @return
*/
public List<Object> multiGet(Collection<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* 設(shè)置ASCII碼, 字符串'a'的ASCII碼是97, 轉(zhuǎn)為二進制是'01100001', 此方法是將二進制第offset位值變?yōu)関alue
*
* @param key 位置
* @param value 值,true為1, false為0
* @return
*/
public boolean setBit(String key, long offset, boolean value) {
return redisTemplate.opsForValue().setBit(key, offset, value);
}
/**
* 將值 value 關(guān)聯(lián)到 key 绅喉,并將 key 的過期時間設(shè)為 timeout
*
* @param key
* @param value
* @param timeout 過期時間
* @param unit 時間單位, 天:TimeUnit.DAYS 小時:TimeUnit.HOURS 分鐘:TimeUnit.MINUTES
* 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
*/
public void setEx(String key, String value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 只有在 key 不存在時設(shè)置 key 的值
*
* @param key
* @param value
* @return 之前已經(jīng)存在返回false, 不存在返回true
*/
public boolean setIfAbsent(String key, String value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 用 value 參數(shù)覆寫給定 key 所儲存的字符串值渠鸽,從偏移量 offset 開始
*
* @param key
* @param value
* @param offset 從指定位置開始覆寫
*/
public void setRange(String key, String value, long offset) {
redisTemplate.opsForValue().set(key, value, offset);
}
/**
* 獲取字符串的長度
*
* @param key
* @return
*/
public Long size(String key) {
return redisTemplate.opsForValue().size(key);
}
/**
* 批量添加
*
* @param maps
*/
public void multiSet(Map<String, String> maps) {
redisTemplate.opsForValue().multiSet(maps);
}
/**
* 同時設(shè)置一個或多個 key-value 對,當且僅當所有給定 key 都不存在
*
* @param maps
* @return 之前已經(jīng)存在返回false, 不存在返回true
*/
public boolean multiSetIfAbsent(Map<String, String> maps) {
return redisTemplate.opsForValue().multiSetIfAbsent(maps);
}
/**
* 增加(自增長), 負數(shù)則為自減
*
* @param key
* @return
*/
public Long incrBy(String key, long increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
/**
* @param key
* @return
*/
public Double incrByFloat(String key, double increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
/**
* 追加到末尾
*
* @param key
* @param value
* @return
*/
public Integer append(String key, String value) {
return redisTemplate.opsForValue().append(key, value);
}
/** -------------------hash相關(guān)操作------------------------- */
/**
* 獲取存儲在哈希表中指定字段的值
*
* @param key
* @param field
* @return
*/
public Object hGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* 獲取所有給定字段的值
*
* @param key
* @return
*/
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 獲取所有給定字段的值
*
* @param key
* @param fields
* @return
*/
public List<Object> hMultiGet(String key, Collection<Object> fields) {
return redisTemplate.opsForHash().multiGet(key, fields);
}
public void hPut(String key, String hashKey, String value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public void hPutAll(String key, Map<String, String> maps) {
redisTemplate.opsForHash().putAll(key, maps);
}
/**
* 僅當hashKey不存在時才設(shè)置
*
* @param key
* @param hashKey
* @param value
* @return
*/
public Boolean hPutIfAbsent(String key, String hashKey, String value) {
return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}
/**
* 刪除一個或多個哈希表字段
*
* @param key
* @param fields
* @return
*/
public Long hDelete(String key, Object... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* 查看哈希表 key 中柴罐,指定的字段是否存在
*
* @param key
* @param field
* @return
*/
public boolean hExists(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 為哈希表 key 中的指定字段的整數(shù)值加上增量 increment
*
* @param key
* @param field
* @param increment
* @return
*/
public Long hIncrBy(String key, Object field, long increment) {
return redisTemplate.opsForHash().increment(key, field, increment);
}
/**
* 為哈希表 key 中的指定字段的整數(shù)值加上增量 increment
*
* @param key
* @param field
* @param delta
* @return
*/
public Double hIncrByFloat(String key, Object field, double delta) {
return redisTemplate.opsForHash().increment(key, field, delta);
}
/**
* 獲取所有哈希表中的字段
*
* @param key
* @return
*/
public Set<Object> hKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* 獲取哈希表中字段的數(shù)量
*
* @param key
* @return
*/
public Long hSize(String key) {
return redisTemplate.opsForHash().size(key);
}
/**
* 獲取哈希表中所有值
*
* @param key
* @return
*/
public List<Object> hValues(String key) {
return redisTemplate.opsForHash().values(key);
}
/**
* 迭代哈希表中的鍵值對
*
* @param key
* @param options
* @return
*/
public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
return redisTemplate.opsForHash().scan(key, options);
}
/** ------------------------list相關(guān)操作---------------------------- */
/**
* 通過索引獲取列表中的元素
*
* @param key
* @param index
* @return
*/
public String lIndex(String key, long index) {
return (String) redisTemplate.opsForList().index(key, index);
}
/**
* 獲取列表指定范圍內(nèi)的元素
*
* @param key
* @param start 開始位置, 0是開始位置
* @param end 結(jié)束位置, -1返回所有
* @return
*/
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 存儲在list頭部
*
* @param key
* @param value
* @return
*/
public Long lLeftPush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lLeftPushAll(String key, String... value) {
return redisTemplate.opsForList().leftPushAll(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lLeftPushAll(String key, Collection<String> value) {
return redisTemplate.opsForList().leftPushAll(key, value);
}
/**
* 當list存在的時候才加入
*
* @param key
* @param value
* @return
*/
public Long lLeftPushIfPresent(String key, String value) {
return redisTemplate.opsForList().leftPushIfPresent(key, value);
}
/**
* 如果pivot存在,再pivot前面添加
*
* @param key
* @param pivot
* @param value
* @return
*/
public Long lLeftPush(String key, String pivot, String value) {
return redisTemplate.opsForList().leftPush(key, pivot, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lRightPush(String key, String value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lRightPushAll(String key, String... value) {
return redisTemplate.opsForList().rightPushAll(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lRightPushAll(String key, Collection<String> value) {
return redisTemplate.opsForList().rightPushAll(key, value);
}
/**
* 為已存在的列表添加值
*
* @param key
* @param value
* @return
*/
public Long lRightPushIfPresent(String key, String value) {
return redisTemplate.opsForList().rightPushIfPresent(key, value);
}
/**
* 在pivot元素的右邊添加值
*
* @param key
* @param pivot
* @param value
* @return
*/
public Long lRightPush(String key, String pivot, String value) {
return redisTemplate.opsForList().rightPush(key, pivot, value);
}
/**
* 通過索引設(shè)置列表元素的值
*
* @param key
* @param index 位置
* @param value
*/
public void lSet(String key, long index, String value) {
redisTemplate.opsForList().set(key, index, value);
}
/**
* 移出并獲取列表的第一個元素
*
* @param key
* @return 刪除的元素
*/
public String lLeftPop(String key) {
return (String) redisTemplate.opsForList().leftPop(key);
}
/**
* 移出并獲取列表的第一個元素徽缚, 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止
*
* @param key
* @param timeout 等待時間
* @param unit 時間單位
* @return
*/
public String lBLeftPop(String key, long timeout, TimeUnit unit) {
return (String) redisTemplate.opsForList().leftPop(key, timeout, unit);
}
/**
* 移除并獲取列表最后一個元素
*
* @param key
* @return 刪除的元素
*/
public String lRightPop(String key) {
return (String) redisTemplate.opsForList().rightPop(key);
}
/**
* 移出并獲取列表的最后一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止
*
* @param key
* @param timeout 等待時間
* @param unit 時間單位
* @return
*/
public String lBRightPop(String key, long timeout, TimeUnit unit) {
return (String) redisTemplate.opsForList().rightPop(key, timeout, unit);
}
/**
* 移除列表的最后一個元素革屠,并將該元素添加到另一個列表并返回
*
* @param sourceKey
* @param destinationKey
* @return
*/
public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
return (String) redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
destinationKey);
}
/**
* 從列表中彈出一個值凿试,將彈出的元素插入到另外一個列表中并返回它; 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止
*
* @param sourceKey
* @param destinationKey
* @param timeout
* @param unit
* @return
*/
public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
long timeout, TimeUnit unit) {
return (String) redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
destinationKey, timeout, unit);
}
/**
* 刪除集合中值等于value得元素
*
* @param key
* @param index index=0, 刪除所有值等于value的元素; index>0, 從頭部開始刪除第一個值等于value的元素;
* index<0, 從尾部開始刪除第一個值等于value的元素;
* @param value
* @return
*/
public Long lRemove(String key, long index, String value) {
return redisTemplate.opsForList().remove(key, index, value);
}
/**
* 裁剪list
*
* @param key
* @param start
* @param end
*/
public void lTrim(String key, long start, long end) {
redisTemplate.opsForList().trim(key, start, end);
}
/**
* 獲取列表長度
*
* @param key
* @return
*/
public Long lLen(String key) {
return redisTemplate.opsForList().size(key);
}
/** --------------------set相關(guān)操作-------------------------- */
/**
* set添加元素
*
* @param key
* @param values
* @return
*/
public Long sAdd(String key, String... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* set移除元素
*
* @param key
* @param values
* @return
*/
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
/**
* 移除并返回集合的一個隨機元素
*
* @param key
* @return
*/
public String sPop(String key) {
return (String) redisTemplate.opsForSet().pop(key);
}
/**
* 將元素value從一個集合移到另一個集合
*
* @param key
* @param value
* @param destKey
* @return
*/
public Boolean sMove(String key, String value, String destKey) {
return redisTemplate.opsForSet().move(key, value, destKey);
}
/**
* 獲取集合的大小
*
* @param key
* @return
*/
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}
/**
* 判斷集合是否包含value
*
* @param key
* @param value
* @return
*/
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
/**
* 獲取兩個集合的交集
*
* @param key
* @param otherKey
* @return
*/
public Set<Object> sIntersect(String key, String otherKey) {
return redisTemplate.opsForSet().intersect(key, otherKey);
}
/**
* 獲取key集合與多個集合的交集
*
* @param key
* @param otherKeys
* @return
*/
public Set<Object> sIntersect(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().intersect(key, otherKeys);
}
/**
* key集合與otherKey集合的交集存儲到destKey集合中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sIntersectAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().intersectAndStore(key, otherKey,
destKey);
}
/**
* key集合與多個集合的交集存儲到destKey集合中
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sIntersectAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().intersectAndStore(key, otherKeys,
destKey);
}
/**
* 獲取兩個集合的并集
*
* @param key
* @param otherKeys
* @return
*/
public Set<Object> sUnion(String key, String otherKeys) {
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**
* 獲取key集合與多個集合的并集
*
* @param key
* @param otherKeys
* @return
*/
public Set<Object> sUnion(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**
* key集合與otherKey集合的并集存儲到destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
}
/**
* key集合與多個集合的并集存儲到destKey中
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sUnionAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
}
/**
* 獲取兩個集合的差集
*
* @param key
* @param otherKey
* @return
*/
public Set<Object> sDifference(String key, String otherKey) {
return redisTemplate.opsForSet().difference(key, otherKey);
}
/**
* 獲取key集合與多個集合的差集
*
* @param key
* @param otherKeys
* @return
*/
public Set<Object> sDifference(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().difference(key, otherKeys);
}
/**
* key集合與otherKey集合的差集存儲到destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sDifference(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().differenceAndStore(key, otherKey,
destKey);
}
/**
* key集合與多個集合的差集存儲到destKey中
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sDifference(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,
destKey);
}
/**
* 獲取集合所有元素
*
* @param key
* @return
*/
public Set<Object> setMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 隨機獲取集合中的一個元素
*
* @param key
* @return
*/
public String sRandomMember(String key) {
return (String) redisTemplate.opsForSet().randomMember(key);
}
/**
* 隨機獲取集合中count個元素
*
* @param key
* @param count
* @return
*/
public List<Object> sRandomMembers(String key, long count) {
return redisTemplate.opsForSet().randomMembers(key, count);
}
/**
* 隨機獲取集合中count個元素并且去除重復的
*
* @param key
* @param count
* @return
*/
public Set<Object> sDistinctRandomMembers(String key, long count) {
return redisTemplate.opsForSet().distinctRandomMembers(key, count);
}
/**
* @param key
* @param options
* @return
*/
public Cursor<Object> sScan(String key, ScanOptions options) {
return redisTemplate.opsForSet().scan(key, options);
}
/**------------------zSet相關(guān)操作--------------------------------*/
/**
* 添加元素,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @param score
* @return
*/
public Boolean zAdd(String key, String value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
* @param key
* @param values
* @return
*/
public Long zAdd(String key, Set<ZSetOperations.TypedTuple<Object>> values) {
return redisTemplate.opsForZSet().add(key, values);
}
/**
* @param key
* @param values
* @return
*/
public Long zRemove(String key, Object... values) {
return redisTemplate.opsForZSet().remove(key, values);
}
/**
* 增加元素的score值似芝,并返回增加后的值
*
* @param key
* @param value
* @param delta
* @return
*/
public Double zIncrementScore(String key, String value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
/**
* 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @return 0表示第一位
*/
public Long zRank(String key, Object value) {
return redisTemplate.opsForZSet().rank(key, value);
}
/**
* 返回元素在集合的排名,按元素的score值由大到小排列
*
* @param key
* @param value
* @return
*/
public Long zReverseRank(String key, Object value) {
return redisTemplate.opsForZSet().reverseRank(key, value);
}
/**
* 獲取集合的元素, 從小到大排序
*
* @param key
* @param start 開始位置
* @param end 結(jié)束位置, -1查詢所有
* @return
*/
public Set<Object> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
/**
* 獲取集合元素, 并且把score值也獲取
*
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start,
long end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
/**
* 根據(jù)Score值查詢集合元素
*
* @param key
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<Object> zRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**
* 根據(jù)Score值查詢集合元素, 從小到大排序
*
* @param key
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key,
double min, double max) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
}
/**
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key,
double min, double max, long start, long end) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
start, end);
}
/**
* 獲取集合的元素, 從大到小排序
*
* @param key
* @param start
* @param end
* @return
*/
public Set<Object> zReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* 獲取集合的元素, 從大到小排序, 并返回score值
*
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeWithScores(String key,
long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
end);
}
/**
* 根據(jù)Score值查詢集合元素, 從大到小排序
*
* @param key
* @param min
* @param max
* @return
*/
public Set<Object> zReverseRangeByScore(String key, double min,
double max) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
/**
* 根據(jù)Score值查詢集合元素, 從大到小排序
*
* @param key
* @param min
* @param max
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeByScoreWithScores(
String key, double min, double max) {
return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
min, max);
}
/**
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<Object> zReverseRangeByScore(String key, double min,
double max, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
start, end);
}
/**
* 根據(jù)score值獲取集合元素數(shù)量
*
* @param key
* @param min
* @param max
* @return
*/
public Long zCount(String key, double min, double max) {
return redisTemplate.opsForZSet().count(key, min, max);
}
/**
* 獲取集合大小
*
* @param key
* @return
*/
public Long zSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
/**
* 獲取集合大小
*
* @param key
* @return
*/
public Long zZCard(String key) {
return redisTemplate.opsForZSet().zCard(key);
}
/**
* 獲取集合中value元素的score值
*
* @param key
* @param value
* @return
*/
public Double zScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
/**
* 移除指定索引位置的成員
*
* @param key
* @param start
* @param end
* @return
*/
public Long zRemoveRange(String key, long start, long end) {
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
/**
* 根據(jù)指定的score值的范圍來移除成員
*
* @param key
* @param min
* @param max
* @return
*/
public Long zRemoveRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
}
/**
* 獲取key和otherKey的并集并存儲在destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
}
/**
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForZSet()
.unionAndStore(key, otherKeys, destKey);
}
/**
* 交集
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, String otherKey,
String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,
destKey);
}
/**
* 交集
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
destKey);
}
/**
* @param key
* @param options
* @return
*/
public Cursor<ZSetOperations.TypedTuple<Object>> zScan(String key, ScanOptions options) {
return redisTemplate.opsForZSet().scan(key, options);
}
}
4.1.4那婉,根據(jù)數(shù)據(jù)庫構(gòu)建相對應的實體類,在service層上編寫相關(guān)業(yè)務党瓮。
dao層上的簡單操作
package com.yy.service;
import com.yy.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface US {
@Select("select * from user where id=#{id}")
User selectUserById(Integer id);
}
業(yè)務類上進行實現(xiàn)
package com.yy.service;
import com.yy.pojo.User;
import com.yy.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class ServiceImpl {
@Autowired
private US us;
@Autowired
RedisUtils redisUtils;
public User select(Integer id) {
if (Boolean.FALSE.equals(redisUtils.hasKey(String.valueOf(id)))) {
log.info("查詢mysql數(shù)據(jù)庫");
User user = us.selectUserById(id);
user.setId(id);
user.setUsername("張三");
user.setPwd("147852");
redisUtils.hPut("user", String.valueOf(id), String.valueOf(user));
return user;
} else {
log.info("查詢redis");
return (User) redisUtils.hGet("user", String.valueOf(id));
}
}
}
4.1.5详炬,測試
編寫一個測試類進行測試:
@Test
void test() {
System.out.println(service.select(1002));
}
運行結(jié)果可以看到數(shù)據(jù)已經(jīng)查詢出來,并且將數(shù)據(jù)存儲在redis中了
但是這種方式寞奸,代碼量和操作都不太方便呛谜。因此,在項目中我跟推薦使用注解去實現(xiàn)枪萄。
4.2隐岛,注解實現(xiàn)緩存
因為導入依賴與上面大致一樣,需要注意的是我在這個新的測試上使用了Mybatis-plus插件瓷翻,所以依賴上還需要加上相關(guān)依賴聚凹,其他地方不重復詳述。
4.2.1逻悠,構(gòu)建數(shù)據(jù)庫并配置yml文件
server:
port: 8090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: 123456
username: root
url: jdbc:mysql://localhost:3306/db_user?&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
redis:
database: 0
port: 6379
host: redis的ip地址
timeout: 50000
lettuce:
pool:
max-active: 8 #連接池最大連接數(shù)
max-wait: -1 #最大阻塞等待時間(負數(shù)表示無限制)
max-idle: 8 #最大空閑連接數(shù)
password: 123456
aop:
proxy-target-class: true
mybatis-plus:
mapper-locations: classPath:/mapper/*.xml
configuration:
map-underscore-to-camel-case: false # 禁止大寫變小寫時自動添加下劃線
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
type-aliases-package: com.yy.pojo
為了測試一下緩存效果元践,這次筆者重新構(gòu)建了一個數(shù)據(jù)庫,并向數(shù)據(jù)庫中導入10萬條測試數(shù)據(jù)童谒。以備后面測試緩存讀取數(shù)據(jù)時的效果单旁。
4.2.2,配置緩存管理器
package com.yy.redisCache.util;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
public class RedisConfig {
/**
* 配置緩存管理器
* @param factory 線程連接工廠
* @return 緩存管理器
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//設(shè)置緩存過期時間5分鐘
config.entryTtl(Duration.ofMinutes(5))
//設(shè)置緩存前綴
.prefixCacheNameWith("redis:cache:")
//禁止緩存null值
.disableCachingNullValues()
//添加鍵值序列化配置
.serializeKeysWith(keyPair())
.serializeValuesWith(valuePair());
return RedisCacheManager.builder(factory)
.withCacheConfiguration("test",config)
.build();
}
/**
* 配置鍵序列化
* @return
*/
@Bean
RedisSerializationContext.SerializationPair<String> keyPair(){
return RedisSerializationContext.SerializationPair.fromSerializer( new StringRedisSerializer());
}
/**
* 配置值序列化
* @return
*/
@Bean
RedisSerializationContext.SerializationPair<Object> valuePair(){
return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
}
}
4.2.3饥伊,開啟注解支持
在啟動類上添加@EnableCache象浑,開啟緩存功能
package com.yy;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableCaching//開啟緩存
@MapperScan({"com.yy.dao","com.yy.redisCache.dao"})
@EnableTransactionManagement
public class SpringbootMongodbStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMongodbStudyApplication.class, args);
}
}
4.2.4,SpringCache的一些相關(guān)注解說明
類型 名稱 解釋
注解
@CacheConfig
一般配置在指定類上用于指定緩存名稱琅豆,并且名稱與緩存管理器中的緩存名保持一致
@Cacheable 可標記在方法上愉豺,也可以標記在類上。標記在類上時表示該類的所有方法都支持緩存茫因。標記在方法上時蚪拦,該方法的返回結(jié)果將會進行緩存。如果已經(jīng)存在該緩存,則會直接從緩存中讀取數(shù)據(jù)驰贷∈⒑伲可以通過key指定入?yún)ⅲ瑅alue為方法返回值緩存在得 Cache名稱
@CachePut 可以標注在類上和方法上括袒。無論是否存在緩存次兆,每次都會重新添加緩存,常用于更新锹锰。
@CacheEvit 可以標記在一個方法上芥炭,也可以標記在一個類上。標記方法上標識方法執(zhí)行時候觸發(fā)清楚緩存操作恃慧,當標記在一個類上時表示其中所有的方法的執(zhí)行都會觸發(fā)緩存的清除操作园蝠。
@Caching 可以讓我們在一個方法或者類上同時指定多個Spring Cache相關(guān)的注解。
注解配置參數(shù) value糕伐,cacheNames 指定(緩存管理器) Cache名稱砰琢,效果類似于@CacheConfig中的指定名稱。如果已經(jīng)用@CacheConfig在類上指定了良瞧,方法中就不需要使用了,就像提取公共名稱一樣
key 緩存的 key训唱,可以為空褥蚯,如果指定要按照 SpEL 表達式編寫,如果不指定况增,則缺省按照方法的所有參數(shù)進行組合赞庶。未設(shè)置則為默認值。
condition 緩存的條件澳骤,可以為空歧强,使用 SpEL 編寫,返回 true 或者 false为肮,只有為 true 才進行緩存
unless 可以用來是決定是否添加到緩存摊册,與 condition不同的是,unless表達式是在方法調(diào)用之后進行評估的颊艳。如果返回false茅特,才放入緩存 (與condition相反)
sync 在多線程環(huán)境下,某些操作可能使用相同參數(shù)同步調(diào)用棋枕。默認情況下白修,緩存不鎖定任何資源,可能導致多次計算重斑,而違反了緩存的目的兵睛。對于這些特定的情況,屬性 sync 可以指示底層將緩存鎖住,使只有一個線程可以進入計算祖很,而其他線程堵塞笛丙,直到返回結(jié)果更新到緩存中
EL表達式可以使用方法參數(shù)及對應的屬性。使用方法參數(shù)時突琳,可以直接使用 "#參數(shù)名" 或者 "#p參數(shù)index9(參數(shù)下標)"若债,#result為返回值
4.2.5,創(chuàng)建pojo類拆融,編寫dao與mapper.xml
package com.yy.redisCache.pojo;
import lombok.Data;
import java.io.Serializable;
/**
- @author young
- @date 2022/9/4 13:02
- @description:
*/
@Data
public class RedisTest implements Serializable {
private static final long serialVersionUID = -7120480143440693836L;
private Integer id;
private String redis_name;
private String redis_pwd;
private String address;
}
4.2.6蠢琳,編寫業(yè)務接口及其實現(xiàn)類
package com.yy.redisCache.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yy.redisCache.pojo.RedisTest;
/**
- @author young
- @date 2022/9/4 13:06
- @description: 業(yè)務接口
*/
public interface RedisTestService extends IService<RedisTest> {
}
在實現(xiàn)類中添加緩存注解,指定的業(yè)務的返回結(jié)果存入緩存镜豹。
package com.yy.redisCache.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yy.redisCache.dao.RedisTestDao;
import com.yy.redisCache.pojo.RedisTest;
import com.yy.redisCache.service.RedisTestService;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
@author young
@date 2022/9/4 13:08
-
@description: service實現(xiàn)類
*/
@Service
@CacheConfig(cacheNames = "test")//緩存名傲须,和管理器中配置的一致
public class RedisTestServiceImpl extends ServiceImpl<RedisTestDao, RedisTest> implements RedisTestService {
@Resource
private RedisTestDao redisTestDao;/**
- 查尋10000條數(shù)據(jù)
- @return
- key 可為空,指定需按照SpEL編寫
- unless 不緩存的條件 條件為true不緩存
*/
@Override
@Cacheable(key = "'allUser'",unless = "#result==null")//#result返回結(jié)果
public List<RedisTest> list() {
QueryWrapper<RedisTest> wrapper = new QueryWrapper<>();
wrapper.le("id",10000);
return redisTestDao.selectList(wrapper);
}
/**
* 批量查詢
* @param idList
* @return
*/
@Override
public List<RedisTest> listByIds(Collection<? extends Serializable> idList) {
return super.listByIds(idList);
}
/**
* 刪除批量信息
* @param list
* @return
*/
@CacheEvict
@Override
public boolean removeByIds(Collection<?> list) {
return super.removeByIds(list);
}
/**
* 修改某個對象
* @param entity
* @return
*/
@Override
@CachePut
public boolean updateById(RedisTest entity) {
return super.updateById(entity);
}
}
4.2.7趟脂,編寫controller層并進行測試
package com.yy.redisCache.controller;
import com.yy.redisCache.pojo.RedisTest;
import com.yy.redisCache.service.impl.RedisTestServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@author young
@date 2022/9/4 13:13
-
@description:
*/
@RestController
@RequestMapping("/Cache")
@Slf4j
public class RedisCacheController {
@Autowired
private RedisTestServiceImpl service;/**
- 刪除指定id以下的所有數(shù)據(jù)及緩存
- @param id id
- @return
*/
@DeleteMapping("/del/{id}")
public Boolean remove(@PathVariable Integer id){
List<Integer> list = new ArrayList<>();
for (int i = 0; i <= id; i++) {
list.add(i);
}
return service.removeByIds(list);
}
/**
- 更新數(shù)據(jù)
- @param redisTest 更新對象
- @return
*/
@PutMapping("/update")
public Boolean update(RedisTest redisTest){
return service.updateById(redisTest);
}
/**
- 測試從數(shù)據(jù)庫/緩存獲取10000條數(shù)據(jù)的時間
- @return
*/
@GetMapping("/get")
public List<RedisTest> getBody(){
Long startTime = System.currentTimeMillis();
List<RedisTest> list = service.list();
//第一次存入數(shù)據(jù)庫查詢3875ms 第二次緩存查詢:1131ms
log.info("查詢時間10000條數(shù)據(jù)的時間是:{}",System.currentTimeMillis()-startTime);
return list;
}
/**
- 批量獲取指定的數(shù)據(jù)
- @param id
- @return
*/
@GetMapping("/getOne/{id}")
public String getOne(@PathVariable Integer... id){
List<RedisTest> ids = service.listByIds(Arrays.asList(id));
return ids.isEmpty()?"獲取成功":"獲取失敗";
}
}
通過接口測試工具測試獲取10000條數(shù)據(jù)的測試后泰讽,可以看到第一次獲取10000條數(shù)據(jù)后,時間為3875ms昔期,并且redis里已經(jīng)有了相關(guān)緩存已卸。
再次執(zhí)行該請求后,獲取時間大大減少為1131ms硼一。此時累澡,表示是從緩存讀取的數(shù)據(jù)。
可以猜想到般贼,由于數(shù)據(jù)庫執(zhí)行查詢操作時愧哟,需要進行數(shù)據(jù)庫連接等操作,因次直接從數(shù)據(jù)庫獲取數(shù)據(jù)的時間會比redis緩存慢哼蛆。最后我們測試一下更新刪除緩存等操作蕊梧,成功達到我們的預期結(jié)果。
五腮介,緩存中常見問題
5.1肥矢,緩存穿透
緩存穿透是指查詢一個不存在的數(shù)據(jù),在緩存層和持久層都查詢不到萤厅,這樣查不到的數(shù)據(jù)不會寫入緩存橄抹,這樣一個不存在的數(shù)據(jù)每次的請求都會去查詢持久層,緩存也就失去了保護后端持久層的意義了惕味。最嚴重的后果就是是后端存儲負載加大楼誓,造成后端存儲宕機。
形成原因:
業(yè)務代碼或數(shù)據(jù)出現(xiàn)問題名挥,比如讀寫key不一致
網(wǎng)絡(luò)爬蟲或惡意攻擊
解決方法:
1疟羹,緩存空對象:對于查詢不到的數(shù)據(jù)給它設(shè)置一個空值。但是可能會造成內(nèi)存空間緊張,因為value為null也會占用內(nèi)存空間榄融,不過我們可以給它設(shè)置一個過期時間参淫,讓空值緩存自動過期。這種方法使用與數(shù)據(jù)經(jīng)常變化愧杯,實時性較高的場景涎才。
2,布隆過濾器攔截:在訪問緩存層和存儲層之前力九,將存在的key用布隆過濾器提前保存起來耍铜,做第一層攔截,當收到一個對key請求時先用布隆過濾器驗證是key否存在跌前,如果存在在進入緩存層棕兼、存儲層〉峙遥可以使用bitmap做布隆過濾器伴挚。這種方法適用于數(shù)據(jù)命中不高、數(shù)據(jù)相對固定灾炭、實時性低的應用場景茎芋,代碼維護較為復雜,但是緩存空間占用少蜈出。
5.2败徊,緩存擊穿
某個key成為一個熱點(商品秒殺時)時,這樣處于一個集中式高并發(fā)的情況下掏缎,如果key突然失效一瞬間,請求就會馬上擊穿緩存層煤杀,直接請求數(shù)據(jù)庫眷蜈,這樣后端負載會很快過載甚至崩潰。
解決辦法:
1沈自,根據(jù)實際情況設(shè)置二級緩存或者熱點緩存永不過期酌儒。
2,設(shè)置分布式互斥鎖枯途,只允許一個線程重建緩存忌怎,其他線程等待重建緩存的線程執(zhí)行完,重新從緩存獲取數(shù)據(jù)
5.3酪夷,緩存雪崩
當緩存層由于某些原因不可用(宕機)或者大量緩存由于超時時間相同在同一時間段失效(大批key失效/熱點數(shù)據(jù)失效)榴啸,大量請求直接到達存儲層,存儲層壓力過大導致系統(tǒng)雪崩晚岭。
解決辦法:
1鸥印,設(shè)置多級緩存,不同的緩存設(shè)置不同的過期時間。
2库说,將緩存層設(shè)計為高可用狂鞋,例如使用sentinel哨兵模式或者cluster,這樣即使個別節(jié)點或機器宕機也不會影響緩存效果實現(xiàn)潜的。
3骚揍,將緩存的過期時間設(shè)置隨機分布的,避免在同一時間內(nèi)緩存集體失效啰挪。
————————————————
版權(quán)聲明:本文為CSDN博主「深情不及里子」的原創(chuàng)文章信不,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明脐供。
原文鏈接:https://blog.csdn.net/qq_42263280/article/details/126760489