Chan Chen Coding...

Memcached实现内存缓存(一)

Memcached是danga.com(运营LiveJournal的技术团队)开发的一套分布式内存对象缓存系统,用于在动态系统中减少数据库负载,提升性能。LJ每秒动态页面访问量几千次,用户700万。Memcached将数据库负载大幅度降低,更好的分配资源,更快速访问。

    关于这个东西,相信很多人都用过,本文意在通过对memcached的实现及代码分析,获得对这个出色的开源软件更深入的了解,并可以根据我们的需要对其进行更进一步的优化。末了将通过对BSM_Memcache扩展的分析,加深对 memcached的使用方式理解。。

1.Memcached是什么

在阐述这个问题之前,我们首先要清楚它“不是什么”。很多人把它当作和SharedMemory那种形式的存储载体来使用,虽然memcached使用了同样的“Key=>Value”方式组织数据,但是它和共享内存、APC等本地缓存有非常大的区别。Memcached是分布式的,也就是说它不是本地的。它基于网络连接(当然它也可以使用localhost)方式完成服务,本身它是一个独立于应用的程序或守护进程(Daemon方式)。即Memcached是高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。

Memcached 使用libevent库实现网络连接服务,理论上可以处理无限多的连接,但是它和Apache不同,它更多的时候是面向稳定的持续连接的,所以它实际的并发能力是有限制的。在保守情况下memcached的最大同时连接数为200,这和Linux线程能力有关系,这个数值是可以调整的。关于 libevent可以参考相关文档。 Memcached内存使用方式也和APC不同。APC是基于共享内存和MMAP的,memcachd有自己的内存分配算法和管理方式,它和共享内存没有关系,也没有共享内存的限制,通常情况下,每个memcached进程可以管理2GB的内存空间,如果需要更多的空间,可以增加进程数。

2. Memcached适合什么场合

在很多时候,memcached都被滥用了,这当然少不了对它的抱怨。我经常在论坛上看见有人发贴,类似于“如何提高效率”,回复是“用memcached”,至于怎么用,用在哪里,用来干什么一句没有。memcached不是万能的,它也不是适用在所有场合。

Memcached 是“分布式”的内存对象缓存系统,那么就是说,那些不需要“分布”的,不需要共享的,或者干脆规模小到只有一台服务器的应用, memcached不会带来任何好处,相反还会拖慢系统效率,因为网络连接同样需要资源,即使是UNIX本地连接也一样。在我之前的测试数据中显示, memcached本地读写速度要比直接PHP内存数组慢几十倍,而APC、共享内存方式都和直接数组差不多。可见,如果只是本地级缓存,使用 memcached是非常不划算的。

 Memcached在很多时候都是作为数据库前端cache使用的。因为它比数据库少了很多SQL解析、磁盘操作等开销,而且它是使用内存来管理数据的,所以它可以提供比直接读取数据库更好的性能,在大型系统中,访问同样的数据是很频繁的, memcached可以大大降低数据库压力,使系统执行效率提升。另外,memcached也经常作为服务器之间数据共享的储媒介,例如在SSO系统中保存系统单点登陆状态的数据就可以保存在memcached中,被多个应用共享。

需要注意的是,memcached使用内存管理数据,所以它是易失的,当服务器重启,或者memcached进程中止,数据便会丢失,所以 memcached不能用来持久保存数据。很多人的错误理解,memcached的性能非常好,好到了内存和硬盘的对比程度,其实memcached使用内存并不会得到成百上千的读写速度提高,它的实际瓶颈在于网络连接,它和使用磁盘的数据库系统相比,好处在于它本身非常“轻”,因为没有过多的开销和直接的读写方式,它可以轻松应付非常大的数据交换量,所以经常会出现两条千兆网络带宽都满负荷了,memcached进程本身并不占用多少CPU资源的情况。

通常的网页缓存方式有动态缓存和静态缓存等几种,在ASP.NET中已经可以实现对页面局部进行缓存,而使用memcached的缓存比 ASP.NET的局部缓存更加灵活,可以缓存任意的对象,不管是否在页面上输出。而memcached最大的优点是可以分布式的部署,这对于大规模应用来说也是必不可少的要求。

