你知道如何用多级缓存提升秒杀系统的服务性能吗
Posted 技术管理者训练营
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你知道如何用多级缓存提升秒杀系统的服务性能吗相关的知识,希望对你有一定的参考价值。
在秒杀系统中,活动信息数据请求量很大,属于热点数据。如果热点数据每次都从 DB 中读取,会给 DB 带来庞大的压力,导致性能大幅下降。所以,我们需要用缓存来提升热点数据的访问性能,比如将活动信息数据在浏览器的缓存中保存一段时间。但缓存中的热点数据通常具有时效性,比如当后端活动数据有更新的时候,浏览器缓存中的原活动信息就会失效,进而导致缓存穿透问题。为此,我们通常会采用多级缓存来避免单级缓存穿透,提升服务性能。
什么是多级缓存
多级缓存通常是由多种不同容量、不同性能的存储器按顺序组成,最有名的是存储器金字塔。
在存储器金字塔中,从上往下看,各层存储器离计算单元越来越远,性能和成本从高到低,容量从小到大。其中的每一层存储器,都是其下一层存储器的缓存,如 L3 缓存是内存的缓存,内存是硬盘的缓存。那么,这个多级缓存各层存储器的性能如何呢?上层的寄存器、L1 缓存、L2 缓存是位于 CPU 核内的高速缓存,访问延迟通常在 10 纳秒以下。L3 缓存是位于 CPU 核外部但在芯片内部的共享高速缓存,访问延迟通常在十纳秒左右。高速缓存具有成本高、容量小的特点,容量最大的 L3 缓存通常也只有几十MB。本地内存是计算机内的主存储器,相比 CPU 芯片内部的高速缓存,内存的成本要低很多,容量通常是 GB 级别,访问延迟通常在几十到几百纳秒。内存和高速缓存都属于掉电易失的存储器,如果机器断电了,这类存储器中的数据就丢失了。本地磁盘是计算机内的辅助存储,负责存储一些希望掉电不丢失的数据,主要有机械磁盘和固态硬盘。它的容量通常在几百 GB ,大的达到了 TB。固态硬盘访问延迟通常在几十到几百微秒,而机械硬盘通常在十几到几十毫秒。网络内存和网络磁盘属于外部存储,通常用于多个计算机之间共享数据。比如 Redis 属于网络内存存储, NAS 文件系统和 mysql 之类的关系型数据库属于网络磁盘存储。网络内存和本地磁盘通常用来当作网络磁盘存储的缓存。
那么,秒杀系统的多级缓存是如何设计的呢?
秒杀系统包含动态数据和静态数据,它的多级缓存包括浏览器端缓存和服务器端缓存。其中浏览器端缓存分为本地内存缓存和本地磁盘缓存,服务器端缓存有本地内存缓存、网络内存缓存。下面我给你介绍下各部分缓存的原理,以及这么设计的原因。
1. 浏览器本地内存缓存
为什么浏览器端需要有内存缓存呢?回想下,浏览器端是如何获取秒杀活动信息的呢?当进入秒杀活动页的时候,浏览器通过 javascript 发起 HTTP 请求,从秒杀接口服务获取活动信息。一个专题活动,一旦上线,在活动期间是不会随意变更的。所以,活动信息是可以在内存里缓存一段时间的。在浏览器端,活动信息可以缓存多久?建议缓存到当前场次活动结束之前。也就是说,当活动场次因为某场活动结束而切换时,应该从后端获取新的活动数据并更新内存缓存。为什么呢?因为如果运营发现后面某场活动配错了,可以在活动开始前修改该活动信息,在场次切换的时候,浏览器将更新到最新的活动信息。另外,浏览器的内存缓存还用于缓存页面渲染的结果,比如从后端获取到商品图片后,将其渲染到页面上展现给用户。
总的来说,浏览器的内存缓存是有时效性的,浏览器内存中缓存的数据会随着页面渲染、关闭而更新、丢弃。
2. 浏览器本地磁盘缓存
那什么情况下需要用到浏览器的本地磁盘缓存呢?前面提到的商品图片的渲染结果是缓存在内存中的,假如用户将活动页面关闭再打开,如果重新从服务端下载所有图片并渲染出来,图片的渲染速度会非常慢。怎么办呢?已经生成的商品图片文件数据是不会随意变动的,所以浏览器端没必要每次都从后端下载图片。但是,也不能将所有图片都缓存到内存里。因为缓存所有的商品图片将会占用大量系统内存,导致系统变慢,影响用户体验。内存通常适合缓存一些比较小的图片,比如图标、缩略图之类的。由于每张商品图片大小在几百 KB 以上,前端通常会采用一种叫作 “懒加载” 的技术——只加载页面可见区内的图片,这么做既可以提高页面首屏渲染速度,还能有效减少浏览器内存的占用。
那浏览器应当如何缓存商品图片呢?当后端给浏览器端返回图片的时候,可以通过一些参数,告诉浏览器端将图片在本地磁盘上缓存一段时间。具体我们可以在后端返回的 HTTP 头中加上浏览器缓存相关参数,如下图所示:
上图 HTTP 返回头中的 age、cache-control、etag、expires、last-modified 便是用来告诉浏览器应该如何缓存该文件。这些参数中,age、cache-control、expires、last-modified 是用来告诉浏览器该文件已经缓存多久了、最多缓存多久、什么时候过期、上一次修改的时间。etag 是怎么用的呢?etag 是由服务端生成的一个唯一标识,当文件的本地缓存到期后,浏览器会将该文件的 etag 发给服务端。服务端如果校验自己本地的 etag 与浏览器发过来的 etag 不一致,则会将最新的文件数据返回给浏览器并告诉它缓存该数据;如果一致,服务端只会返回 HTTP 头,告诉浏览器端继续用浏览器本地缓存,而不会返回实际的数据。由于 HTTP 头通常要比文件数据小很多,在文件没有变更的情况下,通过 etag 能节省很多网络流量,有效提升访问性能。
除了商品图片,为了减少服务端压力,提升服务端性能,秒杀页面用到的 css、js 等静态资源文件,也是通过上述方法缓存在浏览器本地的。
3. 服务端本地内存缓存
前面提到了秒杀活动信息是从秒杀接口服务获取的,那秒杀接口服务又是如何存储活动信息的呢?在做需求分析和总体架构设计的时候我提到过,秒杀活动信息是由秒杀管理后台配置并存储到数据库中的,而数据库最终会将数据存储到磁盘上。假如活动期间每秒一千万次请求都直接从数据库中取数据,你觉得数据库能扛住吗?也许,当数据库资源足够的时候能扛住,但这意味着要投入很大的成本来建设一个能扛住千万并发的数据库。那么,有没有办法用相对较小的成本来提升服务端数据访问性能呢?有,那就是利用服务端本地内存缓存。
对于 Go、java、C/C++ 等静态类型语言来说,最大的好处就是能将热点数据缓存在程序内存中,以便充分利用本地内存的高速读写能力,大幅提升数据访问的性能。这个性能提升多少?打个比方,如果从数据库加载活动信息数据,即使在并发压力不大的情况下,网络延迟加上数据库处理延迟,整个延迟可能达到 10 毫秒以上。如果用本地内存缓存,整个加载过程能降到 100 纳秒以下,也就是低于 10 毫秒的 1/100000,性能提升 100000 倍以上!
当然,本地内存缓存是需要设置有效时间的。当有效时间过期,会有部分请求发生缓存穿透。流量小的时候问题不大,一旦并发量达到千万,哪怕是 1/100 的流量穿透到了数据库,数据库也可能被压垮。怎么解决呢?配置类的数据格式比较固定,数据量比较小,我们可以在程序内部使用定时器,定时从下游数据库中获取最新数据,更新到本地内存缓存中。如果说缓存穿透在软件正常运行时有可能发生,那么有一种情况,缓存穿透必定会发生,那就是在服务启动或重启的时候。 你可以想象一下,程序刚启动的时候,本地内存缓存为空,此时突然有大量请求涌进来,数据库一定能扛住吗?那该怎么办呢?我们可以在本地内存缓存与数据库之间加一层网络内存缓存,为秒杀接口服务各节点提供共享缓存。我上面提到的定时任务,就可以从网络内存缓存中获取最新的数据,并更新到本地内存缓存中,避免本地内存缓存穿透。
4. 服务端网络内存缓存
前面提到了,在秒杀接口服务正常运行时,可以通过程序内部定时任务,来定时更新本地内存缓存。但 Redis 是个通用的开源软件,它内部没有从其他存储同步数据的逻辑,那我们要如何将数据同步到 Redis 里呢?答案是利用秒杀系统的管理后台服务。具体来说,我们可以先在管理后台系统发布修改后的活动信息,然后将最新的活动信息写入到 Redis 中。另外,为了防止 Redis 挂掉重启后缓存数据丢失,管理后台服务中可以实现一个定时器,定时将已发布的活动配置从数据库中取出,并写入到 Redis 中。由于管理后台是实时将最新数据写入到 Redis 中的,并且有定时任务作兜底方案,这也保障了在 Redis 正常运行的过程中不会有缓存穿透的问题。
以上便是秒杀系统多级缓存中各缓存的原理。多种缓存配合使用,最终落到数据库上的请求可能每秒只有几十次,而系统性能和稳定性都可以得到大幅提升。
以上是关于你知道如何用多级缓存提升秒杀系统的服务性能吗的主要内容,如果未能解决你的问题,请参考以下文章