Memcached基本使用、内存存储机制、实际应用

 

 

Memcache是一种key-value的高速缓存系统。

其工作流程如下:先检查客户端的请求数据是否在memcached中,如有,直接把请求数据返回,不再对数据库进行任何操作;如果请求的数据不在memcached中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到memcached中(memcached客户端不负责,需要程序明确实现);每次更新数据库的同时更新memcached中的数据,保证一致性;当分配给memcached内存空间用完之后,会使用LRU(Least Recently Used,最近最少使用)策略加上到期失效策略,失效数据首先被替换,然后再替换掉最近未使用的数据。

使用时可以用Telnet进行连接,cmd如果显示telnet无此命令,需要在控制面板中开启,具体请百度。

连接Memcached:

telnet 127.0.0.1 11211//服务ip和端口号

其命令主要分为三类:

存储命令

set

setkey flags exptime bytes[noreply]

value

key:键名

flags::可以包括键值对的整形参数,客户机使用他存储关于键值对的额外信息

exptime:缓存中保存键值对的时间(秒,0为永远)(其实这里我有一个疑问,memcache是存储在内存中的,永远代表什么?

因为一旦系统重启,所有内存中的存储的都不存在了,后来查了一些资料,即使是memcache设置为永久,最长保存时间

也是30天)

bytes:缓存中存储的字节数

noreply(:非必需)告知服务器不需要返回数据

value:存储的值(始终位于第二行)

add:

add key flags exptime bytes[noreply]
value

replace:

replace key flags exptime bytes [noreply]
value
append:

append key flags exptime bytes [noreply]
value

prepend:

prepend key flags exptime bytes[noreply]
value

cas

cas key flags exptime bytes unique_cas_token[noreply]
value


unique_cas_token:通过gets命令获取的一个位的64位值

步骤:

要在Memcached上使用CAS命令,你需要从Memcached服务商通过gets命令获取令牌(token)。

gets命令的功能类似于基本的get命令。两个命令之间的差异在于,gets返回的信息稍微多一些:64位的整型值非常像名称/值对的"版本"标识符。

实例步骤如下:

  • 如果没有设置唯一令牌,则CAS命令执行错误。
  • 如果键key不存在,执行失败。
  • 添加键值对。
  • 通过gets命令获取唯一令牌。
  • 使用cas命令更新数据
  • 使用get命令查看数据是否更新

查找命令

get

getkey

多个key,使用空格隔开

getkey1 key2 key3

getss: 获取带有 CAS 令牌存 的 value(数据值) ,如果 key 不存在,则返回空

gets key

多个key,使用空格隔开

gets key1 key2 key3

delete:​​​​​

deletekey[noreply]

 

incr/decr:

incrdecr命令用于对已存在的key()的数字值进行自增或自减操作,如果key不存在返回 NOT_FOUND,如果键的值不为数字,则返回 CLIENT_ERROR,其他错误返回 ERROR

incr key increment_value

 

统计命令

stats:返回统计信息,进程号,版本号等。

statsitemss ;显示各个slabitem的数目和存储时长(最后一次访问距离现在的秒数)

显示所有key:

stats items:获得items的id xx

statscachedumpxx 0 (0表示全部,100表示前100),可以用echo..|nc..导出到文件//To Do

//echo这个命令我看到过,但是自己没有复现出来,还需要进一步调研一下

Stats slabs:用于显示各个slab的信息,包括chunk的大小、数目、使用情况等

statssizes:显示所有item的大小和个数。该信息返回两列,第一列是item的大小,第二列是item的个数。

