Memcached

Posted 常生果

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Memcached相关的知识,希望对你有一定的参考价值。

数据库的前端缓存区

文件系统内核缓冲区,位于物理内存的内核地址空间,所有对磁盘文件的读写操作都要经过它,也可以把它看作是磁盘的前端设备。 
这块内核缓冲区实际上包括2个部分:读缓存区、写缓存区。

读缓存区中保存着最近系统从磁盘上读取的数据,一旦下次需要读取这些数据的时候,内核将直接从这里获得,而不需访问磁盘。 
写缓存区的目的主要是为了减少磁盘的物理写操作,内核缓区可以将多次写操作指令累计起来,通过一次物理磁头的移动来完成。当然,写缓存区导致数据真正写入磁盘会产生几秒的延迟,在实际写入之前,这些数据被称为Dirtry Page。

我们可以在数据库动态内容之间建立一层缓存区,它可以部署在独立的服务器上,用于加速数据库读写的操作,这个缓存区实际上的由动态内容来控制的。

memcached

key-value

为了实现高速缓存,我们不会把缓存内容放在磁盘上,memcached使用物理内存来作为缓存区,当我们启动memcached的时候,需要指定分配给缓存区的内存大小。 
比如我们分配4GB的内存:

memcached -d -m 4086 -l 192.168.88.88 -p 11211

如果说memcached最许需要什么,毫无疑问,那就是内存。

memcached使用key-value的方式来存储数据,我们将每个key以及对应的value合起来称为数据项。 
memecached使用了高效的基于key的hash算法来设计存储数据结构,并且使用了精心设计的内存分配器,这意味着不论你存储了多少数据项,查询任何数据项所花费的时间都不变。

数据项过期时间

由于缓冲区空间是有限的,一旦缓存区没有足够的控件存储新的数据项,memcached便会想办法淘汰一些数据项来腾出空间,把最近不常访问的数据项淘汰掉。

当然,我们更原因为数据项设置过期时间。如果你在使用php来编写动态内容:

$mem = memcache_connect("192.168.88.88",11211);
$mem->add("item_key","item_value",false,30);

把item_key这个数据项的过期时间设置为30秒。

网络并发模型

memcached是分布式缓存系统,可以运行在独立的服务器上,动态内容通过TCP Socket来访问它。memcached使用libevent函数库来实现网络并发模型,你可以在较大并发用户数的环境下仍然方向使用memcached。

对象序列化

我们在网络中传输的是二进制数据,那么数组或者对象这样的抽象数据类型,是否可以存入memcached中呢? 
答案是肯定的。因为基于序列化(Serialize)机制,我们可以把抽象数据类型转换为二进制字符串,以便通过网络进入缓存服务器,同时在读取这些数据的时候,二进制字符串又可以转换回原有的数据类型。 
这个“转换”的过程比较复杂,但是在具有动态特性的脚本语言中(比如PHP),这个过程不需要你去实现。

下面是一个简单的把对象写入memcached服务器的例子:

<?php
class  Person

    public $name;
    public function setName($name)
        $this->name = $name;
    


// 实例化
$p = new Person();
$p->setName("jack");// 给对象属性赋值

// 连接memcached
$mem = memcache_connect("192.168.88.88",11211);
$mem->add("p_jack",$p,false,0); // 写入
$obj = $mem->get("p_jack"); // 读取

echo $obj->name; // 输出:jack。

写操作缓存

读操作缓存大家一定很熟悉,可写操作缓存也至关重要。

假设有这样一个需求:就拿我们站点访问量统计功能来说,我们需要记录每个URL的累计访问量,所以每次页面刷新都会伴随着一次访问量的增加。

$page = "artical20180120.html";
$sql = "update page_view set view_count=view_count+1 where page='$page'";
$conn = mysqli_connect("localhost","root","root","db_page");
mysqli_select_db("db_main",$conn);
mysqli_query($sql,$conn);

没有缓存的方法就想上面代码那样,我们称之为“直接更新”。

线程安全和锁竞争

我们先来看一个分布式加运算,下面的代码片段,先从缓存服务器取回一个数值,然而在本地+1,接下来再写回缓存服务器。

$count = $mem->get($key);
$count++;
$mem->set($key,$count,false,0);

嗯,看起来似乎没有什么问题。但是,你可以别忘了可能会有多个用户同时触发这样的计算,你一定能想象到有什么糟糕的后果,最后累计访问量总是小于实际访问量。

事实上,这并不涉及memcacahed本身线程安全问题,而是上面这种加运算的方式不是线程安全的。如果要保证这种加运算同时进行,就要考虑一定的事务隔离机制。 
最简单的办法是使用锁竞争,并且把锁保存在memcached中,存在竞争关系的动态内容可以争夺这个锁,一单某个会话抢到锁,那么其他的会话就必须等待。 
但我们不并鼓励这样做,因为锁竞争带来的等待时间是无法容忍的。

原子加法

memcached提供了原子递增操作,也正是因为这个特性,我们在访问量递增更新的应用中引入写缓存。 
我们来修改代码:

