聊聊緩存:
- 緩存最終的目的是為減輕服務端壓力,減少網絡傳輸請求 比如: 瀏覽器訪問自帶緩存。 App客戶端底層都有緩存技術的。
注: (移動端登錄 用token 本地是有個文件的)
案例:
如果一旦請求到服務器端之后,會在本地緩存一份,快速響應給用戶。
常見的緩存方案:
- 網絡緩存
- 代理緩存(Nginx可以緩存)
- CDN緩存
- 服務器緩存
- 數據庫緩存
- 平台緩存級緩存
總結: 緩存最終的目的是為減輕服務端壓力,減少網絡傳輸請求
單機緩存與分布式緩存:
-
Session 是存放在服務器端(保存在單個JVM中),返回SessionId(響應頭),客戶端存放的SessionId,下次請求時候,直接使用對於的SessionId從服務器端查詢對應Session
-
分布式Session (Session共享的問題)----直接使用token解決
案例單機版: 實現單個JVM緩存框架, Session Key Value 可以用Map集合實現 過期時間需要好好設計了 寫一個給予Map集合實現Jvm緩存框架:
首先定義map實現的緩存類:(concurrentHashmap保證安全)
緩存:
@Component //這樣的話 就是單例的了!!!注入到容器里面 public class MapCache<K,V> { //存放緩存容器 public ConcurrentHashMap<K, V> concurrentHashMap = new ConcurrentHashMap<K,V>(); //純手寫單個JVM緩存框架 緩存概念偏向於臨時 //對傳統的Map包裝 public void put(K key,V value){ concurrentHashMap.put(key, value); } //查詢 public V get(K key){ return concurrentHashMap.get(key); } public void remove(String k){ //這個map是安全的 不需要加鎖! concurrentHashMap.remove(k); } }
controller類:
@RestController public class IndexController { @Autowired private MapCache<String, Object> mapCache; @RequestMapping("/get") public String get(String key){ return (String)mapCache.get(key); } @RequestMapping("/put") public String put(String key, String value){ mapCache.put(key, value); return "成功"; } @RequestMapping("/remove") public String remove(String key){ mapCache.remove(key); return "成功"; } }
啟動類
@SpringBootApplication(scanBasePackages={"com.toov5.*"}) public class app { public static void main(String[] args) { SpringApplication.run(app.class, args); } }
單機版緩存框架
-
單點緩存框架 只能針對單個jvm中,緩存容器存放jvm中,每個緩存互不影響 Ehcache gauva chache 內置緩存框架 jvm緩存框架
-
分布式緩存技術(共享緩存數據) Redis Meacache
example:
mybatis、hibernate底層都使用了Ehcache
本地緩存Ehcache 什么是Ehcache Ehcache是純java的開源緩存框架,具有快速、精干等特點,是Hibernate中默認的CacheProvider。它主要面向通用緩存、Java EE和輕量級容器,具有內存和磁盤存儲、緩存加載器、緩存擴展、緩存異常處理程序。
Ehcache最初由Greg Luck於2003年開始開發。2009年,該項目被Terracotta購買。軟件仍然開源,但一些新的主要功能(例如,快速可重啟性之間的一致性的)只能在商業產品中使用。
Ehcache 被廣泛用於在Hibernate、Spring、Cocoon等其他開源系統。
Ehcache的主要特性 1.快速;
2.簡單;
3.多種緩存策略;
4.緩存數據有兩級:內存和磁盤,因此無需擔心容量問題;
5.緩存數據會在虛擬機重啟的過程中寫入磁盤;
6.可以通過 RMI、可插入 API 等方式進行分布式緩存;
7.具有緩存和緩存管理器的偵聽接口;(為了做集群)
8.支持多緩存管理器實例,以及一個實例的多個緩存區域;
9.提供 Hibernate 的緩存實現;
Ehcache使用介紹
Ehcache是用來管理緩存的一個工具,其緩存的數據可以是存放在內存里面的,也可以是存放在硬盤上的。其核心是CacheManager,一切Ehcache的應用都是從CacheManager開始的。它是用來管理Cache(緩存)的,一個應用可以有多個CacheManager,而一個CacheManager下又可以有多個Cache。Cache內部保存的是一個個的Element,而一個Element中保存的是一個key和value的配對,相當於Map里面的一個Entry。
-
Ehcache緩存過期策略 (操作系統里面就有呀~)
-
當緩存需要被清理時(比如空間占用已經接近臨界值了),需要使用某種淘汰算法來決定清理掉哪些數據。常用的淘汰算法有下面幾種:
FIFO:First In First Out,先進先出。判斷被存儲的時間,離目前最遠的數據優先被淘汰。
LRU:Least Recently Used,最近最少使用。判斷最近被使用的時間,目前最遠的數據優先被淘汰。(默認)
LFU:Least Frequently Used,最不經常使用。在一段時間內,數據被使用次數最少的,優先被淘汰。
注意:
- 緩存是容器存放在內存中,為了保證持久化機制,將緩存中的值持久化到硬盤上(日志緩存文件格式)。否則服務器重啟 就沒了哦!
- 緩存框架都是支持對內存和硬盤支持
- JVM內置緩存實現 (為了減輕數據庫訪問壓力)
- 內存吃不愁銷,限制而容量大小,會有相應的算法處理機制。
單機版緩存框架
-
單點緩存框架 只能針對單個jvm中,緩存容器存放jvm中,每個緩存互不影響 Ehcache gauva chache 內置緩存框架 jvm緩存框架
-
分布式緩存技術(共享緩存數據) Redis Meacache
example:
mybatis、hibernate底層都使用了Ehcache
本地緩存Ehcache 什么是Ehcache Ehcache是純java的開源緩存框架,具有快速、精干等特點,是Hibernate中默認的CacheProvider。它主要面向通用緩存、Java EE和輕量級容器,具有內存和磁盤存儲、緩存加載器、緩存擴展、緩存異常處理程序。
Ehcache最初由Greg Luck於2003年開始開發。2009年,該項目被Terracotta購買。軟件仍然開源,但一些新的主要功能(例如,快速可重啟性之間的一致性的)只能在商業產品中使用。
Ehcache 被廣泛用於在Hibernate、Spring、Cocoon等其他開源系統。
Ehcache的主要特性 1.快速;
2.簡單;
3.多種緩存策略;
4.緩存數據有兩級:內存和磁盤,因此無需擔心容量問題;
5.緩存數據會在虛擬機重啟的過程中寫入磁盤;
6.可以通過 RMI、可插入 API 等方式進行分布式緩存;
7.具有緩存和緩存管理器的偵聽接口;(為了做集群)
8.支持多緩存管理器實例,以及一個實例的多個緩存區域;
9.提供 Hibernate 的緩存實現;
Ehcache使用介紹
Ehcache是用來管理緩存的一個工具,其緩存的數據可以是存放在內存里面的,也可以是存放在硬盤上的。其核心是CacheManager,一切Ehcache的應用都是從CacheManager開始的。它是用來管理Cache(緩存)的,一個應用可以有多個CacheManager,而一個CacheManager下又可以有多個Cache。Cache內部保存的是一個個的Element,而一個Element中保存的是一個key和value的配對,相當於Map里面的一個Entry。
-
Ehcache緩存過期策略 (操作系統里面就有呀~)
-
當緩存需要被清理時(比如空間占用已經接近臨界值了),需要使用某種淘汰算法來決定清理掉哪些數據。常用的淘汰算法有下面幾種:
FIFO:First In First Out,先進先出。判斷被存儲的時間,離目前最遠的數據優先被淘汰。
LRU:Least Recently Used,最近最少使用。判斷最近被使用的時間,目前最遠的數據優先被淘汰。(默認)
LFU:Least Frequently Used,最不經常使用。在一段時間內,數據被使用次數最少的,優先被淘汰。
注意:
- 緩存是容器存放在內存中,為了保證持久化機制,將緩存中的值持久化到硬盤上(日志緩存文件格式)。否則服務器重啟 就沒了哦!
- 緩存框架都是支持對內存和硬盤支持
- JVM內置緩存實現 (為了減輕數據庫訪問壓力)
- 內存吃不愁銷,限制而容量大小,會有相應的算法處理機制。
Spring Boot 整合EhCache:
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.toov5.architect</groupId> <artifactId>architect</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencies> <!-- SpringBoot 對lombok 支持 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- SpringBoot web 核心組件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <!-- SpringBoot 外部tomcat支持 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <!-- springboot-log4j --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.8.RELEASE</version> </dependency> <!-- springboot-aop 技術 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang --> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <!--開啟 cache 緩存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- ehcache緩存 --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.9.1</version><!--$NO-MVN-MAN-VER$ --> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <!-- mysql 依賴 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> </project>
實體類:
@Data public class Users { private String name; private Integer age; }
controller:
@RestController public class IndexController { @Autowired private UserService userService; @RequestMapping("/getUser") public List<Users> getUser(Long id){ return userService.getUser(id); } }
Service
@Service public class UserService { @Autowired private UserMapper userMapper; public List<Users> getUser(Long id){ return userMapper.getUser(id); } }
Mapper:
//引入的jar包后就有了這個注解了 非常好用 (配置緩存的基本信息) @CacheConfig(cacheNames={"userCache"}) //緩存的名字 整個類的 public interface UserMapper { @Select("SELECT ID ,NAME,AGE FROM users where id=#{id}") @Cacheable //讓這個方法實現緩存 查詢完畢后 存入到緩存中 不是每個方法都需要緩存呀!save()就不用了吧 List<Users> getUser(@Param("id") Long id); }
EhCache配置:
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"> <diskStore path="java.io.tmpdir/ehcache-rmi-4000" /> <!-- 默認緩存 --> <defaultCache maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="true" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> <!-- demo緩存 --><!-- name="userCache" 對應我們在 @CacheConfig(cacheNames={"userCache"}) !!!!! --> <!--Ehcache底層也是用Map集合實現的 --> <cache name="userCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <!-- LRU緩存策略 --> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <!-- 用於在初始化緩存,以及自動設置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" /> </cache> </ehcache>
yml:
###端口號配置
server:
port: 8080
###數據庫配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
test-while-idle: true
test-on-borrow: true
validation-query: SELECT 1 FROM DUAL
time-between-eviction-runs-millis: 300000
min-evictable-idle-time-millis: 1800000
# 緩存配置讀取
cache:
type: ehcache
ehcache:
config: classpath:app1_ehcache.xml
啟動:
@EnableCaching //開啟緩存 @MapperScan(basePackages={"com.toov5.mapper"}) @SpringBootApplication(scanBasePackages={"com.toov5.*"}) public class app { public static void main(String[] args) { SpringApplication.run(app.class, args); } }
項目結構:
訪問:
備注介紹:【EhCache參數相關】
1. diskStore :指定數據(.data and .index)存儲位置,可指定磁盤中的文件夾位置期 The diskStore element is optional. It must be configured if you have overflowToDisk or diskPersistent enabled for any cache. If it is not configured, a warning will be issues and java.io.tmpdir will be used. 2. defaultCache : 默認的管理策略 Ehcache 使用Map集合實現的 element 其實就是 key 和value 一、以下屬性是必須的: 1、name: Cache的名稱,必須是唯一的(ehcache會把這個cache放到HashMap里)。 2、maxElementsInMemory:在內存中緩存的element的最大數目。 3、maxElementsOnDisk:在磁盤上緩存的element的最大數目,默認值為0,表示不限制。 4、eternal:設定緩存的elements是否永遠不過期。如果為true,則緩存的數據始終有效,如果為false那么還要根據timeToIdleSeconds,timeToLiveSeconds判斷。 5、overflowToDisk: 如果內存中數據超過內存限制,是否要緩存到磁盤上。 二、以下屬性是可選的: 1、timeToIdleSeconds: 對象空閑時間,指對象在多長時間沒有被訪問就會失效。只對eternal為false的有效。默認值0,表示一直可以訪問。 2、timeToLiveSeconds: 對象存活時間,指對象從創建到失效所需要的時間。只對eternal為false的有效。默認值0,表示一直可以訪問。 3、diskPersistent: 是否在磁盤上持久化。指重啟jvm后,數據是否有效。默認為false。 4、diskExpiryThreadIntervalSeconds: 對象檢測線程運行時間間隔。標識對象狀態的線程多長時間運行一次。 5、diskSpoolBufferSizeMB: DiskStore使用的磁盤大小,默認值30MB。每個cache使用各自的DiskStore。 6、memoryStoreEvictionPolicy: 如果內存中數據超過內存限制,向磁盤緩存時的策略。默認值LRU,可選FIFO、LFU。
EhCache同步DB:
場景描述:
update 或者delete 容易造成緩存和DB不同步問題
解決方案:
- 主動通知或者定時Job
- update語句后,加上通知,進行主動通知
- 先修改成功 然后清理緩存。【都是在同一個事務中的】
- 定時Job健康檢查,檢查DB和緩存是否一致。然后決定執行清理緩存
@RestController public class IndexController { @Autowired private CacheManager cacheManager; @Autowired private UserService userService; //注意引入的jar包是 org.springframework.cache.CacheManager; @RequestMapping("/remoKey") public void remoKey() { cacheManager.getCache("userCache").clear(); //傳入名字 進行清除 } @RequestMapping("/getUser") public List<Users> getUser(Long id){ return userService.getUser(id); } }
1.訪問:
2.修改數據
3.手動清理緩存,接口調用
4.訪問:
分布式緩存
EhCache+Redis實現分布式緩存
Ehcache集群模式介紹
由於 EhCache 是進程中的緩存系統,一旦將應用部署在集群環境中,每一個節點維護各自的緩存數據,當某個節點對緩存數據進行更新,這些更新的數據無法在其它節點中共享,這不僅會降低節點運行的效率,而且會導致數據不同步的情況發生。例如某個網站采用 A、B 兩個節點作為集群部署,當 A 節點的緩存更新后,而 B 節點緩存尚未更新就可能出現用戶在瀏覽頁面的時候,一會是更新后的數據,一會是尚未更新的數據,盡管我們也可以通過 Session Sticky 技術來將用戶鎖定在某個節點上,但對於一些交互性比較強或者是非 Web 方式的系統來說,Session Sticky 顯然不太適合。
EhCache常用集群模式
EhCache從1.7版本開始,支持五種集群方案,分別是:
- Terracotta
- RMI
RMi集群模式 你如何知道集群環境中的其他緩存? • 分布式傳送的消息是什么形式? • 什么情況需要進行復制?增加(Puts),更新(Updates)或是失效(Expiries)? • 采用什么方式進行復制?同步還是異步方式? 1、正確的元素類型:只有可序列化的元素可以進行復制。一些操作,比如移除,只需要元素的鍵值而不用整個元素;在這樣的操作中即使元素不是可序列化的但鍵值是可序列化的也可以被復制。 2、成員發現(Peer Discovery):Ehcache進行集群的時候有一個cache組的概念。每個cache都是其他cache的一個peer,沒有主cache的存在。成員發現(Peer Discovery)正是用來解決 “你如何知道集群環境中的其他緩存?” 這個問題的。Ehcache提供了兩種機制用來進行成員發現,即:自動成員發現和手動成員發現。要使用一個內置的成員發現機制要在ehcache的配置文件中指定cacheManagerPeerProviderFactory元素的class屬性為 net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory。
- JMS
- JGroups
- EhCache Server
Ehcache的使用場景
- 使用純java的ehcache作為本地緩存
- Reids 作為遠程分布式緩存
- 解決redis緩存壓力過大,提高緩存速度,以及緩存性能。
Redis和Ehcache緩存的區別
- 如果是單個應用或者對緩存訪問要求很高的應用,用EhCache。
- 如果是大型系統,存在緩存共享、分布式部署、緩存內容很大的,建議用redis。
- EHCache適合單體項目緩存,不適合於分布式。(不支持共享),因為他們存放在JVM中,互不影響。
- 分布式緩存架構中,使用Redis+EHCache實現分布式緩存。分布式 一級、二級緩存。 比如: 如果Redis集群 因為 某些原因 全部宕機 。 客戶端會一直查詢數據庫!
- Redis支持分布式共享查詢
注: EhCache支持分布式緩存 但是不推薦
EhCache實際使用場景總結:
- 我們在項目中使用集中式緩存(Redis或者式Memcached等),通常都是檢查緩存中是否存在期望值的數據,如果存在直接返回,如果不存在就查詢數據庫讓后在將數據庫緩存,這個時候如果緩存系統因為某寫原因宕機,造成服務無法訪問,那么大的量請求直接穿透到數據庫,最數據庫壓力非常大。這時候我們讓ehcache作為二級緩存,當redis服務器宕機后,可以查詢ehcache緩存。 這樣能夠有效的扛住服務器請求壓力。
分布式緩存圖 :
Spring Boot2.0+Redis+Ehcache實現二級緩存
前言:
- 先走本地,本地沒有再走網絡 盡量少走Redis 效率會高一些
- Ehchache 不需要走網絡,直接從內存中獲取.由於Ehchache容器限制,會持久化在硬盤上。
實現:
-
spring boot中集成了spring cache,並有多種緩存方式的實現,如:Redis、Caffeine、JCache、EhCache等等。但如果只用一種緩存,要么會有較大的網絡消耗(如Redis),要么就是內存占用太大(如Caffeine這種應用內存緩存)。在很多場景下,可以結合起來實現一、二級緩存的方式,能夠很大程度提高應用的處理效率。
-
spring cache:主要包含spring cache定義的接口方法說明和注解中的屬性說明
補充: 緩存、兩級緩存
簡單的理解,緩存就是將數據從讀取較慢的介質上讀取出來放到讀取較快的介質上,如磁盤-->內存。平時我們會將數據存儲到磁盤上,如:數據庫。如果每次都從數據庫里去讀取,會因為磁盤本身的IO影響讀取速度,所以就有了像redis這種的內存緩存。可以將數據讀取出來放到內存里,這樣當需要獲取數據時,就能夠直接從內存中拿到數據返回,能夠很大程度的提高速度。但是一般redis是單獨部署成集群,所以會有網絡IO上的消耗,雖然與redis集群的鏈接已經有連接池這種工具,但是數據傳輸上也還是會有一定消耗。所以就有了應用內緩存,如:caffeine。當應用內緩存有符合條件的數據時,就可以直接使用,而不用通過網絡到redis中去獲取,這樣就形成了兩級緩存。應用內緩存叫做一級緩存,遠程緩存(如redis)叫做二級緩存
spring boot + spring cache 實現兩級緩存(redis + caffeine)代碼實現:
實體類:
@Data public class Users implements Serializable{ private String name; private Integer age; }
controller
@RestController public class IndexController { @Autowired private UserService userService; @RequestMapping("/userId") public Users getUserId(Long id){ return userService.getUser(id); } }
Service
@Component public class EhCacheUtils { // @Autowired // private CacheManager cacheManager; @Autowired private EhCacheCacheManager ehCacheCacheManager; // 添加本地緩存 (相同的key 會直接覆蓋) public void put(String cacheName, String key, Object value) { Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName); Element element = new Element(key, value); cache.put(element); } // 獲取本地緩存 public Object get(String cacheName, String key) { Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName); Element element = cache.get(key); return element == null ? null : element.getObjectValue(); } public void remove(String cacheName, String key) { Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName); cache.remove(key); } }
@Component public class RedisService { @Autowired private StringRedisTemplate stringRedisTemplate; //這樣該方法支持多種數據類型 public void set(String key , Object object, Long time){ //開啟事務權限 stringRedisTemplate.setEnableTransactionSupport(true); try { //開啟事務 stringRedisTemplate.multi(); String argString =(String)object; //強轉下 stringRedisTemplate.opsForValue().set(key, argString); //成功就提交 stringRedisTemplate.exec(); } catch (Exception e) { //失敗了就回滾 stringRedisTemplate.discard(); } if (object instanceof String ) { //判斷下是String類型不 String argString =(String)object; //強轉下 //存放String類型的 stringRedisTemplate.opsForValue().set(key, argString); } //如果存放Set類型 if (object instanceof Set) { Set<String> valueSet =(Set<String>)object; for(String string:valueSet){ stringRedisTemplate.opsForSet().add(key, string); //此處點擊下源碼看下 第二個參數可以放好多 } } //設置有效期 if (time != null) { stringRedisTemplate.expire(key, time, TimeUnit.SECONDS); } } //做個封裝 public void setString(String key, Object object){ String argString =(String)object; //強轉下 //存放String類型的 stringRedisTemplate.opsForValue().set(key, argString); } public void setSet(String key, Object object){ Set<String> valueSet =(Set<String>)object; for(String string:valueSet){ stringRedisTemplate.opsForSet().add(key, string); //此處點擊下源碼看下 第二個參數可以放好多 } } public String getString(String key){ return stringRedisTemplate.opsForValue().get(key); } }
@Component public class UserService { @Autowired private EhCacheUtils ehCacheUtils; @Autowired private RedisService redisService; @Autowired private UserMapper userMapper; //定義個全局的cache名字 private String cachename ="userCache"; public Users getUser(Long id){ //先查詢一級緩存 key以當前的類名+方法名+id+參數值 String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName() + "-id:" + id; //查詢一級緩存數據有對應值的存在 如果有 返回 Users user = (Users)ehCacheUtils.get(cachename, key); if (user != null) { System.out.println("key"+key+",直接從一級緩存獲取數據"+user.toString()); return user; } //一級緩存沒有對應的值存在,接着查詢二級緩存 // redis存對象的方式 json格式 然后反序列號 String userJson = redisService.getString(key); //如果rdis緩存中有這個對應的值,修改一級緩存 最下面的會有的 相同會覆蓋的 if (!StringUtil.isNullOrEmpty(userJson)) { //有 轉成json JSONObject jsonObject = new JSONObject();//用的fastjson Users resultUser = jsonObject.parseObject(userJson,Users.class); ehCacheUtils.put(cachename, key, resultUser); return resultUser; } //都沒有 查詢DB Users user1 = userMapper.getUser(id); if (user1 == null) { return null; } //存放到二級緩存 redis中 redisService.setString(key, new JSONObject().toJSONString(user1)); //存放到一級緩存 Ehchache ehCacheUtils.put(cachename, key, user1); return user1; } }
啟動類
@EnableCaching //開啟緩存 @MapperScan(basePackages={"com.toov5.mapper"}) @SpringBootApplication(scanBasePackages={"com.toov5.*"}) public class app { public static void main(String[] args) { SpringApplication.run(app.class, args); } }
app1.ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"> <diskStore path="java.io.tmpdir/ehcache-rmi-4000" /> <!-- 默認緩存 --> <defaultCache maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="true" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> <!-- demo緩存 --><!-- name="userCache" 對應我們在 @CacheConfig(cacheNames={"userCache"}) !!!!! --> <!--Ehcache底層也是用Map集合實現的 --> <cache name="userCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <!-- LRU緩存策略 --> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" /> <!-- 用於在初始化緩存,以及自動設置 --> <bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" /> </cache> </ehcache>
yml:
###端口號配置
server:
port: 8080
###數據庫配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
test-while-idle: true
test-on-borrow: true
validation-query: SELECT 1 FROM DUAL
time-between-eviction-runs-millis: 300000
min-evictable-idle-time-millis: 1800000
# 緩存配置讀取
cache:
type: ehcache
ehcache:
config: classpath:app1_ehcache.xml
redis:
database: 0
host: 192.168.91.3
port: 6379
password: 123
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
運行結果
后面的訪問,在控制台打印:
代碼一定要注意:
- 一級緩存過期時間, 比二級要短一些。
- 這里的兩級緩存時間問題,執行設置二級緩存時候需要時間的,所以這兩個時間設置問題一定要注意了。如下圖:
分布式緩存的思考:
- 應用緩存: 緩存命中率、緩存回收策略 Java緩存類型、磁盤緩存
- Http緩存: Http客戶端緩存、 NginxHttp緩存設置、Nginx代理緩存
- 多級緩存: 緩存數據、 分布式緩存與負載均衡、熱點數據與更新緩存、緩存崩潰與快速修復