为什么80%的码农都做不了架构师?>>>
在高并发的场景下,为了提升接口的响应速度,增大系统的吞吐量,我们一般需要增加缓存设计来提升相关的性能。那么对于缓存设计需要注意些什么呢?这便是本博客讨论的内容。
缓存设计需要从多个方面去考虑,是否需要采用缓存,采用那种缓存组件,缓存设计的数据结构,设计几层缓存,以及采用缓存带来的维护成本,数据不一致的问题,等等,这些都需要作为缓存设计之初考虑的部分内容。而不是简单的,直接上redis来解决问题。下面从以下几个方面来进行相关的探讨。
一、应用场景
首先我们需要确定,我们的应用是否需要缓存?其实很多情况下我们是不需要缓存的。通常情况下,我们的应用如果是健康的,数据分布是均匀的,sql语句是稳定快速返回的,那么在小用户量的情况下,我们直接通过app+db就可以满足要求,并不需要增加一层缓存层来进行提升性能。因为增加缓存层,必定带来的是不同程度的程序的复杂性,增加的成本和风险。但是当我们改善了既有的设计还是不能满足现有的要求,我们可以考虑增加缓存层来提升我们程序的性能。比如,高并发+低延迟的场景。
二、缓存组件
在缓存设计中,我们需要选用缓存的组件。这里缓存组件有几个方向。我们可以从用户发起一个请求到数据库这样一个流程,来考虑哪些节点需要增加缓存。用户请求(浏览器)==>CDN节点==>NGINX负载均衡==>防火墙==>WEB服务器==>应用服务器(APP)==>DB。在这条线路上,我们可以设置缓存的分地方很多,比如CDN节点就是最靠近用户的缓存,WEB服务器之前可以加上一层Varnish来进行数据缓存,APP和DB之间可以增加一层REDIS通用层来进行缓存,APP内置ehcache内存框架来进行缓存,或者我们自己写一个静态的Map来进行缓存都可以。所以缓存的组件是多种多样的,越靠近用户的,缓存的数据变化性越小,越靠近db的,变化越大。
缓存组件的选择还包括分布式缓存组件的选择和集中式缓存组件的选择。这里的分布式的缓存组件是在各个应用节点之上缓存的,相互之间不进行通信,集中式的,缓存组件可以认为是一个大的单机节点(尽管实现可以是分布式的,例如采用分片的形式和节点相互通信来形成一个大容量的组件),这两种类型的组件在容量上面有很大的差别。并且分布式的缓存,由于数据分片的原因,各个节点之间的由于是无状态的,所以节点间数据相互无感知,无法有一个集中组件进行管理,缓存数据可能存在差异性。
三、缓存数据结构的选择
对于选择的不同的缓存组件,需要根据不同的业务场景设计不同的数据结构。例如,Redis中是选择Hash结构还是选择一次Set,一次String,需要根据相关的场景和数据量大小来进行选择。同时也要考虑到大数据量的Hash值的更新操作的便捷性。
四、多级缓存的设计
在使用缓存的时候,通常的场景下并不是一层缓存就满足业务场景和性能的要求。我们要根据场景可能设计多层缓存,从需要缓存数据的不变形、容量大小,可用性。采用不同的组件来组成多层次,多场景的缓存层。相关的统计数据,可以容忍部分延迟,我们可以缓存在varnish上面,价格信息可以缓存在redis中,库存信息我们可能就不进行缓存了。对于使用同一组件,我们也可以进行全量缓存和热点缓存,来满足场景设计的需要。
五、缓存带来的问题以及相关的解决方案
缓存的使用会带来以下几个方面的问题:
1、维护成本的提高
维护成本的提高包括以下几点:
a、学习新组建的学习成本
b、运维缓存组件的成本(例如Redis集群维护的成本,数据迁移,数据复制等等)
c、使用缓存时候,缓存Miss,引发的缓存穿透,缓存雪崩等问题修复带来的程序设计上面的成本。
缓存穿透,可以通过布隆过滤器来进行预防;缓存雪崩,可以通过设计多层缓存来进行预防
2、数据分片之后一致性达成的困难
缓存和数据库之间进行了数据分片,形成了多个阶段之后,必然就会导致一致性问题的产生。这里的一般思路是Cache Aside Pattern,对于Cache Miss 的场景,先查询缓存,没有缓存的话查询db,然后回写缓存。对于缓存更新,先操作数据库,再删除缓存。这样可以降低缓存数据不一致的可能性。
六、总结
对于缓存,不是简单的直接使用或者不使用,我们需要从业务本身触发,结合本身实际的场景,了解好缓存组件的相关情况来进行设计,这样才能形成真正有效的缓存设计。