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

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

从底层到上层有数据库模型设计,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的一般性原则,希望对各位读者在使用缓存方面有一定的帮助。


相关文章
|
存储 缓存 负载均衡
基于C++的高性能分布式缓存系统设计
基于C++的高性能分布式缓存系统设计
505 1
|
存储 缓存 负载均衡
基于Java的分布式缓存系统设计与实现
基于Java的分布式缓存系统设计与实现
292 1
|
存储 缓存 应用服务中间件
|
存储 缓存 NoSQL
短链系统设计性能优化-缓存提速及CDN
如何提高响应速度,和直接打开原链接一样的效率。 明确,这是个读多写少业务。
280 0
|
6月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
1月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
|
2月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
185 1
Redis专题-实战篇二-商户查询缓存
|
1月前
|
缓存 运维 监控
Redis 7.0 高性能缓存架构设计与优化
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Redis 7.0高性能缓存架构,探索函数化编程、多层缓存、集群优化与分片消息系统,用代码在二进制星河中谱写极客诗篇。
|
6月前
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
934 0
|
2月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。