点击蓝字关注我吧

1

别人都会唱了,而我还没付钱!

2019年9月16日晚23点整,周杰伦发布新歌《说好不哭》。
我经过一系列猛如虎的操作:

咦!这啥?

发生错误了?what the fuck!

虽然说好不哭,但是还没开始听之前我就哭了:
再等二分钟,别人都会唱了,我还没付钱!不要这样吧!QQ音乐你要振作起来啊!

1

知识结构的差别

导致我们看的角度不同

不知道大家有没有看过美剧《越狱》。
引用《奇葩说》辩手,也是我的男神,陈铭说的一段话:

主角迈克进入监狱救他哥哥,他走进了监狱,看到了那所监狱。

那一瞬间,我才发现迈克是个建筑学家。

他看到的监狱和我看到的监狱根本不是同一个监狱,。

我看到的是囚牢、操场、移动的犯人和狱卒。

而迈克一走进去,他看到的是通风管道、下水管道、紧急通道,他看到了墙后面所有的东西。

我这时意识到了一点,建筑学家跟我们因为知识背景和知识框架的不同,我们看到的是两个不一样的世界。

这是知识结构的差别带来的我们眼睛看到的世界的截然不同。

我举这个例子想要说明的是,当我站在程序员的角度看QQ音乐崩了这件事情的时候,我看到了什么,我想到了什么,这是一个由无数服务器、若干微服务、负载均衡、多级缓存、巨大流量、分库分表、读写分离、搜索引擎、性能优化、高速硬盘......组成的世界。
周杰伦站在世界的这头,手机不停的响着:"微信到账三元"。
程序员站在世界的这头,嘴里不停的喊着:"马上撑不住了,快降级、加机器、部服务"。

中间的架构图是我随便找的,和QQ音乐无关

1

正文开始

好了,当顶级流量周杰伦把QQ音乐干翻的时候,我作为程序猿看到了什么?且听我细细道来。
当我点击立即购买,没有弹出支付页面,而是弹出"发生错误了"的提示,如果那一晚你也在关注周董的新歌,我相信你和我做了同样的动作:马上关闭了页面,再次点击了立即购买,但是还是弹出错误提示。那个时候,我才反应过来,哦,原来是QQ音乐崩了。
这个时候我的脑海里面立即浮现出了一个由请求,redis缓存,数据库组成画面。

这图,是我这个灵魂画手亲手画的,当然还是和QQ音乐的架构没有半毛钱关系。甚至QQ音乐这次崩掉也许和缓存也没有半毛钱关系。但是我就是想到了这个画面。
熟知redis的小伙伴一看到这里,下意识的就会说:"哟,这不是缓存击穿吗?"
不知道缓存击穿的小伙伴,不要着急。看完这篇文章后,你不仅懂了缓存击穿,还会懂缓存穿透,缓存雪崩,以及对应的解决方案。
熟知redis,并且对这几个概念烂熟于心的小伙伴这个时候可能想要走了,没关系,答应我,走之前,拉到最后点个"在看"。谢谢!
再开始之前,我想多说一句话,垫个底:
为什么我们要用缓存?
其中大部分的原因是为了提高系统的响应速度,提升并发访问量。因为从内存中,比如redis读取数据和从磁盘中,比如mysql读取数据的响应速度是不在一个级别的。
我给你打个形象但不是很恰到的比方吧:就类似于光速和音速的差距。

1

缓存击穿

缓存击穿的概念

缓存击穿是指一个请求要访问的数据,缓存中没有,但数据库中有。
这种情况一般来说就是缓存过期了。但是这时由于并发访问这个缓存的用户特别多,这是一个热点key,这么多用户的请求同时过来,在缓存里面都没有取到数据,所以又同时去访问数据库取数据,引起数据库流量激增,压力瞬间增大,直接崩溃给你看。
所以一个数据有缓存,每次请求都从缓存中快速的返回了数据,但是某个时间点缓存失效了,某个请求在缓存中没有请求到数据,这时候我们就说这个请求就"击穿"了缓存。

缓存击穿

缓存击穿的解决方案

