软件设计的第一性原理:结构化抽象

软件设计的第一性原理,是结构化抽象。术生于道,技术生于原理。

引语

所谓的第一性原理,就是无论使用什么方法论,都无法绕过的那最最基础的部分。无论是 DDD 设计,还是面向模式的架构设计,或 微服务架构,均建基于结构化抽象。

何为结构化抽象 ?先回答 “何为抽象” 与 “何为结构化” 两个问题。

何为抽象 ? “编程漫谈(三):抽象” 一文阐述了什么是抽象及在编程与计算中的意义; “代码抽象与分层” 则列举了从代码中提炼出来的六类抽象,涵盖了编程开发中常见的实体及处理。

何为结构化 ?结构,是事物的组成元素及关联和作用。 “编程漫谈(十一):编程概要” 一文阐述了编程中的各种数据结构及控制结构,“软件的组合结构:从指令到软件” 则阐述了组合元素的方法与结构。结构化,分为数据结构化和控制结构化。数据结构化指海量数据的组织和存储及需要满足的约束(比如一致性、完整性等);控制结构化,是指,操作的组织和执行以及需要满足的约束。

软件,本质上是一种可动态而弹性变化的逻辑装置。结构化,即是将逻辑进行抽象、提炼、分离、聚合,构建成更加缜密、动态、弹性的结构流。

逻辑,是思维意识的一种形式。软件设计与开发,是与自己的思维意识进行抗争与和解。

理念

设计与开发

软件设计与开发工作渗透着结构化抽象的思想。譬如:

  • 软件建模。是对大量原始数据进行结构化组织,使之更加有序、有意义、可交互。

  • 数据存取。在建立对数据的组织抽象之后,就要进行数据的存取操作。数据是规则的还是不规则的 ?数据量有多少及增长速度如何 ?是否要进行缓存 ?选取合适的数据存储组件。数据存储组件是对海量数据存储和访问的结构化抽象。

  • 流程构造。在确定数据存取方案后,需要构建完整的流程。流程可以使用时序图来表示。流程包含前置、操作与契约。前置,是进行操作需要满足的条件;操作则是获取数据或请求某种执行;契约,是在完成操作之后,必须满足的一系列断言。完整流程通常是:“前置-操作-契约”的子流程的有序组合。

  • 编程实现。需要评估潜在变化的部分,将通用的部分与易变化的部分相分离。

设计模式

设计模式是对象职责及交互的结构化抽象,是体现结构化抽象思想的基本单元。可参阅:“软件设计要素初探:基础设计模式概览” 。

架构模式是基于设计模式的更高层次的结构化抽象。可参阅:“软件设计要素初探:架构模式” 。

设计模式和架构模式主要应对软件的业务可扩展性难题。

技术机制

在软件设计中,技术是绕不开的一道槛。技术的作用在于,在指定的场景下,所执行的操作效果必须满足某种约束。技术机制,是将多个相关联的结构化抽象进行聚合后的成品。

异步

比如先快速响应客户端,再进行请求处理。其结构化抽象是,操作相对于主进程的执行耗时与实际执行耗时无关。

幂等

比如处理资金问题,必须考虑幂等问题,即同一个请求,执行多次的效果必须与执行一次的效果等同。幂等的结构化抽象是,操作的主效果与操作次数无关。

事务

比如多个关联数据的插入、更新和删除,必须保证原子性。要么全部执行,要么一个都不执行。需要使用事务来保证。事务的特性是ACID,结构化抽象是关联数据集合在操作前后必须满足某种一致性约束。确定“关联数据集的一致性约束”是关键,其实现是还原点、快照与回滚日志。

并发

大量请求或数据集的处理,使用串行的方式效率难以满足性能或吞吐量要求,需使用并发的方式,充分利用多核CPU资源。结构化抽象是,多个相互独立的执行单元。这些执行单元拥有独立的CPU和缓存,所使用的内存可以为共享内存型和独立内存型。

