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

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

引语

所谓的第一性原理,就是无论使用什么方法论,都无法绕过的那最最基础的部分。无论是 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,一经查实,立即删除!

相关文章

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

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

.NET Core很酷,你不得不知

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

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

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

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

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

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

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

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

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

奇淫巧技-Flutter调用C#

前言众所周知,Xamarin应该是.net下的跨平台开发工具。2016年之前还处于收费状态,后被微软收购后开源。但似乎有个现象,开源后的Xamarin发展似乎有些停滞,而且维护Xamarin的团队又很固执不愿变通。社区多次建议UI层应该统一绘图引擎…

.NET World——gPRC概览

官方的定义:gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authen…

推荐高质量的公众号,值得跟进学习!

为大家推荐几个高质量的公众号:当你迷茫的时候刷刷这些大公司的牛人所运营的公众号,就可以知道自己的不足,不是仅仅局限于.NET技术之下,通过他们扩展我们的知识宽度,我们可以一起来学习。人工智能爱好者社区专注人工智…

.net core redis的全套操作

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。Redis支持主从同步…

Stack Overflow引入CROKAGE,搜问题不用再东拼西凑

许多开发者使用 Stack Overflow 搜索开发中遇到的问题,有时候需要查询的结果中有通俗的讲解,而且最好还能够带上代码 demo,但是两者同时很完美地满足要求还是比较困难的。不过最近 Stack Overflow 官方博客上介绍了一种智能化的技术&#xff…

使用Kubectl部署应用

目录 使用Kubectl部署应用 Kubectl部署流程 部署一个简单的Demo网站 使用Kubectl部署应用一旦运行了Kubernetes集群,就可以在其上部署容器化应用程序。因此在开始之前,我们需要先确保集群已经准备就绪,无论是使用Minikube还是kubeadm创建的集…

Docker系列之.NET Core入门(三)

在Docker生态系统中除了上一节所讲解的基本概念,还有其他专业术语,本文我们将一笔带过,同时会开始陆续进入到在.NET Core中使用Docker。专业术语Docker Engine(Docker引擎):客户端 - 服务器应用程序。Docke…

通过Blazor使用C#开发SPA单页面应用程序(4) - Ant Design

通过Blazor使用C#开发SPA单页面应用程序(1)通过Blazor使用C#开发SPA单页面应用程序(2)通过Blazor使用C#开发SPA单页面应用程序(3)前面学习了Blazor的特点、环境搭建及基础知识,现在我们尝试的做个实际的组件。Ant Design是蚂蚁金服是基于Ant Design设计体系的 UI 组…

Mercurial黄昏,Bitbucket宣布全面转向Git

源代码托管平台 Bitbucket 宣布将逐步放弃对版本控制系统 Mercurial 的支持。Bitbucket 推出于 2008 年,当时集中式版本控制是比较普遍的,Mercurial 是其中的典型代表,但是当前 Git 才是主流,它已经成为了大部分开源项目的首选版本…

使用 Azure DevTest Lab 搭建云端开发测试环境

点击上方蓝字关注“汪宇杰博客”导语程序员和测试工程师经常需要自己搭环境用于开发和测试目的,这些机器可能只会使用很短一段时间。通常我们会在本机使用 Hyper-V、VMWare 之类的虚拟机产品,或者使用企业IT管理员分配的虚拟机去完成这项工作。然而安装配…

dotNET Core WebAPI 统一处理(返回值、参数验证、异常)

现在 Web 开发比较流行前后端分离现在 Web 开发比较流行前后端分离,我们的产品也是一样,前端使用Vue,后端使用 dotNet Core WebAPI ,在写 API 的过程中有很多地方需要统一处理文档参数验证返回值异常处理本文就说说 API 的统一处理…

.net测试篇之单元测试/集成测试神器Autofixture

autofixture简介有了单元测试框架加上Moq(后面我们会用单独章节来介绍moq),可以说测试问题基上都能搞定了.然而有了AutoFixture对单元测试来说可以说是如虎添翼,AutoFixture并且它能与moq,rhinomock等框架结合,对单元测试带来的便捷性,可维护性和扩展性更是难以言表,只有用用了…

DotNetCore 3.0 助力 WPF本地化

概览随着我们的应用程序越来越受欢迎,我们的下一步将要开发多语言功能。方便越来越多的国家使用我们中国的应用程序,基于 WPF 本地化,我们很多时候使用的是系统资源文件,可是动态切换本地化,就比较麻烦了。实现思路现在…

开源题材征集 + MVCEF Core 完整教程小结

到目前为止,我们的MVCEF Core 完整教程的理论部分就全部结束了,共20篇,覆盖了核心的主要知识点。下一阶段是实战部分,我们将会把这些知识点串联起来,用10篇(天)来完成一个开源项目。现向园友征集题材,你提需…