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设计模式
Memcache的mutex设计模式 -- 高并发解决方案的更多相关文章
- PHP面试(二):程序设计、框架基础知识、算法与数据结构、高并发解决方案类
一.程序设计 1.设计功能系统——数据表设计.数据表创建语句.连接数据库的方式.编码能力 二.框架基础知识 1.MVC框架基本原理——原理.常见框架.单一入口的工作原理.模板引擎的理解 2.常见框架的 ...
- 手把手让你实现开源企业级web高并发解决方案(lvs+heartbeat+varnish+nginx+eAccelerator+memcached)
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://freeze.blog.51cto.com/1846439/677348 此文凝聚 ...
- 高并发解决方案--负载均衡(HTTP,DNS,反向代理服务器)(解决大流量,高并发)
高并发解决方案--负载均衡(HTTP,DNS,反向代理服务器)(解决大流量,高并发) 一.总结 1.什么是负载均衡:当一台服务器的性能达到极限时,我们可以使用服务器集群来提高网站的整体性能.那么,在服 ...
- 关于SQL SERVER高并发解决方案
现在大家都比较关心的问题就是在多用户高并发的情况下,如何开发系统,这对我们程序员来说,确实是值得研究,最近找工作面试时也经常被问到,其实我早有去关心和了解这类问题,但一直没有总结一下,导致面试时无法很 ...
- java并发编程与高并发解决方案
下面是我对java并发编程与高并发解决方案的学习总结: 1.并发编程的基础 2.线程安全—可见性和有序性 3.线程安全—原子性 4.安全发布对象—单例模式 5.不可变对象 6.线程封闭 7.线程不安全 ...
- JAVA系统架构高并发解决方案 分布式缓存 分布式事务解决方案
JAVA系统架构高并发解决方案 分布式缓存 分布式事务解决方案
- Java 高并发解决方案(电商的秒杀和抢购)
转载:https://blog.csdn.net/icangfeng/article/details/81201575 电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对 ...
- java系统高并发解决方案-转
转载博客地址:http://blog.csdn.net/zxl333/article/details/8685157 一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图 ...
- java系统高并发解决方案(转载)
转载博客地址:http://blog.csdn.net/zxl333/article/details/8454319 转载博客地址:http://blog.csdn.net/zxl333/articl ...
随机推荐
- [python] 网络数据采集 操作清单 BeautifulSoup、Selenium、Tesseract、CSV等
Python网络数据采集操作清单 BeautifulSoup.Selenium.Tesseract.CSV等 Python网络数据采集操作清单 BeautifulSoup.Selenium.Tesse ...
- Makefile编写记录
近期学习 Linux 需要使用 Makefile,网上搜罗了很多这方面的资料,所里在这里做一个整理. 1.静态模式 看一个例子: objects = foo.o bar.o all: $(object ...
- 微信小程序--动态添加class样式
尺寸单位: rpx(responsive pixel): 可以根据屏幕宽度进行自适应.规定屏幕宽为750rpx.如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = ...
- PAT 1050 螺旋矩阵
https://pintia.cn/problem-sets/994805260223102976/problems/994805275146436608 本题要求将给定的 N 个正整数按非递增的顺序 ...
- flutter channel master
flutter可能是未来跨平台开发的又一技术框架,那么对于一个app,我们不可能完全用flutter来开发,那么就意味着我们需要在已有的Android和iOS代码中去集成flutter.目前这一技术还 ...
- 【转】百度统计js被劫持用来DDOS Github
原文链接:http://drops.wooyun.org/papers/5398 今天中午刷着全国最大的信息安全从业人员同性交友社区zone.wooyun.org的时候,忽然浏览器每隔2秒就不断的弹窗 ...
- BZOJ 2103/3302/2447 消防站 树的重心【DFS】【TreeDP】
2103: Fire 消防站 Time Limit: 30 Sec Memory Limit: 259 MBSubmit: 157 Solved: 116[Submit][Status][Disc ...
- Java并发编程--CyclicBarrier
概述 CyclicBarrier是一个同步工具类,它允许一组线程互相等待,直到到达某个公共屏障点.与CountDownLatch不同的是该barrier在释放等待线程后可以重用,所以称它为循环(Cyc ...
- [bzoj2301][HAOI2011]Problem B —— 莫比乌斯反演+容斥原理
题意 给定a, b, c, d, k,求出: \[\sum_{i=a}^b\sum_{j=c}^d[gcd(i, j) = k]\] 题解 为方便表述,我们设 \[calc(\alpha, \beta ...
- C 随机不重复元素~转
随机产生不重复元素:如何高效产生m个n范围内的不重复随机数(m<=n) 如何产生不重复的随机数?最容易想到的方法,是逐个产生这些随机数,每产生一个,都跟前面的随机数比较,如果重复,就重新产生.这 ...