• 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 frameworkCache;
    
    /**
     * 缓存模型 - 业务逻辑模型
     *
     * @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设计模式

  • 相关阅读:
    HDU 4472 Count DP题
    HDU 1878 欧拉回路 图论
    CSUST 1503 ZZ买衣服
    HDU 2085 核反应堆
    HDU 1029 Ignatius and the Princess IV
    UVa 11462 Age Sort
    UVa 11384
    UVa 11210
    LA 3401
    解决学一会儿累了的问题
  • 原文地址:https://www.cnblogs.com/52php/p/5677648.html
Copyright © 2020-2023  润新知