Memcached 缓存

文章结构:

1. Memcached 简介

2.Memcached 常用命令

3.Memcached 原理

4.Memcached 客户端操作Memcached 

Memcached 简介

memcached 是以 LiveJournal 旗下 Danga Interactive 公司的 Brad Fitzpatric 为首开发的一款高性能的分布式内存缓存服务器。一般应用的数据都存放在关系数据库中,如果数据量很大,访问很频繁,就出现数据库的负担过重,影响速度慢等问题,所以通过缓存,可以减少数据库的访问次数,提高响应的速度和用户的体验度。使用缓存后,结构如下所示(图来源于网络):

cc18beb83873d74d4250b9a6cf9d405f939.jpg

Memcached 具有如下的特点:

1.协议简单,是基于简单的文本行的协议

2.基于 libevent 的事件处理,

3.内存存储方式

4.各个 Memcached 实例不相互通信

libevent 是个程序库,它将 Linux 的 epoll、BSD 类操作系统的 kqueue 等事件处理功能封装成统一的接口。即使对服务器的连接数增加,也能发挥 O(1)的性能。memcached 使用这个 libevent 库,因此能在 Linux、BSD、Solaris 等操作系统上发挥其高性能

Memcached 常用命令

要想学习一项技术或知识,动手是必不可少的,接下来看看 Memcached 的常用命令的一个用法,可以把常用的命令分为三类,存储命令,查找命令和统计命令;Memcached 的安装和配置不再赘述,下面直接看看命令的用法:

首先使用 telnet 连接 Memcached : telnet 127.0.0.1 11211

5bc78cc9820d47870463e92d7f81737c736.jpg

存储命令

Memcached 存储命令

命令命令格式参数例子
set 命令

将value存储在指定的key

中,如果key存在,则更新旧值

set key flags exptime bytes   [noreply]      
value 

key : 键,用于查找对于的值

flags : 整型参数,客户端使用它来存储

关于键值对的额外信息

exptime : 过期时间,以秒为单位,0 表示永不过期

bytes : 存储的字节数

noreply : 可选,告知服务器不需要返回数据

value : key 对应的值

fedad0bf56a83c1044bc810ea5565058d7f.jpg

 add 命令

将value存储在指定的key

中,如果key存在,

不会更新旧值

add ... [同set]同 set 命令

key不存在:

a1327693962ade5bbd9caed86ecdf6a60a5.jpg

key 已存在:

8cd185e030103a2c33d561a4119ddea3ac3.jpg

replace命令

替换旧值,如果key不存在

,则失败

replace ... [同set]同 set 命令

key存在

c0d66bd93ccb01d2e8cdbdb279ff3a09ca0.jpg

key不存在

2c54fa22b499ede8044178b2e21dd9b8e46.jpg

append 命令

向已存在的key后面追加value

append ...[同set]同 set 命令

784238597bf607949437d6678fc67de7f02.jpg

注意:一个中文三个字节

prepend命令

在已存在的key前面追加value

prepend...[同set]同 set 命令74cbc43b16d6677e2280874ebc954874744.jpg

CAS 命令

线程安全的设置key 的值

cas key flags exptime bytes 

unique_cas_token  [noreply]      
value 

比其他命令多了个 unique_cas_token 参数,

unique_cas_token  :  通过 gets 命令

获取的key-value的版本号,为64位

其他参数同 set 命令

6cb5456a59041ec76795a099c32b9db999d.jpg

 

查找命令

Memcached 查找命令

命令命令格式参数例子

get 命令

查找对应key的value,

如果key不存在,返回空

get key

get key1 key2 key3...

key : 要查找value对应的key

查找单个key

432cdea2dbc770f24f90c2905b7dd1962ba.jpg

查找多个key

a45d940ddeebd3e7ca3283b8276cdac5ce1.jpg

gets 命令

获取带有版本号的value,

如果key不存在,返回空

gets key

get key1 key2 key3...

key : 要查找value对应的key

获取单个key, 417 为版本号

a1e6726d80099072ceb974b3ba2ec50c215.jpg

获取多个key

96b7b071fe98c89a62b4288c8213b8cb012.jpg

delete 命令

删除对应的key

delete key [norepyl]