同步

在并发场景下,要保证多个执行单元(比如线程、进程等)能够看到共享内存的最新更新值。同步的本质是确保指定顺序执行,避免不确定的执行顺序带来不确定性的结果。其结构化抽象是临界区。临界区是约束执行顺序的一种结构。

框架

应用的组件、配置与启动,可以做成通用的框架和脚手架反复使用,快速启动和部署一项工程,减少不必要的重复工作量。框架的结构化抽象是,将工程中的配置、部署与启动、运行结构、通用任务进行提炼并形成固定的模式,应用只需要关注可变的业务部分。

限流

突发的峰值流量,为了避免瞬间占满和击垮服务器的资源和服务能力,需要进行限流。其结构化抽象是,在指定时间间隔内的通过许可数必须满足指定规格。

缓存

对于热点数据,为了避免反复从源存储获取,增大对存储的访问压力,可以使用缓存来存储热点数据,增大命中率,提升性能,减少对存储的不必要的访问压力。缓存的结构化抽象是,使用少而精的空间优先于大而全的空间的搜索。“少而精”是指聚焦应用的经常被访问的热点数据。缓存的衡量指标是命中率和过期时间,操作是缓存与源存储的读写同步。

降级

当非核心的依赖不可用时,可以及时切断依赖,舍小取大,保证整体服务正常运行,不受局部影响。降级的结构化抽象是,是主备策略的设计与切换机制。

重试

当处理数据发生错误时,可以进行重试来进行补偿和恢复。重试的结构化抽象是,至少(且通常只需)保证一次操作成功。

切面

比如耗时统计。耗时统计与操作的执行内容无关,仅关注操作的耗时。切面的结构化抽象是,操作的非功能属性与功能本身的解耦。

代理

比如请求的负载均衡。不是直接执行目标操作,而是创建一个代理,由这个代理转发请求和执行目标操作。代理的结构化抽象是,隐藏目标操作。

版本号

版本号通常用于实现非阻塞式并发,即乐观锁。其结构化抽象是,一个严格保证特征数值单调递增的机制。

实践

示例分析

要对问题进行结构化抽象,需要先提取问题的结构特征。可以分别从数据结构化和操作结构化两个角度来思考。

分页

比如分页功能是大多数信息系统管理的必备功能之一。怎么实现一个通用的分页功能呢 ?

从数据结构化来思考,其结构要素为:偏移量、页大小、起始和结束位置。分页本身和获取何种对象无关。

从操作结构化来思考,其结构要素为:1. select columns from table where condition limit offset, size ; 2. count distinct(id) from table where condition limit offset, size .

columns , table, condition 都是可以根据具体业务来动态生成的,而整个 SQL 的骨架是固定的。table 不一定指 DB , select , count 也不一定是 DB 的 sql ,它只是表示在指定搜索条件下进行对象选择与统计的操作语义。columns , table, cond 的动态生成,可以使用泛型和回调函数来传入和处理。

限购

限购,是指定时间内,指定 key 的实体允许通过的许可数量。与限流是同一类结构化抽象。许可数量,是一个全局性约束。

假设去掉“指定时间”的约束,只限制许可数量, 其结构化抽象是,dec if total - count(key) > 0. 并发的场景下,total - count(key) > 0 的值需要进行全局同步。

如果加上“指定时间”的约束,还需要考虑高并发场景下 dec if total - count(key) > 0 的操作耗时。如果指定时间内不允许超过限购数量(强约束),则必须对 dec if total - count(key) > 0 进行加锁,吞吐量取决于操作耗时而不是指定的限购数;如果指定时间内可允许暂时地超过限购数量(弱约束),则可参考限流算法。

数据同步

数据同步的结构化抽象是,将源存储 S1, S2, ..., Sn 的数据复制到目标存储 D 。

数据同步分为两种不同的场景,有实时同步和离线同步。实时同步对数据延迟性容忍非常低,离线同步则要求更快的吞吐量。

