缓存架构,说白了就是利用各类手段,来实现缓存,从而下降服务器,乃至数据库的压力。html
这里把以前提出的缓存架构的技术分类放出来:前端
前面的《缓存中间件-缓存架构的实现(上)》已经简单说明了浏览器缓存,CDN缓存,负载层缓存。此次将会继续阐述应用层缓存,外部缓存,数据库缓存。mysql
应用层的缓存,每每用户的请求最终达到了应用服务器,可是未达到数据库,其涉及应用服务器的具体开发。redis
之因此将Etag技术放在应用层缓存,是由于用户的请求一定达到应用层。算法
Etag的意思就是,若是连续两次请求的请求内容是一致的,那么两次响应也应该是一致的。那么第一次请求的响应,就能够充当第二次请求的响应。sql
固然实际业务中,也存在两次请求一致,可是响应不一致(如都是查询银行余额,可是并不同,可能两次操做中间,工资到帐了)。这就涉及到缓存的数据一致性问题,后面会提到。这里再也不深刻。数据库
那么应用服务器怎么判断两次请求一致呢。它能够经过两次请求的hash,进行对比判断。其中涉及HTTP协议,如304状态码,请求协议头If-None-Match字段,响应协议头Etag字段。后端
服务端已经作好了对应的开发与设置(如Spring的ShallowEtagHeaderFilter())。数组
上述实际上是功能逻辑,若是按照代码逻辑,其实应该这样说:浏览器
准确地说,这应该是HTTP协议提供的缓存方案,而不只仅只是ETag。由于ETag仅仅与HTTP协议的五大条件请求首部中的If-None-Match与If-Match两个首部相关。除此以外,还有If-Modified-Since,If-Unmodified-Since,If-Range三个条件请求首部。若是之后有机会专门写一篇有关HTTP协议的博客。迫切的小伙伴,也能够翻阅《HTTP权威指南》一书的第七章(尤为是7.8)。
实际应用部分,主要有两点须要说起。
PS:部分人认为只须要Last-Modified-Since便可,可是仅使用Last-Modified-Since存在如下问题:
ThreadLocal是什么,我就不在此解释了。不了解的小伙伴,能够这样理解:ThreadLocal就是一个类中的静态Map,其key就是执行线程(调用类实例的线程)的name,而value就是调用位置设置的值。
在我以前接收的IOT项目中,终端系统经过传感器数据读取程序与传感器配置,得到原始数据(包括原始监测值,以及配置表中对应配置(如硬件标识,报警阈值等))。可是原始数据采集后,会进行数据清洗,数据报警评估,数据保存等多个操做。可是其中的数据清洗并不涉及硬件标识,与报警阈值等。因此采用ThreadLocal来保存对应数据(硬件配置),避免方法接口的污染。固然,后来因为该流程并不都是有先后顺序要求,因此添加了事件监听,进行异步解耦,下降系统复杂度。
Guava表明着应用级缓存,更准确说是单JVM实例缓存。在原单机系统时,咱们每每并非采用Redis这样的分布式缓存(除非是但愿利用其数据处理,如GEO处理,集合处理等),而是采用GuavaCache或自定义缓存(自定义缓存的设计,后面会有一篇专门的博客)。
外部缓存的一个重要表明,就是Redis,Memcache这样的分布式缓存中间件。固然外部缓存,你要把文件系统等划分进来,也不是不行,只要能够知足对缓存的定义便可。
这里以Redis为例。
Redis做为当下最为流行的分布式缓存中间件,其应用能够说是很是普遍的,也是我很是喜欢使用的一种分布式缓存中间件。其是一个开源的,C语言编写的,基于内存,支持持久化的日志型,KV型的网络程序。
在我以前接手过的某综合系统(涵盖社交,在线教育,直播等),其Session服务器是经过Redis进行支撑的。经过将<SessionId,Session>的方式,存储在Redis,而SeesionId会保存在用户的Cookie中(至于某些小伙伴担忧的Cookie禁用问题,这就涉及Cookie的知识内容了。Cookie会保存在URL中)
再举一个例子(Redis的应用场景太多了)。以前负责的IOT项目中,其中控系统的报警模块有这么一个需求:同一个终端的同一个传感器在30min中,只报警一次,避免报警刷屏的现象。而中控系统已经采用了Redis(中控系统是能够集群部署,确保可用性,避免性能瓶颈),因此利用Redis的集合特性与expire特性,进行了对应的缓存设计。这个在以后会专门写一篇博客,进行阐述。
这里说的数据库,是指Mysql,Oracle这样的数据库,而不是Redis这样的。
这里就以Mysql举例,这个你们应该是最熟悉的。
Mysql缓存机制,就是缓存sql文本,及其对应的缓存结果,经过KV形式保存到Mysql服务器内存中。以后Mysql服务器,再次遇到一样的sql语句,就会从缓存中直接返回结果,而不须要再进行sql解析,优化,执行。
可能某些人担忧,若是数据改变了,而请求的语句是select * from xxx,那不就一直拿到旧数据了嘛。放心,mysql有这方面的处理,当对应表的数据有所修改,那么使用了这个表的数据的缓存就所有失效。因此对于常常变更的数据表,缓存并无太大价值。
在我以前接收的IOT项目中,不管是终端系统,仍是中控系统,每每都存在大数据量的数据查询,单次的数据查询每每涉及万级,十万级数据的查询,而且可能频繁查询(就是屡次刷新页面数据)。
一方面,我经过批量写入(下降数据库链接的占用频次),下降数据库对应数据表的修改频次(从原来的几秒一次,变为一分钟一次)。另外一方面,进行数据库缓存相关配置,确保在一分钟内的数据库不须要进行索引操做与硬盘操做,直接返回内存内的结果。从而有效提升了前端页面数据展现效果。
固然后续,我为了针对这一特定业务场景与需求,对业务稍作了调整,从而大大提升了数据查询效果,大幅下降应用系统资源消耗(这个我会专门写一篇博客,甚至专门开一个系列,用来描写这种粒度的特定业务场景的方案设计)。
以前有人私信我,认为布隆过滤器应该归类于缓存架构的一部分。
我开始认为这有必定道理,由于布隆过滤器确实涉及数据的缓存,它须要以往数据的记录,来实现。可是后来我想了想,布隆过滤器并不该该划分为缓存中,由于布隆过滤器是基于缓存的,应用缓存的。就像你能够说Redis缓存属于缓存架构的一部分,可是你不能够说调用缓存的应用服务器属于缓存。因此最终,我并无将布隆过滤器划分为缓存的一部分。而是将它做为一种很是有意思的过滤器,一种限流方式,一种安全手段等。
不过做为扩展,这里简单说一下布隆过滤器。说白了,就是利用Hash的散列映射特性,进行数据过滤。如我在应用中设置一个数组Array(其全部值都为0),其长度为固定的10W。我针对每一个用户计算一个hash值,并将这个hasn值对10W进行取余操做,得到index值(如1000)。我将Array中第index位置的value设置为1。这样放在生产环境后,若是有一个用户,其计算出来的index在Array中对应位置的值为0,则说明这个用户在系统中不存在(固然,若是是1,也并不能就说明其就是系统的用户,毕竟存在哈希冲突与取余冲突,不过几率较低)。经过这样的手段,有效避免无效请求等。
后续可能会专门写一篇有关布隆过滤器的博客。
以上就是缓存架构相关的知识了。固然,这些知识都是粒度比较大的,虽然我举了一些实际例子,可是须要你们针对具体应用场景,进行调整应用。另外,这些知识都是比较通用的。可能在特定业务场景下,还有一些方案没有列在这里。最后,没有最好的技术,只有最合适的技术。这里的许多技术都须要必定的业务规模(数据量,请求数,并发量等),采用比较好的性价比,须要你们仔细考虑。
若是有什么问题或者想法,能够私信或@我。
愿与诸君共进步。