方案一 互斥锁
互斥锁方案的思路就是如果从redis中没有获取到数据,就让一个线程去数据库查询数据,然后构建缓存,其他的线程就等着,过一段时间后再从redis中去获取。

伪代码如下:
String get(String jay) {String music = redis.get(jay);if (music == null) {//nx的方式设置一个key=jay_lock,//value=aiwobieku_lock的数据,60秒后过期if (redis.set("jay_lock", "aiwobieku_lock","nx",60)) {//从数据库里获取数据music = db.query(jay);//构建数据,24*60*60s后过期redis.set(jay, music,24*60*60);//构建成功,可以删除互斥锁redis.delete("jay_lock");} else {//其他线程休息100ms后重试Thread.sleep(100);//再次获取数据,如果前面在100ms内设置成功,则有数据music = redis.get(jay);}}
}

这个方案能解决问题,但是一个线程构建缓存的时候,另外的线程都在睡眠或者轮询。
而且在这个四处宣讲高并发,低延时的时代,你居然让你的用户等待了宝贵的100ms。有可能别人比你快100ms,就抢走了大批用户。
你说,你是何居心?是不是敌人派来的卧底?
方案二 后台续命
后台续命方案的思想就是,后台开一个定时任务,专门主动更新即将过期的数据。
比如程序猿设置jay这个热点key的时候,同时设置了过期时间为60分钟,那后台程序在第55分钟的时候,会去数据库查询数据并重新放到缓存中,同时再次设置缓存为60分钟。
呃,这个方案呢。怎么说呢,我感觉很奇怪。
可能是没有想到合适的应用场景,而且觉得代码实现起来比较复杂。
但是这种思想是没问题的,我之前就借助这样的思想开发过一个功能:
简单的描述一下就是:
流水号系统,采用数据库自增主键来保证唯一性,但是属于非常关键的系统,为了降低数据库异常对服务带来的冲击,所以服务启动后会就会预先在缓存中缓存5000个流水号。然后后台job定时检查缓存中还剩下多少流水号,如果小于1000个,则再从数据库中生成流水号,补充到缓存中,让缓存中的流水号再次回到5000个。这样做的好处就是数据库异常后,我至少保证还有5000个缓存可以保证上游业务,我有一定的时间去恢复数据库。

后台续命的另一种展示

方案三 永不过期
这个方案就有点简单粗暴了。
见名知意,如果结合实际场景你用脚趾头都能想到这个key是一个热点key,会有大量的请求来访问这个数据。对于这样的数据你还设置过期时间干什么?直接放进去,永不过期。

永不过期

这个热点流量就类似于
周董发新歌,
鹿晗爱晓彤,
唱跳rap和篮球的流量。

比起产品经理给你提需求,让你开发的时候,给你预报的流量靠谱多了。
我就遇见过产品经理来提需求的时候说:
这个需求特别急,最好明天就上线。
上线流量马上来,你的系统要抗住。

结果往往是:
熬夜加班通宵干,终于爆肝弄上线。
结果上线没动静,他说商户不接了。

大道至简,我个人喜欢这个方案。
但是具体情况具体分析,没有一套方案走天下的。
比如,如果这个key是属于被各种"自来水"活生生的炒热的呢?就像哪吒一样,你预想不到这个key会闹出这么大的动静。这种情况你这么处理?
所以,具体情况,具体分析。但是思路要清晰,最终方案都是常规方案的组合或者变种。

1

缓存穿透

缓存穿透是指一个请求要访问的数据,缓存和数据库中都没有,而用户短时间、高密度的发起这样的请求,每次都打到数据库服务上,给数据库造成了压力。一般来说这样的请求属于恶意请求,

缓存穿透

根据图片显示的,缓存中没有获取到数据,然后去请求数据库,没想到数据库里面也没有。
比如明明是周杰伦的演唱会,你冲过保安大哥,上台对周董说:"给我来个林俊杰的签名"。
最可恶的是你也知道,周杰伦那里没有林俊杰的签名。
恶意请求,占用资源。当有成千上万这样的恶意请求的时候,你不做处理,就会给周杰伦,哦不,数据库带来压力。

缓存穿透的解决方案

方案一 --- 缓存空对象
缓存空对象就是在数据库即使查到的是空对象,我们也把这个空对象缓存起来。

缓存空对象

下次同样请求就会命中这个空对象,缓存层就处理了这个请求,不会对数据库产生压力。
这样实现起来简单,开发成本很低。但这样随之而来的两个面试题必须要注意一下:
第一个问题:如果在某个时间,缓存为空的记录,在数据库里面有值了,你怎么办?
我知道三个解决方法:
解决方法一:设置缓存的时候,同时设置一个过期时间,这样过期之后,就会重新去数据库查询最新的数据并缓存起来。
解决方法二:如果对实时性要求非常高的话,那就写数据库的时候,同时写缓存。这样可以保障实时性。
解决方法三:如果对实时性要求不是那么高,那就写数据库的时候给消息队列发一条数据,让消息队列再通知处理缓存的逻辑去数据库取出最新的数据。

另外说明一下:对于数据库和缓存一致性的问题,我不打算在这篇文章讨论。个人感觉这是一个引战的问题。后面会单独介绍我自己对于这个问题的看法。请大神不要在这"开杠"。
第二个问题:对于恶意攻击,请求的时候key往往各不相同,且只请求一次,那你要把这些key都缓存起来的话,因为每个key都只请求一次,那还是每次都会请求数据库,没有保护到数据库呀?
这个时候,你就告诉他:"布隆过滤器,了解一下"。
方案二 --- 布隆过滤器
什么是布隆过滤器?

本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。
所以布隆过滤器返回的结果是概率性的,所以它能缓解数据库的压力,并不能完全挡住,这点必须要明确。
guava组件可以开箱即用的实现一个布隆过滤器,但是guava是基于内存的,所以不太适用于分布式环境下。
要在分布式环境下使用布隆过滤器,那还得redis出马,redis可以用来实现布隆过滤器。
看到了吗,redis不仅仅是拿来做缓存的。这就是一个知识点呀。
什么?你想看他是怎么实现的?对不起,我也只是知道并且会用它,内部原理我还说不太清楚,你可以自行查阅一下。所以:

1

缓存雪崩

缓存雪崩是指缓存中大多数的数据在同一时间到达过期时间,而查询数据量巨大,这时候,又是缓存中没有,数据库中有的情况了。请求都打到数据库上,引起数据库流量激增,压力瞬间增大,直接崩溃给你看

和前面讲的缓存击穿不同的是,缓存击穿指大量的请求并发查询同一条数据。
缓存雪崩是不同数据都到了过期时间,导致这些数据在缓存中都查询不到,

或是缓存服务直接挂掉了,所以缓存都没有了。

总之,请求都打到了数据库上。对于数据库来说,流量雪崩了,很形象。

缓存雪崩的解决方案

方案一 --- 加互斥锁
如果是大量缓存在同一时间过期的情况,那么我们可以加互斥锁。
等等,互斥锁不是前面介绍过了吗?
是的,缓存雪崩可以看成多个缓存击穿,所以也可以使用互斥锁的解决方案,这里就不再赘述。
方案二 --- "错峰"过期
如果是大量缓存在同一时间过期的情况,我们还有一种解决方案就是在设置key过期时间的时候,在加上一个短的随机过期时间,这样就能避免大量缓存在同一时间过期,引起的缓存雪崩。
比如设置一类key的过期时间是10分钟,在10分钟的基础上再加上60秒的随机事件,就像这样:
redis.set(key,value,10*60+RandomUtils.nextInt(0, 60),TimeUnit.SECONDS)


方案三 --- 缓存集群
如果对于缓存服务挂掉的情况,大多原因是单点应用。那么我们可以引入redis集群,使用主从加哨兵。用Redis Cluster部署集群很方便的,可以了解一下。
当然这是属于一种事前方案,在使用单点的时候,你就得考虑服务宕机后引起的问题。所以,事前部署集群,提高服务的可用性。
   
方案四 --- 限流器+本地缓存
那你要说如果Cluster集群也挂了怎么办呢?

如果你能层层深入考虑到集群也挂了怎么办的话,那你可真是一个爱思考的好同学。其实就是对服务鲁棒性的考虑:

鲁棒性(robustness)就是系统的健壮性。它是指一个程序中对可能导致程序崩溃的各种情况都充分考虑到,并且作相应的处理,在程序遇到异常情况时还能正常工作,而不至于死机。

这个时候,可以考虑一下引入限流器,比如Hystrix,然后实现服务降级。
假设你的限流器设置的一秒钟最多5千个请求,那么这个时候来了8千个请求,多出来的3000个就走降级流程,对用户进行友好提示。
进来的这5000个请求,如果redis挂了,还是有可能会干翻数据库的,那么这个时候我们在加上如果redis挂了,就查询类似于echcache或者guava cache本地缓存的逻辑,则可以帮助数据库减轻压力,挺过难关。

方案五 --- 尽快恢复
这个没啥说的了吧?
大哥,你服务挂了诶?赶紧恢复服务啊。
这个时候就涉及到redis的持久化和恢复数据的逻辑了,这里由于篇幅关系,就不展开讲述了,也是知识点啊,朋友们,全是知识点啊。

但是你要说这是一个解决方案,我自己都觉得有点牵强,主要意思你懂的吧?尽快,争分夺秒的恢复数据。
至于是勇敢承担还是积极甩锅的事,恢复后再慢慢考虑。

1

缓存之外

据官方数据,周杰伦《说好不哭》发售不到半小时,销量200万张!

由于我之前是做支付相关开发的,从程序猿的角度,不仅看到了白花花的银子,还去算了一下平均每秒的销售量。
2000000/30/60=1111

约等于每秒1111张,一张就是一笔交易,按照规律,我保守猜测,在刚刚开始发售的时候,请求量至少是平均数的3倍吧。那么就是一秒约3500笔的交易。每一笔都是疯狂的写请求,再加上这期间大量的评论和转发。有可能这才是导致QQ音乐崩溃的诱因。

1

总结

前面介绍了缓存击穿,缓存穿透,缓存雪崩的场景和其对应的各种解决方案。但是不同的解决方案有不同的适用场景和优缺点,你需要仔细权衡自己的需求之后妥善适用它们。

先学习方案的思想,融会贯通之后,你就能触类旁通。
写代码难吗?不难。相反应该是整个开发过程中最简单的一步。难的是你要理解需求,了解场景,拿出对应的解决方案。解决方案都有了,代码不就是呼之欲出吗?
到了写代码的这一步了,难的不是实现需求,难的是你怎么优雅的实现需求。优雅,你懂的吧?程序猿的自我修养之一。
最后,还是之前说的:
知识结构的差别,带来的我们眼睛看到的世界的截然不同。
以上,是我个人看到周杰伦凭一首单曲,把QQ音乐干翻之后的一些思考和感悟。个人拙见,不足之处,欢迎大家指出问题。

1

表白这个男人

最后,放一段我2014年,年终总结中的一段话吧:

表白这个男人。
《说好不哭》,你可以说不好听。那是你的话语权,我捍卫你说话的权利。但是我不接受这个观点,这是我的权利。
或者说,这是我和我身边大多数人的青春。
完结撒花,下周再见。
谢谢大家的阅读,如果觉得还不错的话,欢迎关注,再看并转发哦。

往期精彩回顾

图:why技术

文:why技术

排版:why技术

扫码查看更多内容

当周杰伦把QQ音乐干翻的时候,作为程序猿我看到了什么?相关推荐

  1. 音乐翻唱软件测试初学者,音乐APP听歌识曲大评测,QQ音乐独家“翻唱识别”领跑...

    原标题:音乐APP听歌识曲大评测,QQ音乐独家"翻唱识别"领跑 不知道大家有没有遇到这样一种情况,看综艺时,突然听到一首很好听的歌曲,但是就是不知道歌名,在心里急得呀. 每每遇到这 ...

  2. 程序猿转行,干了2个月销售,经验总结分享

    干了10年的程序猿,没啥经验可分享,干了2个月类似销售的工作(因为实际没卖东西,所以只能说是类似),有不少经验想分享. 以下信息根据这2个月经验,主观上分析总结得出,纯属娱乐,请勿对于入座,切记切记! ...

  3. python爬取qq音乐周杰伦_Python爬取20万条评论,告诉你周杰伦为啥弄崩QQ音乐?

    作者 | 哈哈浩 责编 | 伍杏玲 9 月 16 日晚间,周董在朋友圈发布了最新单曲<说好不哭>. 发布后,真的让一波人哭了,一群想抢鲜听的小伙伴直接泪奔. 因为 QQ 音乐直接被搞崩了! ...

  4. Python 爬取 20 万条评论,告诉你周杰伦新歌为啥弄崩 QQ 音乐?

    作者 | 哈哈浩 责编 | 伍杏玲 9 月 16 日晚间,周董在朋友圈发布了最新单曲<说好不哭>. 发布后,真的让一波人哭了,一群想抢鲜听的小伙伴直接泪奔. 因为 QQ 音乐直接被搞崩了! ...

  5. 不愧是击溃服务器的男人!周杰伦《说好不哭》刷新QQ音乐单曲纪录

    9月16日晚11点,周杰伦2019新单曲<说好不哭>首播,歌曲一经推出,瞬间刷屏朋友圈,甚至将QQ音乐平台一度搞崩溃. 9月17日午间,QQ音乐官方微博称,周杰伦全新数字单曲<说好不 ...

  6. 击溃音乐服务器第一人!周杰伦新歌首发,QQ音乐服务器一度崩溃

    一直以来,在大家眼里,只有微博服务器是"不堪一击"的,明星结个婚,宣布个恋情,微博都会崩.但昨天晚上,击溃音乐平台服务器的男人出现了. 预热多天后,周杰伦的新歌<说好不哭&g ...

  7. python爬取qq音乐周杰伦首页歌词

    #爬取qq音乐周杰伦首页歌词 #未名编程 import requests from bs4 import BeautifulSoup import json import reURL = 'https ...

  8. 怎么在网易云或者QQ音乐上上传自己翻唱的歌

    假期无聊,在全民K歌上唱了几首歌,有朋友想下载来听,我就找了一下怎么将翻唱的歌曲上传到网易云或者QQ音乐.就弄了一下,发现目前只能做到自己听,其他人搜索不到(可以直接把音频发给其他人下载保存)看看以后 ...

  9. 爬取QQ音乐(周杰伦)

    首先呢,我们打开QQ音乐搜索周杰伦 https://y.qq.com/portal/search.html#page=1&searchid=1&remoteplace=txt.yqq. ...

最新文章

  1. 博客重构 / Blog Refactoring
  2. redis 基础操作学习
  3. python函数和方法概念_第48p,什么是函数?,Python中函数的定义
  4. 【干货】吴甘沙:你是数据,我即生意
  5. @SpringBootApplication揭秘
  6. FreeEIM 在盛大的开幕仪式结束之后
  7. 彻底搞清 Flink 中的 Window 机制
  8. Day6 数据清洗(2)
  9. android 支付宝 地图,利用百度地图实现支付宝“到位”功能(地图模式)
  10. Asp 解析 XML并分页显示
  11. 在 js 中应用 订阅发布模式(subscrib/public)
  12. Java程序员转行可以做什么?程序员
  13. 企微开发SDK下载地址
  14. Android 按钮点击设置静音
  15. 专访中国数据科技集团CEO林道进:把无序变有序 让数据更有价
  16. 【转载】KAB春纳 | 一同乘风破浪吧
  17. 小程序上传头像图片裁剪
  18. 아프리카 BJ 박현서,
  19. MBR膜生物反应器,生物反应器原理-世来福
  20. 使用PostgreSQL以正确的顺序获取名称

热门文章

  1. PDPS软件:机器人仿真动画视频输出功能介绍与使用方法
  2. Match Points CodeForces 1156C 二分答案
  3. java中文分词的简单实现
  4. qt 获取当前程序运行路径_Qt 程序获取程序所在路径、用户目录路径、临时文件夹等特殊路径的方法...
  5. 矩阵的对数运算公式_2021高考总复习数学对数与对数函数
  6. 《Windows驱动开发技术详解》学习笔记
  7. python 多态app_多态简介 | Python从入门到精通:高阶篇之三十三-阿里云开发者社区...
  8. Android多点触控揭秘
  9. ROCKCHIP PWM模块开发指南
  10. 实操石英钟改5V充电器供电