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