一、Memcached概念
Memcached是NoSQL产品之一,是一个临时性键值存储NoSQL数据库,过去被大量使用在互联网网站中,作为应用和数据库之间的缓存层,大大提高查询和访问速度。
Memcached有以下特点:
1、全内存运转:数据从来不保存在硬盘中,机器一重启,数据就全部没有了,所有又称临时性数据库;
2、哈希方式存储:
3、简单文本协议进行数据通信:不需要特定二进制代码,只需要用telnet连通memcached的监听端口,打入简单浅显的代码就能操作;
4、只操作字符型数据:无论往memcached放入什么,都是以字节的方式来处理。还原成数组、哈希表、字符串和数值等都交给应用层来解释。应用读写memcached的数据时,进行序列化和反序列化,把其解释成应用所能理解的数据类型;
5、集群也由应用进行控制,采用一致性散列(哈希)算法。
二、安装Memcached
1、在linux上搭建yum环境
2、使用yum命令进行安装Memcached的rpm包
[root@nn init.d]# yum install memcached
3、启动Memcached,
首先要cd到相应目录
[root@nn ~]# cd /etc/rc.d/init.d/
运行memcached安装脚本
[root@nn init.d]# ./memcached start
4、查看Memcached是否启动
[root@nn init.d]# pstree
表示Memcached进程被启动了,下面开了5个线程
或者使用[root@nn init.d]# ps aux命令
memcached -d -p 11211 -u memcached -m 64 -c 1024 -P /var/run/memcached/memcached.pid
-d表示程序要后台化运行,-p指定端口,-u表示用memcached这个身份来运行,后面的都是memcached的控制参数
5、连接Memcached
[root@nn init.d]# telnet localhost 11211
三、Memcached常用命令
命令格式:
<command name> <key> <flags> <exptime> <bytes>
<data block>
参数说明如下:
<command name> |
set/add/replace |
<key> |
查找关键字 |
<flags> |
客户机使用它存储关于键值对的额外信息,用于指定是否压缩,0不压缩,1压缩 |
<exptime> |
该数据的存活时间,0表示永远 |
<bytes> |
存储字节数 |
<data block> |
存储的数据块(可直接理解为key-value结构中的value) |
1、增加:set、add、cas
2、获取:get、gets、
3、追加:append、prepend
4、删除:delete
5、清除所有:flush_all
6、加减:incr、decr
7、退出:quit
三、用java连接Memcached
目前java提供了三种API供我们实现与Memcached的连接和存取
1、memcached client for java
较早推出的memcached JAVA客户端API,应用广泛,运行比较稳定。
2、pymemcached
A simple, asynchronous, single-threaded memcached client written in java. 支持异步,单线程的memcached客户端,用到了java1.5版本的concurrent和nio,存取速度会高于前者,但是稳定性不好,测试中常报timeOut等相关异常。
3、xmemcached
XMemcached同样是基于java nio的客户端,java nio相比于传统阻塞io模型来说,有效率高(特别在高并发下)和资源耗费相对较少的优点。传统阻塞IO为了提高效率,需要创建一定数量的连接形成连接池,而nio仅需要一个连接即可(当然,nio也是可以做池化处理),相对来说减少了线程创建和切换的开销,这一点在高并发下特别明显。因此XMemcached与Spymemcached在性能都非常优秀,在某些方面(存储的数据比较小的情况下)Xmemcached比Spymemcached的表现更为优秀,具体可以看这个Java Memcached Clients Benchmark。
本文章使用memcached client for java为例
在使用java连接远程的PC机的Memcached时,记得保证两台机都开启telnet服务,并且本机能telnet通远程机,远程机必须关闭防火墙。
实例代码1:(java连接Memcached并实现数据的存取)
import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
public class memcachedTest {
public static void main(String[] args) {
//初始化SockIOPool,管理Memcached的连接池
String[] servers = {"192.183.3.230:11211"};
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 < 100000;i++){
//将对象加入到memcached缓存
boolean success = memCachedClient.set(""+i, "hello!");
}
for(int i = 0;i < 100000;i++){
//从memcached缓存中按key值取对象
String result = (String)memCachedClient.get(""+i);
System.out.println(String.format("get(%d):%s", i,result+i));
}
}
}
四、测试Memcached性能
为性能对比测试准备数据
1、插入数据到oracle
/**
* 插入测试数据到oracle数据库
* @param count插入记录数
* @return
*/
public static boolean insertIntoOracle(int count){
try {
con = dbConn("feng","feng");
if(con == null){
System.out.println("连接失败");
System.exit(0);
}
System.out.println("truncate table memcached_test......");
sql = "truncate table memcached_test";
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
System.out.println("truncate table memcached_test finish.");
System.out.println("insert "+count+" values");
sql = "insert into memcached_test (memcachedId,memcachedvalues) values (?,?)";
pstmt = con.prepareStatement(sql);
for(int i = 1;i <= count;i++){
pstmt.setInt(1, i);
pstmt.setString(2, "Memcached is a good thing.I like it very much !-----------"+i);
pstmt.executeUpdate();
}
System.out.println("insert "+count+" values finish.");
rs.close();
pstmt.close();
con.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return true;
}
public static Connection dbConn(String name,String pass) throws ClassNotFoundException, SQLException{
Connection conn = null;
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@192.183.3.230:1522:myorcl",name,pass);
return conn;
}
2、插入数据到Memcached
/**
* 插入测试数据到Memcached
* @param count插入记录数
* @return
*/
public static boolean insertIntoMemcached(int count){
//初始化SockIOPool,管理Memcached的连接池
String[] servers = {"192.183.3.230:11211"};
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();
System.out.println("insert "+count+" values into memcached......");
for(int i = 1;i < count;i++){
//将对象加入到memcached缓存
boolean success = memCachedClient.set("testData"+i, insertStr+i);
}
System.out.println("insert "+count+" values into memcached finish.");
return true;
}
Main函数调用这两个方法后,会将count条记录,值为insertData,插入到Oracle数据和set进Memcached中。
1、比较同时插入100000条数据的时间
从运行结果可以看出,插入10万条数据到Memcached比插10万条数据入Oracle所用时间有一个质的减少。
2、比较查询时间
以下是连接oracle数据并查找10000条数据的方法
/**
* oracle数据库查找
* @param count记录数
* @return
* @throws ParseException
*/
public static long searchOracle(int count) throws ParseException{
long useTime = 0;
try {
con = dbConn("feng","feng");
if(con == null){
System.out.println("连接失败");
System.exit(0);
}
StringBuffer sql =new StringBuffer("select memcachedid,memcachedvalues from memcached_test where memcachedid = ?");
pstmt = con.prepareStatement(sql.toString());
String memcachedvalues = "";
System.out.println("search table memcached_test......");
String beginTime = d.format(new Date());
for(int i = 1;i <= count;i++){
if(i%10 == 0){
pstmt.setInt(1, i);
rs = pstmt.executeQuery();
while(rs.next()){
memcachedvalues = rs.getString(2);
}
}
}
System.out.println("search table memcached_test finish.");
String endTime = d.format(new Date());
useTime = d.parse(endTime).getTime() - d.parse(beginTime).getTime();
long ss = (useTime/1000)%60;//秒
long MM = useTime/60000;//分
System.out.println("Oracle中查找10000条记录的开始时间:"+beginTime);
System.out.println("Oracle中查找10000条记录的结束时间:"+endTime);
System.out.println("Oracle中查找10000条记录的所用时间: "+MM+"分"+ss+"秒");
rs.close();
pstmt.close();
con.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return useTime;
}
以下是连接Memcached并查找10000条数据的方法
/**
* Memcached查找
* @param count
* @return
* @throws ParseException
*/
public static long searchMemcached(int count) throws ParseException{
//初始化SockIOPool,管理Memcached的连接池
String[] servers = {"192.183.3.230:11211"};
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();
System.out.println("search 10000 data in Memcached......");
String memcachedvalues = "";
String beginTime = d.format(new Date());
for(int i = 1;i <= count;i++){
//从memcached缓存中按key值取对象
if(i%10 == 0){
memcachedvalues = (String)memCachedClient.get("testData"+i);
}
}
System.out.println("search 10000 data in Memcached finish.");
String endTime = d.format(new Date());
long useTime = d.parse(endTime).getTime() - d.parse(beginTime).getTime();
long ss = (useTime/1000)%60;//秒
long MM = useTime/60000;//分
System.out.println("从Memcached查找10000条记录的开始时间:"+beginTime);
System.out.println("从Memcached查找10000条记录的结束时间:"+endTime);
System.out.println("从Memcached查找10000条记录的所用时间: "+MM+"分"+ss+"秒");
return useTime;
}
运行结果如下:
从运行结果可以看出,同时查找10000条数据,Memcached所用时间比1Oracle所用时间减少了29秒。
四、启动多个节点的Memcached
由于实验器材有限,现在在同一台pc机中启动多个Memcached,只要设定端口不一样,这些Memcached之间互相不会干扰。
启动命令如下:
[root@nn init.d]# memcached -d -p 11212 -u memcached -m 64 -c 1024
[root@nn init.d]# memcached -d -p 11213 -u memcached -m 64 -c 1024
其中-d表示在后台运行,-p表示端口号,-u表示用户
启动之后用pstree查看
[root@nn init.d]# pstree
3*[memcached───5*[{memcached}]]表示有3组Memcached的进程。
或者用ps aux命令
[root@nn init.d]# ps aux
往多节点的Memcached中插入数据的java代码
/**
* 往节点Memcached插入数据
* @param count
*/
public static void testManyNode(int count){
//初始化SockIOPool,管理Memcached的连接池
String[] servers = {"192.183.3.230:11211","192.183.3.230:11212","192.183.3.230:11213"};
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();
String beginTime = d.format(new Date());
for(int i = 1;i <= count;i++){
//将对象加入到memcached缓存
boolean success = memCachedClient.set("node"+i, insertStr+i);
}
}
Memcached的查询结果:
从结果中可以看出,数据分布到Memcached的不同节点上。
五、高可用方案repcached
假如Memcached中有一个节点失效了,这个节点所管辖的数据都没有,我们必须重新去数据库中获取数据放入新的节点中,这样会引发数据库性能的波动。这里就需要我们做一个高可用的Memcached,使得Memcached中的每一个节点都有另外一个节点与之一一对应,这两个一一对应的节点中的数据是一模一样的。这样当其中一个节点失效了,另外一个节点就能马上接管缓存的工作,这样就不需要重新从数据库中获取数据库。
下面我们使用repcached来实现Memcached的高可用
1、下载repcached
[root@nn ~]# wget http://downloads.sourceforge.net/repcached/memcached-1.2.8-repcached-2.2.tar.gz
2、杀死本机上的所有Memcached
[root@nn ~]# killall memcached
3、解压下载的repcached
[root@nn ~]# tar zxvf memcached-1.2.8-repcached-2.2.tar.gz
4、进入所解压的目录
[root@nn ~]# cd memcached-1.2.8-repcached-2.2
5、安装依赖包,运行编译repcached所需要的
[root@nn memcached-1.2.8-repcached-2.2]# yum install libevent-devel
6、开始安装repcached
[root@nn memcached-1.2.8-repcached-2.2]# ./config --enable-replication --program-transform-name=s/memcached/repcached/
安装好之后就会产生一个Makefile文件
7、可以使用Makefile来编译
[root@nn memcached-1.2.8-repcached-2.2]# make
[root@nn memcached-1.2.8-repcached-2.2]# make install
可以看到主要安装的程序有repcached和repcached-debug
8、启动Memcached的高可用集群(注意不能用root用户来启动)
[oracle@nn ~]$ /usr/local/bin/repcached -p 11211 -v -d
[oracle@nn ~]$ /usr/local/bin/repcached -p 11212 -x localhost -v -d
其中-x表示要监听高可用机器,如果是其他端口要写上“:端口号”,如果是默认端口(11211),就不需要写。
9、测试:从11211端口插入数据,到11212端口去查找;从11212端口插入数据,到11211端口去查找
从测试结果可以看出,这个高可用复制是双向的。无论在哪一个节点插入数据,都可以在另外一个节点中查到。
六、Memcached的一致性
如果有两个不同终端连接同一Memcached服务,对同一key值进行修改,结果会如何呢?
下面来做实验
1、A终端连接Memcached并set入key为counter的一个值1
用gets命令看出,比用get命令多最后一个数字,这个数字表示这个key的版本号
2、B终端连接Memcached并set入key为counter的一个值2
3、用set命令去改会改变一致性,这里改用cas命令
我们用gets查看当前的版本号是3
用cas最后一个参数表示版本号,如果版本号不一样,不能修改,会有EXISTS提示,表示修改失败;如果版本号一致,就能修改成功。
Memcached的缺点
1、纯内存操作的数据库,关机或者关闭Memcached进程后数据全部丢失;
2、保存字节数,数据类型贫乏,其他数据类型都要通过应用程序来解释,这样应用端要做的事情会很多,服务器端要做的事情就很好很好了;
3、兼容性差,不同编程语言之间不能相互调用;
4、LRU算法导致数据不可控的丢失;
5、一致性处理简单;
6、应用场景有限,难以被看成是完整的数据库产品,仅仅用来做数据库和应用之间的缓存层。