memcatched集群方式整合Spring          返回主页

    Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。
    它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。
    Memcached基于一个存储键/值对的hashmap。
    其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。
                                --百度百科

本文第一部分对memcatched的集群方式进行实战讲解,并使用Spring进行整合。第二部分阐述memcatched与redis的联系和区别。

application-memcatched.xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--memcatched连接池-->
        <bean id="memcachedPool" class="com.danga.MemCached.SockIOPool"
            factory-method="getInstance" init-method="initialize">
            <constructor-arg>
                <value>neeaMemcachedPool</value>
            </constructor-arg>
            <property name="servers">
            <!-- 分布式,添加多台 -->
                <list>
                    <value>127.0.0.1:11211</value>
                    <value>127.0.0.1:8888</value> 
                </list>
            </property>
            <!--初始连接数-->
            <property name="initConn">
                <value>20</value>
            </property>
            <!--最小连接数-->
            <property name="minConn">
                <value>10</value>
            </property>
            <!--最大连接数-->
            <property name="maxConn">
                <value>50</value>
            </property>
            <property name="nagle">
                <value>false</value>
            </property>
            <property name="socketTO">
                <value>3000</value>
            </property>
        </bean>
        <!-memcatchedClient实例,作为构造函数参数注入连接池中-->
        <bean id="memcachedClient" class="com.danga.MemCached.MemCachedClient">
            <constructor-arg>
                <value>neeaMemcachedPool</value>
            </constructor-arg>
        </bean>
    </beans>

这段代码主要进行了连接池的设置,连接池构造方法接受一个memcachedClient作为参数。

TestBean.java测试类,实现序列化接口,省略get、set方法

    public class TestBean implements Serializable {

        private static final long serialVersionUID = -3584400733835277542L;

        private String name;
        private int id;
        ...
    }

SpringMemcatchedTest.java Junit测试类

    public class SpringMemcatchedTest {
        //memcatchedClient声明
        private MemCachedClient catchedClient;

        //加载初始化参数
        @Before
        public void init() {
            ApplicationContext context = new ClassPathXmlApplicationContext("application-memcatched.xml");
            catchedClient = (MemCachedClient) context.getBean("memcachedClient");
        }

        //测试方法
        @Test
        public void test() {
            TestBean testBean = new TestBean();
            testBean.setId(2333);
            testBean.setName("memcatched");
            TestBean testBean3 = new TestBean();
            testBean3.setId(3333);
            testBean3.setName("testBean3");

            TestBean Bean = (TestBean) catchedClient.get("test1");
            TestBean Bean3 = (TestBean) catchedClient.get("test2");
            System.out.println("memcatched取值" + Bean.getId() + " " + Bean.getName());
            System.out.println("memcatched取值" + Bean3.getId() + " " + Bean3.getName());
        }

    }

这段代码构造两个TestBean实体,并放入memcatched中,memcatched通过自带的策略将两个实体放入到不同的主机中。余数计算分散法是memcached标准的memcached分布式方法。

运行测试

  1. 我在本地开启两台memcatched实例,通过如下命令 memcatched.exe -p 11211 memcatched.exe -p 8888
  2. 运行测试类
  3. 显示运行结果如下 ...省略日志... memcatched取值2333 memcatched memcatched取值3333 testBean3

redis和memcatched区别联系

Memcached和Redis关键技术对比

作为内存数据缓冲系统,Memcached和Redis均具有很高的性能,但是两者在关键实现技术上具有很大差异,这种差异决定了两者具有不同的特点和不同的适用条件。下面我们会对两者的关键技术进行一些对比,以此来揭示两者的差异。

3.1 Memcached和Redis的内存管理机制对比 对于像Redis和Memcached这种基于内存的数据库系统来说,内存管理的效率高低是影响系统性能的关键因素。传统C语言中的malloc/free函数是最常用的分配和释放内存的方法,但是这种方法存在着很大的缺陷:首先,对于开发人员来说不匹配的malloc和free容易造成内存泄露;其次,频繁调用会造成大量内存碎片无法回收重新利用,降低内存利用率;最后,作为系统调用,其系统开销远远大于一般函数调用。所以,为了提高内存的管理效率,高效的内存管理方案都不会直接使用malloc/free调用。Redis和Memcached均使用了自身设计的内存管理机制,但是实现方法存在很大的差异,下面将会对两者的内存管理机制分别进行介绍。

