Java常用缓存
memcache:
是一種高性能、分布式對象緩存系統,最初設計于緩解動態網站數據庫加載數據的延遲性,你可以把它想象成一個大的內存HashTable,就是一個key-value鍵值緩存。C語言所編寫,依賴于最近版本的GCC和libevent。GCC是它的編譯器,同時基于libevent做socket io。在安裝memcache時保證你的系統同時具備有這兩個環境。支持多個cpu同時工作,在memcache安裝文件下有個叫threads.txt中特別說明,By default, memcached is compiled as a single-threaded application.默認是單線程編譯安裝,如果你需要多線程則需要修改./configure --enable-threads,為了支持多核系統,前提是你的系統必須具有多線程工作模式。開啟多線程工作的線程數默認是4,如果線程數超過cpu數容易發生操作死鎖的概率。結合自己業務模式選擇才能做到物盡其用。
本身沒有內置分布式功能,只能在客戶端通過像一致性哈希這樣的分布式算法來實現Memcached的分布式存儲【當客戶端向Memcached集群發送數據之前,首先會通過內置的分布式算法計算出該條數據的目標節點,然后數據會直接發送到該節點上存儲。但客戶端查詢數據時,同樣要計算出查詢數據所在的節點,然后直接向該節點發送查詢請求以獲取數據】。無法實現使用多臺Memcache服務器來存儲不同的數據,最大程度的使用相同的資源;無法同步數據,容易造成單點故障。
(memagent代理實現集群)通過Magent緩存代理,防止單點現象,緩存代理也可以做備份,通過客戶端連接到緩存代理服務器,緩存代理服務器連接緩存連接服務器,緩存代理服務器可以連接多臺Memcached機器可以將每臺Memcached機器進行數據同步。如果其中一臺緩存服務器down機,系統依然可以繼續工作,如果其中一臺Memcached機器down掉,數據不會丟失并且可以保證數據的完整性。
Redis:
支持持久化【數據swap時會阻塞,設置I/O線程池的大小】;豐富的數據類型結構存儲;單線程網絡IO;數據存儲即時申請內存【不會預分配內存池,不會自動剔除數據】;提供事務操作;消息隊列【不支持持久化,消費方連接閃斷或重連,之間過來的消息會全部丟失】;集群方式【沒有中心節點,具有線性可伸縮的功能】
Redis cluster配置參考:
在每臺服務器上執行如下操作:
#cd /opt/redis-3.0.2【必須先安裝gcc、make】
#make
#make install【將生成的可執行程序復制到/usr/local/bin目錄中】
#vim redis.conf
port?6379
bind ip【本機ip,其他節點可以訪問】
daemonize?yes【可保留】
cluster-enabled?yes
cluster-config-file?nodes-6379.conf
cluster-node-timeout?15000
appendonly?yes【可保留】
#cp redis.conf redis1.conf
#vim redis1.conf【修改port: 6380, cluster-config-file nodes-6380.conf】
#redis-server redis.conf &
#redis-server redis1.conf &
#redis-cli -h 10.30.17.15 -p 6379【測試】
安裝ruby 及 rubygems
#apt-get install ruby rubygems
安裝ruby 的redis 接口支持包
#gem install redis
建立集群
#/opt/redis-3.0.2/src/redis-trib.rb create --replicas 1 10.30.17.85:6379 10.30.17.85:6380 10.30.17.94:6379 10.30.17.94:6380 10.30.17.15:6379 10.30.17.15:6380
測試
#redis-cli -p 6379【exit退出】
>cluster info
>set hello tang
>keys *
>set user_id 1234
>get user_id
在另一臺機器上執行#redis-cli -p 6380
>keys *
>set user_id 1234【error,需#redis-cli -c -p 6380】
在另一臺機器上執行#redis-cli -p 6379
>keys *【empty】
>set user_id 1234【error,需#redis-cli -c -p 6379】
Ehcache【java進程中的緩存系統】:
ehcache直接在jvm虛擬機中緩存,速度快,效率高;但是緩存共享麻煩,集群分布式應用不方便。redis是通過socket訪問到緩存服務,效率比ecache低,比數據庫要快很多,處理集群和分布式緩存方便,有成熟的方案。如果是單個應用或者對緩存訪問要求很高的應用,用ehcache。如果是大型系統,存在緩存共享、分布式部署、緩存內容很大的,建議用redis。
補刀:ehcache也有緩存共享方案,不過是通過RMI或者Jgroup多播方式進行廣播緩存通知更新,緩存共享復雜,維護不方便;簡單的共享可以,但是涉及到緩存恢復,大數據緩存,則不合適。
一旦將應用部署在集群環境中,每一個節點維護各自的緩存數據,當某個節點對緩存數據進行更新,這些更新的數據無法在其它節點中共享,這不僅會降低節點運行的效率,而且會導致數據不同步的情況發生,所以就需要用到 EhCache 的集群解決方案。EhCache 從 1.7 版本開始,支持五種集群方案,分別是:
Ehcache可以對頁面、對象、數據進行緩存:
ehcache-core-2.5.2.jar 主要針對對象、數據緩存
ehcache-web-2.0.4.jar 主要針對頁面緩存
頁面緩存主要用Filter過濾器對請求的url進行過濾,如果該url在緩存中出現。那么頁面數據就從緩存對象中獲取,并以gzip壓縮后返回。其速度是沒有壓縮緩存時速度的3-5倍,效率相當之高!其中頁面緩存的過濾器有CachingFilter,一般要擴展filter或是自定義Filter都繼承該CachingFilter。CachingFilter功能可以對HTTP響應的內容進行緩存。這種方式緩存數據的粒度比較粗,例如緩存整張頁面。它的優點是使用簡單、效率高,缺點是不夠靈活,可重用程度不高。EHCache使用SimplePageCachingFilter類實現Filter緩存。該類繼承自CachingFilter,有默認產生cache key的calculateKey()方法,該方法使用HTTP請求的URI和查詢條件來組成key。也可以自己實現一個Filter,同樣繼承CachingFilter類,然后覆寫calculateKey()方法,生成自定義的key。CachingFilter輸出的數據會根據瀏覽器發送的Accept-Encoding頭信息進行Gzip壓縮。
在使用Gzip壓縮時,需注意兩個問題:
1. Filter在進行Gzip壓縮時,采用系統默認編碼,對于使用GBK編碼的中文網頁來說,需要將操作系統的語言設置為:zh_CN.GBK,否則會出現亂碼的問題。
2. 默認情況下CachingFilter會根據瀏覽器發送的請求頭部所包含的Accept-Encoding參數值來判斷是否進行Gzip壓縮。雖然IE6/7瀏覽器是支持Gzip壓縮的,但是在發送請求的時候卻不帶該參數。為了對IE6/7也能進行Gzip壓縮,可以通過繼承CachingFilter,實現自己的Filter,然后在具體的實現中覆寫方法acceptsGzipEncoding。
具體實現參考:
protected boolean acceptsGzipEncoding(HttpServletRequest request) {
boolean ie6 = headerContains(request, "User-Agent", "MSIE 6.0");
boolean ie7 = headerContains(request, "User-Agent", "MSIE 7.0");
return acceptsEncoding(request, "gzip") || ie6 || ie7;
}
對象緩存就是將查詢的數據,添加到緩存中,下次再次查詢的時候直接從緩存中獲取,而不去數據庫中查詢。對象緩存一般是針對方法、類而來的,結合Spring的Aop對象、方法緩存就很簡單。這里需要用到切面編程,用到了Spring的MethodInterceptor或是用@Aspect。這里的方法攔截器主要是對你要攔截的類的方法進行攔截,然后判斷該方法的類路徑+方法名稱+參數值組合的cache key在緩存cache中是否存在。如果存在就從緩存中取出該對象,轉換成我們要的返回類型。沒有的話就把該方法返回的對象添加到緩存中即可。值得主意的是當前方法的參數和返回值的對象類型需要序列化。我們需要在src目錄下添加applicationContext.xml完成對MethodCacheInterceptor攔截器的配置,該配置主意是注入我們的cache對象,哪個cache來管理對象緩存,然后哪些類、方法參與該攔截器的掃描。
Spring自身并沒有實現緩存解決方案,但是對緩存管理功能提供了聲明式的支持,能夠與多種流行的緩存實現進行集成。
Spring Cache是作用在方法上的(不能理解為只注解在方法上),其核心思想是:當我們在調用一個緩存方法時會把該方法參數和返回結果作為一個鍵值存放在緩存中,等到下次利用同樣的參數調用該方法時將不再執行該方法,而是直接從緩存中獲取結果進行返回。所以在使用Spring Cache的時候我們要保證我們的緩存的方法對于相同的方法參數要有相同的返回結果。
Spring對Cache的支持有兩種方式:
基于注解驅動的緩存
基于XML配置聲明的緩存
啟用Spring對注解驅動緩存的支持,也是有兩種方式的:
Java配置(這個方法可以比較清晰的了解緩存管理器是如何聲明的)
XML配置(這個方法比較方便簡單,這里的XML配置不能跟上面的“基于XML配置聲明的緩存”搞混,兩者不是同一層概念)
這兩種方式,Java配置方式是通過使用@EnableCaching啟用注解驅動的緩存,XML配置方式是通過使用<cache:annotation-driven/>啟用注解驅動的緩存。本質上來講,這兩種工作方式是相同的,它們都會創建一個切面(AOP)并出發Spring緩存注解的切點(pointcut)。
在這兩個程序清單中,不僅僅啟用了注解驅動的緩存,還聲明了一個緩存管理器(cache manager)的bean。緩存管理器是Spring抽象的核心,它能夠與多個流行的緩存實現進行集成。
數據緩存是在hibernate或mybatis中加入ehcache
配置ehcache.xml:
maxElementsInMemory:緩存中允許創建的最大對象數
eternal:緩存中對象是否為永久的,如果是,超時設置將被忽略,對象從不過期。
timeToIdleSeconds:緩存數據的鈍化時間,也就是在一個元素消亡之前,兩次訪問時間的最大時間間隔值,這只能在元素不是永久駐留時有效,如果該值是 0 就意味著元素可以停頓無窮長的時間。
timeToLiveSeconds:緩存數據的生存時間,也就是一個元素從構建到消亡的最大時間間隔值,這只能在元素不是永久駐留時有效,如果該值是0就意味著元素可以停頓無窮長的時間。
overflowToDisk:內存不足時,是否啟用磁盤緩存。
memoryStoreEvictionPolicy:緩存滿了之后的淘汰算法。
配置applicationContext.xml:
...
<!--? 緩存? 屬性-->?
<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">???
???????? <property name="configLocation"? value="classpath:com/config/ehcache.xml"/>??
</bean>??????
<!-- 默認是cacheManager -->?
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">???
???????? <property name="cacheManager"? ref="cacheManagerFactory"/>???
</bean>
<!-- 支持緩存注解 -->?
<cache:annotation-driven cache-manager="cacheManager" />
實現(常在@Service服務文件,緩存數據):
@Cacheable(value = "serviceCache", key="#id")【當調用其方法時,會從一個名叫 serviceCache 的緩存中查詢,如果沒有,則執行實際的方法(即查詢數據庫),并將執行的結果存入緩存中,否則返回緩存中的對象。serviceCache為ehcache.xml中定義的緩存名稱】
@CacheEvict(value="serviceCache",allEntries=true)【標記要清空緩存的方法,allEntries 表示調用之后,清空緩存,默認false, 還有個beforeInvocation 屬性,表示先清空緩存,再進行查詢】
Google Guava
Google Guava工具包是一個非常方便易用的本地化緩存實現,基于LRU算法實現,支持多種緩存過期策略。Guava在每次訪問緩存的時候判斷cache數據是否過期,如果過期,這時才將其刪除,并沒有另起一個線程專門來刪除過期數據。內部維護了2個隊列accessQueue和writeQueue來記錄緩存中數據訪問和寫入的順序。訪問緩存時,先用key計算出hash,從而找出所在的segment,然后再在segment中尋找具體數據,類似于使用ConcurrentHashMap數據結構來存放緩存數據。
Caffeine
Caffeine是基于Java8,對Guava緩存的重寫版本,在Spring Boot 2.0中將取代Guava,基于LRU算法實現,支持多種緩存過期策略。Caffeine使用Disruptor框架的RingBuffer數據結構記錄,RingBuffer是一個環形隊列,并且是無鎖的,利用的是緩存行的特性。Caffeine讀比寫的性能比Guava和EhCache要高很多。
總結
- 上一篇: 简单的学习心得:网易云课堂Android
- 下一篇: Java 面试题(3)—— JVM