【系统设计】如何使用缓存

简介:
一 引言
  在构建和维护业务服务应用时,大多数情况下业务系统的性能瓶颈往往是在数据库,解决应用到数据库之间瓶颈,系统的性能会得到极大提升。系统的数据库性能优化方法有很多:

从底层到上层有数据库模型设计,SQL优化,使用缓存等等。从图中的优化模式来看,其中数据库模型设计的合理程度奠定了应用系统优化的基石,如果模型设计得不合理,那么统随着业务发展,系统后续的优化困难重重,另一方SQL优化也是数据库优化的一个重要方面,慢SQL和 topSQL往往是系统性能杀手,它们是导致系统故障的重要潜在危险;还有一个就是今天和大家一起探讨的是应用缓存。
 
缓存在系统的性能优化中有着举足轻重的作用,它比sql优化 所带来的效果更直接,更高效。但是相比其他优化手段,缓存的使用并不是零成本的,任何系统使用缓存,都会遇到两大问题:
第一,数据不一致问题;
第二,系统复杂度大幅度增加;
这给系统的维护和测试增加了一定的成本,所以缓存的设计就显得非常重要了,糟糕的缓存设计可能让系统累赘不堪,而且让系统变得极难以维护。另外对于不同类型的数据,由于实时性,准确性,复杂性的不同,缓存的策略也有些不同。

二 如何设计缓存
其实缓存的设计面临三个问题,缓存哪些数据,怎么缓存,怎么保证数据一致性。
2.1 缓存哪些数据
系统优化时有一句话必须切记:“优化是无止境的”,所以如果缓存不是必须的,请去掉它,要知道越是业务上复杂的系统,对Cache的使用反而越简单,这是因为对于一个复杂、多变、历史悠久的系统,在Cache方面做过度设计会让人深陷其中;缓存的数据越多,系统的维护成本就越高,所以找准需要缓存的点尤为重要。一般情况下,我们只会缓存给系统带来巨大瓶颈的IO操作,在普通应用里尤其指由top SQL 或者慢 SQL 所带来的DAO查询;找准需要优化的sql,你可以找DBA帮忙。
 
2.2 怎么缓存
首先是缓存存储介质的选择: 你可以直接缓存在JVM内存里 或者app 应用服务器本地的localcache,你也可以采用专门的缓存服务器 如果tair、memcache等; 另外DB、文件其实也可以做缓存,他们一般缓存的事复杂计算的中间结果,这我们一般很少用到;如果你的缓存是存放在 jvm本地,那么通常是用 map 实现,如果缓存数据更新比较频繁且对数据正确性比较高,那么你需要考虑为其添加并发控制和失效策略。还有一点比较重要的就是,在集群环境下想要做到数据一致性,比较困难,主动更新比较麻烦而且达不到其想要的降低数据库等IO操作的效果,所以本地缓存的适用场景一般是在读访问非常高,而写操作极少,对数据一致性要求不是特别高的场景;如果采用专门的缓存服务器则避免了很多麻烦,tair (阿里开发的一套缓存系统),是我们经常使用的缓存中间件,它提供了很好的并发控制和失效机制,另外还提供了不同存储引擎可以供我们选择,mdb,rdb,ldb;普通的缓存可以mdb和rdb,两者分别有memcache和redis的影子,不提供持久化,但其响应时间和高QPS的表现都非常好,如果要确保数据不丢失,那么可以采用ldb引擎存储,它提供了对数据持久化的支持,相反牺牲了一点点性能。

缓存的方式:缓存的方式一般都非常简单,很多时候我们都经常缓存一个方法的执行结果:


public void getData()
 
{

  Object data = null;

  data = getDataFromCache();

  if(data == null){

    data = getDataFromDB();

    putDataToCache(data);

    }

} 

如果缓存的添加比较多,重复的为每个方法都添加一模样的缓存代码非常不方便,我们很容易会想到用AOP去做方法的缓存框架,然后用@注解去为方法添加缓存。其实Spring3.1已经有现成的缓存框架了,使用@Cacheable 注释可以很方便的为某个方法添加缓存(有兴趣的读者可以去查阅资料研究)


@Cacheable
 
public Object getData(){

   return getDataFromDB();

} 

当然实际上添加缓存没有那么简单,你还有很多事要去做,比如我们下面要谈到的,怎么保证数据的一致性。
2.3  怎么保证数据一致性
数据的一致性,其实就是缓存与数据源的同步机制,这非常重要,它其实直接确定了你的缓存策略是什么样的,一般缓存的同步模式我把它分为以下几种:
 a. Cache Miss Reload 
 b. Update Then SetCache 
 c. 补偿机制 
 d. Reload(Rebuilt)All 等,每种同步机制各有优缺点,在我们的实践中,经常把这几种方法相结合;
【Cache Miss Reload】:最简单,就是上面getData()函数的方式,它的好处是代码较为简洁,数据同步时在数据库CUD的时候,将缓存失效即可,如果业务对数据实时性要求不高,可以直接设置缓存失效时间,而不需要去手动失效它,这可以让代码达到最简的地步;坏处是当缓存失效瞬间,所有的请求都会经过数据库(调用getDataFromDB()函数),可能导致数据库压力过大,导致缓存一直加不上,可能会引发DB故障。
【Update Then SetCache】:顾名思义就是在缓存的数据更新的同时也触发程序更新缓存:


