缓存使用场景
缓存是提升应用程序性能的最多见的一种技术,常适用于读多写少的场景。按照架构层次能够分为:客户端缓存、页面缓存、应用缓存、持久层缓存。
其中应用缓存设计最为复杂,本文主要讨论应用缓存的设计。redis
更新or淘汰
缓存的存储通常为键值对的方式,存储的值可能为对象或集合或单值,好比用户信息、购物车列表、阅读计数等。而缓存发生变化时的更新则存在两种策略:更新or淘汰。通常来讲淘汰最为简单。
淘汰数据能够简单的将缓存删除,更新数据则须要将新的值放入缓存中;淘汰数据会致使查找缓存时的一次miss,而更新缓存则不存在此问题,特别时某些复杂场景下淘汰缓存可能会致使缓存穿透。
若是更新缓存不须要增长额外的复杂计算或者分支流程推荐使用更新缓存的策略。另外若是对主线流程的响应速度要求比较高能够采用异步方式更新缓存。算法
缓存对象or单值数据
通常倾向于缓存单值数据而非对象,固然不包含其它业务对象的数据可视为单值数据(能够存储为string)。例如redis,支持List、Hash、Set(ZSet)和String,对于其中的集合对象能够根据不一样的业务场景选用。
存储一个复杂对象须要将对象的属性拆分红不一样的key-value来存储,好比:数据库
Book{ string id; int buyCount; string name; }。
能够拆分红:
Book.ID.buyCount = buyCount、Book.ID.name = name。
这样的存储会加大缓存对象的读写开销。若是buyCount不须要单独读写则可视为单值数据,能够以json的方式存取。具体如何选择须要视不一样场景而定。json
缓存穿透与失效
缓存设计不得不考虑的另外一个问题是缓存穿透与失效时的雪崩效应。缓存穿透是指查询一个必定不存在的数据,因为缓存miss时会从持久存储(DB)中查询数据,而且出于容错考虑查不到数据的时候不会写入缓存,这将致使每次查询这个数据时都要到存储层去查询,而失去了缓存的意义。
缓存穿透:有不少种方法能够有效地解决缓存穿透问题,最多见的则是采用布隆过滤器,将全部可能存在的数据hash到一个足够大的bitmap中,一个必定不存在的数据会被 这个bitmap拦截掉,从而避免了对持久存储(DB)的查询压力,这种方法的缺陷是hash及查询bitmap的性能开销以及bitmap的存储开销,在极端状况下会致使得不偿失。另一种方法是将n巨ull最为这个数据的值放入缓存并设定缓存的失效时间,这种方法的缺陷是低频的缓存穿透以及失效时间可能带来的性能开销(特别是在分布式的缓存系统中)。
缓存失效:这个问题目前并无很完美的解决方案。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到持久存储层(DB)上。固然并发量不是特别大的系统不须要处理。缓存
触发时机
程序中咱们能够在数据持久之间更新缓存或者在数据持久以后更新缓存。
在数据持久以前更新缓存会存在这样的问题:缓存更新成功,数据持久失败。这种状况下须要额外的回馈机制处理缓存的回滚,复杂且不够高效。
而在数据持久以后更新缓存则可能会存在数据持久成功,缓存更新失败的状况,这种状况下须要再次更新缓存或者直接失效缓存,一样须要额外的回馈机制处理缓存更新失败的状况。
如何选择须要考虑的是对主线业务的影响大小以及缓存脏数据对系统的影响大小,通常来讲缓存脏数据并不会形成系统性的影响。架构
分布式缓存
缓存系统须要考虑几个问题:并发
而由此催发出来的分布式缓存系统则须要额外考虑这几个问题异步
成熟的分布式缓存系统有Redis、Memcached、Ehcache、阿里云的OSC等等,各个系统的使用场景和实现方式各不相同这里再也不深究。分布式
系统自己的管理问题,包括了存储空间的分配、扩展、回收机制性能
分布式节点管理和路由算法
缓存键值的管理
缓存自己的水平线性扩展问题
缓存大并发下的自己的性能问题
缓存的单点故障问题(多副本和副本一致性)
缓存+数据库
一般咱们使用缓存是配合数据库特别是关系型数据库,而数据库则存在主从、读写分离等提升性能和可用性的方案。在这样的状况下,设计缓存系统须要特别注意数据的分发传递速度以及数据库单点故障可能致使的缓存更新失败。没有特殊处理的话可能会引起数据库数据与缓存数据的不一致。
服务化 缓存系统须要对外提供统一透明的服务API,你懂的,不解释!