在我们开发的诸多系统,基本都可以视为“数据密集型系统”,数据是一切物质的载体,我们依靠数据做存储记录,通过数据进行信息传递交换,最终还要数据来呈现和展示等,从一定视角而言,系统中最核心、最底层、最密集的是数据,时刻都在围绕数据构建服务运转并驱动业务。
我们先来思考几个比较本宗的问题 ——— 我们为何要开发一个系统?怎么体现一个系统的好坏?系统在为谁服务?
为何要开发一个系统?
在当前这个信息化、互联网的时代,软件系统已经根入到生活生产、衣食住行的每一个环节,无论是ToC或ToB业务,无论是天上飞的还是地上跑的,都有软件系统存在的身影。从最朴素的视角出发,我们是在用软件工程、技术手段来构造一个组合要件,完成功能满足需求,让它的形式看起来像一个整体,来服务一定范围的业务或者人群,并且会存在一定生命周期的持续性技术集合,在服役期间要尽最大能力达成业务诉求,要做到运转稳定、数据准确、体验良好等,最终收益是通过技术投入来助力业务运作的高效收益。
怎么体现一个系统的好坏?
随着承载业务的发展,系统也在随之变化,大部分时间是从0.0.1到0.0.2的逐层迭代,也会有1.x到2.x的改造升级甚至重构。小改动是量变,可以是功能优化,也可以是问题修复,投入可控,挑战不大,大改动是质变,非常考量团队及个人的能力水平,是对系统前期搭建可塑性、扩展性的考验和回溯,成功的质变带来的是无穷无尽的能量,它能紧跟业务的迭代进行适配推动其蓬勃发展,而糟糕的劣变会让系统埋下雷区,让团队成员疲惫不堪,从而与业务节奏脱节。
因此,一个大部分人眼里好的系统不应仅仅是让上游业务使用者感觉”厉害“,还要让参与系统开发、维护的参与者们感觉”舒适“,既要外表光鲜,也要自得其所。能够同时满足以上两点在诸多的项目实践过程中的确很难,但是如果能做到的确很棒,这就是一个”好系统“的标准。
系统在为谁服务?
角色 | 服务内容 |
---|---|
系统使用者 | 提供系统功能,提高作业效率,完善作业流程,基于信息化实现最高收益 |
系统建设者 | 反哺式反馈,决定方案和实施细节,又时刻获取正负面系统构建效果 |
基于上述,我们可以从系统使用者、系统建设者两个方面进行划分:
SLA (Service Level Agreement),即服务级别协议,是指提供服务的企业与客户之间就服务的品质、水准、性能等方面所达成的双方共同认可的协议或契约。
运动场上的健儿们追求的目标是奋力夺冠,技术角逐中研发小伙儿们的目标是打造"三高"。
除了对外部交付负责,还要对内部自身负责,体现在负责服务的工程质量,其中包括方案合理性、编码标准化等多方面,这需要参与系统建设的每一位小伙伴的共同努力和守护。
所谓系统,围绕业务展开,根据业务的复杂度可以非常庞大,也可以非常精简,可以是12306网站、京东商城这种量级的大家伙,也可以是一个班级人员查询、加减乘除计算这种基础功能。我们围绕复杂度适中的一般性系统构成来说明,从上到下大概可以分为用户层、应用层、数据层。
系统的分层划分是为了保持职责单一,让每个模块功能更内聚。每一层都很重要,每一部分的参与合作组成了系统全貌。
作为一名前端研发的话,相信你的视角会聚焦在用户层交互体验,作为一名服务端研发的话,相信你的关注点会放在应用层逻辑开发和一部分数据层交互,如果你是一名DBA或者数据BP同学,相信你的工作范畴会全部在数据层上。如果每一位同学都能够在做好本位工作的基础上跳出自身层次象限来全局审视的话,相信会有更多的认知和思考,会拥有一种全局视角,能够帮助更好地理解层次交互和递进关系。
相信嗅觉灵敏的你能够看到系统整体建构也很契合”底层结构决定上层建筑“的建造规律,而我们的系统搭建又何尝不是在做一次”建筑“之旅?在陪家里小朋友搭建乐高积木的时候,小朋友总是想搭得高高,在小朋友的世界里,搭得越高就意味着他的”系统“越厉害,大人总会引导说一定要把最下面的搭稳牢固,不然晃晃悠悠的很容易倒塌。数据层的构建就好比”乐高“最需要稳固的,我们要选择那些适合的存储中间件,追求大容量的上Hive
、Hbase
这种大数据存储,需要保证数据完整性的上Oracle
、MySQL
这些保证事务的DBMS
,对数据有聚合计算诉求的可以使用列式存储加快取数效率,需要条件筛选检索的搜索需要ES
、MongoDB
这些强大引擎支持,等等。这些数据组件就像形状各异的”积木“,平摊的桌面一定是用方形最牢固,拼接的缝隙根据缺口需要选择三角形、圆形等其他多边形组件来支持做到适配。
还是回到”程序=数据结构+算法“的话题上,数据层存储方式简单而言就是各种数据结构的具象化,从最简易的数据结构到从简易数据结构组合变化而来的复杂数据结构,都需要我们非常熟悉其特点,选择数据中间件其实就是在选择它背后包含和支持的数据结构,通过选择适合的要件才可以巧妙结合算法发挥出强大作用,比如你非要在一个朴素的单链表结构上进行顺序查找,它的平均时间复杂度永远定格在O(N)
这是不会改变的,而你把数据存储在数组里,借助数组索引就能有O(1)
时间复杂度的取数效率,反过来如果插入数据的话两种数据结构的效率又反之大相径庭。
底层的不牢靠,方案有问题,会直接影响应用层、展示层,而且上层建筑累死累活打补丁做优化也是治标不治本,这点对于服务端小伙伴来说特别重要,因为应用层、数据层设计都是从服务端发起并实现的,大部分同学刚刚入行时比较聚焦应用层语言、框架、设计模式的学习和积累,鲜有耐心埋头对数据层进行深入学习和分析,因为工作中大部分创造性工作在编码中进行,而且一些工作业绩产出息息相关,而数据层一般由DBA、数据BP进行维护和开发,所以技术实践过程中应用层方面设计做的相对较好,数据层中间件选择和库表设计相对比较初级,出现严重的”偏科“问题,木桶原理深刻的告诉我们一定要补足自己的短板,两个60分是都及格了,一个20分一个100分还需要参加补考。
前面讲到了数据层基石的重要性,下面来看看下系统中数据层究竟存储着哪些数据?
我们常说,要站在业务视角,离开业务空谈技术就像花朵离开了土壤,失去了技术产值。业务它在做什么、做了什么、做得如何,全都离不开数据进行承载和流转。
我们可以将与业务息息相关的数据称之为“事实数据”,这部分数据是真实的业务要存储和使用的,比如我们做一个订单系统,必然要存储订单的单号、下单时间、下单人信息等;把一些辅助支撑作用或衍生的数据称之为“辅助数据”,比如订单作为数据会有创建时间、删除时间、更新时间,为了加快订单中某些字段的查询我们会做索引数据来帮助加快查询等,这些便是起辅助和支持性作用的数据,更多的是帮助系统构建使用到的,不被业务直接感知和使用。
存在形式 | 举例 | 作用 |
---|---|---|
业务字段 | 订单号、下单人、下单时间、单据状态… | 交易记录、付款凭证 |
事实数据的规模是和其承载业务的复杂度、繁忙度是正相关的,两者的交织使得系统服务本身天生就是一个数据密集型的存在。
所谓复杂度就是把业务横切面来看,如果是一个UserInfo服务,涵盖的是查询、认证相关能力,支撑业务的模块版图就不大,所需要的存储结构相对就少,那么它的复杂度就是较低的,倘若换做成一个支付系统,复杂度就会提高非常多,订单、核销、履约、对账、渠道等等诸多环境和模块,那么底层所依赖的存储结构的子单元数量、关联关系都会增加。
所谓繁忙度就是把业务纵切面来看,代表业务的吞吐体量,一个县级别的农村合作社业务肯定比不过五大行,时间维度上数据的自然增长体量会有天壤之别。一个业务线上跑5年一张表存百万级数据和一个业务每小时存亿级数据的技术挑战、存储规格以及背后的数据检索、更新等捆绑要求是不可同日而语的。
存在形式 | 举例 | 作用 |
---|---|---|
辅助字段 | 创建时间、更新时间… | 辅助性、功能性记录 |
关系表 | 主外键映射关系表 | 解耦、关联 |
索引 | MySQL二级索引、ES倒排索引 | 加快检索 |
日志 | 应用日志、操作日志… | 便于追踪,排查问题 |
关于辅助数据的用途真的太多太多,它的呈现形式也是多种多样,即可以用来做技术优化,又可以用来做问题排查、辅助功能。
这里举一个例子,我们简单的写一个Query接口把它部署到运行环境中,一次简单请求串联和涉及了多少数据以及数据相关能力的支持呢?在微服务体系的架构中,首先要经过鉴权、链路、网格各类基础运维系统进行日志记录、链路追踪、染色路由等,当终于抵达目标业务系统时候必然要经过自身数据存储进行数据读取,这部分数据源还可能已经经过你的索引优化、数据层抽象关联等,还可能需要通过Http、RPC等协议再关联其他系统进行数据读取和关联,最终组装编排返回到上游呈现在顶层。
对比来看,如果事实数据的规模是1
,那么辅助数据会是1*N
,为了“更高、更快、更强”地支撑事实数据运转流通,需要诸多的辅助数据配合,这使得原本就足够数据密集的应用附带了更多的数据环绕进行加持,加剧了系统数据密集的规模和体量。
关于数据使用的场景一般而言,我们会分为OLAP和OLTP。
场景 | 侧重点 |
---|---|
OLTP | 偏向数据存储,强调事务性(ACID)、实时性 |
OLAP | 偏向数据分析,强调计算、聚合、筛选、转换 |
(On-Line Transaction Processing)联机事务处理
能够迅速、一致、交互地从各个方面观察信息,以达到深入理解数据的目的。它具有FASMI(Fast Analysis of Shared Multidimensional Information),即共享多维信息的快速分析的特征。主要应用是传统关系型数据库。OLTP系统强调的是内存效率,实时性比较高。
(On-Line Analytical Processing)联机分析处理
基本特征是前台接收的用户数据可以立即传送到计算中心进行处理,并在很短的时间内给出处理结果,是对用户操作快速响应的方式之一。应用在数据仓库,使用对象是决策者。OLAP系统强调的是数据分析,响应速度要求不高。
中医的治病的手段是“望、闻、问、切”,这是从事实出发、实践出真知的典范,技术方案的调研环节也应该有同等思维做储备和引导。OLAP和OLTP的划分,对于数据密集型下应用开发的方案判断选择是有很好指导意义的,能够让我们摒弃那些根本就不适合场景的蹩脚设计以及后续无穷无尽的“优化”,让我们选择正确的数据存储以及适合该场景的下技术方案进行演进,从“底层结构决定上层建筑”这一根本的开始就做好做对。
一般来说,关系型数据库具备较为强大的对象结构与关系的描述能力,大部分时间我们选择使用MySQL
、Oracle
这些老牌产品进行核心业务的存储,它们是数据层存储的主力军,对于数据来说无非就是写入、查询两种操作,关系型数据组件的标配之一就是支持完整的事务性(ACID),业务数据是核心,而完整记录和存储它是核心的核心,破坏数据的完整性其他功能将没有价值,因此数据存储都会抱紧关系型数据做数据层设计和展开。
在项目实践中基本都有一个“读多写少”的共识,如何最快、多样化地检索到数据是每个系统搭建过程中的必经一课。搜索引擎一类数据组件提供的能力,而这些搜索引擎大部分加快查询的思路之一就是通过空间换时间提前存储目标数据以及结构加快特定查询诉求。
比如ES
能够很好地弥补关系型数据库中条件筛选或模糊查询能力的不足,就是通过倒排索引的构建来完成的;还有我们在关系型数据库中也会使用到二级索引,增加特定字段的存储与关联,减少回表的检索路径,甚至进行覆盖索引以求达到最佳查询效率。
只要使用硬盘存储,都无法越过I/O读取这层屏障。更快的查询我们还可以选择使用应用内存、外部内存来加速我们的查询,从而获得更高的吞吐和性能。
Redis
、Memcached
可以让数据检索和更新从硬盘I/O级别读取能力提升到内存I/O级别,而应用级内存则比外部内存更进一步,抛开了协议通信和数据交换,更快一步。但是需要明确的是,好的东西都很稀缺,内存资源是成本相对宝贵的,只适合那些短小精悍的数据内容,需要我们把好钢用在刀刃上,而且我们需要不能光聚焦在SLA的提升还需要考虑ROI,比如你的数据就存储在硬盘上,服务查询响应时间是100ms,吞吐量也足够满足业务需求,即使提升到5ms对业务也没有任何增长作用,但是却需要投入资源开发改造,徒然提高成本,这便有些偏离初衷了。
消息队列是高并发、大数据环境下很好的产物,它给系统交互增加了一层Buffer,这层缓冲可以让不相关的系统实现交互的解耦,提供异步重试能力,能够有效限制流速等等,让服务在构建过程中更具有伸缩性、扩展性。出镜率较高的有应用层开发涉及的RocketMQ
和数据层涉及的Kafka
等。
长久以来,数据存储的先入为主的都是以行式结构进行存储,因为通过主键或者辅助索引关联查询到后就可以拿到这一行数据记录,读取效率很高。随着大数据化,对数据分析的诉求日益增多已经成为常态,不光光是单纯的检索出原始数据行,而是要条件筛选然后进行聚合完成统计工作,这种场景下行式存储就显得有些笨拙,比如表里存了用户的年龄,现在要统计所有用户的平均年龄,那么需要把所有行数据都检索出来,而且不需要统计的行内其他字段也需要通过I/O读取出来定位到需要的年龄字段后一个个缓存下来,然后把所有的年龄字段计算好返回结果,这个过程中大量不必要的I/O读取非常浪费资源。
而列式存储就是为了适配这种统计场景应运而生的产物,在数据存储上得天独厚
的优势给这个系列的数据组件产品能够在较短响应时间内返回统计结果,代表性产品如Clickhouse
。
系统构建本身是冰冷的,而参与构建它的人是有温度的。如果系统可以规划为三层,那么系统建造者的自我演化也可以划分为三个阶段,分别是新生期、进阶期、高能期。
如何成为高能期的系统建造者呢?请关注与你息息相关的业务,业务让技术有价值。
如何去关注业务呢?请你拥有数据视角,数据让业务有方向。
以上便是本章的全部内容,如果觉得有所收获,欢迎 『点赞』、『收藏』、『关注』 一键三连支持喔~