LiveJournal.com使用了memcached在前端进行缓存,取得了良好的效果,而像wikipedia,sourceforge等也采用了或即将采用memcached作为缓存工具。memcached可以大规模网站应用发挥巨大的作用。

2.1 memcached 的工作原理

首先 memcached 是以守护程序方式运行于一个或多个服务器中,随时接受客户端的连接操作,客户端可以由各种语言编写,目前已知的客户端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。PHP 等客户端在与 memcached 服务建立连接之后,接下来的事情就是存取对象了,每个被存取的对象都有一个唯一的标识符 key,存取操作均通过这个 key 进行,保存到 memcached 中的对象实际上是放置内存中的,并不是保存在 cache 文件中的,这也是为什么 memcached 能够如此高效快速的原因。注意,这些对象并不是持久的,服务停止之后,里边的数据就会丢失。

.2  memcached 安装

首先是下载 memcached 了,目前最新版本是 1.1.12,直接从官方网站即可下载到 memcached-1.1.12.tar.gz。除此之外,memcached 用到了 libevent,我下载的是 libevent-1.1a.tar.gz

接下来是分别将 libevent-1.1a.tar.gz 和 memcached-1.1.12.tar.gz 解开包、编译、安装:

# tar -xzf libevent-1.1a.tar.gz 
# cd libevent-1.1a 
# ./configure --prefix=/usr 
# make 
# make install 
# cd .. 
# tar -xzf memcached-1.1.12.tar.gz 
# cd memcached-1.1.12 
# ./configure --prefix=/usr 
# make 
# make install
安装完成之后,memcached 应该在 /usr/bin/memcached。

3.如何使用memcached-Server端?

在服务端运行:

# ./memcached -d -m 2048 -l 10.0.0.40 -p 11211  -u httpd
-d 以守护程序(daemon)方式运行 memcached;
-m 设置 memcached 可以使用的内存大小,单位为 M;

-l 设置监听的 IP&nb

sp;地址,如果是本机的话,通常可以不设置此参数;
-p 设置监听的端口,默认为 11211,所以也可以不设置此参数;
-u 指定用户,如果当前为 root 的话,需要使用此参数指定用户。

这将会启动一个占用2G内存的进程,并打开11211端口用于接收请求。由于32位系统只能处理4G内存的寻址,所以在大于4G内存使用PAE的32位服务器上可以运行2-3个进程,并在不同端口进行监听。

4. 如何使用memcached-Client端?

在应用端包含一个用于描述Client的Class后,就可以直接使用,非常简单。

PHP Example:
$options["debug"] = false;

$memc = new MemCachedClient($options);

$myarr = array("one","two", 3);

$memc->set("key_one", $myarr);
$options["servers"] = array("192.168.1.41:11211", "192.168.1.42:11212");

$val = $memc->get("key_one");

print $val[0]."\n"; // prints 'one‘

print $val[1]."\n"; // prints 'two‘

print $val[2]."\n"; // prints 3
5.为什么不使用数据库做这些?

暂且不考虑使用什么样的数据库(MS-SQL, Oracle, Postgres, MysQL-InnoDB, etc..), 实现事务(ACID,Atomicity, Consistency, Isolation, and Durability )需要大量开销,特别当使用到硬盘的时候,这就意味着查询可能会阻塞。当使用不包含事务的数据库(例如Mysql-MyISAM),上面的开销不存在,但读线程又可能会被写线程阻塞。Memcached从不阻塞,速度非常快。

6.为什么不使用共享内存?

最初的缓存做法是在线程内对对象进行缓存,但这样进程间就无法共享缓存,命中率非常低,导致缓存效率极低。后来出现了共享内存的缓存,多个进程或者线程共享同一块缓存,但毕竟还是只能局限在一台机器上,多台机器做相同的缓存同样是一种资源的浪费,而且命中率也比较低。

Memcached Server和Clients共同工作,实现跨服务器分布式的全局的缓存。并且可以与Web Server共同工作,Web Server对CPU要求高,对内存要求低,Memcached Server对CPU要求低,对内存要求高,所以可以搭配使用。

