分布式缓存: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),这个过程不需要你去实现。

扫描二维码关注公众号,回复: 1429418 查看本文章

下面是一个简单的把对象写入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中?

猜你喜欢

转载自blog.csdn.net/github_26672553/article/details/79113461