场景

Mutex主要用于有大量并发访问并存在cache过期的场合,如

  • 首页top 10, 由数据库加载到memcache缓存n分钟;
  • 微博中名人的content cache, 一旦不存在会大量请求不能命中并加载数据库;
  • 需要执行多个IO操作生成的数据存在cache中, 比如查询db多次;

问题

在大并发的场合,当cache失效时,大量并发同时取不到cache,会同一瞬间去访问db并回设cache,可能会给系统带来潜在的超负荷风险。我们曾经在线上系统出现过类似故障。

解决方法 

方法一

高并发时,增加data_lock信号标识,只充许一个用户update cache,在cache数据期间,其它并发用户等待,只到这个用户cache成功,当然这个地方需要设置最大等待时间,毕竟当很长时间cache不成功时,不能让用户一直等待:

<?php
function get_my_data2(){
$cache_id = "mykey";
$data = $memcache_obj->get($cache_id);
if (!$data) {
// check to see if someone has already set the lock
$data_lock = $memcache_obj->get($cache_id . '_qry_lock');
if ($data_lock) {
$lock_counter = 0; // loop until you find that the lock has been released. that implies that the query has finished
while ($data_lock) {
// you may only want to wait for a specified period of time.
// one second is usually sufficient since your goal is to always have sub-second response time
// if you query takes more than 1 second, you should consider "warming" your cached data via a cron job
if ($lock_counter > $max_time_to_wait) {
$lock_failed = true;
break;
} // you really want this to be a fraction of a second so the user waits as little as possible
// for the simplicity of example, I'm using the sleep function.
sleep(1);
$data_lock = $memcache_obj->get($cache_id . '_qry_lock');
} // if the loop is completed, that either means the user waited for too long
// or that the lock has been removed. try to get the cached data again; it should exist now
$data = $memcache_obj->get($cache_id);
if ($data) {
return $data;
}
} // set a lock for 2 seconds
$memcache_obj->set($cache_id . '_qry_lock', true, 2);
$data = get_data_from_db_function();
$memcache_obj->set($cache_id, $data, $sec_to_cache_for); // don't forget to remove the lock
$memcache_obj->delete($cache_id . '_qry_lock');
} return $data;
}

方法二 

需要给数据增加一个cache过期时间标识,高并发时,在用户取数据时,检查cache是否快要过期,如果即将过期,则充许一个用户去更新cache,其它用户依然访问没有update的数据。

<?php
function get_my_data3() {
$cache_id = "mykey";
$data = $memcache_obj->get($cache_id); // if there is cached data and the expire timestamp has already expired or is within the next 2 minutes
// then we want the user to freshen up the cached data
if ($data && ($data['cache_expires_timestamp'] - time()) < 120) {
// if the semaphore lock has already been set, just return the data like you normally would.
if ($memcache_obj->get($cache_id . '_expire_lock')) {
return $data;
} // now we want to set the lock and have the user freshen the data.
$memcache_obj->set($cache_id . '_expire_lock', true, 2); // by unsetting the data it will cause the data gather logic below to execute.
unset($data);
} if (!$data) {
// be sure to include all of the semaphore logic from example 2 // set the _qry_lock for 2 seconds
$memcache_obj->set($cache_id . '_qry_lock', true, 2);
$raw_data = get_data_from_db_function();
$data['cache_expires_timestamp'] = time() + $sec_to_cache_for;
$data['cached_data'] = $raw_data;
$memcache_obj->set($cache_id, $data, $sec_to_cache_for); // remove the _qry_lock
$memcache_obj->delete($cache_id . '_qry_lock'); // remove the _expires_lock
$memcache_obj->delete($cache_id . '_expires_lock');
} return $data;
}

项目中的一个缓存类参考:

CacheModel.class.php

