Memcache的mutex设计模式 -- 高并发解决方案

场景

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设计模式

时间: 2024-10-16 14:34:03

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

161219、大型网站应用之海量数据和高并发解决方案总结一二

一.网站应用背景 开发一个网站的应用程序,当用户规模比较小的时候,使用简单的:一台应用服务器+一台数据库服务器+一台文件服务器,这样的话完全可以解决一部分问题,也可以通过堆硬件的方式来提高网站应用的访问性能,当然,也要考虑成本的问题. 当问题的规模在经济条件下通过堆硬件的方式解决不了的时候,我们应该通过其他的思路去解决问题,互联网发展至今,已经提供了很多成熟的解决方案,但并不是都具有适用性,你把淘宝的技术全部都搬过来也不一定达到现在淘宝的水平,道理很简单. 当然,很多文章都在强调,一个网站的发展

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

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://freeze.blog.51cto.com/1846439/677348 此文凝聚笔者不少心血请尊重笔者劳动,转载请注明出处.违法直接人肉出电话 写大街上. http://freeze.blog.51cto.com/个人小站刚上线 http://www.linuxwind.com 有问题还可以来QQ群89342115交流. 今儿网友朋友说:freeze黔驴技穷了,博客也不更新,也

淘宝下单高并发解决方案(转)

淘宝下单高并发解决方案 周末参加了@淘宝技术嘉年华 主办的技术沙龙, 感觉收获颇丰,非常感谢淘宝人的分享.这里我把淘宝下单高并发解决方案的个人理解分享一下.我不是淘宝技术人员,本文只是写自己的理解,所以肯定是会有一些出入的. 在session中牧劳为我们介绍了淘宝下单部分的技术方案变迁,我不介绍变迁,而只对现有系统做介绍. 要优化下单,提高下单的TPS (Transaction per second),我们首先要做的是对下单的逻辑剥离,只保留核心部分,而把附加功能剔除出去.比如说下单要考虑库存量

[转]淘宝下单高并发解决方案

周末参加了@淘宝技术嘉年华 主办的技术沙龙, 感觉收获颇丰,非常感谢淘宝人的分享.这里我把淘宝下单高并发解决方案的个人理解分享一下.我不是淘宝技术人员,本文只是写自己的理解,所以肯定是会有一些出入的. 在session中牧劳为我们介绍了淘宝下单部分的技术方案变迁,我不介绍变迁,而只对现有系统做介绍. 要优化下单,提高下单的TPS (Transaction per second),我们首先要做的是对下单的逻辑剥离,只保留核心部分,而把附加功能剔除出去.比如说下单要考虑库存量,考虑发短信,要给卖家发

关于SQL SERVER高并发解决方案

原文地址:http://www.cnblogs.com/zuowj/p/3566247.html 现在大家都比较关心的问题就是在多用户高并发的情况下,如何开发系统,这对我们程序员来说,确实是值得研究,最近找工作面试时也经常被问到,其实我早有去关心和了解这类问题,但一直没有总结一下,导致面试时无法很完整全面的回答,所以今天我专门总结概况了一下关于SQL SERVER高并发解决方案,希望能帮助大家,若有不对之外,还请及时告之,谢谢! SQL SERVER高并发解决方案主要是从以下几个方面: 1.SQ

淘宝下单高并发解决方案

这里我把淘宝下单高并发解决方案的个人理解分享一下.我不是淘宝技术人员,本文只是写自己的理解,所以肯定是会有一些出入的. 在session中牧劳为我们介绍了淘宝下单部分的技术方案变迁,我不介绍变迁,而只对现有系统做介绍. 要优化下单,提高下单的TPS (Transaction per second),我们首先要做的是对下单的逻辑剥离,只保留核心部分,而把附加功能剔除出去.比如说下单要考虑库存量,考虑发短信,要给卖家发旺旺消息通 知,要对订单做统计,要做销售额统计等等,这些功能是必要的,但是也是附加

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

一.程序设计 1.设计功能系统--数据表设计.数据表创建语句.连接数据库的方式.编码能力 二.框架基础知识 1.MVC框架基本原理--原理.常见框架.单一入口的工作原理.模板引擎的理解 2.常见框架的特性--PHP框架的差异和优缺点 三.算法与数据结构 1.常见算法--算法的概念.时间复杂度和空间复杂度.常见排序算法.常见查找算法 2. 3. 4. 四.高并发解决方案 1. 2. 原文地址:https://www.cnblogs.com/darklights/p/9275751.html

长文慎入-探索Java并发编程与高并发解决方案

所有示例代码,请见/下载于https://github.com/Wasabi1234/concurrency #1 基本概念##1.1 并发同时拥有两个或者多个线程,如果程序在单核处理器上运行多个线程将交替地换入或者换出内存,这些线程是同时"存在"的,每个线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行.##1.2 高并发( High Concurrency) 互联网分布式系统架构设计中必须考虑的因素之一,通常是指

手把手让你实现开源企业级web高并发解决方案

本来想起个比较风趣点的标题,可想来思去,还是走常规路线,做一系列的手把手吧. 这样一来,便于我的老朋友们识别,也让我对这篇文章的粒度把我有个定位.   本篇博文主要介绍利用开源的解决方案,来为企业搭建web高并发服务器架构花了一个多小时,画了张图片,希望能先帮你理解整个架构,之后我在一一介绍.linux的大型架构其实是一点点小架构拼接起来的,笔者从各个应用开始配置,最后在完全整合起来,以实现效果. 笔者所使用的环境为RHEL5.4 内核版本2.6.18 实现过程在虚拟机中,所用到的安装包为DVD