key : 要删除的key

norepyl : 可选,该参数告知服务

器不需要返回数据

717c1270af40492412cbb4d3d784bc52344.jpg

 incr 与 decr 命令

用于对已存在的 key 的数字值

进行自增或自减操作

incr key increment_value

decr key decrement_value

key : 要自增值对应的key

increment_value:自增的值

decrement_value:自减的值

7f34d8eb4277bf0bec32d371844512ce23d.jpg

统计命令

Memcached 统计命令

命令命令格式参数例子

stats 命令

用于返回统计信息

例如 PID(进程号)、版本号、连接数等

stats59c45559f6300d2f30381b3b47c920b9ac2.jpg

stats items 命令

用于显示各个 slab 中 item 的数目、

和存储时长(最后一次访问距离现在的秒数)

stats itemsc469db0be6eafb9337511d12a867a7a9bdb.jpg

stats slabs 命令

用于显示各个slab的信息,

包括chunk的大小、数目、使用情况

stats slabsbca844b9684d528562b3c747c3d3681b1b8.jpg

stats sizes 命令

用于显示所有item的大小和个数

 

stats sizes

9d80c189d0fd15c24e5a9af16baf951a076.jpg

第一列为 item 大小,第二列为 item 个数

flush_all 命令

清理缓存中的所有key-value

flush_all [time] [noreply]

time:在指定的时间后执行清理操作

norepyl : 可选,该参数告知服务

器不需要返回数据

aad098e76734e470842ec92454c75ea5c63.jpg

之前存储的key不再存在

获取 Memcached 中的列表数据

1. 使用 stats items 获取所有的item

2. 使用 stats cachedump id 0 获取, 0表示全部列出

18fc8a0bac1d7e6dbe4cfb8a05cdb1650ee.jpg

Memcached 原理

memcached 的内存存储机制

memcached 采用了名为 Slab Allocator 的机制来分配、管理内存,可以有效的减少内存的碎片问题。

Slab Allocator的原理:按照预先规定的大小,将分配的内存分割成各种尺寸的块(chunk),并把尺寸相同的块分成组(chunk 的集合),而且,slab allocator 还有重复使用已分配的内存的目的。也就是说,分配到的内存不会释放,而是重复利用;
如下图所示(图片来源于网络)

a0cb79f35c914790fd342d56807cebabf6b.jpg

Slab Allocator 相关概念:

Page : 分配给 Slab 的内存空间,默认为 1M,分配给 Slab 之后,根据 Slab 的大小切分为 chunk

chunk : 用于缓存记录的内存空间

Slab Class : 特定大小的 chunk 组

可以通过 stats slabs 命令来查看:

可以看到,每个chunk的大小为96byte,共有 10922 个chunk,共有1个page...............

5b63b6a6230b71e8ff20d8b0d0a02ce292d.jpg

Slab Allocator的缺点:无法有效的利用内存,因为内存是按照特定长度进行分配的,如果把一个 100 byte的数据存放到 128byte的 chunk中,则会有 28 byte的空间浪费。如果预先知道客户端发送的数据的公用大小,或者仅缓存大小相同的数据的情况下,只要使用适合数据大小的组的列表,就可以减少浪费,如可以调节 slab class的大小的差别

memcached 的删除

memcached 不会释放已分配的内存,如果缓存的记录失效的话,客户端是不能看到该值,但是数据还在,存储空间可以重复使用;在memcached内部不会去监视记录是否过期,只是在get的时候,查看记录的时间戳是否过期,好处就是,不会在过期监视上面浪费时间。

当向缓存中添加记录的时候,会优先那些已经过期记录的空间,但是如果添加时,空间还是不够的话,则会根据LRU原则删除相关的记录,把这部分空间分配给新记录。

LRU:Least Recently Used,即最近最少使用原则,也就是最长时间未被使用。

memcached 分布式

memcached 虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能,完全是由客户端进行控制;

客户端的分布式算法主要有两种

1. 求余,即根据 key的hash值,对memcached 服务器节点数进行求余即可得到该key应该存放在哪个节点上,缺点就是,如果服务器增加或减少,则缓存的命中率会大大降低。

2. 一致性哈希算法,后面在详细介绍。


