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