★ 目录 ★
01 | 前言 |
02 | 场景介绍 2.1 XML格式数据缓存(1.0版本) 2.2 XML及.Net DataTable格式数据缓存(2.0版本) 2.3 Protobuf格式数据缓存(3.0版本) 2.4 Protobuf结合LocalCache多级缓存(4.0版本) |
03 | 缓存应用 |
04 | 实施成果 |
05 | 总结及后续扩展 |
随着业务的发展,数据内容越来越复杂,用户、访问量也在增长。访问量的增多,导致数据库计算查询压力增大,从而导致响应变慢,因此对部分(指:更新频率较低,使用很频繁)数据进行缓存,来减轻数据库压力和提高响应速度。
初期技术语言使用单一,业务场景简单,因此采用缓存手段也相对简单。在业务高速发展的情况下,为了支撑更大的吞吐量并且响应足够及时,无论是系统架构还是缓存架构,都进行了多次升级。
场景介绍
在业务快速发展和技术架构迭代升级的同时,缓存架构迭代主要分为4个大的阶段。分别为:
1、XML格式数据缓存(1.0版本) :
在业务发展初期,基于C#语言。基础业务相对简单,当时数据库的读取量及服务器资源占用相对较低,但是也存在偶发性的流量峰值时数据库IO过高的问题。对于这个问题,提出了对部分基础配置数据进行数据缓存,用空间换时间,一是能减轻数据库IO过高的压力,二是提高应用响应速度。
图1 缓存1.0
2、XML及.Net DataTable格式数据缓存(2.0版本):
随着业务的发展,数据实体越来越多且结构越来越复杂,以及应用的增多导致服务器的增加。1.0版本的缓存方式弊端显露,存储数据形式单一、需要推送多台服务器、保证数据的一致性的成本也随之变高。且采用XML格式对数据进行序列化时,虽然时间消耗上性能尚可,但使用XML格式对数据进行反序列化时的时间花费上,耗时长,性能差,在高并发下,尤为明显,原因是XML格式为了保持较好的可读性,引入了一些冗余的文本信息。所以在使用XML格式进行存储数据时,也会占用更多空间。因此为了解决这些问题,在1.0缓存的基础上,增加Redis做为缓存层,一是丰富缓存数据存储形式(使用.Net DataTable格式),二是缓存数据可用性、一致性得到更有效保障。
图2 缓存2.0
3、Protobuf格式数据缓存(3.0版本):
随着近年业务快速发展及流量高速增长,为了更好的支持业务发展,进行了多次的技术架构升级,开发中采用了支持Linux平台的.Net Core、生态圈更为丰富的Java、性能更好的Golang等多种语言来支持。也就是说新的缓存方案,就要考虑到支持跨平台,跨语言的问题,而现有的缓存架构无法满足这些要求,经过调研和对比,最终采用Protocol Buffers作为结构化数据缓存格式,全部存储到Redis,以解决跨平台、跨语言的问题。
简单介绍下Protobuf优势:
性能好/效率高
Protocol Buffer序列化之后得到的数据不是可读的字符串,而是二进制流,所以在占用空间极小,序列化性能非常好。
代码生成机制
在分布式系统中,因为程序代码时分开部署,比如分别为A、B。A系统只负责将调用和通信的数据以二进制数据流的形式传递给B系统,由B系统根据获取到的数据包,自己构建出对应的数据对象,生成数据对象定义代码文件。能解放开发者编写数据协议解析过程的时间,提高工作效率;其次,易于开发者维护和迭代,当需求发生变更时,开发者只需要修改对应的数据传输文件内容即可完成所有的修改。
支持“向后兼容”和“向前兼容”
向后兼容:在软件开发迭代和升级过程中,"后"可以理解为新版本,越新的版本越靠后;而“前”意味着早起的版本或者先前的版本。向“后”兼容即是说当系统升级迭代以后,仍然可以处理老版本的数据业务逻辑。
向前兼容:向前兼容即是系统代码未升级,但是接受到了新的数据,此时老版本生成的系统代码可以处理接收到的新类型的数据。
支持多种编程语言
Protobuf不仅仅是Google开源的一个数据协议,还有很多种语言的开源项目实现。在Google官方发布的Protobuf的源代码中包含了C++、Java、Python三种语言,在我们业务中使用了Java、C#、Golang。
整体而言,Protobuf以高效的二进制方式存储,比XML小3到10倍,快20到100倍。在应用中,数据读取、数据解析等性能方面有了很大的提升。
图3 缓存3.0
4、Protobuf结合LocalCache多级缓存(4.0版本):
图4 缓存应用场景
实际应用场景中,对各类数据进行划分存储。比如车型库基础信息、车源标签信息、商家基础信息、车源基础信息等等。因为部分业务在同一次请求中对缓存的读取次数相当高,高达几十次,比如二手车车源列表页数据,在渲染中,需要获取车源的基础数据,配置数据,标签等数据,都是同一时间大量请求Redis。Redis固然查询速度很快,但在高并发下大量网络IO也会导致响应变慢。在流量高峰或者突发性的增长下,网络IO的抖动容易造成服务器IO的堵塞,存在很大宕机风险。因此引入了本地缓存,由本地缓存来承接流量,从而减少网络IO的开销,有效的避免由网络IO不稳定而带来的风险。
图5 缓存4.0
缓存应用
图6 应用缓存数据时序图
图7 数据同步流程
在缓存的应用中,数据主要分为Redis、LocalCache两部分存储。
整体可分为DB、调度、Redis、LocalCache、应用,分别包含对多级缓存数据的读、写。
写:
由定时调度从DB拉取对应数据,转为二进制流,不同数据,设置不同Key,存入Redis。
如(Java):
读:
服务器应用使用缓存配置插件后,在应用启动后会自动同步所有缓存数据到本地内存中,同时开启自动同步机制,根据数据时效性高低,分别间隔不同时间从Redis更新数据到本地内存中,供应用随时读取。
如(读取以Golang为例):
实施成果
选择了每天1500w左右访问量的某一API业务(同一网络环境、服务器环境配置、网络配置都相同)分别在使用缓存和未使用缓存的情况下做了对比。(说明:对比均为正常业务,其中部分业务计算逻辑复杂或涉及三方API,仅对比使用到缓存部分业务逻辑),通过对比数据的分析,对API响应速度提升70%左右。
图8 API 99TP响应对比图
表1 未使用缓存API请求响应耗时
表2 使用缓存API请求响应耗时
总结及后续扩展
在使用缓存来支撑业务发展的过程中,随着业务的快速发展及流量的增长,碰到了磁盘IO、数据库资源、网络IO等各种问题,通过对实际问题的逐一解决,逐步升级缓存架构到现有的4.0版本多级缓存。多级缓存的使用在实际应用中,极大的提高了服务器并发处理能力,提高服务响应速度,在对业务、用户体验提升的同时,也提高了前端应用服务器资源的利用率,减轻了后端服务器的压力,避免了增加后端服务器的成本。
在实际业务中,还存在部分业务对数据时效性有较高的要求。针对这个问题,后续对缓存的升级有了初步规划,考虑数据的实时更新、主动更新来确保数据的及时性,更好的支撑业务,为用户带来更好的体验。