<?php
namespace framework;
use framework\Cache; /**
* 缓存模型 - 业务逻辑模型
*
* @example
* setType($type) 主动设置缓存类型
* set($key, $value, $expire = 0) 设置缓存key=>value,expire表示有效时间,0表示永久
* get($key, $mutex = false) 获取缓存数据,支持mutex模式
* getList($prefix, $key) 批量获取指定前缀下的多个key值的缓存
* rm($key) 删除缓存
*/
class CacheModel {
protected $config = array(); // 缓存配置文件
protected $handler = null; // 当前缓存操作对象
protected $type = ''; // 当前缓存类型 /**
* 取得缓存类实例
*
* @param array $config 缓存节点
* @return mixed 返回类实例
*/
public static function getInstance($connection = 'default') {
static $_instance = null; if (!isset($_instance)) {
$_instance = new self($connection);
} return $_instance;
} /**
* 初始化缓存模型对象,缓存类型
*
* @return void
*/
public function __construct($connection = '') {
$this->_initHandler($connection);
} /**
* 初始化配置文件
*/
private function _initHandler($connection = '') {
// 获取缓存配置信息
$connection = $connection ? $connection : 'default';
if (!isset($this->config[$connection])) {
$this->config[$connection] = array_merge(get_config('cache/__common__'), get_config('cache/' . $connection));
} $this->type = strtolower($this->config[$connection]['type']);
$this->handler = Cache::getInstance($this->config[$connection]);
} /**
* 链式设置缓存节点
*
* @param string $type 缓存类型
* @return object 缓存模型对象
*/
public function setConnection($connection = '') {
$this->_initHandler($connection);
return $this;
} /**
* 设置缓存
*
* @param string $key 缓存Key值
* @param mix $value 缓存Value值
* @param int $expire 有效时间(单位:秒,0表示永不过期)
* @param boolean 是否设置成功
*/
public function set($key, $value, $expire = 0) {
$value = array(
'cache_data' => $value, // 缓存数据
'cache_mtime' => time(), // 缓存修改时间戳
'cache_expire' => is_null($expire) ? 0 : intval($expire) // 缓存有效时间
); return $this->handler->set($key, $value);
} /**
* 获取缓存操作,支持mutex模式
* mutex使用注意
* 1.设置缓存(set)时,需要设置有效时间
* 2.获取缓存(get)时,需要主动创建缓存
*
* @param string $key 缓存Key值
* @param boolean $mutex 是否启用mutex模式,默认启用
* @return mix 缓存数据
*/
public function get($key, $mutex = true) {
// 静态缓存
$sc = get_static('cache_model_' . $key);
if (isset($sc)) {
return $sc;
} // 获取缓存数据
$data = $this->handler->get($key); // 未成功取到缓存
if ($data === false) {
return false;
} // 未过期
if (($data ['cache_expire'] === 0) || (($data ['cache_mtime'] + $data ['cache_expire']) > time ())) {
return $this->_returnData($data['cache_data'], $key);
} // 已过期
if ($mutex) { // mutex模式开启
$data['cache_mtime'] = time();
$this->handler->set($key, $data); // 返回false,让调用程序去主动更新缓存
set_static('cache_model_' . $key, null); return false;
} else { // mutex模式没开启
$this->rm($key);
return false;
}
} /**
* 删除缓存
*
* @param string $_key 缓存Key值
* @return boolean 是否删除成功
*/
public function rm($key) {
set_static('cache_model_' . $key, null);
return $this->handler->rm($key);
} /**
* 清除缓存
*
* @access public
* @return boolen
*/
public function clear() {
return $this->handler->clear();
} /**
* 根据某个前缀,批量获取多个缓存
*
* @param string $prefix 缓存前缀
* @param string $keys 缓存Keys值
* @return mix 缓存数据
*/
public function getList($prefix = '', $keys = array()) {
if ($this->type == 'memcache') {
// Memcache有批量获取缓存的接口
$_data = $this->handler->getMulti($prefix, $keys);
if ($_data) {
foreach ($_data as $key => $val) {
$data[$key] = $this->_returnData($val['cache_data'], $prefix . $key);
}
}
} else {
foreach ($keys as $key) {
$_k = $prefix . $key;
$data[$key] = $this->get($_k);
}
} return $data;
} /**
* 返回缓存数据操作,方法中,将数据缓存到静态缓存中
*
* @param mix $data 缓存数据
* @param string $key 缓存Key值
* @return mix 缓存数据
*/
private function _returnData($data, $key) {
set_static('cache_model_' . $key, $data);
return $data;
}
}

参考:

memcached PHP semaphore & cache expiration handling

[Tim]Memcache mutex设计模式