3.1.1 Memcached的内存管理机制 Memcached默认使用Slab Allocation机制管理内存,其主要思想是按照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题。Slab Allocation机制只为存储外部数据而设计,也就是说所有的key-value数据都存储在Slab Allocation系统里,而Memcached的其它内存请求则通过普通的malloc/free来申请,因为这些请求的数量和频率决定了它们不会对整个系统的性能造成影响 Slab Allocation的原理相当简单。 如图3所示,它首先从操作系统申请一大块内存,并将其分割成各种尺寸的块Chunk,并把尺寸相同的块分成组Slab Class。其中,Chunk就是用来存储key-value数据的最小单位。每个Slab Class的大小,可以在Memcached启动的时候通过制定Growth Factor来控制。假定Figure 1中Growth Factor的取值为1.25,所以如果第一组Chunk的大小为88个字节,第二组Chunk的大小就为112个字节,依此类推。

当Memcached接收到客户端发送过来的数据时首先会根据收到数据的大小选择一个最合适的Slab Class,然后通过查询Memcached保存着的该Slab Class内空闲Chunk的列表就可以找到一个可用于存储数据的Chunk。当一条数据库过期或者丢弃时,该记录所占用的Chunk就可以回收,重新添加到空闲列表中。从以上过程我们可以看出Memcached的内存管理制效率高,而且不会造成内存碎片,但是它最大的缺点就是会导致空间浪费。因为每个Chunk都分配了特定长度的内存空间,所以变长数据无法充分利用这些空间。如图 4所示,将100个字节的数据缓存到128个字节的Chunk中,剩余的28个字节就浪费掉了。

3.1.2 Redis的内存管理机制 Redis的内存管理主要通过源码中zmalloc.h和zmalloc.c两个文件来实现的。Redis为了方便内存的管理,在分配一块内存之后,会将这块内存的大小存入内存块的头部。如图 5所示,realptr是redis调用malloc后返回的指针。redis将内存块的大小size存入头部,size所占据的内存大小是已知的,为sizet类型的长度,然后返回retptr。当需要释放内存的时候,retptr被传给内存管理程序。通过retptr,程序可以很容易的算出realptr的值,然后将real_ptr传给free释放内存。

Redis通过定义一个数组来记录所有的内存分配情况,这个数组的长度为ZMALLOCMAXALLOCSTAT。数组的每一个元素代表当前程序所分配的内存块的个数,且内存块的大小为该元素的下标。在源码中,这个数组为zmallocallocations。zmallocallocations[16]代表已经分配的长度为16bytes的内存块的个数。zmalloc.c中有一个静态变量usedmemory用来记录当前分配的内存总大小。所以,总的来看,Redis采用的是包装的mallc/free,相较于Memcached的内存管理方法来说,要简单很多。

3.2 Redis和Memcached的集群实现机制对比

Memcached是全内存的数据缓冲系统,Redis虽然支持数据的持久化,但是全内存毕竟才是其高性能的本质。作为基于内存的存储系统来说,机器物理内存的大小就是系统能够容纳的最大数据量。如果需要处理的数据量超过了单台机器的物理内存大小,就需要构建分布式集群来扩展存储能力。

3.2.1 Memcached的分布式存储 Memcached本身并不支持分布式,因此只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储。图6 给出了Memcached的分布式存储实现架构。当客户端向Memcached集群发送数据之前,首先会通过内置的分布式算法计算出该条数据的目标节点,然后数据会直接发送到该节点上存储。但客户端查询数据时,同样要计算出查询数据所在的节点,然后直接向该节点发送查询请求以获取数据。

3.2.2 Redis的分布式存储 相较于Memcached只能采用客户端实现分布式存储,Redis更偏向于在服务器端构建分布式存储。尽管Redis当前已经发布的稳定版本还没有添加分布式存储功能,但Redis开发版中已经具备了Redis Cluster的基本功能。预计在2.6版本之后,Redis就会发布完全支持分布式的稳定版本,时间不晚于2012年底。下面我们会根据开发版中的实现,简单介绍一下Redis Cluster的核心思想。 Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本,它没有中心节点,具有线性可伸缩的功能。图7给出Redis Cluster的分布式存储架构,其中节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。在数据的放置策略上,Redis Cluster将整个key的数值域分成4096个哈希槽,每个节点上可以存储一个或多个哈希槽,也就是说当前Redis Cluster支持的最大节点数就是4096。Redis Cluster使用的分布式算法也很简单:crc16( key ) % HASHSLOTSNUMBER。

为了保证单点故障下的数据可用性,Redis Cluster引入了Master节点和Slave节点。如图4所示,在Redis Cluster中,每个Master节点都会有对应的两个用于冗余的Slave节点。这样在整个集群中,任意两个节点的宕机都不会导致数据的不可用。当Master节点退出后,集群会自动选择一个Slave节点成为新的Master节点。

3.3 Redis和Memcached整体对比 Redis的作者Salvatore Sanfilippo曾经对这两种基于内存的数据存储系统进行过比较,总体来看还是比较客观的,现总结如下:

1)性能对比:由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。

2)内存使用效率对比:使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。

3)Redis支持服务器端的数据操作:Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。