Memcache(Memcached)是集羣環境下的緩存解決方案。前端
Memcache 是danga.com的一個項目,最先是爲 LiveJournal 服務的,目前全世界很多人使用這個緩存項目來構建本身大負載的網站,來分擔數據庫的壓力。它能夠應對任意多個鏈接,使用非阻塞的網絡IO。它的工做機制是在內存中開闢一塊空間,而後創建一個HashTable,Memcached自管理這些HashTable。java
Memcache官方網站:http://www.danga.com/memcached,更多詳細的信息能夠來這裏瞭解。git
其實Memcache是這個項目的名稱,而memcached是它服務器端的主程序文件名,知道個人意思了吧。一個是項目名稱,一個是主程序文件名,在網上看到了不少人不明白,因而混用了。github
在Java開發中使用Memcache,通常要用到如下幾個程序:web
該程序用來在Linux或Windows服務器上創建和管理緩存。算法
其項目網址爲:http://danga.com/memcached/。數據庫
Magent是一款開源的Memcached代理服務器軟件,使用它能夠搭建高可用性的集羣應用的Memcached服務,其項目網址爲:http://code.google.com/p/memagent/。apache
至於Memcached的客戶端程序,通常推薦用memcached client for java,爲何推薦用這種客戶端,後面會講到具體的緣由,其項目的網址爲:http://github.com/gwhalin/Memcached-Java-Client/。後端
在 Linux環境下應用Memcache時,Memcache用到了libevent這個庫,用於Socket的處理,因此還須要安裝libevent。 libevent的最新版本是libevent-1.4.13。(若是你的系統已經安裝了libevent,能夠不用安裝)。緩存
官網:http://www.monkey.org/~provos/libevent/
下載:http://www.monkey.org/~provos/libevent-1.4.13-stable.tar.gz
Memcache也能夠安裝在Windows服務器下,安裝程序:memcached-1.2.1-win32.zip
能夠從這裏下載:http://jehiah.cz/projects/memcached-win32/。
進入到memcached的安裝目錄,如:
#cd /usr/local/memcached-1.4.5
# ./memcached -d -m 10 -u root -l 10.11.15.222 -p 12000 -c 256 -P /tmp/memcached.pid
? -d 選項是啓動一個守護進程,
? -m 是分配給Memcache使用的內存數量,單位是MB,我這裏是10MB,
? -u 是運行Memcache的用戶,我這裏是root,
? -l 是監聽的服務器IP地址,我這裏指定了服務器的IP地址10.11.15.222,
? -p 是設置Memcache監聽的端口,我這裏設置了12000,最好是1024以上的端口,
? -c 是最大運行的併發鏈接數,默認1024,這裏設置了256,按照服務器的負載量來設定,
? -P 是設置保存Memcache的pid文件,我這裏是保存在/tmp/memcached.pid,
若是要結束Memcache進程,執行:
# kill `cat /tmp/memcached.pid`
? 注意,上面命令中的符號是 `,不是單引號’
也能夠啓動多個守護進程,不過端口不能重複。
Magent已保存到/usr/bin目錄下,能夠直接執行該命令。如:
#magent -u root -n 51200 -l 127.0.0.1 -p 12000 -s 127.0.0.1:11211 -s 127.0.0.1:11212 -b 127.0.0.1:11213
命令參數:
? -h this message
? -u uid
? -g gid
? -p port, default is 11211. (0 to disable tcp support)
? -s ip:port, set memcached server ip and port
? -b ip:port, set backup memcached server ip and port
? -l ip, local bind ip address, default is 0.0.0.0
? -n number, set max ...
magent 採用的是:Consistent Hashing原理,Consistent Hashing以下所示:首先求出memcached服務器(節點)的哈希值, 並將其配置到0~232的圓(continuum)上。 而後用一樣的方法求出存儲數據的鍵的哈希值,並映射到圓上。而後從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。 若是超過232仍然找不到服務器,就會保存到第一臺memcached服務器上。
從上圖的狀態中添加一臺memcached服務器。餘數分佈式算法因爲保存鍵的服務器會發生巨大變化 而影響緩存的命中率,但Consistent Hashing中,只有在continuum上增長服務器的地點逆時針方向的第一臺服務器上的鍵會受到影響。
利用magent實現對memecache的分佈式管理,搭建一套memcache集羣服務:
? 前端java對magent的訪問跟對memcache訪問相同,不須要作任何更改,對於插入的key,magent會把值散列到各個memcache服務上,只操做magent,不用關心後端處理;
? 項目應用:以深圳電信爲例,其商呼系統如圖部署三臺機器作爲集羣,假設IP分別是:10.11.15.31, 10.11.15.32, 10.11.15.33;
? 每一個前端安裝memcached服務(大內存機器能夠啓動多個服務),如端口都爲12001,每一個前端都安裝magent服務,端口都爲12000,後端掛載所有機器的memcached服務,
? 啓動參數示例:magent -p 12000 -s 10.11.15.31:12001 -s 10.11.15.32:12001 -s 10.11.15.33:12001,這裏將三臺機器都配置進來,如集集羣增長了機器,只須要在啓動參數裏添加進來便可。全部前端配置都是相同的,任何一個前端只需訪問本地端口的magent,這樣的memcache集羣對應用帶來很大便利。
? 這種部署能夠解決session共享的應用
項目中多處已經實際應用,magent對memcache的均衡和穩定性都很是不錯,推薦使用。
此處以二機集羣爲例。
啓動兩個memcached進程,端口分別爲11211和11212:
memcached -m 1 -u root -d -l 127.0.0.1 -p 11211
memcached -m 1 -u root -d -l 127.0.0.1 -p 11212
再啓動兩個magent進程,端口分別爲10000和11000:
magent -u root -n 51200 -l 127.0.0.1 -p 10000 -s 127.0.0.1:11211 -b 127.0.0.1:11212
magent -u root -n 51200 -l 127.0.0.1 -p 11000 -s 127.0.0.1:11212 -b 127.0.0.1:11211
-s 爲要寫入的memcached, -b 爲備份用的memcached。
說明:測試環境用magent和memached的不一樣端口來實現,在生產環境中能夠將magent和memached做爲一組放到兩臺服務器上。也就是說經過magent可以寫入兩個memcached。
[root@odb ~]# telnet 127.0.0.1 10000
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
set key 0 0 8 <—在10000端口設置key的值
88888888
STORED
quit
Connection closed by foreign host.
[root@odb ~]# telnet 127.0.0.1 11211
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
get key <—在11211端口獲取key的值成功
VALUE key 0 8
88888888
END
quit
Connection closed by foreign host.
[root@odb ~]# telnet 127.0.0.1 11212
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
get key <—在11212端口獲取key的值成功
VALUE key 0 8
88888888
END
quit
Connection closed by foreign host.
[root@odb ~]# ps aux |grep -v grep |grep memcached
root 23455 0.0 0.0 5012 1796 ? Ss 09:22 0:00 memcached -m 1 -u root -d -l 127.0.0.1 -p 11212
root 24950 0.0 0.0 4120 1800 ? Ss 10:58 0:00 memcached -m 1 -u root -d -l 127.0.0.1 -p 11211
[root@odb ~]# ps aux |grep -v grep |grep ‘magent -u’
root 25919 0.0 0.0 2176 484 ? Ss 12:00 0:00 magent -u root -n 51200 -l 127.0.0.1 -p 10000 -s 127.0.0.1:11211 -b 127.0.0.1:11212
root 25925 0.0 0.0 3004 484 ? Ss 12:00 0:00 magent -u root -n 51200 -l 127.0.0.1 -p 11000 -s 127.0.0.1:11212 -b 127.0.0.1:11211
[root@odb ~]# telnet 127.0.0.1 10000
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
set stone 0 0 6 <—在10000端口設置stone的值
123456
STORED
quit
Connection closed by foreign host.
[root@odb ~]# telnet 127.0.0.1 11000
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
set shidl 0 0 6 <—在11000端口設置shidl的值
666666
STORED
get stone <—在11000端口獲取stone的值成功
VALUE stone 0 6
123456
END
incr stone 2 <—在11000端口修改stone的值成功
123458
get stone
VALUE stone 0 6 <—在11000端口驗證stone的值,證實上面的修改爲功
123458
END
get shidl <—在11000端口獲取shidl的值成功
VALUE shidl 0 6
666666
END
quit <—退出11000端口
Connection closed by foreign host.
[root@odb ~]# telnet 127.0.0.1 10000
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
get stone <—在10000端口獲取stone的值,已被修改
VALUE stone 0 6
123458
END
get shidl <—在10000端口獲取shidl的值成功
VALUE shidl 0 6
666666
END
delete shidl <—在10000端口刪除shidl
DELETED
get shidl <—在10000端口刪除shidl生效
END
quit
Connection closed by foreign host.
[root@odb ~]# telnet 127.0.0.1 11000
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
get shidl <—在11000端口驗證刪除shidl生效
END
get stone <—在11000端口獲取stone的值成功
VALUE stone 0 6
123458
END
quit
Connection closed by foreign host.
[root@odb ~]# kill -9 24950
[root@odb ~]# telnet 127.0.0.1 10000
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
get stone <—在10000依然能夠獲取stone的值
VALUE stone 0 6
123458
END
quit
Connection closed by foreign host.
[root@odb ~]# telnet 127.0.0.1 11000
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
get stone <—在11000依然能夠獲取stone的值
VALUE stone 0 6
123458
END
quit
Connection closed by foreign host.
[root@odb ~]# kill -9 25925
[root@odb ~]# telnet 127.0.0.1 10000
Trying 127.0.0.1…
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]’.
get stone <—在10000依然能夠獲取stone的值
VALUE stone 0 6
123458
END
quit
Connection closed by foreign host.
[root@lh-web-test memcached-1.4.5]# magent -u root -n 51200 -l 127.0.0.1 -p 11000 -s 127.0.0.1:11212 -b 127.0.0.1:11211
[root@lh-web-test memcached-1.4.5]# telnet 127.0.0.1 11000
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
get stone <—在11000依然能夠獲取stone的值
VALUE stone 0 6
123458
END
quit
Connection closed by foreign host.
Memcached的java客戶端已經存在三種了:
? 官方提供的基於傳統阻塞io由Greg Whalin維護的客戶端
? Dustin Sallings實現的基於java nio的Spymemcached
? XMemcached
較早推出的memcached JAVA客戶端API,應用普遍,運行比較穩定。
A simple, asynchronous, single-threaded memcached client written in java. 支持異步,單線程的memcached客戶端,用到了java1.5版本的concurrent和nio,存取速度會高於前者,可是穩定性很差,測試中常報timeOut等相關異常。
XMemcached 一樣是基於java nio的客戶端,java nio相比於傳統阻塞io模型來講,有效率高(特別在高併發下)和資源耗費相對較少的優勢。傳統阻塞IO爲了提升效率,須要建立必定數量的鏈接造成鏈接池,而nio僅須要一個鏈接便可(固然,nio也是能夠作池化處理),相對來講減小了線程建立和切換的開銷,這一點在高併發下特別明顯。所以 XMemcached與Spymemcached在性能都很是優秀,在某些方面(存儲的數據比較小的狀況下)Xmemcached比 Spymemcached的表現更爲優秀,具體能夠看這個Java Memcached Clients Benchmark。
因爲memcached client for java發佈了新版本,性能上有所提升,而且運行穩定,因此建議使用memcached client for java。
XMemcached 也使用得比較普遍,並且有較詳細的中文API文檔,具備以下特色:高性能、支持完整的協議、支持客戶端分佈、容許設置節點權重、動態增刪節點、支持 JMX、與Spring框架和Hibernate-memcached的集成、客戶端鏈接池、可擴展性好等。
下面給出這三種客戶端的示例程序。
從前面介紹的Java環境的Memcached客戶端程序項目網址裏,下載最新版的客戶端程序包:java_memcached- release_2.5.1.zip,解壓後,文件夾裏找到java_memcached-release_2.5.1.jar,這個就是客戶端的JAR 包。將此JAR包添加到項目的構建路徑裏,則項目中,就可使用Memcached了。
示例代碼以下:
package temp;
import com.danga.MemCached.*;
import org.apache.log4j.*;
public class CacheTest {
public static void main(String[] args) {
/**
* 初始化SockIOPool,管理memcached的鏈接池
* */
String[] servers = { "10.11.15.222:10000" };
SockIOPool pool = SockIOPool.getInstance();
pool.setServers(servers);
pool.setFailover(true);
pool.setInitConn(10);
pool.setMinConn(5);
pool.setMaxConn(250);
pool.setMaintSleep(30);
pool.setNagle(false);
pool.setSocketTO(3000);
pool.setAliveCheck(true);
pool.initialize();
/**
* 創建MemcachedClient實例
* */
MemCachedClient memCachedClient = new MemCachedClient();
for (int i = 0; i < 1000; i++) {
/**
* 將對象加入到memcached緩存
* */
boolean success = memCachedClient.set("" + i, "Hello!");
/**
* 從memcached緩存中按key值取對象
* */
String result = (String) memCachedClient.get("" + i);
System.out.println(String.format("set( %d ): %s", i, success));
System.out.println(String.format("get( %d ): %s", i, result));
}
}
}
spymemcached當前版本是2.5版本,官方網址是:http://code.google.com/p/spymemcached/。能夠從地址:http://spymemcached.googlecode.com/files/memcached-2.5.jar下載最新版原本使用。
示例代碼以下:
package temp;
import java.net.InetSocketAddress;
import java.util.concurrent.Future;
import net.spy.memcached.MemcachedClient;
public class TestSpyMemcache {
public static void main(String[] args) {
// 保存對象
try {
/* 創建MemcachedClient 實例,並指定memcached服務的IP地址和端口號 */
MemcachedClient mc = new MemcachedClient(new InetSocketAddress("10.11.15.222", 10000));
Future<Boolean> b = null;
/* 將key值,過時時間(秒)和要緩存的對象set到memcached中 */
b = mc.set("neea:testDaF:ksIdno", 900, "someObject");
if (b.get().booleanValue() == true) {
mc.shutdown();
}
} catch (Exception ex) {
ex.printStackTrace();
}
// 取得對象
try {
/* 創建MemcachedClient 實例,並指定memcached服務的IP地址和端口號 */
MemcachedClient mc = new MemcachedClient(new InetSocketAddress("10.11.15.222", 10000));
/* 按照key值從memcached中查找緩存,不存在則返回null */
Object b = mc.get("neea:testDaF:ksIdno");
System.out.println(b.toString());
mc.shutdown();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Xmemcached的官方網址是:http://code.google.com/p/xmemcached/,能夠從其官網上下載最新版本的1.2.4來使用。地址是:http://xmemcached.googlecode.com/files/xmemcached-1.2.4-src.tar.gz。
示例代碼以下:
package temp;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import net.rubyeye.xmemcached.utils.AddrUtil;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.exception.MemcachedException;
public class TestXMemcache {
public static void main(String[] args) {
MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil
.getAddresses("10.11.15.222:10000"));
MemcachedClient memcachedClient;
try {
memcachedClient = builder.build();
memcachedClient.set("hello", 0, "Hello,xmemcached");
String value = memcachedClient.get("hello");
System.out.println("hello=" + value);
memcachedClient.delete("hello");
value = memcachedClient.get("hello");
System.out.println("hello=" + value);
// close memcached client
memcachedClient.shutdown();
} catch (MemcachedException e) {
System.err.println("MemcachedClient operation fail");
e.printStackTrace();
} catch (TimeoutException e) {
System.err.println("MemcachedClient operation timeout");
e.printStackTrace();
} catch (InterruptedException e) {
// ignore
}catch (IOException e) {
System.err.println("Shutdown MemcachedClient fail");
e.printStackTrace();
}
}
}