在分布式Web程序设计中,解决高并发以及内部解耦的关键技术离不开缓存和队列,而缓存角色类似计算机硬件中CPU的各级缓存。如今的业务规模稍大的互联网项目,即使在最初beta版的开发上,都会进行预留设计。但是在诸多应用场景里,也带来了某些高成本的技术问题,需要细致权衡。

服务端数据缓存

一种区分

缓存基于不同的条件有很多种划分方式,本地缓存(Local cache)和分布式缓存(Distributed cache)是一种常见分类,两者自身又包含很多细类。

本地并不是指程序所在本地服务器(从严格概念来说),而是更细粒度的指位于程序自身的内部存储空间,而分布式更多强调的是存储在进程之外的一个或者多个服务器上,彼此交互通信,在具体软件项目的设计和应用中,多数时候是混合一体。当然,个人认为对缓存本质的理解才是最重要的,至于概念上的分类只是一个不同理解下的划分而已。

一些技术成本

在具体项目架构设计时,单纯使用前者(本地缓存)的开发成本毋庸置疑是极低的,主要考虑的是本机的内存负载或者极少量的磁盘I/O影响。而后者的设计初心是为了利于分布式程序之间缓存数据的高效共享和管理,除了考虑缓存所在服务器自身的内存负载,设计时更需要充分考虑网络I/O、CPU的负载,以及某些场景下的磁盘I/O的代价,同时还在具体设计时尽可能规避和权衡整体稳定性和效率,这些不仅仅只是作为缓存服务器的硬件成本和技术维护。需要谨慎考虑的底层问题包括缓存间通信、网络负载和延迟等各种需要权衡的细节。

其实如果理解了缓存本质就该知道,任何存储介质在适当的场景下都可以充当一个高效的缓存角色并进行项目集成和缓存间集群。常见主流的Memcached和Redis等均是属于后者范畴,甚至可以包括如基于NoSQL设计的MongoDB这类文档数据库(但这是从角色角度讲,而狭义划分上这是基于磁盘的存储库,需要注意,各有专攻)。

这些第三方缓存在进行项目集成和缓存间集群,也需要解决一些问题。甚至项目迭代到了后期阶段,往往还需要具备较高专业知识的运维同时参与,并且在开发中的逻辑设计和代码实现也会增加一定的工作量。所以有时候在具体项目的设计上,一方面要尽可能预留,一方面还得根据实际情况尽可能精简。

额外说下其他体会:在个人有限的技术学习和实践里,关于节点数据交互,尤其是服务间通信,是不存在完美的闭环的,理论上也都是在“当前阶段”面向“高一致”的权衡罢了(大概跟生活是一样的吧)。

缓存数据库结构设计细节

由于目前个人工作中大多数情况应用的是Redis 3.x,以下若有特性关联,均是以此作为参照说明。

实例(Instance)

根据业务场景,公共数据和业务耦合数据,一定分别使用不同的实例。如果是单实例,才可以考虑以DB划分。当你使用的是Redis,那么DB在Redis里是有数据隔离,但没有严格权限限制,所以划库只是一种选择。在Cluster集群里则是保持默认单个库,不过实际中我会尝试根据项目大小来调整,至于在哪个开发阶段则是作为预留设计。

额外需要注意的是,作为重度依赖服务器内存的缓存产品,如果开启了持久化(后面会提到),并且在为并发量极大的服务提供支持时,服务器硬件资源会出现大量抢占,请结合持久策略配置,考虑实例是否进行分盘存储。

持久化本质是将内存数据同步写入硬盘(刷盘),而磁盘I/O实在有限,被迫的写入阻塞除了造成线程阻塞和服务超时,还会导致额外异常甚至波及其他底层依赖服务。当然,我的建议是,如果条件允许,最好是在项目初期设计时就进行规划并确定。

缓存“表”(Table)

一般缓存中并没有传统RDBMS中直观的表概念(往往以键值对“KV”形式存在),但从结构上来讲,键值对本身就可以组装为各种表结构。一般我会先生成数据库表关系图,然后分析什么时候存储字符串,什么时候存储对象,然后使用缓存键(KEY)进行表和字段(列)分割。

