缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载。缓存是解决这个问题的好办法。
Memcached是什么?
Memcached是由Danga Interactive开发的,高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。
Memcache是什么
Memcache是danga.com的一个项目,最早是为 LiveJournal 服务的,目前全世界不少人使用这个缓存项目来构建自己大负载的网站,来分担数据库的压力。
它可以应对任意多个连接,使用非阻塞的网络IO。由于它的工作机制是在内存中开辟一块空间,然后建立一个HashTable,Memcached自管理这些HashTable。
Memcache是高性能的分布式内存缓存服务器。 一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、 提高可扩展性。
为什么会有Memcache和memcached两种名称?
其实Memcache是这个项目的名称,而memcached是它服务器端的主程序文件名,
Memcache工作原理
首先 memcached 是以守护程序方式运行于一个或多个服务器中,随时接受客户端的连接操作,客户端可以由各种语言编写,目前已知的客户端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。客户端在与 memcached 服务建立连接之后,接下来的事情就是存取对象了,每个被存取的对象都有一个唯一的标识符 key,存取操作均通过这个 key 进行,保存到 memcached 中的对象实际上是放置内存中的,并不是保存在 cache 文件中的,这也是为什么 memcached 能够如此高效快速的原因。注意,这些对象并不是持久的,服务停止之后,里边的数据就会丢失。
与许多 cache 工具类似,Memcached 的原理并不复杂。它采用了C/S的模式,在 server 端启动服务进程,在启动时可以指定监听的 ip,自己的端口号,所使用的内存大小等几个关键参数。一旦启动,服务就一直处于可用状态。Memcached 的目前版本是通过C实现,采用了单进程,单线程,异步I/O,基于事件 (event_based) 的服务方式.使用 libevent 作为事件通知实现。多个 Server 可以协同工作,但这些 Server 之间是没有任何通讯联系的,每个 Server 只是对自己的数据进行管理。Client 端通过指定 Server 端的 ip 地址(通过域名应该也可以)。需要缓存的对象或数据是以 key->value 对的形式保存在Server端。key 的值通过 hash 进行转换,根据 hash 值把 value 传递到对应的具体的某个 Server 上。当需要获取对象数据时,也根据 key 进行。首先对 key 进行 hash,通过获得的值可以确定它被保存在了哪台 Server 上,然后再向该 Server 发出请求。Client 端只需要知道保存 hash(key) 的值在哪台服务器上就可以了。
其实说到底,memcache 的工作就是在专门的机器的内存里维护一张巨大的 hash 表,来存储经常被读写的一些数组与文件,从而极大的提高网站的运行效率。
memcached的特征
memcached作为高速运行的分布式缓存服务器,具有以下的特点。
- 协议简单
- 基于libevent的事件处理
- 内置内存存储方式
- memcached不互相通信的分布式
协议简单
memcached的服务器客户端通信并不使用复杂的XML等格式, 而使用简单的基于文本行的协议。因此,通过telnet也能在memcached上保存数据、取得数据。下面是例子。
$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
set foo 0 0 3 (保存命令)
bar (数据)
STORED (结果)
get foo (取得命令)
VALUE foo 0 3 (数据)
bar (数据)
协议文档位于memcached的源代码内,也可以参考以下的URL。
基于libevent的事件处理
libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能 封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。 关于事件处理这里就不再详细介绍,可以参考Dan Kegel的The C10K Problem。
内置内存存储方式
为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。 由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。 另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。 关于内存存储的详细信息,本连载的第二讲以后前坂会进行介绍,请届时参考。
memcached不互相通信的分布式
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。 各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢? 这完全取决于客户端的实现。本连载也将介绍memcached的分布式。
Memcached能缓存什么?
通过在内存里维护一个统一的巨大的hash表,Memcached能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。
Memcached快么?
非常快。Memcached使用了libevent(如果可以的话,在linux下使用epoll)来均衡任何数量的打开链接,使用非阻塞的网络I/O, 对内部对象实现引用计数(因此,针对多样的客户端,对象可以处在多样的状态), 使用自己的页块分配器和哈希表, 因此虚拟内存不会产生碎片并且虚拟内存分配的时间复杂度可以保证为O(1).。
Danga Interactive为提升Danga Interactive的速度研发了Memcached。目前,LiveJournal.com每天已经在向一百万用户提供多达两千万次的页面访问。而这 些,是由一个由web服务器和数据库服务器组成的集群完成的。Memcached几乎完全放弃了任何数据都从数据库读取的方式,同时,它还缩短了用户查看 页面的速度、更好的资源分配方式,以及Memcache失效时对数据库的访问速度。
Memcached的特点
Memcached的缓存是一种分布式的,可以让不同主机上的多个用户同时访问, 因此解决了共享内存只能单机应用的局限,更不会出现使用数据库做类似事情的时候,磁盘开销和阻塞的发生。
Memcached的使用
一 、Memcached服务器端的安装 (此处将其作为系统服务安装)
下载文件:memcached 1.2.1 for Win32 binaries (Dec 23, 2006)
1 解压缩文件到c:\memcached
2 命令行输入 'c:\memcached\memcached.exe -d install'
3 命令行输入 'c:\memcached\memcached.exe -d start' ,该命令启动 Memcached ,默认监听端口为 11211
通过 memcached.exe -h 可以查看其帮助
ps:安装时报错误的话,如果是WIN7系统,那么用管理员身份进入CMD模式,找到CMD.EXE,用右键选择管理员模式进入就可以了。
memcached.exe -p 11211 -m 64m -vv
-p |
使用的TCP端口。默认为11211 |
-m |
最大内存大小。默认为64M |
-vv |
用very vrebose模式启动,调试信息和错误输出到控制台 |
-d |
作为daemon在后台启动 |
-d restart 重启memcached服务
-d stop|shutdown 关闭正在运行的memcached服务
-M 内存耗尽时返回错误,而不是删除项
-c 最大同事连接数,默认是1024
-f 块大小增长因子,默认是1.25
-n 最小分配空间 key+value+flags默认是48
-h 显示帮助
二、客户端使用
下载memcached java client:http://www.whalin.com/memcached/#download
1 解压后将java_memcached-release_2.0.1.jar jar包添加到工程的classpath中
2 利用memcached java client 一个简单的应用
- public class TestMemberCache {
-
- protected static MemCachedClient mcc = new MemCachedClient();
- static {
-
- String[] servers = { "127.0.0.1:11211" };
- Integer[] weights = { 3 };
-
- SockIOPool pool = SockIOPool.getInstance();
-
- pool.setServers(servers);
- pool.setWeights(weights);
-
- pool.setInitConn(5);
- pool.setMinConn(5);
- pool.setMaxConn(250);
- pool.setMaxIdle(1000 * 60 * 60 * 6);
-
- pool.setMaintSleep(30);
-
-
-
-
- pool.setNagle(false);
- pool.setSocketTO(3000);
- pool.setSocketConnectTO(0);
-
- pool.initialize();
-
- mcc.setCompressEnable(true);
- mcc.setCompressThreshold(64 * 1024);
- }
- public static void bulidCache() {
-
-
-
-
- mcc.set("test", "This is a test String", new Date(10000));
- mcc.set("test", "This is a test String111", new Date(10000));
- User u = new User();
- u.setUsername("aaaa");
- mcc.set("user", u);
- User u1 = (User) mcc.get("user");
- System.out.println(u1.getUsername());
-
-
-
- }
- public static void output() {
-
- String value = (String) mcc.get("test");
- System.out.println(value);
- }
- public static void main(String[] args) {
- bulidCache();
- output();
- }
- }
public class TestMemberCache {
// 创建全局的唯一实例
protected static MemCachedClient mcc = new MemCachedClient();
static {
// 服务器列表和其权重
String[] servers = { "127.0.0.1:11211" };
Integer[] weights = { 3 };
// 获取socke连接池的实例对象
SockIOPool pool = SockIOPool.getInstance();
// 设置服务器信息
pool.setServers(servers);
pool.setWeights(weights);
// 设置初始连接数、最小和最大连接数以及最大处理时间
pool.setInitConn(5);
pool.setMinConn(5);
pool.setMaxConn(250);
pool.setMaxIdle(1000 * 60 * 60 * 6);
// 设置主线程的睡眠时间
pool.setMaintSleep(30);
// Tcp的规则就是在发送一个包之前,本地机器会等待远程主机
// 对上一次发送的包的确认信息到来;这个方法就可以关闭套接字的缓存,
// 以至这个包准备好了就发;
// 设置TCP的参数,连接超时等
pool.setNagle(false);
pool.setSocketTO(3000);
pool.setSocketConnectTO(0);
// 初始化连接池
pool.initialize();
// 压缩设置,超过指定大小(单位为K)的数据都会被压缩
mcc.setCompressEnable(true);
mcc.setCompressThreshold(64 * 1024);
}
public static void bulidCache() {
// set(key,value,Date) ,Date是一个过期时间,如果想让这个过期时间生效的话,这里传递的new Date(long
// date) 中参数date,需要是个大于或等于1000的值。
// 因为java client的实现源码里是这样实现的 expiry.getTime() / 1000 ,也就是说,如果
// 小于1000的值,除以1000以后都是0,即永不过期
mcc.set("test", "This is a test String", new Date(10000));
mcc.set("test", "This is a test String111", new Date(10000));// 十秒后过期
User u = new User();
u.setUsername("aaaa");
mcc.set("user", u);
User u1 = (User) mcc.get("user");
System.out.println(u1.getUsername());
// add 當這個 key 不存在的時候才保存 value
// replace 當 key 相同的時候才替換 value
// set 直接寫入新的 value ,如果 key 存在就是替換 value
}
public static void output() {
// 从cache里取值
String value = (String) mcc.get("test");
System.out.println(value);
}
public static void main(String[] args) {
bulidCache();
output();
}
}
输出结果为:
aaaa
This is a test String111
MemberCache及其他缓存等相关知识:
1)JBOSS CACHE, EHCACHE, 等JAVA写成的CACHE,一般都是和主程序在同一机器上,内存直接访问,比memcached要快。但如果有多个server,每个server都有 自己的一份cache,要采用一些notification or replicate机制才能synchronize。而且,cache size受制于heap size setting。memcached 运行在主程序以外的机器上,通过网络访问来传递数据。因为是专门的server,所以size不受限制。如果cache data 不大(300MB以下?),无须用memcached.
2)memcached组成它的n台机器里有一个down了,并不会整个挂掉,只会访问某些cache内容无法命中,就算全部挂掉,Cache就是减少对数据库访问的,所以无非就是对数据库压力大一些而已,如果希望Cache持久化,或者带有故障切换功能,可以用memcachedb。
3)Cache Server的可靠性比DB Server还要高
4)memcached 要求set的对象必须是可序列化对象,jboss cache等java obect cache是没有这个说法的,这是本质的不同的,但是他可以在网络上用,所以必须序列化也可理解
5)memcached 并发连接可以上到1w,我手头的应用常常保持在3-5k;它的快不仅是因为用了libevent,还因为它采取了“用内存冗余换存取速度”的内存管理策 略,网上有文章专门分析它的内存分配回收管理的源码,讲的很清楚,在这上面jboss cache、ehcache、oscache跟它没法比;memcached的集群也非常好,听说国外有200+的memcached集群,我们也有这方 面的尝试,效果也很好,一台down掉根本不会引起其他机器down掉,只是这台的数据丢了,需要慢慢积累回来;而且支持多客户端,java、php、python、ruby可以共享数据,就把它当作数据库用。
我的建议是:你的应用访问量比较大,对响应速度要求很高,对数据一致性要求一般时,用它,挡在数据库前面,非常爽(memcached是互联网公司开发的,正好满足这三个条件);如果应用不忙,用用ehcache就行了。
6)ehcache、oscache 的数据都是在本机服务器上的,访问时走的仅是系统总线。而memcached走的是网络。关于传输速度来讲我们的应用读取memcached的网络流量是每秒有2MB的流量,是读取数据库的网络流量的大约5倍。但是你要知道现在随便的PC机都是千兆网卡,因此memcached的get/set操作的延时非常少,并不比echache的get/set慢多少。在一个完整的web应用当中,我的压力测试表明,性 能差异 <= 5%
7)论坛、sns这样的应用,会使用多种技术进行缓存。
拿sohu的bbs来说吧,pv为5000w,峰值8000w
其中帖子、评论读写频繁,其他部分读频繁。
帖子列表、评论列表使用c开发(其中排序算法很巧妙),socket调用
帖子、评论内容使用squid缓存
其他读频繁的部分使用memcached、squid、定时生成静态页面等多种技术。
数据库用mysql,分表。
个人认为,小规模应用中,jvm级别的cache可以用用,memcached可用可不用
大规模网站应用,肯定是系统水平切分,多种cache结合。
8)memcached非常快,但我没说过比本机的ehcache还快,但用本地缓存有两点不爽:
1.缓存放在内存or放在磁盘?应用重启会导致内存缓存丢失,放在磁盘又不够快。内存开多大合适?如果是大访问量应用,缓存对象集中淘汰可能引起服务器load急剧波动(我们吃过亏,现在也开ehcache,但缓存对象的上限开的很小)
2.集群应用里缓存对象如何共享?jboss cache用的是广播,访问量大的时候,可以把你的服务拖死,这个我们也吃过亏
当然,有人谈到了sohu的例子,一旦访问量大了,各种缓存都得用着,目前我们就是squid + memcached + ehcache。squid也是好东西,但缓存内容删除不太灵活,比较适合web1.0,比如新浪搜狐的新闻
另外,java memcached client用1.6好了,没必要升级到2.1,2.X似乎还不太稳定。