$page = "artical20180120.html";

$mem = memcache_connect("192.168.88.88",11211);
$count = $mem->increment($page,1); // 累加1
if ($count === false)
    $mem->add($page,1);
    exit(1);

if ($count == 1000)
    $mem->set($page,0,false,0);
    $sql = "update page_view set view_count=view_count+$count where page='$page'";
    $conn = mysqli_connect("localhost","root","root","db_page");
    mysqli_select_db("db_main",$conn);
    mysqli_query($sql,$conn);

上面代码完全改变了“直接更新”方式,它做了以下工作: 
1、为memcached缓存中对应数据项加1,如果该数据项不存在,则创建该数据项,并且赋值为1,代表这个页面是第一次被访问; 
2、如果memcached缓存中存在对应数据项,并且累加后的数值为1000,则把这个数据项设置为0,并且更新数据库,将数据库对应数值加1000。

也就是火,改造后的程序每经历1000次递增后才写一次数据库。这就大大减少了直接对数据库的操作。如果你的数据库因为大量的写操作而繁忙,那么应该仔细考虑,哪些写操作可以缓存到memcached中?

----------------------------------------------------------

Memcached Client目前有一下四种:
Memcached Client for Java,比 SpyMemcached更稳定、更早、更广泛;
SpyMemcached,比 Memcached Client for Java更高效;
XMemcached,比 SpyMemcache并发效果更好。 
alisoft-xplatform-asf-cache阿里软件的架构师岑文初进行封装的。里面的注释都是中文的,比较好
-----------------------
Memcached有两个核心组件组成:服务端(ms)和客户端(mc)。
首先mc拿到ms列表,并对key做hash转化,根据hash值确定kv对所存的ms位置。
然后在一个memcached的查询中,mc先通过计算key的hash值来确定kv对所处在的ms位置。
当ms确定后,客户端就会发送一个查询请求给对应的ms,让它来查找确切的数据。
因为ms之间并没有护卫备份,也就不需要互相通信,所以效率较高。

----------------------

默认情况下,ms是用一个内置的叫“块分配器”的组件来分配内存的。
舍弃c++标准的malloc/free的内存分配,而采用块分配器的主要目的 是为了避免内存碎片,
否则操作系统要花费更多时间来查找这些逻辑上连续的内存块(实际上是断开的)。
用了块分配器,ms会轮流的对内存进行大块的分配,并 不断重用。
当然由于块的大小各不相同,当数据大小和块大小不太相符的情况下,还是有可能导致内存的浪费。

同时,ms对key和data都有相应的限制,key的长度不能超过250字节,data也不能超过块大小的限制 --- 1MB。
因为 mc所使用的hash算法,并不会考虑到每个ms的内存大小。理论上mc会分配概率上等量的kv对给每个ms,这样如果每个ms的内存都不太一样,那可能 会导致内存使用率的降低。所以一种替代的解决方案是,根据每个ms的内存大小,找出他们的最大公约数,然后在每个ms上开n个容量=最大公约数的 instance,这样就等于拥有了多个容量大小一样的子ms,从而提供整体的内存使用率。

缓存策略

当ms的hash表满了之后,新的插入数据会替代老的数据,更新的策略是LRU(最近最少使用),以及每个kv对的有效时限。Kv对存储有效时限是在mc端由app设置并作为参数传给ms的。
同时ms采用是偷懒替代法,ms不会开额外的进程来实时监测过时的kv对并删除,而是当且仅当,新来一个插入的数据,而此时又没有多余的空间放了,才会进行清除动作。

Memcached文件采用块分配器,有效避免了文件的碎片化,这一点很像hadoop文件块,默认是64M是一个块,保证core并发条线线程竞争,一个快1core负责运行

缓存策略,很像android硬缓存,最近最少使用原则。

Memcached是多台机器共同共享,故而需要ip/host进行通讯,如果不需要做大规模的并发,只需要一台服务器的情况下,其实单机缓存就ok,不需要再通过网络获取一下数据。

根据业务场景,事实进行策略调整。Memcached读写速度快,主要数据不落地,没有IO瓶颈的限制。但是这也是他的弱点,如果服务重启,数据会被清空。

由于Memcached设计的初衷是分布式,有机器之间的通讯和数据传输,故而如果保存对象就需要序列化和反序列时间。由于java本身的序列化和反序列话速度较慢。
一般都会选择口碑较好的fastJson进行数据见转化。

-----------------------------------------------------------------------

集群架构方案
magent+keepalived+memcached+repcache
1)magent:代理memcached实现负载均衡
2)keepalived:magent主从HA高可用
3)memcached:缓存对象

4)repcache:实现memcached单点恢复数据同步

2.安装libevent

 

以上是关于Memcached的主要内容,如果未能解决你的问题,请参考以下文章

memcached 系列知识 1

Redis和Memcached区别

Redis与Memcached的区别

Memcached学习--消息回应

redis 和memcached区别

redis和memcached的区别(总结)