实时同步通常采用流式同步,通过接收消息流来处理。操作的结构化是,Receiver (msg) -> Format(msg) -> Save for each msg . msg 是源存储中的一条数据或一个单位数据集。

离线同步通常采用批量处理,实现方式是批量获取数据并批量格式化后存储。操作的结构化是,Divide(S) into N part(S) ; Select part(S) Then Format part(S) and Save for each part(S) 。可以根据业务唯一 ID 实现一个通用的 Divide 算法。

离线同步要注意 format 的超时和健壮性处理,记录下异常以便重试,同时不因单个数据处理失败而中断整体流程;流式同步则要注意控制好并发情形下的准确性。由于对吞吐量要求比较高,往往采用乐观锁的方式,找到某种能够控制全局版本号单调递增的机制,或者尽量避免“多表同步到单表”的场景【并发场景下会带来更多复杂性】。

积累抽象

在实际工作中,可以反思和提炼设计中所用到的结构化抽象。如果现有的结构化抽象及组合难以解决问题,是否需要新的结构化抽象 ?创建新的结构化抽象,并使之与已有的进行组合和集成。

规模化挑战

如果程序媛猿面对的是几万的数据量,那是可以夜夜笙歌的。然而,现实情况是,面对的是亿级以上规模的数据量,且数据量仍然在指数级增长。为了人类社会的无理性发展,程序媛猿们真是费尽了心思花白了头。

为了应对亿级规模的数据量,并发、分布式等方案层出不穷,结合变化的业务场景,又衍生出更多的挠人烧脑的复杂问题。如何应对呢?

规模化挑战的结构化抽象是,在指定时间内,每秒处理的请求/单位数据集的吞吐量,处理一个请求或单位数据集的平均响应时间,以及在流量剧烈变化时的弹性扩展能力。

并发

并发和并行机制取代了串行机制。单CPU和单机的性能基本抵达瓶颈,只能从多核CPU和多机上想办法。将要处理的数据量分解为多个相互独立的子数据集,并在不同的执行实体里相互独立地执行。比如线程池,Fork/Join , Map-Reduce 执行模型。并发和并行虽然解决了单机性能不足的问题,却引发了更多的问题。

同步

有了并发执行之后,由于有些资源是共享的,而一些热点数据往往被多个执行实体同时读取和修改,又产生了竞争问题。为了解决竞争,引入了同步机制。大量对同一资源、数据的操作进行同步,引起性能问题。

缓存

对于热点数据的读取和操作,通过“空间换时间”的策略,使用缓存来提升性能,降低对源存储的访问压力。

限流

由于允许并发请求进入,则必须应对瞬间的峰值流量(可见世态)。限流必须有度,不能因噎废食。需要对系统承载的负荷及极限负荷进行测量,根据测量值来确定一个可动态调整的限流值。极限负荷测量即是压测。

降级

由于业务特性的不同,环境的不稳定波动,以及采用方案的局限性,依赖服务有可能出现部分失败,对于调用极其频繁的服务来说,依赖服务的少许失败可能导致上层的雪崩效应。因此在依赖服务出现问题时,必须进行适当的降级熔断。

测量

为了避免大流量或大数据结构导致软件运行出现问题,降低维护成本,不能仅仅停留在定性分析上,还要进行量化。需要对系统进行仔细的测量。服务接口的 RT 和 吞吐量 如何 ?存取操作耗时如何 ?消息处理耗时和吞吐量如何 ?内存占用如何 ?大数据对象占用内存多大 ?能够承载的极限流量有多大 ?能够承载的极限对象大小是多大 ?指定时间段的失败数和失败率有多大 ?

集群

