查看: 163|回复: 0

高并发请求的缓存设计策略

[复制链接]

该用户从未签到

发表于 2019-11-7 18:46:52 | 显示全部楼层 |阅读模式
前几天,我司出了个篓子。当时正值某喜闻乐见的关键比赛结束,一堆人打开我司app准备看点东西,结果从来没有感受到过这么多关注量的该功能瞬间幸福到眩晕,触发了熔断,结果就是大量兴致冲冲打开app准备看该比赛结果的人被迫刷了十分钟三天前的野外跑酷,负责内容的人火大到直接骂娘。
虽然这个业务不是我负责,但是也跟相干的人聊了下情况,感慨了一下,于是有了这一篇文章
1.为何必要缓存?

在高并发请求时,为何我们频仍提到缓存技术?最直接的缘故起因是,目前磁盘IO和网络IO相对于内存IO的成百上千倍的性能劣势。
做个简单计算,如果我们必要某个数据,该数据从数据库磁盘读出来必要0.1s,从交换机传过来必要0.05s,那么每个请求完成最少0.15s(固然,事实上磁盘和网络IO也没有这么慢,这里只是举例),该数据库服务器每秒只能响应67个请求;而如果该数据存在于本机内存里,读出来只必要10us,那么每秒钟可以或许响应100,000个请求。
通过将高频使用的数据存在离cpu更近的位置,以减少数据传输时间,从而提高处理服从,这就是缓存的意义。
2.在哪里用缓存?

统统地方。例如:

  • 我们从硬盘读数据的时候,其实操纵系统还额外把附近的数据都读到了内存里
  • 例如,CPU在从内存里读数据的时候,也额外读了很多数据到各级cache里
  • 各个输入输出之间用buffer生存一批数据统一发送和接受,而不是一个byte一个byte的处理
上面这是系统层面,在软件系统设计层面,很多地方也用了缓存:

  • 浏览器会缓存页面的元素,这样在重复访问网页时,就避开了要从互联网上下载数据(例如大图片)
  • web服务会把静态的东西提前部署在CDN上,这也是一种缓存
  • 数据库会缓存查询,以是同一条查询第二次就是要比第一次快
  • 内存数据库(如redis)选择把大量数据存在内存而非硬盘里,这可以看作是一个大型缓存,只是把整个数据库缓存了起来
  • 应用程序把近来频频计算的结果放在本地内存里,如果下次到来的请求还是原请求,就跳过计算直接返回结果
3.本次事故分析

回到本文开始的问题上,该系统是怎么设计的呢?底层是数据库,中间放了一层redis,前面的业务系统所需的数据都直接从redis里取,然后计算出结果返回给app;数据库和redis的同步另外有程序保证,避免redis的穿透,防止了程序里出现大量请求从redis里找不到,于是又一窝蜂的去查数据库,直接压垮数据库的情况。从这个角度讲,其实这一步是做的还可以的。
但是这个系统有两个问题:
1.业务系统必要的数据虽然都在redis里,但是是分开存放的。什么意思呢,好比我前台发起一个请求,背景先去redis里取一下标题,然后再取一下作者,然后再取一下内容,再取一下评论,再取一下转发数等等……结果前台一次请求,背景要请求redis十频频。高并发的时候,压力一下被放大十几倍,redis响应、网络响应必然会变慢。
2.其实做业务的那波人也意识到了这个情况可能发生,以是做了熔断机制,另起了一个缓存池,里面放了一些备用数据,如果主业务超时,直接从缓存池里取数据返回。但是他们设计的时候没想周全,这个备选池的数据逾期时间设计的太长了,里面居然还有三天前更新进去的数据,最终导致了一大波用户刷出来三天前的野外生态小视频……
说到这,不知道读者有没有意识到他们最致命的一个问题:这个业务系统完全没有考虑本地缓存(也就是在业务服务器内存里做缓存)。好比像我们这种app,一旦大量用户同一时间涌进来,必定都是奔着少数几个内容去的,这种特别集中的高频次极少量数据访问,又不必要对每个用户做特化的,简直就是在脸上写上“请缓存我”。
这时候,如果能在业务端做一层本地缓存,直接把算好的数据本地存一份,那么就会极大减少网络和redis的压力,不至于就地触发熔断了。
4.浅谈缓存的那些坑

缓存很有用,但是缓存用不好也会埋很多坑:
缓存穿透

缓存穿透是说收到了一个请求,但是该请求缓存里没有,只能去数据库里查询,然后放进缓存。这里面有两个风险,一个是同时有好多请求访问同一个数据,然后业务系统把这些请求全发到了数据库;第二个是有人恶意构造一个逻辑上不存在的数据,然后大量发送这个请求,这样每次请求都会被发送到数据库,可能导致数据挂掉。
怎么应对这种情况呢?对于恶意访问,一个思路是事先做校验,对恶意数据直接过滤掉,不要发到数据库层;第二个思路是缓存空结果,就是对查询不存在的数据仍然记录一条该数据不存在在缓存里,这样能有用的减少查询数据库的次数。
那么非恶意访问呢?这个要结合缓存击穿来讲。
缓存击穿

上面提到的某个数据没有,然后好多请求都被发到数据库其实可以归为缓存击穿的范畴:对于热点数据,当数据失效的一瞬间,所有请求都被下放到数据库去请求更新缓存,数据库被压垮。
怎么防范这种问题呢?一个思路是全局锁,就是所有访问某个数据的请求都共享一个锁,得到锁的谁人才有资格去访问数据库,其他线程必须等待。但是现在的业务都是分布式的,本地锁没法控制其他服务器也等待,以是要用到全局锁,好比用redis的setnx实现全局锁。
另一个思路是对即将逾期的数据主动刷新,做法可以有很多,好比起一个线程轮询数据,好比把所有数据划分为不同的缓存区间,定期分区间刷新数据等等。这第二个思路又和我们接下来要讲的缓存雪崩有关系。
缓存雪崩

缓存雪崩是指好比我们给所有的数据设置了同样的逾期时间,然后在某一个汗青性时候,整个缓存的数据全部逾期了,然后瞬间所有的请求都被打到了数据库,数据库就崩了。
办理思路要么是分治,划分更小的缓存区间,按区间逾期;要么是给每个key的逾期时间加个随机值,避免同时逾期,达到错峰刷新缓存的目标。
缓存刷新

说到刷新缓存,其实也有坑的。好比我之前的一份工作里,有一次大活动,正是风起云涌的时候,所有的广告位突然都变空白了。厥后追查缘故起因,所有的广告素材都在缓存里,然后起了个程序,专门负责刷新缓存,每次把当前的素材全量刷新。
坏就坏在这个全量上。因为大活动的时候流量极大,广告更新压力也很大,把负责提供更新素材的程序压崩了。刷新缓存的程序在请求时,收到了一个返回结果null。接下来就喜闻乐见了,刷新程序根据这个null,清空了整个缓存,所有广告素材都失效了。
总之,想要做好高并发系统的缓存,就要考虑到各种边角情况,警惕设计,任何细小的疏忽都可能导致系统崩溃。
末了,欢迎关注我的公众号,一起交换技术,或者职业生活?

相关技术服务需求,请联系管理员和客服QQ:2753533861或QQ:619920289
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

帖子推荐:
客服咨询

QQ:2753533861

服务时间 9:00-22:00

快速回复 返回顶部 返回列表