`

“本地缓存”架构设计

阅读更多

前言

 

最近在做的项目其实是对老系统的一个深度改造,在老系统里缓存使用这块感觉有些瑕疵。在老系统里不管是“配置数据”还是“业务数据”都统一使用redis作为缓存。

 

“业务数据”使用redis作为缓存无可厚非,但“配置数据”使用使用redis就感觉不是很妥。

首先:过渡依赖redis,一些开关配置都依赖redis,如果redis服务挂掉整个服务瘫痪;

其次:增加redis服务的存取压力,几乎每个流程都会判断各种开关是否开启,对应的每个请求都会有数次redis读取请求。

最后:性能上也不好(与“本地jvm缓存相比”),读取redis是毫秒级的开销。

 

基于上述原因,决定对缓存结构进行重新梳理,整体采用共享缓存+本地jvm缓存的方式。

 

共享缓存+本地缓存

 

共享缓存:采用redis,如果能读取到缓存直接缓存返回,如果读取不到缓存先读取数据库,再写入redis缓存。

优点:全局共享,无需同步,一次设置,可以在多个jvm实例共享;

      存储量大,可以缓存上百G的数据;

缺点:依赖redis集群基础服务;

      由于存在网络开销,存取速度较慢(相对于本地缓存)

     

 

本地缓存:针对后端应用服务器,本地缓存指的就是jvm内存。

优点:访问数据非常快,纳秒级别(相对于redis的毫秒级别);

      不依赖外部基础服务。

缺点:但容量有限,不能存放过多内容;

      每个jvm实例都会存一份,存在数据冗余;

      修改后不便于同步等问题。

 

结合各自的优缺点,针对不同的业务场景采用不同的缓存方式,可以使系统性能达到最优。

 

共享内存redis的使用不用多说,对于正常的大量的业务数据缓存,基本都会采用redis做为缓存。对于少量配置数据、开关标记、固定的启动参数,可以采用本地缓存,针对不同的数据类型又有几种不同的本地缓存实现方式,初步分三种不同“本地缓存”,如下图所示:

 

 



 

如前所述,本地jvm缓存的难点在于保证实例间的数据同步,以及缓存数据大小的控制。根据不同业务场景,分为三类缓存数据:“配置开关”、“固定参数”、“热点数据”。本地缓存的引入是这次优化的核心,下面分别对三类本地缓存数据的同步和更新策略进行讲解。

 

1、“配置开关”数据

 

所谓“配置开关”指的是系统“降级开关”或者“备用切换开关”,这种类型的配置数据要求必须在线修改,及时生效。要做到这点,需要借助配置管理工具来完成,一般公司内部都有自己的配置管理工具,如果没有推荐使用淘宝开源的配置管理工具diamond。源码地址:https://github.com/takeseem/diamond,申请一个账号,即可下载源码。

关于淘宝diamond具体用法,可以自行查阅相关资料。大概流程如下:



 

diamond的配置以文件为单位,客户端会定期(如每隔15秒)向服务端发起检查请求配置文件内容是否发生变化(通过比较配置内容的md5值),如果变化则拉取配置。

然后解析配置文件,把变更的配置key-value,更新到本地JVM内存。

 

在任意一个server端修改配置后,同步到各个系统会有一个延迟时间(比如15秒),即客户端轮询的间隔时间。可以根据自己业务需要 适当调整这个时间。

 

小结:client端在启动的时候会把最新配置写入到jvm内存,当服务端配置发生变化后,会自动拉取变化的配置,更新jvm内存。修改后的参数会有短暂的延迟。

如果你的业务要求0延迟,最好用nettyserver端和client端建立长链接来实现同步,成本会稍微高一点。

 

2、“固定参数”数据

 

这种配置数据一般不会改变,我们可以认为这类数据是不变的,程序启动时直接读入jvm内存,如果要改变数据就只能重启程序。或者把频繁变化的数据划分为第一类“配置开关”数据。

 

在我们系统里,根据使用方式的不同“固定参数”数据又被划为两种:

a、独立的配置数据,在程序启动时写入一个全局的HashMap(由于是单线程写,不用考虑线程安全问题),在使用时,根据key直接从HashMap get即可。这种方式很容易扩展,但需要一个常量类来维护这些key的名称。

另外你也可以把参数类型分类,对每个类型定义一个枚举类,初始化的时候初始化枚举值,使用的时候直接指定某个枚举值即可。这种方式个人觉得更优雅些。

 

b、用于生产模板对象的数据,比如在我们系统里,创建一个新页面,需要一个页面模板对象作为“骨架”。这个模板对象,系统设计之初就已经确定,并且不会改变。我们以前的做法是把这些配置数据写到数据库。在需要的创建页面时 首先new一个页面对象,再从数据库中查询数据set到对象中。当然这里的配置数据,也可以放到jvm内存里,每次new对象的时候,从内存中获取set到新对象。

 

改进做法:在程序启动时,创建一个“全局模板对象”需要的参数依旧从数据库中查询(或者配置文件)。在需要创建新页面时,直接调用这个“全局模板对象”的clone方法。这种做法相对来说更优雅,前提是需要“全局模板对象”类实现Cloneable 重写clone接口,实现“深度克隆”。关于如果实现“深克隆”可以参考这篇博客:http://moon-walker.iteye.com/blog/2374195

 

3、“热点数据”

 

这里的“热点数据”可以是配置数据,也可以是业务数据。

场景一:如果配置数据太多,全部放到内存,会占用太多内存,但经常使用的数据又很少。

场景二:如果某类业务数据很多,但只有少量的数据会被经常用到。

针对这两种场景 我们通常第一时间想到的是使用redis这类的全局共享缓存,修改数据时清除redis缓存,下次查询直接查库,再同步缓存。

 

但如果这两种场景中的数据几乎都是查询,没有修改,或者说修改后有一定延迟可以接受,这时可以采用,通过LRU算法(淘汰最近最少使用的缓存算法)实现的“本地缓存”会更合理一些。关于LUR“本地缓存”可以自己实现(采用双向链表即可实现),也可以采用本地Ehcache实现。

 

如果redis挂掉

 

回到文章开头的问题,如果核心配置数据也采用redis,一旦redis挂掉,整个系统服务就会崩溃。现在我们来看看如果使用“本地缓存”来存放核心配置数据,如果redis挂掉,怎么做到系统不挂。

 

首先我们在调用redis存取服务时,使用“本地缓存”做个开关,如果redis缓存出现问题,就绕过redis缓存,直接操作数据库,这个道理很简单:



 

 

 

有人会问,不使用redis你的系统抗得住嘛?我们会对系统按照业务模块进行“微服务”拆分成多个子工程,对每个子工程进行限流处理:也就是系统的最大处理能力,我们做压力测试,计算在没有redis缓存的情况下,系统处理的最大并发数,以这个最大并发数作为redis缓存失效情况下的限流依据:



 

1、获取当前支持的最大并发数,这个采用“本地缓存”中的“配置开关”配置,如果redis挂掉,获取非redis缓存模式下系统支持的最大并发数(注意在redis缓存模式下的并发数肯定大很多,提前通过压力测试评估得到)。

2、获取当前系统正在执行的请求并发数,具体怎么获取可以参考另一篇博文:http://moon-walker.iteye.com/blog/2375240 中的限流部分。

3、判断当前正在执行的并发数是否大于步骤1中获取的最大并发数,如果已经大于,则直接返回“限流提示”,防止服务挂掉。

 

通过上述处理可以保证即便是在redis挂掉的情况下,系统依旧可以运行,由于并发处理能力降低,只能支持部分用户在系统里进行操作;另一部分用户,可能会被要求重试。但比起系统直接挂掉,这已经是较好的降级措施了。如果采用redis缓存做配置管理,就达不到上述效果。

 

 

当然这里指的是后端操作系统,如果是面向全国客户的前端系统,动不动就限流,肯定无法满足需求,对应前端系统可以采用整页静态化,缓存前置等措施,可以参考另一篇博文:http://moon-walker.iteye.com/blog/2332314

  • 大小: 34.1 KB
  • 大小: 62.4 KB
  • 大小: 22.8 KB
  • 大小: 26.4 KB
0
0
分享到:
评论

相关推荐

    Android之本地缓存——LruCache(内存缓存)与DiskLruCache(硬盘缓存)统一框架

    Android之本地缓存——LruCache(内存缓存)与DiskLruCache(硬盘缓存)统一框架 [注:本内容来自网络,在此分享仅为帮助有需要的网友,如果侵犯了您的权利,麻烦联系我,我会第一时间删除,谢谢您。]

    蜜蜂缓存:支持多服务架构下,使用本地缓存

    多服务架构下,依旧使用本地缓存 减少第三方缓存服务的压力 避免网路I / O和资料结构转换的消耗,提高效能 兼容Spring Cacheable 设计 由于缓存通常应用在不重复异动的资料上,所以不应该每次都向向第三方缓存服务...

    网站架构技术

    网站架构设计误区 一味追随大公司的解决方案 为技术而技术 企图用技术解决一切问题 大型网站架构模式 架构模式 分层 分割 分布式 分布式应用和服务 分布式静态资源 分布式数据和存储 ...

    大规模分布式系统架构与设计实战.完整版

    《大规模分布式系统架构与设计实战》写到,分布式并行计算的基本原理解剖;分布式协调的实现,包括如何实现公共配置管理,如何实现分布式锁,如何实现集群管理等;分布式缓存的实现,包括如何提供完整的分布式缓存来...

    UML和模式应用(架构师必备).part06.rar

    使用本地缓存提高性能 35.3 处理故障 35.4 通过代理(PGoF)使用本地服务进行容错 35.5 对非功能性或质量需求的设计 35.6 使用适配器访问外部物理设备 35.7 对一组相关的对象使用抽象工厂模式 35.8 使用多态性...

    大规模分布式系统架构与设计实战

    《大规模分布式系统架构与设计实战》从作者的实战经验出发,深入浅出地讲解了如何建立一个Hadoop那样的分布式系统,实现对多台计算机CPU、内存、硬盘的统一利用,从而获取强大计算能力去解决复杂问题。一般互联网...

    硬盘缓存增强软件 PrimoCache Desktop Edition 3.0.2 中文多语免费版.zip

    PrimoCache基于双级缓存系统架构设计,该架构由一个一级缓存(level-1 cache)和一个二级缓存(level-2 cache)组成。一级缓存使用物理内存作为缓存设备,而二级缓存则通常使用SSD固态硬盘、闪存盘或其它永续性存储...

    Cola-Designer拖拽式网站设计器

    注意事项:项目默认使用接口模式,完整功能需要连接后端程序,若只想体验设计器部分可将env.js中active改为preview(本地缓存模式),然后访问http://localhost:8009/#/design ,本地缓存模式暂时不能使用图片上传。

    基于社交网络的Web缓存架构研究

    为了提升用户在Web访问过程的体验,降低网络带宽成本和减轻服务端负载,文章从Web访问过程中本地浏览器端、CDN缓存、服务器端等3个层面来设计缓存方案,阐述了Web缓存在网络中的传递过程,深入描述Web缓存的不同策略...

    开涛高可用高并发-亿级流量核心技术

    16.6 详情页架构设计原则 332 16.6.1 数据闭环 332 16.6.2 数据维度化 333 16.6.3 拆分系统 334 16.6.4 Worker无状态化+任务化 334 16.6.5 异步化+并发化 335 16.6.6 多级缓存化 335 16.6.7 动态化 336 16.6.8 弹性...

    构建高性能WEB站点(完整版)

    包括数据的网络传输、服务器并发处理能力、动态网页缓存、动态网页静态化、应用层数据缓存、分布式缓存、Web服务器缓存、反向代理缓存、脚本解释速度、页面组件分离、浏览器本地缓存、浏览器并发请求、文件的分发、...

    服务器架构方案总结.doc

    通过NetConnection连接到FMS服务器,并实 时播放服务器的FLV文件,这种方式可以任意选择视频播放点(SEEK()),并不象HTTP方 式需要缓存完整个FLV文件到本地才可以任意选择播放点,其优点就是在本地缓存里是找 不到...

    UML和模式应用(架构师必备).part01.rar

    使用本地缓存提高性能 35.3 处理故障 35.4 通过代理(PGoF)使用本地服务进行容错 35.5 对非功能性或质量需求的设计 35.6 使用适配器访问外部物理设备 35.7 对一组相关的对象使用抽象工厂模式 35.8 使用多态性...

    UML和模式应用(架构师必备).part07.rar

    使用本地缓存提高性能 35.3 处理故障 35.4 通过代理(PGoF)使用本地服务进行容错 35.5 对非功能性或质量需求的设计 35.6 使用适配器访问外部物理设备 35.7 对一组相关的对象使用抽象工厂模式 35.8 使用多态性...

    UML和模式应用(架构师必备).part02.rar

    使用本地缓存提高性能 35.3 处理故障 35.4 通过代理(PGoF)使用本地服务进行容错 35.5 对非功能性或质量需求的设计 35.6 使用适配器访问外部物理设备 35.7 对一组相关的对象使用抽象工厂模式 35.8 使用多态性...

    UML和模式应用(架构师必备).part03.rar

    使用本地缓存提高性能 35.3 处理故障 35.4 通过代理(PGoF)使用本地服务进行容错 35.5 对非功能性或质量需求的设计 35.6 使用适配器访问外部物理设备 35.7 对一组相关的对象使用抽象工厂模式 35.8 使用多态性...

    UML和模式应用(架构师必备).part04.rar

    使用本地缓存提高性能 35.3 处理故障 35.4 通过代理(PGoF)使用本地服务进行容错 35.5 对非功能性或质量需求的设计 35.6 使用适配器访问外部物理设备 35.7 对一组相关的对象使用抽象工厂模式 35.8 使用多态性...

    UML和模式应用(架构师必备).part08.rar

    使用本地缓存提高性能 35.3 处理故障 35.4 通过代理(PGoF)使用本地服务进行容错 35.5 对非功能性或质量需求的设计 35.6 使用适配器访问外部物理设备 35.7 对一组相关的对象使用抽象工厂模式 35.8 使用多态性...

    UML和模式应用(架构师必备).part05.rar

    使用本地缓存提高性能 35.3 处理故障 35.4 通过代理(PGoF)使用本地服务进行容错 35.5 对非功能性或质量需求的设计 35.6 使用适配器访问外部物理设备 35.7 对一组相关的对象使用抽象工厂模式 35.8 使用多态性...

Global site tag (gtag.js) - Google Analytics