若以单机视角去考虑问题,就会殚精竭虑地在“并发、同步、缓存、限流、降级”等上做到极致,就像在单核时代将CPU主频做到极致一样。然而,即使做到极致,为了峰值流量所构建的机器资源,在平均流量场景下会造成很大的浪费;并且,很难预估峰值流量会在什么时候来到。如果有一个超级大的聚合的池化资源,总可以提供足够强大而弹性的CPU计算能力、内存能力、磁盘空间,那么以上规模化引起的问题也将迎刃而解。这个超级大的聚合的池化资源,就来自于构成集群的分布式系统的资源虚拟化后的能力,即云计算能力。

可视化

为了从数据集中发现整体性的规律和趋势,通过将大量数据集进行可视化,拥有一个简明的全局视角。

AI

以数据为基础,以规则集为准绳,训练机器通过基本的规则集与数据的计算而获得某种“学习”能力,从而能够分析更多的数据集,调整现实活动和方式,获得人力所无法得到的见解和经济效益。

经验与洞见

招聘或找工作的人,常常说要“工作经验多少年才行”,然而,在软件设计中,却容易“有经验而无洞见”。

经验是什么?经验是工作中遇到的问题及解决方案。有些问题,只有在规模达到足够大的时候才会出现;而大多数问题,只要根据原理就能推导出来。在软件世界里,并没有一成不变的最佳经验。

洞见是什么?洞见来自于究根追源,从最基本的公理进行推导得出定理和定律,从而能够预知问题。逻辑,本质上是一种数学结构,而数学正是能够进行推导的严密思维体系。

软件设计的洞见来自哪里 ?究根追源,软件逻辑建始于两条公理:

  • 1 + 1 = 2

  • 机器的字长有限,内存有限;执行指令需要 CPU 时钟周期

几乎所有软件问题都是这两个基本要素的组合和叠加而产生的。软件设计崇尚“自顶向下”的方式,然而,要获得洞见,却需要“自底向上”的思考和推导,通过多个层次的结构化抽象来建立。万变不离其宗。

注意到,第二条公理描述了现实系统的局限性。局限性,正是经验的用武之地。

在初期,可以 80% 依据经验,20% 依据原理;而在后期,则应该 80% 依靠原理,20% 依靠经验。那么,不依靠经验应该怎么做呢 ?举一个 API 调用的例子。可以测量这个 API 的平均耗时,耗时分布,大体了解这个 API 的对外输出指标。API 超时是因为什么呢 ?可能是网络环境波动导致。很难通过设计和开发优化环境的波动。如果不是,从 API 的实现层面来说,很可能是因为并发的大流量导致了竞争加剧,线程死锁或者大量线程阻塞。API 超时又会导致多个子系统间数据不一致。可见,并发与大流量,是系统必须要面对的“对手”。并发与大流量一定需要通过工作经验来获得吗?非也。并发与大流量的场景是可以模拟的。

可以通过原理推导,结合实验,来模拟各种现实场景,测量数据, 并制订应对方案。 这是细功夫,也是洞见的产生之源。

小结

本文阐述了软件设计中的结构化抽象的理念及实践方法,涉及设计与开发、设计模式、技术机制、示例分析、规模化挑战,及设计中的经验与洞见。结构化,即是将逻辑进行抽象、提炼、分离、聚合,构建成更加缜密、动态、弹性的结构流。

做软件设计,要同时看到骷髅与美人。论理,要看到骷髅;论情,要看到美人。理,即是结构化抽象。

原文链接:https://www.cnblogs.com/lovesqcc/p/11220727.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/314327.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HDU 6706 huntian oy (欧拉函数 + 杜教筛)

huntian oy 推式子 ∑i1n∑j1igcd(ia−ja,ib−jb)(gcd(i,j)1)∑i1n∑j1i(i−j)(gcd(i,j)1)∑i1ni∑j1i(gcd(i,j)1)−∑i1n∑j1ij(gcd(i,j)1)∑i1niϕ(i)−∑i1niϕ(i)(i1)2∑i1niϕ(i)−(i1)2然后套路地变成求S(n)∑i1niϕ(i)g(n)nϕ(n)这里直接套用杜教筛化简得到g(1)S(n)∑i…

