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