Memcached 客户端操作Memcached 

使用 XMemcached 客户端来操作 memcached ,关于 XMemcached 的相关信息可以参考官网文档:XMemcached

首先导入相关jar包:

<dependency>
      <groupId>com.googlecode.xmemcached</groupId>
      <artifactId>xmemcached</artifactId>
      <version>2.0.0</version>
</dependency>

之后创建客户端实例对象:

    MemcachedClient memcache;

    @Before
    public void init() {
        try {
            memcache = new XMemcachedClientBuilder(AddrUtil.getAddresses("127.0.0.1:11211")).build();
        } catch (IOException e) {
            System.out.println("初始化失败...");
            e.printStackTrace();
        }
    }

    @After
    public void close() throws IOException {
        memcache.shutdown();
    }

set 命令相关方法:

    @Test
    public void testSet() throws InterruptedException, MemcachedException, TimeoutException {
        // key, 过期时间:默认为30天, 对应的值
        memcache.set("bj", 0, "北京");
        System.out.println("bj = " + memcache.get("bj"));
        // key, 过期时间, 对应的值, 操作时超时时间:默认为5s, 超过则抛出 TimeoutException
        memcache.set("cd",0, "成都", 2);
        System.out.println("cd = " + memcache.get("cd"));
    }

bj = 北京
cd = 成都

add 命令相关方法

    @Test
    public void testAdd() throws InterruptedException, MemcachedException, TimeoutException {
        // 如果 key 已经存在,则不会更新对应的值
        memcache.add("bj", 0, "北京2");
        System.out.println("bj = " + memcache.get("bj"));
        memcache.add("gy", 0, "贵阳");
        System.out.println("gy = " + memcache.get("gy"));
    }

bj = 北京
gy = 贵阳

replace 命令相关方法

    @Test
    public void testReplace() throws InterruptedException, MemcachedException, TimeoutException {
        memcache.replace("cd", 0, "成都2");
        System.out.println("cd = " + memcache.get("cd"));
        memcache.replace("no", 0, "noKey");
        System.out.println("no = " + memcache.get("no"));
    }

cd = 成都2
no = null

append 命令相关方法:

    @Test
    public void testAppend() throws InterruptedException, MemcachedException, TimeoutException {
        memcache.set("sc", 0, "四川");
        System.out.println("sc = " + memcache.get("sc"));
        memcache.append("sc", "成都");
        System.out.println("sc = " + memcache.get("sc"));
        memcache.append("sc", "高新");
        System.out.println("sc = " + memcache.get("sc"));
        // 不存在,则为null
        memcache.append("a", "A");
        System.out.println("a = " + memcache.get("a"));
    }

sc = 四川
sc = 四川成都
sc = 四川成都高新
a = null

prepend 命令相关方法

    @Test
    public void testPrepend() throws InterruptedException, MemcachedException, TimeoutException {
        memcache.set("gy", 0, "贵阳");
        System.out.println("gy = " + memcache.get("gy"));
        memcache.prepend("gy", "中国");
        System.out.println("gy = " + memcache.get("gy"));
    }

gy = 贵阳
gy = 中国贵阳

CAS 命令相关方法

    @Test
    public void testCas() throws InterruptedException, MemcachedException, TimeoutException {
        memcache.set("sh", 0, "上海");
        long cas = memcache.gets("sh").getCas();
        memcache.cas("sh",  0, "上海摊", cas);
        System.out.println("sh = " + memcache.get("sh") + ", cas = " + cas);

        memcache.cas("sh", 0, new CASOperation<String>() {
            @Override
            public int getMaxTries() {
                return 1; // 重试次数
            }
            @Override
            public String getNewValue(long currentCAS, String currentValue) {
                return "上海摊2";
            }
        });
        System.out.println("sh = " + memcache.get("sh"));
    }

sh = 上海摊, cas = 437
sh = 上海摊2