假定需要存储一个登录服务器表数据,包含字段(列):name、sign、addr,那么可以考虑将数据结构拆分为以下形式:

  { key : "server:name" , value : "xxxx" }

  { key : "server:sign" , value : "yyyy" }

  { key : "server:addr" , value : "zzzz" }

需要注意的是,往往在分布式缓存产品中,例如Redis,存在多种数据结构(如String、Hash等),还需要根据数据关联性和列的数量,来选择对应缓存的存储数据结构,相关存储空间和时间复杂度是完全不同的,而这个在初期阶段是很难感受到的。

同时,就算缓存的内存设置的足够大,剩余也很多,也同样需要考虑类似RDBMS中的单表容量问题,控制条目数量不能无限增长(比如预知到存储条目可以轻松达到百万级),“分库分表”的设计思路都是相通的。

缓存键(Key)

上面提到了基于缓存键来设计表,这里再单独说明一下键相关的个人规范。在键长度足够简短的前提下,如果关联相同业务模块,则必须设计为以同一个标识(代号)开头,目的是方便查找和统计管理。

如用户登录服务器列表:

  { key : "ul:server:a" , value : "xxxx" }

  { key : "ul:server:b" , value : "yyyy" }

另外,每个独立业务系统可考虑配置一个唯一的通用前缀标识。当然,这里不是必需,若实际工作中,如果使用的是不同库,则可以忽略。

缓存值(value)

缓存中的值(这里指单一条目)的大小没有平均标准,但Size自然是越小越好(若使用的是Redis,一次操作的value较大会直接影响整个Redis的响应时间,不仅仅是指网络I/O)。如果存储占用空间直达10M+,建议考虑关联的业务场景是否可以拆分为热点和非热点数据。

持久化(Permanence)

上面也简单提了下,一般来说,持久和缓存本身是没有直接关系的,可以粗略想象为一个面向硬盘一个面向内存。但如今的Web项目里,有些业务场景是高度依赖缓存的,持久化可以一方面帮助提高缓存服务重启后的快速恢复,另一方面提供特定场景下的存储特性。当然,由于持久化必然需要牺牲一些性能,包括CPU的抢占和硬盘I/O影响。不过大多数时候是利大于弊,建议在应用缓存的时候,没有特别情况的话,尽量搭配持久化,无论是使用自身机制还是第三方来实现。

如果是使用的Redis,其自身就具备相关持久策略,包含AOF和RDB,我在大多数情况下是两者同时配置的(当然,最新官方版本本身也提供了混合模式)。如果在一些非高并发的场景下,或者说在一些中小项目的管理模块里,仅仅只是作为优化手段,确定了不需持久,也可以直接设置关闭,节约性能开销损耗,但建议在程序中将该实例做好标注,确保该实例的公共使用范围。

淘汰(Eliminate)

缓存如果无限制的增长,即使设置了较短的过期(Expiration ),在一些时间点上,高并发的一批大数据会在较短时间内就达到了可使用内存的峰顶,此时程序中与缓存服务器的交互会出现大量延迟和错误,甚至给服务器自身都带来了严重的不稳定性。所以在生产环境里尽量给缓存配置最大内存限制,以及适当的淘汰策略。

如果使用的是Redis,自身淘汰策略选择比较灵活。

个人的设计是,在数据呈现类似幂律分布情况下,总有大量数据访问较低,我会选择配置allkeys-lru、volatile-lru,将最少访问的数据进行淘汰。再比如缓存是作为日志应用的,那么我一般是项目前期是配置no-enviction,后期会配置为volatile-ttl。

当然,我也见过一种特殊业务下的设计,缓存直接用来作为轻量的持久数据库使用,而且是终端,开始觉得有些新奇,后来发现是非常符合业务设计的(比如几乎没有任何复杂逻辑和强事务)。所以合情合理,确实不应该禁锢在传统设计里,毕竟架构总是基于业务去实时组合和改变的。

转载于:https://blog.51cto.com/13801890/2310228

