【缓存框架】设计一套缓存框架需要注意的点
转载:https://blog.csdn.net/sinat_29581293/article/details/51956964
在数据层引入缓存,有以下几个好处:
- 提升数据读取速度
- 提升系统扩展能力,通过扩展缓存,提升系统承载能力
- 降低存储成本,Cache+DB的方式可以承担原有需要多台DB才能承担的请求量,节省机器成本
根据业务场景,通常缓存有以下几种使用方式
- 懒汉式(读时触发):写入DB后, 然后把相关的数据也写入Cache
- 饥饿式(写时触发):先查询DB里的数据, 然后把相关的数据写入Cache
- 定期刷新:适合周期性的跑数据的任务,或者列表型的数据,而且不要求绝对实时性
======================
最近关注了一些缓存框架的特性和实现,包括OSCache、JCS、Ehcache、Memcached等等,公司的两个缓存框架,以及一个标准JSR 107(JCache),发现一些诸多类同的方面。如果你不够熟悉以上,不妨先看看这两篇文章:《OSCache框架源码解析》和《Ehcache详细解读》,再看下面的内容也许会有更多想法。之后再思考,如果要自己去实现一套缓存框架,需要考虑哪些东西?
1、为哪些数据做缓存?
- 模型对象,这在业务逻辑层面最常见。
- 数据库查询结果集。
- 页面缓存、页面片段缓存。
- 运算结果集,尤其对于幂等性服务。
- 外部接口查询结果。
2、缓存框架的核心:
缓存生命周期管理,很多重要特性都是围绕它来展开的。
举例:
3、重要特性,这些特性不一定全部要具备,但是多数都要包含:
- 一致性选择。缓存框架的设计必须首先考虑这一点。通常我们见到的缓存框架都是最终一致性的,允许获取数据有一定的延迟窗口。一致性关系到缓存的生命周期,是缓存的核心理念之一。
- 分级存储。也和缓存生命周期密切相关。至少应包括内存和磁盘两级存储,有些缓存框架包含组网内部节点的分级等等,允许用户管理缓存数据在不同级别存储中的跃迁。分级存储还包括对存储数据的管理,以提高数据获取的效率;包括跃迁策略的定制,比如在某一级满足怎样的超时策略可以发生向下跃迁。
- 规约配置,默认配置。可以支持XML、properties、DSL编程等等多种配置方式,但是最重要的是,要提供一个默认配置,允许用户在简单配置或者零配置的情况下使用缓存。
- 集群、分布式,这意味着一定的伸缩性。包括内部通信协议选择,比如节点之间使用JMS、RMI或RESTful方式通信等等;包括节点热部署和节点发现能力,这通常都使用组播消息来实现;包括集群的方式,是Server-Client群、消息总线方式还是节点对等,等等。
- 定制扩展性。尤其是淘汰算法、事件监听、持久化策略等等,都要允许用户方便地自定义。
4、相对较次要的特性:
- 统计能力。包括各级缓存命中情况统计,生命周期长度统计。
- 批量接口、异步接口。包括缓存分组能力。
- 缓存数据存储校验。
- Web支持。特指Web容器中,对于页面存储的额外支持。
- 免锁数据处理。
- 缓存状态监控。
- 无侵入式拦截,注解编程支持。
- 运行时参数调整。
……
5、核心模型应该包括哪些?
- CacheManager:模型管理对象,可以是多实例的,也可以是单实例的。
- Cache:通过CacheManager创建出来的缓存容器,内部包含了真正的缓存承载体,至少开放add/remove/flush等接口。
- CacheMap:真正的缓存承载体,大致上都是一个Map,各种类型的Map。
- CacheEntity:缓存条目,相当于CacheMap里面的每一条Entry。
- CacheEvent:缓存事件,比如CacheEntity的创建、更新、删除等等。
- CacheEventListener:缓存事件相应的监听器。
- CacheEvictionAlgorithm:缓存淘汰算法,常见的有LRU、LFU、FIFO等等。
--------------------------------------------------------------------------------------------------------------------------------
2012-6-10:
任何一个缓存框架,它要解决什么样的问题?
数据的访问、存取、计算太慢、太不稳定、太消耗资源,同时,这样的操作存在重复性。因此希望有这样一种中间媒介,放置在其间,只保存自己关心的数据,而不关心具体数据逻辑内容,对于重复性的操作给出响应。对于数据和服务的使用者,它是透明的。
从请求和数据流向的角度看,一个完整的缓存框架应该包括这样几个部分:
- 操作捕获
- 缓存数据存储
- 缓存数据读取
- 缓存数据流动
因此缓存框架的功能都是围绕数据展开的,它的核心就是缓存数据的整个生命周期。
但是其中每一项都可以拆分和解耦成许多部分,以缓存数据存储为例,可以拆分成:
- key生成
- value封装、元数据封装
- 索引生成
- 文件结构生成
- 序列化、反序列化
- 淘汰算法
- 过期检查
- 存储数据预处理
- 持久化媒介
……转自:http://raychase.iteye.com/blog/1549570
但是看的到的不同实现越多,越发发现难以跳出这个思维惯性的圈子了。
===============================缓存在高并发场景下常见的问题=============================
转载:https://www.cnblogs.com/dinglang/p/6133501.html
缓存一致性问题
当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。这就比较依赖缓存的过期和更新策略。一般会在数据发生更改的时,主动更新缓存中的数据或者移除对应的缓存。
缓存并发问题
缓存过期后将尝试从后端数据库获取数据,这是一个看似合理的流程。但是,在高并发场景下,有可能多个请求并发的去从数据库获取数据,对后端数据库造成极大的冲击,甚至导致 “雪崩”现象。此外,当某个缓存key在被更新时,同时也可能被大量请求在获取,这也会导致一致性的问题。那如何避免类似问题呢?我们会想到类似“锁”的机制,在缓存更新或者过期的情况下,先尝试获取到锁,当更新或者从数据库获取完成后再释放锁,其他的请求只需要牺牲一定的等待时间,即可直接从缓存中继续获取数据。
缓存穿透问题
缓存穿透在有些地方也称为“击穿”。很多朋友对缓存穿透的理解是:由于缓存故障或者缓存过期导致大量请求穿透到后端数据库服务器,从而对数据库造成巨大冲击。
这其实是一种误解。真正的缓存穿透应该是这样的:
在高并发场景下,如果某一个key被高并发访问,没有被命中,出于对容错性考虑,会尝试去从后端数据库中获取,从而导致了大量请求达到数据库,而当该key对应的数据本身就是空的情况下,这就导致数据库中并发的去执行了很多不必要的查询操作,从而导致巨大冲击和压力。
可以通过下面的几种常用方式来避免缓存传统问题:
- 缓存空对象
对查询结果为空的对象也进行缓存,如果是集合,可以缓存一个空的集合(非null),如果是缓存单个对象,可以通过字段标识来区分。这样避免请求穿透到后端数据库。同时,也需要保证缓存数据的时效性。这种方式实现起来成本较低,比较适合命中不高,但可能被频繁更新的数据。
- 单独过滤处理
对所有可能对应数据为空的key进行统一的存放,并在请求前做拦截,这样避免请求穿透到后端数据库。这种方式实现起来相对复杂,比较适合命中不高,但是更新不频繁的数据。
缓存颠簸问题
缓存的颠簸问题,有些地方可能被成为“缓存抖动”,可以看做是一种比“雪崩”更轻微的故障,但是也会在一段时间内对系统造成冲击和性能影响。一般是由于缓存节点故障导致。业内推荐的做法是通过一致性Hash算法来解决。这里不做过多阐述,可以参照其他章节
缓存的雪崩现象
缓存雪崩就是指由于缓存的原因,导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃,发生灾难。导致这种现象的原因有很多种,上面提到的“缓存并发”,“缓存穿透”,“缓存颠簸”等问题,其实都可能会导致缓存雪崩现象发生。这些问题也可能会被恶意攻击者所利用。还有一种情况,例如某个时间点内,系统预加载的缓存周期性集中失效了,也可能会导致雪崩。为了避免这种周期性失效,可以通过设置不同的过期时间,来错开缓存过期,从而避免缓存集中失效。
从应用架构角度,我们可以通过限流、降级、熔断等手段来降低影响,也可以通过多级缓存来避免这种灾难。
此外,从整个研发体系流程的角度,应该加强压力测试,尽量模拟真实场景,尽早的暴露问题从而防范。
缓存无底洞现象
该问题由 facebook 的工作人员提出的, facebook 在 2010 年左右,memcached 节点就已经达3000 个,缓存数千 G 内容。
他们发现了一个问题---memcached 连接频率,效率下降了,于是加 memcached 节点,
添加了后,发现因为连接频率导致的问题,仍然存在,并没有好转,称之为”无底洞现象”。
目前主流的数据库、缓存、Nosql、搜索中间件等技术栈中,都支持“分片”技术,来满足“高性能、高并发、高可用、可扩展”等要求。有些是在client端通过Hash取模(或一致性Hash)将值映射到不同的实例上,有些是在client端通过范围取值的方式映射的。当然,也有些是在服务端进行的。但是,每一次操作都可能需要和不同节点进行网络通信来完成,实例节点越多,则开销会越大,对性能影响就越大。
主要可以从如下几个方面避免和优化:
- 数据分布方式
有些业务数据可能适合Hash分布,而有些业务适合采用范围分布,这样能够从一定程度避免网络IO的开销。
- IO优化
可以充分利用连接池,NIO等技术来尽可能降低连接开销,增强并发连接能力。
- 数据访问方式
一次性获取大的数据集,会比分多次去获取小数据集的网络IO开销更小。
当然,缓存无底洞现象并不常见。在绝大多数的公司里可能根本不会遇到。