我们的商城首页卡死了。。。
<h2 id="前言">前言</h2><p>最近我们的商城系统出现一个线上问题,用户访问商城首页的时候要差不多20秒,才返回数据,可以说卡爆了。</p>
<p>到底怎么回事呢?</p>
<h2 id="1案发现场">1.案发现场</h2>
<p>上周四晚上,我们有一个正常的迭代版本按照预期的时候上线。</p>
<p>本次迭代,我所涉及的功能,很快上线,并且测试通过了。</p>
<p>但没法下班,因为项目组其他同事,还有线上问题在紧急处理。</p>
<p>我过去了解了一下情况,用户访问商城首页的时候响应太慢了,要20秒才返回,有用户投诉过来了。</p>
<p>进一步了解之后发现,造成这个问题的根本原因是redis服务器挂了。</p>
<p>为什么会挂呢?</p>
<p>是因为一次性往redis中存储的数据太多了,导致内存不足。</p>
<p>这个商城系统部署到了阿里云上,当时购买了1G的内存空间。</p>
<p>但由于这次上线,有个新功能,需要在商城首页上,按不同的地区,推荐不同的商品。商品还要按不同的分类做区分。</p>
<p>原本商品只有几十万其实不多,但是按地区和分类做区分之后,保存的数据量乘以了几百倍,一下子占用了大量的内存。</p>
<p>redis挂了为什么会导致首页慢呢?</p>
<p>答:因为代码中有业务逻辑,如果从redis中没有获取到数据,或者访问redis失败了,会从数据库中获取。虽说当时是晚上,用户并发量不大,但是直接访问数据库,响应时间一下子下降了很多。<br>
<img src="https://files.mdnice.com/user/5303/db9aeda4-4821-4e30-b62e-86c5bf937750.png"></p>
<h2 id="2如何快速解决问题">2.如何快速解决问题?</h2>
<p>目前的这套方案,先从redis中获取数据,如果失败了,再从数据库中获取。</p>
<p>现在的问题是:redis内存不足,临时解决问题,只能加内存资源了。</p>
<p>因为加内存是最快的,直接加到了4G。如果要改代码,这个功能今天晚上可能没法上线,之前购买的1G的资源确实有点小。</p>
<p>在阿里云上redis加了内存之后,这个问题很快解决了,首页访问速度一下子提升。</p>
<p>但这不是问题的本质。</p>
<h2 id="3复盘">3.复盘</h2>
<p>第二天,我们开始复盘问题。</p>
<p>发现之前的方案有点问题:</p>
<ol>
<li>这次新增的推荐商品功能,保存到redis的数据量太大了,把有些为null值的字段,或者前端用不到的字段也保存到redis中了,数据结构设计不合理。</li>
<li>redis出现问题之后的兜底方案有点问题,如果redis挂了,就直接访问了数据库,导致了用户访问慢的问题。如果是白天用户并发量上来,可能会直接导致数据库挂掉。</li>
</ol>
<p>那么,如何优化呢?</p>
<h2 id="4如何优化">4.如何优化?</h2>
<p>数据结构不合理的问题,可以通过调整数据结构解决,非常容易。</p>
<p>但如果redis挂了该如何处理呢?</p>
<h3 id="41-页面静态化">4.1 页面静态化</h3>
<p>其实对于商城首页,最好的方案是做页面静态化处理。</p>
<p>但由于目前商城的用户并发量,还不算很大,而且如果改成页面静态化,前后端的改动都太大了。</p>
<p>因此,这个方案最先被我们否定了。</p>
<h3 id="42-加本地缓存">4.2 加本地缓存</h3>
<p>为了防止后面再次出现商城首页访问慢的问题,可以在应用服务增加本地缓存。</p>
<p>这样不管redis以后能否正常运行,都不影响商城首页的功能。</p>
<p>但需要考虑一个事情:应用服务的内存是否够用?</p>
<p>显然如果将所有推荐的商品数据,都保存到应用服务的本地内存中,同样可能会导致应用服务的内存不足的问题。</p>
<p>因此,直接加本地内存是不行的。</p>
<h3 id="43-改成mongodb">4.3 改成MongoDB</h3>
<p>使用MongoDB替代Redis保存数据。</p>
<p>Redis: 数据全部存在内存,定期写入磁盘,当内存不够时,可以选择指定的 LRU 算法删除数据。</p>
<p>MongoDB: 数据存在内存,由 linux系统 mmap 实现,当内存不够时,只将热点数据放入内存,其他数据存在磁盘。</p>
<p>显然MongoDB更适合保存大批量的结构化的文档数据。</p>
<p>由于我们之前在做其他功能时,使用过MongoDB,它的性能也是挺不错的。</p>
<p>但如果直接改成从MongoDB中获取数据,商城首页的访问速度可能会有所下降。</p>
<h3 id="44-本地缓存--mongodb">4.4 本地缓存 + MongoDB</h3>
<p>上面说到过的加本地缓存,和使用MongoDB都有各自的优缺点。</p>
<p>为什么不把两种方案结合一下呢?</p>
<p>在本地缓存中保存热点数据,每隔5分钟更新一次。</p>
<p><img src="https://files.mdnice.com/user/5303/32d1e936-9b78-44fc-a055-b9b23648ea2d.png"><br>
用户的请求过来,先从本地缓存中获取推荐商品数据,如果有则直接返回。</p>
<p>如果没有,则从MongoDB获取数据。</p>
<p>这样可以解决性能的问题,也可以解决保存大量的数据。</p>
<h2 id="5兜底方案">5.兜底方案</h2>
<p>上面的说的本地缓存 + MongoDB,基本可以解决redis挂了的问题。</p>
<p>但如果MongoDB挂了该怎么办呢?</p>
<p>这就需要有一套更好的兜底方案。</p>
<h3 id="51-使用apollo配置">5.1 使用Apollo配置</h3>
<p>如果MongoDB挂了,则直接返回Apollo配置中默认数据,默认是北京市东城区的推荐商品数据。</p>
<p>该配置由于在Apollo中,我们可以根据实际情况动态调整。</p>
<p>我们都知道Apollo可以配置成集群模式,是高可用的,一般不容易挂掉。</p>
<p>但它有一个硬伤,就是如果数据并更了,需要人手动调整数据。</p>
<p>没法保证数据的实时性。</p>
<h3 id="52-再从数据库访问数据">5.2 再从数据库访问数据</h3>
<p>如果从MongoDB中获取数据失败了,则直接从数据库中获取数据。</p>
<p>该方案从业务的角度来说,确实没有问题。</p>
<p>但万一真的出现这种情况,同样会出现商城首页访问很慢的问题。</p>
<h3 id="53-再从redis访问数据">5.3 再从redis访问数据</h3>
<p>如果从MongoDB中获取数据失败了,则直接从redis中获取数据。</p>
<p>Redis中只保留热点商品数据。</p>
<p>这也是一种方案,不过要维护两份数据:MongoDB一份,Redis一份。</p>
<p>可能会存在数据不一致的问题。</p>
<h3 id="54-再加一个本地缓存">5.4 再加一个本地缓存</h3>
<p>在从数据库获取数据之后,再加一个本地缓存,保存默认的数据,即:北京市东城区的推荐商品数据。</p>
<p>这个本地缓存,只有在第一次访问数据库时写入,并且有效期是24小时。</p>
<p>相当于在MongoDB和数据库之间,再加了一层默认的本地缓存。</p>
<p>这样就能解决数据库访问慢的问题。</p>
<h3 id="6最终方案">6.最终方案</h3>
<p>经过激烈讨论之后,我们最终选择的方案是:本地缓存+MongoDB+本地默认缓存+数据库。<br>
<img src="https://files.mdnice.com/user/5303/aebceef5-9b65-4852-9e2c-e7df016acd7f.png"><br>
有时候选择的某一个技术方案,是根据当前的业务发展,或者公司现状,资金,资源,人手,技术能力等多方面考虑的。</p>
<p>很多技术问题都没有最完美的解决方案,只有最适合的方案。</p>
<h2 id="最后说一句求关注别白嫖我">最后说一句(求关注,别白嫖我)</h2>
<p>如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。<br>
求一键三连:点赞、转发、在看。<br>
关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。<br>
此外,如果想要加入我的技术交流群,可以添加微信:li_su123</p><br><br>
来源:https://www.cnblogs.com/12lisu/p/17925762.html
頁:
[1]