随着存储技术飞速的发展,如今我们系统的数据量不知不觉发生了指数级别的增长,在数据爆发式增长的情况下,企业与用户对于应用程序的响应性能指标要求也逐步增高,这也导致对面海量数据的情况下,为了保证优秀的产品体验,数据库存储也面临着非常大的挑战。
举个例子,当我们使用MySQL
作为数据持久化存储引擎时,免不了需要通过磁盘I/O来将数据加载到内存并处理,从而返回给客户端。
磁盘I/O是影响数据库性能的一个关键因素。大量的磁盘读取会导致查询响应时间变慢,影响应用程序的性能,尤其是数据量庞大且在高并发的情况下。
或许你可能可以通过优化SQL查询、分片分区等方式来减少磁盘IO的读写,但避免不了随着并发的增加,存储数据量的增多,数据库的磁盘I/O逐渐成了系统的瓶颈。
为了降低请求响应时间,提升我们系统整体的性能,使用缓存成为了我们必要的工具。
一、什么是缓存
缓存实际上是存储数据的一种组件,它的作用在于能够快速的获取数据并返回。
通常来说缓存的数据会放在内存中,相比于磁盘读取,在内存中获取数据是更快的,可能一次内存寻址大概需要100ns左右,但一次磁盘查找则可能需要10ms左右,可见从内存中获取数据非常之快。
二、缓存分类
在日常开发中,常见的缓存主要有三类:
- 静态缓存
- 本地缓存
- 分布式缓存
静态缓存
静态缓存通常来说是指将不经常变化的数据或内容存储在缓存中,以便快速访问。静态缓存通常用于存储静态资源,这些资源数据一般来说,在一定时间内不会发生变化,或者发生变化的频率较低。
在Web
页面,我们常常可以将HTML
、CSS
、JavaScript
文件、图片等静态资资源部署到Nginx
上,通过这种方式减少对于后台应用服务器的压力,从而降低延迟和资源消耗。
例如一些游戏官网的实现,游戏官网通常来说在进行一次版本更新后,页面数据才会发生一些变化,或许我们可以将这些页面数据持久化到数据库,在前端加载时通过调用接口获取到数据并展示,但这样可能会对数据库造成一定的负担。
通过了解游戏官网的数据特性,我们完全可以通过在后台录入一些准备好的文章与图片,并将其渲染成静态页面,放置在所有的前端Nginx
或者Squid
等Web
服务器上,这样在用户在访问官网时会优先访问Web
服务器上的静态页面,从而减少后台服务器的压力,并且一段时间到版本更新后,这些官网数据才会进行更新,此时我们可以重复同样的操作重新生产静态页面数据。
本地缓存
本地缓存是指在应用程序的本地环境中存储数据,以便快速访问。与分布式缓存(如Redis、Memcached)不同,本地缓存通常是存储在应用程序的内存中,适用于单个实例或单个用户的场景。例如,我们可能会在程序中初始化一些对象,例如slice、map等,来存放一些配置数据,复杂的计算结果,一些不经常变化的数据库查询结果等,即将这些数据放到本地缓存。
另外,当我们遇到一些极端的热点数据查询时我们可以将这些热点数据加载到应用服务器的代码中,用于阻挡热点查询对于分布式缓存节点或者数据库的压力。
不过,本地缓存也存在缺陷,由于本地缓存的数据是存放在应用服务器中内存之中,而应用服务器通常会部署多台,当数据更新时,我们不能确定哪台服务器本地中了缓存,因此更新或者删除本地缓存也是需要考虑的问题。
如果需要更新本地缓存的数据,可能需要考虑重启服务,或者通过维护一个定时任务来持续更新本地缓存的数据。
分布式缓存
分布式缓存是一种将缓存数据分散存储在多个节点上的缓存系统。它能够支持高并发访问、横向扩展和高可用性,适用于需要处理大量数据和高请求量的应用场景。
分布式缓存通常用来存放一些数据变化较为频繁的动态数据,此外也可以用于解决单一节点缓存的局限性,如内存限制、单点故障等问题。例如,我们日常工作中经常使用的Memcached
、Redis
就是分布式缓存的典型例子。
我们在聊到缓存,都是针对分布式缓存来进行深入与扩展。
三、使用缓存的场景
使用缓存的场景非常之多,例如MySQL
数据的写入、HTTP
缓存机制、视频流的加载等。
对于我们耳熟能详的MySQL
,其对于数据的写入也运用的缓存的思想,简单来说,当我们更新数据库中的一条记录时,如果该数据页加载到了内存中,则直接会将修改内容写到缓冲区的数据页中,并将该数据页标记为脏页,并通过记录redo log
日志来避免修改数据的内容在内存中丢失,然后等待后续将数据刷到磁盘中。
MySQL
中的Buffer Pool
缓冲区则是通过缓存的方式来解决访问数据页随机写的情况,提高数据写入的效率。缓冲区是一块临时存储数据的区域,当经过一定时间或者脏缓冲区比例到达一定阈值时,由单独的线程把脏块刷新到硬盘上。
四、缓存的缺陷
我们可以了解到,使用缓存,能够让我们的系统访问速度得到很大的提升,从而抗住更高的并发请求。然而缓存也存在一定的缺陷。
当我们使用缓存时,也需要考虑实际的业务场景。缓存比较适合于读多写少的业务场景或者数据带有一定的热点属性。当数据有热点属性时,我们能够保证一定的缓存命中率,提高缓存的使用率。
另外,缓存的引入也会带来一定的系统复杂度,同时也会带来数据不一定的风险。当我们在成功更新数据库时,如果缓存更新失败,则缓存中的数据与数据库的数据则会发生不一致,因此,对于缓存的使用我们也需要考虑不同情况下数据不一致的处理方式,在实际业务中评估缓存的使用场景。