《精通 Spring Boot 42 講》共分五大部分,第四部分主要講解 Spring Boot 和中間件的使用,共 10 課,中間件是互聯網公司支撐高併發業務的必備組件,經常使用的組件有緩存、消息中間件、NoSQL 數據庫、定時任務等。經常使用的緩存中間件有 Memcache 和 Redis ,緩存主要支撐業務架構中高速讀寫;經常使用的消息中間件有 ActiveMQ 、RabbitMQ ,使用消息中間件的意義是,儘快地完成主線交易,其餘非實時業務異步或者解耦完成;最主流的 NoSQL 有 MongoDB、 ElasticSearch,前者主要是解決分佈式存儲和檢索的問題,後者主要解決分佈式文檔檢索的解決方案;定時任務經常使用開源框架 Quartz。以上的這些內容咱們都會在第四部分進行學習。java
在常見的企業架構中,隨着公司業務高速發展,最早出現瓶頸的是數據庫,這個時候不少企業就會考慮使用緩存來緩解數據庫的壓力,這是緩存使用最多的場景之一;另外在高併發搶購、分佈式 Session 等場景下,也會使用緩存來提升系統的高可用性。經常使用的緩存中間件有 Memcache 和 Redis,今天咱們先來學習 Memcache 的使用。git
Memcache 是一個自由和開放源代碼、高性能、分配的內存對象緩存系統。簡單來講,Memcache 是一個高性能的分佈式內存對象的 key-value 緩存系統,用於加速動態 Web 應用程序,減輕數據庫負載,如今也有不少人將它做爲內存式數據庫在使用。github
它能夠應對任意多個鏈接,使用非阻塞的網絡 IO,因爲它的工做機制是在內存中開闢一塊空間,而後創建一個 Hash 表,Memcached 自動管理這些 Hash 表。算法
Memcache 由國外社區網站 LiveJournal 開發團隊開發,設計理念就是小而強大,它簡單的設計促進了快速部署、易於開發並解決面對大規模的數據緩存的許多難題,而所開放的 API 使得 Memcache 能用於 Java、C/C++/C#、Perl、Python、PHP、Ruby 等大部分流行的程序語言。spring
Memcache 和 Memcached 的區別:Memcache 是這個項目的名稱,而 Memcached 是服務器端的主程序名稱。數據庫
協議簡單編程
Memcache 的服務端客戶端通訊使用簡單的文本協議,經過 Telnet 便可在 Memcached 上存取數據。緩存
基於 Libevent 的事件處理安全
Libevent 是一套跨平臺的事件處理接口的封裝,可以兼容包括這些操做系統:Windows/Linux/BSD/Solaris 等操做系統的的事件處理,包裝的接口包括:poll、select(Windows)、epoll(Linux)、kqueue(BSD)/dev/pool(Solaris)。ruby
Memcache 使用 Libevent 來進行網絡併發鏈接的處理,可以保持在很大併發狀況下,仍舊可以保持快速的響應能力。
內置內存存儲方式
Memcache 中保存的數據都存儲在 Memcache 內置的內存存儲空間中。因爲數據僅存在於內存中,所以重啓 Memcache、重啓操做系統會致使數據所有丟失。Memcache LRU(Least Recently Used)算法自動刪除不使用的緩存,不過這個功能是能夠配置的,Memcache 啓動時經過「-M」參數能夠禁止 LRU。不過,Memcache 自己是爲緩存而設計的,建議開啓 LRU。
不適應場景
在 Centos 下安裝使用 yum 命令安裝 Memcache 很是簡單:
yuminstall -y memcached
啓動:
11211150>> /usr/bin/memcached -b -p-m-u root/tmp/memcached.log &
啓動參數能夠配置,經常使用的命令選項以下:
查看 memcached 是否在運行:
ps-ef | grep memcached
Memcached Client 目前有 3 種:
Memcached Client for Java 比 SpyMemcached 更穩定、更早、更普遍;SpyMemcached 比 Memcached Client for Java 更高效;XMemcached 比 SpyMemcache 併發效果更好。
曾經有一段時間 SpyMemcached 使用比較普遍,我簡單介紹一下。
Spymemcached 介紹
Spymemcached 是一個採用 Java 開發的異步、單線程的 Memcached 客戶端,使用 NIO 實現。Spymemcached 是 Memcached 的一個流行的 Java Client 庫,性能表現出色,普遍應用於 Java + Memcached 項目中。
Spymemcached 最先由 Dustin Sallings 開發,Dustin 後來和別人一塊兒創辦了 Couchbase(原NorthScale),職位爲首席架構師,2014 年加入 Google。
XMemcached 簡介
如今使用最普遍的 Memcache Java 客戶端是 XMemcached,它是一個新的 Java Memcache Client 。Memcached 經過它的自定義協議與客戶端交互,而 XMemcached 就是它的一個 Java 客戶端實現。相比其餘客戶端,XMemcached 有什麼優勢呢?
XMemcached 的主要特性
XMemcached 支持設置鏈接池、宕機報警、使用二進制文件、一致性哈希算法、進行數據壓縮等操做,總結以下:
上面介紹了這麼多,最須要關注的是 XMemcached 是最佳的選擇,下面咱們先用一個示例,來感覺一下 Spring Boot 使用 Xmemcached 集成 Memcache。
添加依賴包:
<dependency>
<groupId></groupId> com.googlecode.xmemcached
<artifactId></artifactId> xmemcached
<version></version> 2.4.5
</dependency>
添加配置文件:
# 單個 Memcached 配置
memcached.servers=192.168.0.161:11211
# 鏈接池
memcached.poolSize=10
#操做超時時間
memcached.opTimeout=6000
配置 Memcached 的地址和端口號、鏈接池和操做超時時間,使用集羣時能夠拼接多個地址:"host1:port1 host2:port2 …"。
建立 XMemcachedProperties 類,讀配置信息:
@Component
@ConfigurationProperties"memcached"(prefix =)
publicclass XMemcachedProperties {
private String servers;
privateint poolSize;
privatelong opTimeout;
//省略 getter/setter
}
利用 @Configuration 註解,在啓動時對 Memcached 進行初始化。
@Configuration
publicclass MemcachedBuilder {
protectedstatic Logger logger = LoggerFactory.getLogger(MemcachedBuilder.class);
@Resource
private XMemcachedProperties xMemcachedProperties;
@Bean
public MemcachedClient getMemcachedClient() {
null MemcachedClient memcachedClient =;
try {
new MemcachedClientBuilder builder =XMemcachedClientBuilder(AddrUtil.getAddresses(xMemcachedProperties.getServers()));
builder.setConnectionPoolSize(xMemcachedProperties.getPoolSize());
builder.setOpTimeout(xMemcachedProperties.getOpTimeout());
memcachedClient = builder.build();
catch }(IOException e) {
"inint MemcachedClient failed " logger.error(,e);
}
return memcachedClient;
}
}
由於 XMemcachedClient 的建立有比較多的可選項,因此提供了一個 XMemcachedClientBuilder 類用於構建 MemcachedClient。MemcachedClient 是主要接口,操做 Memcached 的主要方法都在這個接口,XMemcachedClient 是它的一個實現。
在方法 getMemcachedClient() 添加 @Bean 註解,表明啓動時候將方法構建好的實例注入到 Spring 容器中,後面在須要使用的類中,直接注入 MemcachedClient 便可。
咱們建立一個 MemcachedTests 類,來測試 Memcached 配置信息是否配置正確。
@RunWith(SpringRunner.class)
@SpringBootTest
publicclass MemcachedTests {
@Autowired
private MemcachedClient memcachedClient;
}
測試 Memcached 的 get 、set 方法。
Test@
public void testGetSet() throws Exception {
set"hello"0"Hello,xmemcached" memcachedClient.(,,);
valueget"hello" String= memcachedClient.();
out"hello="value System..println(+);
"hello" memcachedClient.delete();
}
存儲數據是經過 set 方法,它有三個參數,第一個是存儲的 key 名稱,第二個是 expire 時間(單位秒),超過這個時間,memcached 將這個數據替換出去,0 表示永久存儲(默認是一個月),第三個參數就是實際存儲的數據,能夠是任意的 Java 可序列化類型。
獲取存儲的數據是經過 get 方法,傳入 key 名稱便可;若是要刪除存儲的數據,能夠經過 delete 方法,它也是接受 key 名稱做爲參數。
執行 testMemcached() 單元測試以後,控制檯會輸出:
hello=Hello,xmemcached
證實 Memcached 配置、設置和獲取值成功。
XMemcached 有很是豐富的語法來支持,咱們對緩存使用的各類場景,接下來一一介紹。
除過上面的 get、set、delete 等方法外,Memcache 還有不少經常使用的操做。
Test@
public void testMore() throws Exception {
ifset"hello"0"world" (!memcachedClient.(,,)) {
"set error" System.err.println();
}
ifadd"hello"0"dennis" (!memcachedClient.(,,)) {
"Add error,key is existed" System.err.println();
}
if"hello"0"dennis" (!memcachedClient.replace(,,)) {
"replace error" System.err.println();
}
"hello"" good" memcachedClient.append(,);
"hello""hello " memcachedClient.prepend(,);
get"hello"new String name = memcachedClient.(,StringTranscoder());
out System..println(name);
"hello" memcachedClient.deleteWithNoReply();
}
Incr 和 Decr 相似數據的增和減,兩個操做相似 Java 中的原子類如 AtomicIntger,用於原子遞增或者遞減變量數值,示例以下:
Test@
public void testIncrDecr() throws Exception {
memcachedClient.deleteIncr ("");
memcachedClient.deleteDecr ("");
System.out.printlnmemcachedClient.incrIncr (("", 6, 12));
System.out.printlnmemcachedClient.incrIncr (("", 3));
System.out.printlnmemcachedClient.incrIncr (("", 2));
System.out.printlnmemcachedClient.decrDecr (("", 1, 6));
System.out.printlnmemcachedClient.decrDecr (("", 2));
}
爲了防止數據干擾,在測試開始前前調用 delete() 方法清除兩個 key 值。
輸出:
12
15
17
6
4
Incr 和 Decr 都有三個參數的方法,第一個參數指定遞增的 key 名稱,第二個參數指定遞增的幅度大小,第三個參數指定當 key 不存在的狀況下的初始值,兩個參數的重載方法省略了第三個參數,默認指定爲 0。
Xmemcached 還提供了一個稱爲計數器的封裝,它封裝了 incr/decr 方法,使用它就能夠相似 AtomicLong 那樣去操做計數,示例以下:
Test@
public void testCounter() throws Exception {
MemcachedClient memcachedClient = memcachedUtil.getMemcachedClient();
"counter"10 Counter counter=memcachedClient.getCounter(,);
out"counter="get System..println(+counter.());
long c1 =counter.incrementAndGet();
out"counter=" System..println(+c1);
long c2 =counter.decrementAndGet();
out"counter=" System..println(+c2);
long-10 c3 =counter.addAndGet();
out"counter=" System..println(+c3);
}
memcachedClient.getCounter("counter",10)
,第一個參數爲計數器的 key,第二參數當 key 不存在時的默認值;counter.incrementAndGet()
,執行一次給計數器加 1;counter.decrementAndGet()
,執行一次給計數器減 1。查看 counter.addAndGet(-10) 源碼(以下),發現 addAndGet() 會根據傳入的值的正負來判斷,選擇直接給對應的 key 加多少或者減多少,底層也是使用了 incr() 和 decr() 方法。
public long addAndGet(long delta) throws MemcachedException, InterruptedException, TimeoutException {
return0Lthisthisthisthisthisthis delta >=?.memcachedClient.incr(.key, delta,.initialValue) :.memcachedClient.decr(.key, -delta,.initialValue);
}
Counter 適合在高併發搶購場景下作併發控制。
Memcached 是經過 CAS 協議實現原子更新,所謂原子更新就是 Compare and Set,原理相似樂觀鎖,每次請求存儲某個數據同時要附帶一個 CAS 值,Memcached 比對這個 CAS 值與當前存儲數據的 CAS 值是否相等,若是相等就讓新的數據覆蓋老的數據,若是不相等就認爲更新失敗,這在併發環境下特別有用。XMemcached 提供了對 CAS 協議的支持(不管是文本協議仍是二進制協議),CAS 協議實際上是分爲兩個步驟:獲取 CAS 值和嘗試更新,所以一個典型的使用場景以下:
"a"GetsResponse<Integer> result = client.gets();
longcas = result.getCas();
//嘗試將 a 的值更新爲 2
if"a"02(!client.cas(,,, cas)) {
"cas error" System.err.println();
}
首先經過 gets 方法獲取一個 GetsResponse,此對象包裝了存儲的數據和 CAS 值,而後經過 CAS 方法嘗試原子更新,若是失敗打印「cas error」。顯然,這樣的方式很繁瑣,而且若是你想嘗試多少次原子更新就須要一個循環來包裝這一段代碼,所以 XMemcached 提供了一個 CASOperation 接口包裝了這部分操做,容許你嘗試 N 次去原子更新某個 key 存儲的數據,無需顯式地調用 gets 獲取 CAS 值,上面的代碼簡化爲:
"a"0newclient.cas(,,CASOperation<Integer>() {
public int getMaxTries() {
return1 ;
}
public Integer getNewValue(long currentCAS, Integer currentValue) {
return2 ;
}
});
CASOpertion 接口只有兩個方法,一個是設置最大嘗試次數的 getMaxTries 方法,這裏是嘗試一次,若是嘗試超過這個次數沒有更新成功將拋出一個 TimeoutException,若是你想無限嘗試(理論上),能夠將返回值設定爲 Integer.MAX_VALUE;另外一個方法是根據當前得到的 GetsResponse 來決定更新數據的 getNewValue 方法,若是更新成功,這個方法返回的值將存儲成功,其兩個參數是最新一次 gets 返回的 GetsResponse 結果。
XMemcached 因爲是基於 nio,所以通信過程自己是異步的,client 發送一個請求給 Memcached,你是沒法肯定 Memcached 何時返回這個應答,客戶端此時只有等待,所以還有個等待超時的概念在這裏。客戶端在發送請求後,開始等待應答,若是超過必定時間就認爲操做失敗,這個等待時間默認是 5 秒,也能夠在獲取的時候配置超時時間。
valueget"hello"3000=client.(,);
就是等待 3 秒超時,若是 3 秒超時就跑出 TimeutException,用戶須要本身處理這個異常。由於等待是經過調用 CountDownLatch.await(timeout) 方法,因此用戶還須要處理中斷異常 InterruptException,最後的 MemcachedException 表示 Xmemcached 內部發生的異常,如解碼編碼錯誤、網絡斷開等異常狀況。
常常有這樣的需求,就是但願更新緩存數據的超時時間(expire time),如今 Memcached 已經支持 touch 協議,只須要傳遞 key 就更新緩存的超時時間:
newclient.touch(key,-expire-time);
有時候你但願獲取緩存數據並更新超時時間,這時候能夠用 getAndTouch 方法(僅二進制協議支持):
newclient.getAndTouch(key,-expire-time);
若是在使用過程當中報如下錯誤:
Causederrorerrorby: net.rubyeye.xmemcached.exception.UnknownCommandException: Response,message:Unknow command TOUCH,key=Touch
250 at net.rubyeye.xmemcached.command.Command.decodeError(Command.java:)
說明安裝的 Memcached 服務不支持 touch 命令,建議升級。
測試示例:
Test@
public void testTouch() throws Exception {
set"Touch"2"Touch Value" memcachedClient.(,,);
1000 Thread.sleep();
"Touch"6 memcachedClient.touch(,);
2000 Thread.sleep();
valueget"Touch"3000 String=memcachedClient.(,);
out"Touch="value System..println(+);
}
Memcached 的分佈是經過客戶端實現的,客戶端根據 key 的哈希值獲得將要存儲的 Memcached 節點,並將對應的 value 存儲到相應的節點。
XMemcached 一樣支持客戶端的分佈策略,默認分佈的策略是按照 key 的哈希值模以鏈接數獲得的餘數,對應的鏈接就是將要存儲的節點。若是使用默認的分佈策略,不須要作任何配置或者編程。
XMemcached 一樣支持一致性哈希(Consistent Hash),經過編程設置:
new"server1:11211 server2:11211 server3:11211"MemcachedClientBuilder builder =XMemcachedClientBuilder(AddrUtil.getAddresses());
newbuilder.setSessionLocator(KetamaMemcachedSessionLocator());
MemcachedClient client=builder.build();
XMemcached 還提供了額外的一種哈希算法——選舉散列,在某些場景下能夠替代一致性哈希:
newMemcachedClientBuilder builder =XMemcachedClientBuilder(
"server1:11211 server2:11211 server3:11211" AddrUtil.getAddresses());
newbuilder.setSessionLocator(ElectionMemcachedSessionLocator());
MemcachedClient mc = builder.build();
在集羣的狀態下能夠給每一個服務設置不一樣的權重:
new"localhost:12000 localhost:12001"newint13MemcachedClientBuilder builder =XMemcachedClientBuilder(AddrUtil.getAddresses(),[]{,});
MemcachedClient memcachedClient=builder.build();
SASL 驗證
Memcached 1.4.3 開始支持 SASL 驗證客戶端,在服務器配置啓用 SASL 以後,客戶端須要經過受權驗證才能夠跟 Memcached 繼續交互,不然將被拒絕請求,XMemcached 1.2.5 開始支持這個特性。假設 Memcached 設置了 SASL 驗證,典型地使用 CRAM-MD 5 或者 PLAIN 的文本用戶名和密碼的驗證機制,假設用戶名爲 cacheuser,密碼爲 123456,那麼編程的方式以下:
newMemcachedClientBuilder builder =XMemcachedClientBuilder(
"localhost:11211" AddrUtil.getAddresses());
"localhost:11211"builder.addAuthInfo(AddrUtil.getOneAddress(), AuthInfo
"cacheuser""123456" .typical(,));
// Must use binary protocol
newbuilder.setCommandFactory(BinaryCommandFactory());
MemcachedClient client=builder.build();
請注意,受權驗證僅支持二進制協議。
Memcached 提供了統計協議用於查看統計信息:
MapMapStringString<InetSocketAddress,<,>> result=client.getStats();
getStats 方法返回一個 map ,其中存儲了全部已經鏈接而且有效的 Memcached 節點返回的統計信息,你也能夠統計具體的項目,如統計 items 項目:
MapMapStringString"items"<InetSocketAddress,<,>> result=client.getStatsByItem();
只要向 getStatsByItem 傳入須要統計的項目名稱便可,咱們能夠利用這個功能,來作 Memcached 狀態監控等。
Memcached 是一款很是流行的緩存中間件,被普遍應用在各場景中,使用緩存能夠環境數據庫壓力,某些場景下使用緩存能夠大大提升複用的 Tps 。 XMemcached 是 Memcached 的一個高性能 Nio 客戶端,支持 Memcached 底層各類操做,而且在 Memcached 協議的基礎上進行了封裝和完善,提供了鏈接池、集羣、數據壓縮、分佈式算法等高級功能,不管是完善度和性能各方面來看,XMemcached 都是目前最爲推薦的一款 Memcached 客戶端。