【LOJ#6682】梦中的数论(min_25筛)

【LOJ#6682】梦中的数论 https://www.cnblogs.com/cjyyb/p/11178395.html 利用min_25筛,求解约数个数函数平方的前缀和。

基于.NetCore结合docker-compose实践Gitlab-CI/CD 排坑指南

引言看过docker-compose真香的园友可能留意到当时是【把部署dll文件拷贝到生产机器】,即时打包成镜像并启动容器,并没有完成CI/CD。经过长时间实操验证,终于完成基于Gitlab的CI/CD实践,本次实践的坑位很多, 实操过程尽…

51 NOD 1227 平均最小公倍数(杜教筛)

1227 平均最小公倍数 推式子 S(n)∑i1n∑j1ilcm(i,j)i∑i1n∑j1iijigcd(i,j)∑i1n∑j1ijgcd(i,j)∑i1n∑d1i∑j1ijd(gcd(i,j)d)∑i1n∑d1i∑j1idj(gcd(j,id)1)∑i1n∑d1iidϕ(id)(id1)2∑d1n∑i1ndiϕ(i)(i1)2接下来就是杜教筛求∑i1niϕ(i)了,g(1)S(n)∑i1n(f∗g)…

【BZOJ3512】DZY Loves Math IV(杜教筛)

【BZOJ3512】DZY Loves Math IV(杜教筛) https://www.cnblogs.com/cjyyb/p/10165338.html

.NET Core很酷,你不得不知

我一直回想我的第一篇博文,那是关于多个服务的服务器平台的详细教程,它使用 GitLab CI 在 AWS 上,当时使用单个命令行进行部署, 至今回想,令人感觉很酷。前几天,我偶然听说一些软件公司的 HR 在招聘原则上拒…

51 NOD 1363 最小公倍数之和 (欧拉函数思维应用)

1363 最小公倍数之和 推式子 ∑i1nlcm(i,n)n∑i1nigcd(i,n)n∑d∣n∑i1nid(gcd(i,n)d)n∑d∣n∑i1ndi(gcd(i,nd)1)n∑d∣ndϕ(d)(d1)2\sum_{i 1} ^{n} lcm(i, n)\\ n\sum_{i 1} ^{n} \frac{i}{gcd(i, n)}\\ n \sum_{d \mid n} \sum_{i 1} ^{n} \frac{i}{d}(gcd(i, n) d)…

【BZOJ3930】选数(莫比乌斯反演倍数形式,杜教筛)

【BZOJ3930】选数 https://www.cnblogs.com/cjyyb/p/8303813.html

程序员修神之路--高并发系统设计负载均衡架构

点击上方“蓝字”关注,酷爽一夏菜菜哥,上次你给我讲的分库分表策略对我帮助很大有帮助就好,上次请我的咖啡也很好喝~呵呵,不过随着访问量的不断加大,网站我又加了nginx做负载均衡好呀,看来要进阶高级工程师…

类欧几里得算法详细推导过程(附带模板)