get 命令相关方法

    @Test
    public void testGet() throws InterruptedException, MemcachedException, TimeoutException {
        String val = memcache.get("sh");
        System.out.println(val);

        List<String> keys = Arrays.asList("sh", "bj", "sc");
        Map<String, String> valsMap = memcache.get(keys);
        for (Map.Entry<String, String> entry : valsMap.entrySet()) {
            System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
        }

        GetsResponse getsResponse = memcache.gets("bj");
        long cas = getsResponse.getCas();
        String val2 = (String) getsResponse.getValue();
        System.out.println("cas = " + cas + ", Val = " + val2);
    }

上海摊2
key = bj, value = 北京
key = sc, value = 四川成都高新
key = sh, value = 上海摊2
cas = 428, Val = 北京

delete命令相关方法

    @Test
    public void testDelete() throws InterruptedException, MemcachedException, TimeoutException {
        memcache.set("t", 0, "tttt");
        String t = memcache.get("t");
        System.out.println("t = " + t);

        memcache.delete("t");
        t = memcache.get("t");
        System.out.println(t);
    }

t = tttt
null

incr, decr 命令相关方法

    @Test
    public void testIncrDecr() throws InterruptedException, MemcachedException, TimeoutException {

        memcache.set("i", 0, "100");
        long ret = memcache.incr("i", 5);
        System.out.println(ret);

        ret = memcache.decr("i", 10);
        System.out.println(ret);
    }

105
95

XMemcached 的分布式

XMemcached支持客户端的分布策略,默认分布的策略是按照key的哈希值模以连接数得到的余数,对应的连接就是将要存储的节点。如果使用默认的分布策略,不需要做任何配置或者编程。

如要是使用一致性哈希策略,则需要进行以下设置

        MemcachedClientBuilder builder = new XMemcachedClientBuilder(...)
        builder.setSessionLocator(new KetamaMemcachedSessionLocator());
        MemcachedClient client=builder.build();

接下来看下两种策略的源码实现:

默认求余策略:

public class XMemcachedClientBuilder implements MemcachedClientBuilder {

	private MemcachedSessionLocator sessionLocator = new ArrayMemcachedSessionLocator();

    .........................
}
public class ArrayMemcachedSessionLocator extends AbstractMemcachedSessionLocator {

	public ArrayMemcachedSessionLocator() {
		this.hashAlgorighm = HashAlgorithm.NATIVE_HASH;
	}

   	public final long getHash(int size, String key) {
		long hash = this.hashAlgorighm.hash(key);
		return hash % size;
	}

  .........................
}

一致性哈希策略:

        builder.setSessionLocator(new KetamaMemcachedSessionLocator());
public class KetamaMemcachedSessionLocator extends AbstractMemcachedSessionLocator {

	private transient volatile TreeMap<Long, List<Session>> ketamaSessions = new TreeMap<Long, List<Session>>();

	public KetamaMemcachedSessionLocator() {
		this.hashAlg = HashAlgorithm.KETAMA_HASH; // 一致性哈希
		this.cwNginxUpstreamConsistent = false;
	}

 .........................


	private final void buildMap(Collection<Session> list, HashAlgorithm alg) {
		TreeMap<Long, List<Session>> sessionMap = new TreeMap<Long, List<Session>>();

		for (Session session : list) {
			String sockStr = null;
			.........................
			int numReps = NUM_REPS;
			
			if (alg == HashAlgorithm.KETAMA_HASH) {
				for (int i = 0; i < numReps / 4; i++) {
					byte[] digest = HashAlgorithm.computeMd5(sockStr + "-" + i);
					for (int h = 0; h < 4; h++) {
						long k = (long) (digest[3 + h * 4] & 0xFF) << 24
								| (long) (digest[2 + h * 4] & 0xFF) << 16
								| (long) (digest[1 + h * 4] & 0xFF) << 8
								| digest[h * 4] & 0xFF;
						this.getSessionList(sessionMap, k).add(session);
					}

				}
			} else {
				for (int i = 0; i < numReps; i++) {
					long key = alg.hash(sockStr + "-" + i);
					this.getSessionList(sessionMap, key).add(session);
				}
			}
		}
		this.ketamaSessions = sessionMap;
		this.maxTries = list.size();
	}

......................................

}

 

上述就是memcached的简单操作,memcached 虽然简单,但是动手和看还是不一样的,动手之后,理解会更加深刻,印象会更深。

转载于:https://my.oschina.net/mengyuankan/blog/1922805

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值