Flush_all:用于清理缓存中的所有key=>value(=>)对。该命令提供了一个可选参数time,用于在制定的时间后执行清理缓存操作(该操作只会把keyvalue值删除,而不会删除key

这里对内存分配不太明白,所以查了一下相关的知识:

MemCached的内存存储机制:

Slab Allocator的基本原理是按照预先规定的大小,将分配的内存以page为单位,默认情况下一个page1M,可以通过-I参数在启动时指定,分割成各种尺寸的块(chunk), 并把尺寸相同的块分成组(chunk的集合),如果需要申请内存时,memcached会划分出一个新的page并分配给需要的slab区域。page一旦被分配在重启前不会被回收或者重新分配,以解决内存碎片问题。

每个slab只存储大于其上一个slabsize并小于或者等于自己最大size的数据。例如:100字节大小的字符串会被存到slab288-112)中,每个slab负责的空间是不等的,memcached默认情况下下一个slab的最大值为前一个的1.25倍,这个可以通过修改-f参数来修改增长比例。chunkmemcached实际存放缓存数据的地方,这个大小就是管理它的slab的最大存放大小。每个slab中的chunk大小是一样的,如上图所示slab1chunk大小是88字节,slab2112字节。由于分配的是特定长度的内存,因此无法有效利用分配的内存。例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了。这里需要注意的是chunk中不仅仅存放缓存对象的value,而且保存了缓存对象的keyexpire timeflag详细信息。所以当set 1字节的item,需要远远大于1字节的空间存放。

slab的内存分配具体过程如下:

      Memcached在启动时通过-m参数指定最大使用内存,但是这个不会一启动就占用完,而是逐步分配给各slab的。如果一个新的数据要被存放,首先选择一个合适的slab,然后查看该slab是否还有空闲的chunk,如果有则直接存放进去;如果没有则要进行申请,slab申请内存时以page为单位,无论大小为多少,都会有1M大小的page被分配给该slab(该page不会被回收或者重新分配,永远都属于该slab)。申请到page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk的数组,再从这个chunk数组中选择一个用于存储数据。若没有空闲的page的时候,则会对改slab进行LRU,而不是对整个memcache进行LRU

非常感谢这位大大写的,很清楚:reference

最后就是实际的代码实现了,基本都注释清楚了,挺好懂的。这里需要一个包,可以搜一下,搜不到留言给我,上传太麻烦了~.~

package memcached;

import net.spy.memcached.CASResponse;
import net.spy.memcached.CASValue;
import net.spy.memcached.MemcachedClient;

import java.net.InetSocketAddress;
import java.util.concurrent.Future;

public class MemCachedjava {
    public static void main(String[] args){
        try{
            //根据Ip和端口号建立连接
            MemcachedClient mcc = new MemcachedClient(new InetSocketAddress("127.0.0.1",11211));
            System.out.println("Connection is establishing!");
            //增加数据
            Future fo = mcc.set("java",900,"free education");
            //获取数据
            System.out.println("stats :"+ fo.get());
            //添加数据,与Add不同,如果key在缓存中存在,则会执行,但是不会存储,如果需要,可以使用set或replace
            if(mcc.get("c++")==null) {
                fo = mcc.add("c++", 900, "fee");
                System.out.println("stats :" + fo.get());
            }


            mcc.set("css",900,"ac");
            System.out.println("stats :"+ fo.get());
            //后部添加数据
             mcc.append("css","hello");
            //前部添加数据
             mcc.prepend("css","pre");
            //获取服务器设定的cas值。check and set 用于检测在没有被其他客户端修改的情况下修改数据。
            CASValue casValue = mcc.gets("css");
            //cas修改之前的存储的数据
            System.out.println("casValue="+casValue.getValue());

            CASResponse casResponse = mcc.cas("css",casValue.getCas(),"casUpdate");
            //cas执行是否成功
            System.out.println("casresponse:"+casResponse);

            mcc.set("number",900,12);
            //测试数据增加
            mcc.incr("number",4);

            System.out.println(mcc.get("number"));
            //测试数据减小
            mcc.decr("number",7);
            System.out.println(mcc.get("number"));
            //测试缓存数据删除
            if (mcc.get("css")!=null){
                mcc.delete("css");
            }
            System.out.println(mcc.get("css")+"---"+mcc.get("number"));
            //清除缓存,可以在flush中添加一个Integer来延迟清除缓存的时间
            mcc.flush();
            System.out.println(mcc.get("number"));
            mcc.shutdown();
        }
        catch (Exception e){
            System.err.println(e.getMessage());
        }
    }
}


版权声明:本文为HepBen原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。