Memcache的mutex设计模式 -- 高并发解决方案的更多相关文章

  1. PHP面试(二):程序设计、框架基础知识、算法与数据结构、高并发解决方案类

    一.程序设计 1.设计功能系统——数据表设计.数据表创建语句.连接数据库的方式.编码能力 二.框架基础知识 1.MVC框架基本原理——原理.常见框架.单一入口的工作原理.模板引擎的理解 2.常见框架的 ...

  2. 手把手让你实现开源企业级web高并发解决方案(lvs+heartbeat+varnish+nginx+eAccelerator+memcached)

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://freeze.blog.51cto.com/1846439/677348 此文凝聚 ...

  3. 高并发解决方案--负载均衡(HTTP,DNS,反向代理服务器)(解决大流量,高并发)

    高并发解决方案--负载均衡(HTTP,DNS,反向代理服务器)(解决大流量,高并发) 一.总结 1.什么是负载均衡:当一台服务器的性能达到极限时,我们可以使用服务器集群来提高网站的整体性能.那么,在服 ...

  4. 关于SQL SERVER高并发解决方案

    现在大家都比较关心的问题就是在多用户高并发的情况下,如何开发系统,这对我们程序员来说,确实是值得研究,最近找工作面试时也经常被问到,其实我早有去关心和了解这类问题,但一直没有总结一下,导致面试时无法很 ...

  5. java并发编程与高并发解决方案

    下面是我对java并发编程与高并发解决方案的学习总结: 1.并发编程的基础 2.线程安全—可见性和有序性 3.线程安全—原子性 4.安全发布对象—单例模式 5.不可变对象 6.线程封闭 7.线程不安全 ...

  6. JAVA系统架构高并发解决方案 分布式缓存 分布式事务解决方案

    JAVA系统架构高并发解决方案 分布式缓存 分布式事务解决方案

  7. Java 高并发解决方案(电商的秒杀和抢购)

    转载:https://blog.csdn.net/icangfeng/article/details/81201575 电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对 ...

  8. java系统高并发解决方案-转

    转载博客地址:http://blog.csdn.net/zxl333/article/details/8685157 一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图 ...

  9. java系统高并发解决方案(转载)

    转载博客地址:http://blog.csdn.net/zxl333/article/details/8454319 转载博客地址:http://blog.csdn.net/zxl333/articl ...

随机推荐

  1. leetcode 211. 添加与搜索单词 - 数据结构设计 解题报告

    设计一个支持以下两种操作的数据结构: void addWord(word) bool search(word) search(word) 可以搜索文字或正则表达式字符串,字符串只包含字母 . 或 a- ...

  2. [译]如何将docker日志重定向到单个文件里

    原文来源: how-to-redirect-docker-logs-to-a-single-file 问题: 我想把某一个docker的log全部导出到一个文件里进行分析,我该怎么做? 其实不用那样操 ...

  3. JSP/Servlet Web 学习笔记 DayThree

    JSP内置对象 使用JSP语法可以存取这些内置对象来执行JSP网页的Servlet环境相互作用.内置对象其实是由特定的Java类所产生的.每一种内置对象都映射到一个特定的Java类或者端口,在服务器运 ...

  4. Dubbo基础介绍

    基础知识 Dubbo是什么:Dubbo是一个分布式的服务框架,提供高性能和透明化的RPC远程调用方案,以及SOA服务治理方案 Dubbo涉及的知识: 远程调用:RMI.hassion.webservi ...

  5. [洛谷P2801]教主的魔法

    题目大意:有$n$个数,$q$个操作.两种操作: $M\;l\;r\;w:$把$[l,r]$所有数加上$w$ $A\;l\;r\;c:$查询$[l,r]$内大于等于$c$的元素的个数. 题解:分块,对 ...

  6. BZOJ 1036 [ZJOI2008]树的统计Count | 树链剖分模板

    原题链接 树链剖分的模板题:在点带权树树上维护路径和,最大值和单点修改 这里给出几个定义 以任意点为根,然后记 size (u ) 为以 u 为根的子树的结点个数,令 v 为 u 所有儿子中 size ...

  7. BZOJ2208 [Jsoi2010]连通数 【图的遍历】

    题目 输入格式 输入数据第一行是图顶点的数量,一个正整数N. 接下来N行,每行N个字符.第i行第j列的1表示顶点i到j有边,0则表示无边. 输出格式 输出一行一个整数,表示该图的连通数. 输入样例 3 ...

  8. 原生js提取非行间样式

    js用style属性可以获得html标签的样式,但是不能获取非行间样式,如何获取css的非行间样式呢,在低版本ie我们可以用currentStyle,在其他浏览器我们可以用getComputedSty ...

  9. 华为上机测试题(求亮灯数量-java)

    PS:自己写的,自测试OK,供大家参考. /* 一条长廊里依次装有n(1 ≤ n ≤ 65535)盏电灯,从头到尾编号1.2.3.…n-1.n.每盏电灯由一个拉线开关控制.开始,电灯全部关着.有n个学 ...

  10. 前端知识学习——html

    <!-- Html,CSS,JS 三者的关系 ==> 人,衣服,动作. 以下展示 html 常用基本编码 --><!-- Html 在PyCharm中新建html文件默认给出的 ...