详谈分布式系统缓存的设计细节相关推荐

  1. 缓存架构设计细节二三事

    缓存架构设计细节二三事 原创 2016-03-08 58沈剑 架构师之路 本文主要讨论这么几个问题: (1)"缓存与数据库"需求缘起 (2)"淘汰缓存"还是&q ...

  2. 论大规模分布式系统缓存设计策略

      声明:本文为本人在软考系统架构设计师备考期间的练手写作,不保证内容的原创性与正确性,仅供参考,请勿照抄和用于学术论文等正规场合,因不当使用产生后果一律自负. 摘要   2019年3月,我单位联合某 ...

  3. 分布式系统概念和设计 第十五章 (1)

    COORDINATION AND AGREEMENT http://www.cdk5.net/wp/ 背景知识点:Reliable failure detector 实际系统中没有reliable f ...

  4. 分布式系统概念和设计-操作系统中的支持和设计

    分布式系统概念和设计 操作系统支持 中间件和底层操作系统的关系,操作系统如何满足中间件需求. 中间件需求:访问物理资源的效率和健壮性,多种资源管理策略的灵活性. 任何一个操作系统的目标都是提供一个在物 ...

  5. 商品详情页动态渲染系统:大型网站的多机房4级缓存架构设计

    124_大型电商网站的商品详情页的深入分析 之前,咱们也是说在讲解这个商品详情页系统的架构 缓存架构,高可用服务 商品详情页系统,我们只是抽取了其中一部分来讲解,而且还做了很大程度的简化 主要是为了用 ...

  6. 分布式系统概念和设计——特征,实例,Web,Future

    分布式系统概念和设计 分布式系统的特征 关于分布式系统的定义产生的结论 并发性,如何协调并发执行的共享资源型的程序 缺乏全局时钟,程序协作需要通过交换信息完成,紧密的协调依赖于对程序动作发生时的时间共 ...

  7. 深度介绍分布式系统原理与设计

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 1 概念 1.1 模型 1.2 副本 1.3 衡量分布式系 ...

  8. 《大规模分布式系统架构与设计实战》

    <大规模分布式系统架构与设计实战> 基本信息 作者: 彭渊 丛书名: 大数据技术丛书 出版社:机械工业出版社 ISBN:9787111455035 上架时间:2014-2-21 出版日期: ...

  9. 【 C++ 技术】 C++ 高性能服务器网络框架设计细节

    作者:范蠡  原文:C++ 高性能服务器网络框架设计细节 前言 这篇文章我们将介绍服务器的开发,并从多个方面探究如何开发一款高性能高并发的服务器程序.需要注意的是一般大型服务器,其复杂程度在于其业务, ...

最新文章

  1. redistemplate分布式锁实现_基于 Redis SETNX 实现分布式锁
  2. python判断 t1 树是否有与 t2 树拓扑结构完全相同的子树
  3. angularjs与后台传值接收值
  4. Maven整合SSM测试
  5. 含类定义的完整python程序_Python——变量,运算,条件,循环
  6. 【SAP】自定义权限对象
  7. java使用不存在的字符串_jpa – java.lang.IllegalArgumentException:您试图使用查询字符串中不存在的字符串名称设置参数值...
  8. 使用jOOQ DSL
  9. python 网络请求框架_python 框架
  10. 学知识的时候,把自己放的低一点
  11. DTU和RTU的区别
  12. 1.爬虫系统学习--爬虫应知知识(后续还会更新)
  13. 计算机cpu近几年价格,2014年6月15日电脑CPU最新报价(表格)
  14. 追光的人 团队团队展示
  15. java类名不能以数字开头_java变量为什么不能以数字开头
  16. 苹果4如何添加时间插件_Pr快速批量制作和添加字幕,节省时间还不用插件的做法...
  17. C/C++数据结构——虚虚实实(并查集欧拉路)
  18. 微信公众号验证Token
  19. FreeRTOS学习-队列管理
  20. C51 - DS18B20

热门文章

  1. 超简单的Tomcat安装过程
  2. https加密过程(详细)
  3. 面试官:什么是JDK什么是JRE?服务器可以只安装JRE吗?
  4. 在spring配置中出现的问题,解决方案
  5. Memstore数据刷写与阻塞机制深入剖析及参数优化
  6. Hibernate初探之单表映射——Hibernate概念及插件的安装
  7. Python 新式类与经典类
  8. Tomcat设置cmd窗口的title属性
  9. 我希望早几年知道的5个Unix命令
  10. 获取 Windows 窗体 DataGridView 控件中选定的单元格、行和列