public void updateData(Object data){
 
  boolean success = doUpdateData(data);

  if(success){

    data = getDataFromDB();

    putDataToCache(data);

   }

} 

这可以在很大程度上避免上述所说的缓存失效雪崩效应;坏处是由于并发的原因,存在极小几率你更新的缓存会导致脏数据进入缓存中,可以采用tair提供的 version 参数避免脏数据进入tair中,不过编码复杂度又上升了。
【补偿机制】有些时候缓存的更新不一定能够成功,也有可能会有脏数据进入缓存,如果要确保数据‘绝对’一致性,我们可以采取适当的补偿机制,如 定时从数据库的值更新到缓存,或者在更新缓存失败时,插入失败日志,定时重新执行缓存更新等
【Reload(Rebuilt)All】 有些时候你会发现上面的所有同步模式都不生效了,因为有些查询对象的写远大于读(可能是因为最初的数据库设计并没有读写分离),那么这是如果一定要缓存它的话,那可能就要以牺牲一部分的数据实时性为代价了,我们一般采用定时程序 Reload或Rebuilt所有的缓存;

三 小结
    在系统优化的过程中,缓存的使用也是一门艺术。本文阐述了应用系统使用cache的一般性原则,希望对各位读者在使用缓存方面有一定的帮助。


相关文章
|
8月前
|
存储 缓存 应用服务中间件
|
存储 缓存 NoSQL
短链系统设计性能优化-缓存提速及CDN
如何提高响应速度,和直接打开原链接一样的效率。 明确,这是个读多写少业务。
129 0
|
5天前
|
消息中间件 缓存 NoSQL
Redis经典问题:缓存雪崩
本文介绍了Redis缓存雪崩问题及其解决方案。缓存雪崩是指大量缓存同一时间失效,导致请求涌入数据库,可能造成系统崩溃。解决方法包括:1) 使用Redis主从复制和哨兵机制提高高可用性;2) 结合本地ehcache缓存和Hystrix限流降级策略;3) 设置随机过期时间避免同一时刻大量缓存失效;4) 使用缓存标记策略,在标记失效时更新数据缓存;5) 实施多级缓存策略,如一级缓存失效时由二级缓存更新;6) 通过第三方插件如RocketMQ自动更新缓存。这些策略有助于保障系统的稳定运行。
136 1
|
8天前
|
存储 消息中间件 缓存
Redis缓存技术详解
【5月更文挑战第6天】Redis是一款高性能内存数据结构存储系统,常用于缓存、消息队列、分布式锁等场景。其特点包括速度快(全内存存储)、丰富数据类型、持久化、发布/订阅、主从复制和分布式锁。优化策略包括选择合适数据类型、设置过期时间、使用Pipeline、开启持久化、监控调优及使用集群。通过这些手段,Redis能为系统提供高效稳定的服务。
|
14天前
|
存储 缓存 NoSQL
【Go语言专栏】Go语言中的Redis操作与缓存应用
【4月更文挑战第30天】本文探讨了在Go语言中使用Redis进行操作和缓存应用的方法。文章介绍了Redis作为高性能键值存储系统,用于提升应用性能。推荐使用`go-redis/redis`库,示例代码展示了连接、设置、获取和删除键值对的基本操作。文章还详细阐述了缓存应用的步骤及常见缓存策略,包括缓存穿透、缓存击穿和缓存雪崩的解决方案。利用Redis和合适策略可有效优化应用性能。
|
17天前
|
存储 缓存 NoSQL
Redis多级缓存指南:从前端到后端全方位优化!
本文探讨了现代互联网应用中,多级缓存的重要性,特别是Redis在缓存中间件的角色。多级缓存能提升数据访问速度、系统稳定性和可扩展性,减少数据库压力,并允许灵活的缓存策略。浏览器本地内存缓存和磁盘缓存分别优化了短期数据和静态资源的存储,而服务端本地内存缓存和网络内存缓存(如Redis)则提供了高速访问和分布式系统的解决方案。服务器本地磁盘缓存因I/O性能瓶颈和复杂管理而不推荐用于缓存,强调了内存和网络缓存的优越性。
132 47
|
3天前
|
缓存 NoSQL 安全
Redis经典问题:缓存击穿
本文探讨了高并发系统中Redis缓存击穿的问题及其解决方案。缓存击穿指大量请求同一未缓存数据,导致数据库压力过大。为解决此问题,可以采取以下策略:1) 热点数据永不过期,启动时加载并定期异步刷新;2) 写操作加互斥锁,保证并发安全并设置查询失败返回默认值;3) 预期热点数据直接加缓存,系统启动时加载并设定合理过期时间;4) 手动操作热点数据上下线,通过界面控制缓存刷新。这些方法能有效增强系统稳定性和响应速度。
59 0
|
4天前
|
缓存 NoSQL 应用服务中间件
Redis多级缓存
Redis多级缓存
8 0
|
4天前
|
缓存 NoSQL 关系型数据库
Redis 缓存 一致性
Redis 缓存 一致性
7 0
|
4天前
|
缓存 监控 NoSQL
Redis经典问题:缓存穿透
本文介绍了缓存穿透问题在分布式系统和缓存应用中的严重性,当请求的数据在缓存和数据库都不存在时,可能导致数据库崩溃。为解决此问题,提出了五种策略:接口层增加校验、缓存空值、使用布隆过滤器、数据库查询优化和加强监控报警机制。通过这些方法,可以有效缓解缓存穿透对系统稳定性的影响。
85 3