类欧几里得算法推导 初识 给出三种形式: f(a,b,c,n)∑i0n⌊aibc⌋f(a, b, c, n) \sum_{i 0} ^{n} \lfloor\frac{ai b}{c}\rfloorf(a,b,c,n)∑i0n​⌊caib​⌋g(a,b,c,n)∑i0ni⌊aibc⌋g(a, b, c, n) \sum_{i 0} ^{n}i \lfloor \frac{ai b}{c}\rfloorg(a,b,c…

【Luogu3768】简单的数学题(莫比乌斯反演/杜教筛/欧拉函数)

【Luogu3768】简单的数学题 https://www.cnblogs.com/cjyyb/p/8298339.html

【学习笔记】Docker - 01. Docker是啥

我只是把之前的学习笔记整理一下,贴到这里,可能会显得比较凌乱。。。1.1 啥是Docker?Docker 是一个开源项目,它被用来做构建、打包和运行程序。它是一个命令行程序,一个后台进程,也是一组使用逻辑方法来解决常见软件问…

使用 .NET CORE 创建 项目模板,模板项目,Template

场景:日常工作中,你可能会碰到需要新建一个全新的解决方案的情况(如公司新起了一个新项目,需要有全新配套的后台程序),如果公司内部基础框架较多、解决方案需要DDD模式等,那么从新起项目到各种依…

【BZOJ4916】神犇和蒟蒻(杜教筛)

【BZOJ4916】神犇和蒟蒻(杜教筛) https://www.cnblogs.com/cjyyb/p/8297338.html 杜教筛技巧

E. Number Challenge

E. Number Challenge 推式子 ∑i1a∑j1b∑k1cσ(ijk)∑i1a∑j1b∑k1c∑x∣i∑y∣j∑z∣k(gcd(x,y)1)(gcd(x,z)1)(gcd(y,z)1)∑x1a∑y1b∑z1c⌊ax⌋⌊by⌋⌊cz⌋(gcd(x,y)1)(gcd(x,z)1)(gcd(y,z)1)∑d1aμ(d)∑x1⌊ad⌋⌊adx⌋∑y1⌊bd⌋⌊bdy⌋∑z1c⌊cd⌋(gcd(x,z)1)(gcd(y,z…

时间表(日记)

2021/1/14 今天白天学习还算成功,但是晚饭后拿起手机就开始颓废了,特别是因为听相声实在是无聊而且低俗,所以又忍不住诱惑下载了b站,相比于相声b站的确是有营养的多,但是一旦激起了我的好奇心,尤其是吸引了…

谈谈surging 微服务引擎 2.0的链路跟踪和其它新增功能

一、前言surging是基于.NET CORE 服务引擎。初始版本诞生于2017年6月份,经过NCC社区二年的孵化,2.0版本将在2019年08月28日进行发布,经历二年的发展,已经全部攘括了微服务架构的技术栈,覆盖了从服务注册、服务发现、中…

2019-ACM-ICPC-南京区网络赛-E. K Sum(莫比乌斯反演 + 杜教筛)

K Sum 推式子 Fn(k)∑l11n∑l21n⋯∑lk1n(gcd(l1,l2,…,lk))2∑d1nd2∑l11nd∑l21nd⋯∑lk1nd(gcd(l1,l2,…,lk)1)∑d1nd2∑l11nd∑l21nd⋯∑lk1nd∑t∣gcd(l1,l2,…,lk)μ(t)∑d1nd2∑t1ndμ(t)(ntd)2另Ttd∑T1n(nT)k∑d∣Td2μ(Td)∑i2nFn(i)∑T1n∑i2k(nT)i∑d∣Td2μ(Td)到…

【BZOJ4028】[HEOI2015]公约数数列(分块/数量级很小法)

【BZOJ4028】[HEOI2015]公约数数列 https://www.luogu.com.cn/problem/P4108 求解最靠前的一个位置前缀gcd和前缀异或的乘积恰好等于x,并且要求支持单点修改 首先我们可以发现这个问题没法使用线段树维护,因为不仅前缀没有单调性,而且修改也…

GuGuFishtion(2018 Multi-University Training Contest 7)

GuGuFishtion 推式子 ∑a1m∑b1nϕ(a,b)ϕ(a)ϕ(b)∑a1m∑b1ngcd(a,b)ϕ(gcd(a,b))∑d1mdϕ(d)∑a1md∑b1mdgcd(a,b)1∑d1mdϕ(d)∑a1md∑b1md∑k∣gcd(a,b)μ(k)我们约定n,m&#xff0c;满足n<m∑d1ndϕ(d)∑k1ndμ(k)⌊nkd⌋⌊mkd⌋\sum_{a 1 } ^{m} \sum_{b 1 } ^{n} …