7.Mysql 4.x的缓存怎么样?

Mysql查询缓存不是很理想,因为以下几点:

当指定的表发生更新后,查询缓存会被清空。在一个大负载的系统上这样的事情发生的非常频繁,导致查询缓存效率非常低,有的情况下甚至还不如不开,因为它对cache的管理还是会有开销。

在32位机器上,Mysql对内存的操作还是被限制在4G以内,但memcached可以分布开,内存规模理论上不受限制。

Mysql上的是查询缓存,而不是对象缓存,如果在查询后还需要大量其它操作,查询缓存就帮不上忙了。

如果要缓存的数据不大,并且查询的不是非常频繁,这样的情况下可以用Mysql 查询缓存,不然的话memcached更好。

8.数据库同步怎么样?

这里的数据库同步是指的类似Mysql Master-Slave模式的靠日志同步实现数据库同步的机制。

你可以分布读操作,但无法分布写操作,但写操作的同步需要消耗大量的资源,而且这个开销是随着slave服务器的增长而不断增长的。

下一步是要对数据库进行水平切分,从而让不同的数据分布到不同的数据库服务器组上,从而实现分布的读写,这需要在应用中实现根据不同的数据连接不同的数据库。

当这一模式工作后(我们也推荐这样做),更多的数据库导致更多的让人头疼的硬件错误。

Memcached可以有效的降低对数据库的访问,让数据库用主要的精力来做不频繁的写操作,而这是数据库自己控制的,很少会自己阻塞 自己。

9.Memcached快吗?

非常快,它使用libevent,可以应付任意数量打开的连接(使用epoll,而非poll),使用非阻塞网络IO,分布式散列对象到不同的服务器,查询复杂度是O(1)。

10.memcached的相关抽象类

public abstract class BaseManager<T extends BaseObject> implements Manager<T> 
 
     private MemcachedClient memcached; 
 
     protected int default_cache_time_second = 3600;  
 
      public void setMemcached(MemcachedClient memcached) 
 
         this.memcached = memcached; 
 
      } 
 
      public MemcachedClient getMemcached() 
 
         return memcached; 
 
     } 
 
      public void setDefault_cache_time_second(int default_cache_time_second) 
 
         this.default_cache_time_second = default_cache_time_second; 
 
    } public Object getCacheValueFromMemcached(String key) {

        return getCacheValueFromMemcached(key, null);

    }

    public Object getCacheValueFromMemcached(String key, Object obj) {

        Object new_obj = null;

        try {

            new_obj = memcached.get(key);

            } catch (Exception e) {

            if (logger.isWarnEnabled()) {

logger.warn("Failed to get from MeMCache", e); 
 
             } 
 
         } 
 
         return (new_obj != null) ? new_obj : obj; 
       } 
 
       public void setCacheValueToMemcached(String cacheKey, int time_to_live, Serializable obj) 
 
          if (null != memcached.get(cacheKey)) 
 
             memcached.replace(cacheKey, time_to_live, obj); 
 
          } else 
 
             memcached.add(cacheKey, time_to_live, obj); 
 
         } 
 
       }  
     }
11.service调用memcache的一个例子:

public List<Integer> getLastModifyAlbumMember(int limit) {

        String cacheKey = this.createCachekey(new

       Object[]{"schedule","lastmodifyalbumember",limit});

        List list = (List) this.getCacheValueFromMemcached(cacheKey);

        List<Integer> list1 = new ArrayList<Integer>();

        if(null!=list&&list.size()>0){

            for (Object aList : list) {

                list1.add(Integer.parseInt(String.valueOf(aList)));

            }

        }

        return list1;

    }
参考资料:

http://www.danga.com/

http://www.linuxjournal.com/article/7451



-----------------------------------------------------
Silence, the way to avoid many problems;
Smile, the way to solve many problems;

posted on 2012-04-19 17:36 Chan Chen 阅读(4035) 评论(1)  编辑  收藏 所属分类: Memcached

评论

# re: Memcached实现内存缓存(一) 2014-09-23 17:47 周小帅

太牛了 ,大神 膜拜啊 收了我把  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航: