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:
incr与decr命令用于对已存在的key(键)的数字值进行自增或自减操作,如果key不存在返回 NOT_FOUND,如果键的值不为数字,则返回 CLIENT_ERROR,其他错误返回 ERROR。
incr key increment_value
统计命令
stats:返回统计信息,进程号,版本号等。
statsitemss ;显示各个slab中item的数目和存储时长(最后一次访问距离现在的秒数)。
显示所有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,用于在制定的时间后执行清理缓存操作(该操作只会把key的value值删除,而不会删除key)
这里对内存分配不太明白,所以查了一下相关的知识:
MemCached的内存存储机制:
Slab Allocator的基本原理是按照预先规定的大小,将分配的内存以page为单位,默认情况下一个page是1M,可以通过-I参数在启动时指定,分割成各种尺寸的块(chunk), 并把尺寸相同的块分成组(chunk的集合),如果需要申请内存时,memcached会划分出一个新的page并分配给需要的slab区域。page一旦被分配在重启前不会被回收或者重新分配,以解决内存碎片问题。
每个slab只存储大于其上一个slab的size并小于或者等于自己最大size的数据。例如:100字节大小的字符串会被存到slab2(88-112)中,每个slab负责的空间是不等的,memcached默认情况下下一个slab的最大值为前一个的1.25倍,这个可以通过修改-f参数来修改增长比例。。chunk是memcached实际存放缓存数据的地方,这个大小就是管理它的slab的最大存放大小。每个slab中的chunk大小是一样的,如上图所示slab1的chunk大小是88字节,slab2是112字节。由于分配的是特定长度的内存,因此无法有效利用分配的内存。例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了。这里需要注意的是chunk中不仅仅存放缓存对象的value,而且保存了缓存对象的key,expire time,flag等详细信息。所以当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());
}
}
}