缓存穿透、并发和失效、同步中断,最佳实践及优化方案

原文摘自:

缓存穿透、并发和失效,来自一线架构师的解决方案
https://community.qingcloud.com/topic/463

在我们的实践中,原文中有部分解决方案已经过时,在原文的基础上,添加了几个我们常用的方案。
by shuhai, admin@4wei.cn


我们在用缓存的时候,不管是Redis或者Memcached,基本上会通用遇到以下三个问题:

  • 缓存穿透
  • 缓存并发
  • 缓存失效
  • 同步、复制中断

##缓存穿透

![https://pek3a.qingstor.com/community/resource/pic/2016-6-23-cache-1.png][1]
![https://pek3a.qingstor.com/community/resource/pic/2016-6-23-cache-2.png][2]
![https://pek3a.qingstor.com/community/resource/pic/2016-6-23-cache-3.png][3]

注:上面三个图会有什么问题呢?

我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。

这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,可能DB就挂掉了。

那这种问题有什么好办法解决呢?

要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。有一个比较巧妙的作法是,可以将这个不存在的key预先设定一个值。比如,"key" , “&&”。

在返回这个&&值的时候,我们的应用就可以认为这是不存在的key,那我们的应用就可以决定是否继续等待继续访问,还是放弃掉这次操作。如果继续等待访问,过一个时间轮询点后,再次请求这个key,如果取到的值不再是&&,则可以认为这时候key有值了,从而避免了透传到数据库,从而把大量的类似请求挡在了缓存之中。

你应该注意,这里缓存未命中的原因,更值得我们关注。

当缓存空间满了,同步失败,网络阻塞,缓存写失败等原因,会出现缓存服务器上并没有这个key。
或者因为同步中断,在主从架构中,写到主却未同步到从的悲剧,就会出现请求穿透到DB层的情况。

出现这样的情况,一定不能直接将请求穿透到DB层,避免DB当机影响其它业务。
我们的解决方案可以参考。

  • 当业务中请求量特别高,缓存未命中的情况,应该在建立DB保护的基础上,放弃一定比例的请求,直接返回空
  • 可以随机释放一些请求到DB,控制好流量的话,能保证缓存重建且DB不受极端压力
  • 后端异步定时检查缓存,主动建立这些缓存
  • 通过建立二级缓存,把之前成功获取的缓存数据放到本机缓存,文件也好,共享内存也好,接受一些过期数据

##缓存并发

有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。
我现在的想法是对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。

这种情况和刚才说的预先设定值问题有些类似,只不过利用锁的方式,会造成部分请求等待。

##缓存失效

引起这个问题的主要原因还是高并发的时候,平时我们设定一个缓存的过期时间时,可能有一些会设置1分钟啊,5分钟这些,并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间都一样,这个时候就可能引发一当过期时间到后,这些缓存同时失效,请求全部转发到DB,DB可能会压力过重。

那如何解决这些问题呢?

其中的一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

我们讨论的第二个问题时针对同一个缓存,第三个问题时针对很多缓存。

接下来我们将发表一些自己的缓存高可用实践,如《基于云平台的缓存集群高可用实践》,欢迎关注。

总结
1、缓存穿透:查询一个必然不存在的数据。比如文章表,查询一个不存在的id,每次都会访问DB,如果有人恶意破坏,很可能直接对DB造成影响。
2、缓存失效:如果缓存集中在一段时间内失效,DB的压力凸显。这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。

当发生大量的缓存穿透,例如对某个失效的缓存的大并发访问就造成了缓存雪崩。

精彩问答
问题:如何解决DB和缓存一致性问题?

当修改了数据库后,有没有及时修改缓存。这种问题,以前有过实践,修改数据库成功,而修改缓存失败的情况,最主要就是缓存服务器挂了。而因为网络问题引起的没有及时更新,可以通过重试机制来解决。而缓存服务器挂了,请求首先自然也就无法到达,从而直接访问到数据库。那么我们在修改数据库后,无法修改缓存,这时候可以将这条数据放到数据库中,同时启动一个异步任务定时去检测缓存服务器是否连接成功,一旦连接成功则从数据库中按顺序取出修改数据,依次进行缓存最新值的修改。

问题:问下缓存穿透那块!例如,一个用户查询文章,通过ID查询,按照之前说的,是将缓存的KEY预先设置一个值,,如果通过ID插过来,发现是预先设定的一个值,比如说是“&&”,那之后的继续等待访问是什么意思,这个ID什么时候会真正被附上用户所需要的值呢?

我刚说的主要是咱们常用的后面配置,前台获取的场景。前台无法获取相应的key,则等待,或者放弃。当在后台配置界面上配置了相关key和value之后,那么以前的key &&也自然会被替换掉。你说的那种情况,自然也应该会有一个进程会在某一个时刻,在缓存中设置这个ID,再有新的请求到达的时候,就会获取到最新的ID和value。

问题:其实用Redis的话,那天看到一个不错的例子,双key,有一个当时生成的一个附属key来标识数据修改到期时间,然后快到的时候去重新加载数据,如果觉得key多可以把结束时间放到主key中,附属key起到锁的功能。

这种方案,之前我们实践过。这种方案会产生双份数据,而且需要同时控制附属key与key之间的关系,操作上有一定复杂度。

问题:多级缓存是什么概念呢?

多级缓存就像我今天之前给大家发的文章里面提到了,将Ehcache与Redis做二级缓存,就像我之前写的文章 http://www.jianshu.com/p/2cd6ad416a5a 提到过的。但同样会存在一致性问题,如果我们需要强一致性的话,缓存与数据库同步是会存在时间差的,所以我们在具体开发的过程中,一定要根据场景来具体分析,二级缓存更多的解决是,缓存穿透与程序的健壮性,当集中式缓存出现问题的时候,我们的应用能够继续运行。

发布于2016年7月6日分类php于缓存穿透、并发和失效、同步中断,最佳实践及优化方案留下评论

php版本的文件夹合并

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/bin/env php
<?php
error_reporting(7);
 
$dir1 = $argv[1];
$dir2 = $argv[2];
 
if(empty($dir1) || empty($dir2))
{
    echo "php {$argv[0]} dir_merged dir_merge_save\n";
    exit(0);
}
 
if(! is_dir($dir1))
{
    echo "{$dir1} is not a dir\n";
    exit(0);
}
if(! is_dir($dir2))
{
    echo "{$dir2} is not a dir\n";
    exit(0);
}
 
merge_dir($dir1, $dir2);
 
function merge_dir($from, $to)
{
    $from  = rtrim($from, "/")."/";
    $to    = rtrim($to, "/")."/";
    echo "search file from {$from}";
    $files = glob($from."*");
    echo "\t\tdone\n";
 
    foreach($files as $f)
    {
        $relative_path = substr_replace($f, "", 0, strlen($from));
        $ff = $to . $relative_path;
 
        //目录不存在则直接复制目录
        if(is_dir($f))
        {
            if(!is_dir($ff))
            {
                $cmd = "cp -r '{$f}' '{$ff}'";
                `$cmd`;
                logs($relative_path);
            } else {
                merge_dir($f, $ff);
            }
        }else{
            if(!is_file($ff))
            {
                copy($f, $ff);
                logs($relative_path);
                continue;
            }
 
            //对特殊文件进行检查
            $ext = substr($relative_path, strrpos($relative_path, ".")+1);
            if($ext == "apk" && filesize($f) != filesize($ff))
            {
                $compare = apk_version_compare($f, $ff);
                if($compare)
                {
                    copy($f, $ff);
                    logs($relative_path);
                }
            }
        }
    }
}
 
function apk_version_compare($apk1, $apk2)
{
    return aapt($apk1) > aapt($apk2);
}
 
function aapt($apk)
{
    $apk = realpath($apk);
    $aapt = "aapt d badging {$apk} | grep versionCode | awk '{print $3}'";
    $aapt = `$aapt`;
 
    return substr($aapt, 13, -2);
}
 
function logs($file)
{
    printf("%s\n", $file);
}

发布于2016年1月27日分类php于php版本的文件夹合并留下评论

基于CloudFlare + Conoha搭建的企业级廉价CDN

中国的流量很贵,流量越大越贵。

目前我负责的业务一半国内,一半海外。目前日均请求过千万,流量过10T,总成本不超过500元/天。跟传统的CDN服务相比,便宜到可以忽略。

CDN介绍
在项目早期,为了业务简单,直接使用传统CDN提供商网宿,在CDN宽带超过 5G 的时候,CDN的成本开始显现出来。
一般国内的CDN(网宿、帝联、蓝汛,还有一些如七牛、又拍这样的二手贩子)均有宽带和流量计费,新起的云平台(阿里、腾讯、uCloud)基本上使用流量计费。

国内的CDN价格,网宿的95峰值计费,一般价格在45-55元/M,根据宽带高值、公司背景、销售人员关系等等因素,合同的价格可以签到30-40元左右,其它二线的CDN可以签到25-30元左右。

其它的云服务平台,价格相对固定,业务购买一般是自助服务,价格基本上没得可谈,普遍价格在0.9元-1.2元/G。

国内服务商运营的国外CDN价格比国内高出几个数量级,网宿的报价为150元/M,其海外节点为少数自建加akamai代理。其它几家传统的CDN服务商完全代理甚至没有海外业务。

海外的CDN服务商,计费一般是按流量计费,常见的如MaxCDN, KeyCDN,价格一般为0.04-0.1美元/G。

业务介绍
我们的业务早期使用网宿,业务从0M跑到3G,业务一直比较稳定。后来业务推到海外,直到CDN的成本越来越高,CDN成本差不多占了利润的一半。

网宿现在有市场优势,价格一直砍不下来。经过跟几家CDN服务咨询、测试,从最初的七牛,MaxCDN,KeyCDN,到最后的CloudFlare+Conoha,终于实现成本与速度的平衡。

在第一次使用CloudFlare和Conoha的时候,都被其极低的价格吓得不敢相信,企业级项目中总觉得低价的服务肯定会出问题(事实上确实是这样,毕竟价格高的也会出问题)。


cloudflare.com

和国内的安全宝、百度云加速的业务类似,CloudFlare提供的安全服务是帮助网站阻止来自网络的黑客攻击、垃圾邮件等,并提升网页的浏览速度,这和一般的安全软件往往会影响网页的运行速度大相径庭。目前CloudFlare在全球拥有23个数据中心,如果用户使用了其服务,那么网络流量将通过CloudFlare的全球网络智能路由。CloudFlare会自动优化用户的网页交付,以期达到最快的页面加载时间以及最佳性能。 CloudFlare提供包括CDN、优化工具、安全、分析以及应用等服务。

2015年9月,CloudFlare正式宣布与百度合作改善外国网站在中国的可访问性。双方早在去年7月就签署了合作协议,CloudFlare将其技术转让给百度(CEO称此举是为了增加信任),而采用CloudFlare技术的百度云加速服务于去年12月开始运作。

CloudFlare称,百度在中国大陆的17个中心地区节点与 CloudFlare 全球的45个节点结合起来,提升中国国内外的访问体验,当客户激活中国网络服务后,他们的中国访问者将会访问百度节点,而CloudFlare节点则继续服务海外访问者,可将中国流量的响应时间缩短超过200毫秒。
但外国客户如果要激活中国网络服务将需要ICP备案。网站备案是中国工信部要求所有在中国大陆使用主机或CDN服务的许可证书。

via:http://www.cnbeta.com/articles/429815.htm

conoha.jp

ConoHa日本gmo.jp旗下的一个VPS主机商,成立于2014年。ConoHa提供日本、新加坡及美国机房云VPS服务。

ConoHa的业务跟国内的云服务平台类似。说是云服务器,个人感觉跟vps差不多,跟常见的Linode和digitalocean基本上相同。
ConoHa的官网支持中文,支持信用卡和支付宝,支持扩容支持按时间收费,可以按小时按月计费。

ConoHa的价格非常廉价,常见的配置如1GB、2Core、SSD50GB,每个月50元人民币。

最重要的是,ConoHa不限流量,100M宽带,可扩容,可加IP。

ConoHa有激励政策,通过我的邀请链接注册,你可以获取1000日元,相当于免费赠送一台主机,
https://www.conoha.jp/referral/?token=V3xoVa5812CYk15rhJkKiiNc5E340f3uNaNjQCiaBmnWZdA30Zk-0VJ

业务实现
在CloudFlare中,添加一个免费(我们使用付费方案是200美元/月)的域名,主要使用其提供的IP隐藏、文件缓存来实现防DDOS和CDN需求。

在我们的单个项目中,CloudFlare每天可以缓存数百万次的请求,差不多8-12TB左右的流量。按最低4美分的价格,每天的CDN成本应该在3000元左右,一个月10万元,一年超过100百万元。
采用了CloudFlare以后,只需要1200元一个月,一年只需要1万元,CloudFlare实实再再的帮老板省了一大笔钱。
老板是不是该奖励我一台特斯拉了哎?

QQ20160110-1

经过我们跟数家CDN服务商的对比和测速,给CloudFlare的节点和速度5个星,稳定性和速度让我们非常意外,点赞。
但作为一个非专业的CDN解决方案,CloudFlare目前还没有完善的数据报表。不能分析请求的URL列表,不能按常见维度分析用户和行为,对运营人员来讲是个缺憾。
同时,CloudFlare默认只对一些基本的文本、图片文件进行缓存,有特殊的文件,比如apk、exe等文件,则颇费周折。

via:https://support.cloudflare.com/hc/en-us/articles/200172516

CloudFlare中非默认缓存的文件,比如apk文件,如果需要缓存,则不能携带任何参数,否则会回源,无法缓存。
CloudFlare另外一个巨坑便是其缓存重建只能通过pull方式。回源pull方式存在严重的风险。我们这边出现过一个50M的文件清理缓存以后,源站瞬间出现近千个请求。这包括CloudFlare的全球节点的缓存请求,也有用户的的真实请求。
CloudFlare无法设置限速,所有请求全落到源站,源站的流量瞬间飙上500M。放在云上的整个路由器下的所有业务全部卡死。

源站的流量可以通过扩容的方式提升上限,为了解决巨大的回源请求,我们曾差不多把宽带提高了到1G。
云平台1.2元/G的流量价格也是贵到滴血,逼得我们必须要为回源的请求再构建一个廉价的中转服务器。

在测试过Linode和digitalocean以后,我们选中了ConoHa。主要看中其极低的价格、无限流量、多节点等优势。
我们把文件rsync到ConoHa的多台主机以后,再将CloudFlare的回源请求重定向到不同的ConoHa服务器。
虽然业务架构越来越复杂,但比起源站业务被中断,以及宽带扩容的成本,ConoHa上一个月不到一千块钱的成本实再便宜得让人心疼。

其它
在目前的架构中,所采用的方案均为比较新的服务商,稳定性存在非常大的风险,如果有一天CloudFlare或者ConoHa倒闭,则会对现在的业务造成致命的影响。

为了降低风险,我们也在采用了一些优化手段,也在积极寻找备用方案。
比如,为了避免单个域名流量太大,被CloudFlare封掉,或者要求我们使用更高的付费方案,我们把CDN拆到多个域名下,减少单个域名的请求和流量。我们也在寻找与CloudFlare相同的的平台,比如Incapsula。甚至有人建议直接购买廉价vps自建CDN。考虑到现在云计算基础服务相对完善,自建一个全球性的CDN平台也不是太大难事。

为了避免ConoHa出故障,我们也在其它几个廉价的vps服务商购买了几个备用服务器,定期将文件同步过去进行灾备。

同时,我们发现CloudFlare中添加一个在国内备案的域名,其节点尚不能确认是否已经使用了百度云加速的节点,有没有知晓内幕的朋友。

最后
经过项目的实践和数月的运营,这个廉价的CDN实现,节点数可以达到成熟商用CDN节点数的30%-80%(海外优势尤其明显)。跟同行业的一些朋友交流时发现,也有其它同行业的业务使用CloudFlare+Digitalocean实现,稳定支撑月流水数百万美元的业务,而其成本非常低廉。

云服务越来越成熟,成本越来越低廉,对于我们这样的创业公司来讲,是一大福音。而对于那些传统的基础服务商来讲,又是什么呢?

发布于2016年1月10日分类php基于CloudFlare + Conoha搭建的企业级廉价CDN有4条评论

简单获取并验证client ip

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
function get_ip_address()
{
    foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key)
    {
        if (array_key_exists($key, $_SERVER) === true)
        {
            foreach (explode(',', $_SERVER[$key]) as $ip)
            {
                $ip = trim($ip);
 
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
                {
                    return $ip;
                }
            }
        }
    }
}
posted @ 2019-05-01 13:46  appledady  阅读(438)  评论(0编辑  收藏  举报