0%

如何做系统设计

序言

系统设计是作为一个架构师最基本的素质和能力要求;本人对于过往的工作经历来关于系统设计总结(方法论);

Tips:
系统设计的范畴要比软件工程小;系统设计对于软件工程占据着最重要的位置。

系统设计流程

  1. 需求分析
  2. 资源评估
  3. 定义目标
  4. 领域设计
  5. 技术设计
  6. 数据库设计
  7. 代码实现
  8. 交付测试&上线

步骤详解

需求分析

正如每个技术框架是为了解决某一类问题一样,系统必然是为了解决或基础平台,或业务诉求,或市场问题而出发设计的。 明确系统产生的背景, 准确的识别方案与需求不同对于设计一个真正能解决问题的系统至关重要。一个优秀的系统设计必然能够全面的了解系统的背景。

ps:
常见基础平台技术需求总结:

  • Dubbo, HSF, gRPC解决了RPC(服务治理)的问题
  • azkaban, elastic-job, dophinescheduler解决了任务调度的问题
  • mysql, redis, tidb, posgres,hdfs,hbase解决不同场景下的数据存储问题
  • clickhouse, impala, kylin, presto,druid解决的是olap(ad-hoc)问题
  • sentile, guava rate-limiter解决限流,降级的问题
  • zookeeper, etcd解决了分布式共识问题

常见的业务类需求:

  • emall (电商系统)
  • wms(仓库管理系统)
  • crm (客户管理管理)
  • iot (物联网)
  • im (通讯)
  • ERP(企业资源管理)

如何做好需求分析

  • 明确问题的边界: 是做好需求分析的第一步; 这个边界可能会很大,也可能会很小,但是我们必须要明确一个边界。脱离了边界或者偏离问题域,那么就是浪费时间,事倍功半。
  • 不谋全局者, 不足以谋一域: 不要仅仅看到产品或者业务提出的需求,要从市场,企业的高度认识该系统在市场和企业中的位置和如何和上下游系统的交互,深刻的理解系统的价值和内涵以及系统存在的意义。 (透过现象看本质)
  • userstory: 根据数据流向以及用户故事,准备的画出核心业务流程图或者数据流向图(画图是非常棒的帮助理解需求和系统流程的方法)
  • 耐心细致: 这点容易被忽略,所以导致开发后会存在返工的现象。

    关于第二点我就自己的工作经验做个分享 - 租赁系统 - 算了吧,篇幅有限。

资源评估

好的系统设计不仅实现企业的既定业务需求,还要在不影响用户体验的前提的为公司节省人力和资源成本,而且能够评估出未来半年到3年范围内,系统设计能够保持一个较好的可扩展性和可维护性,快速迭代,保持产品竞争优势。

这里说的资源评估包括但不限于 中间件的维护以及服务器资源,服务实例部署资源,公网IP, 带宽以及运维成本等。合理评估用户增长速度和规模对于资源评估至关重要。

这里需要经验积累,不过要是没有也不重要, 完全可以做到在需要的时候,进行基准测试或者网络搜索既有的测试结果来完成评估。 比如根据消息数量来评估kafka集群配置,根据qps和数据量来评估redis配置,根据用户数据以及业务系统评估磁盘容量以及部署服务实例的个数。

定义目标

一切业务数字化,一切数字业务化;定义目标的基本的原则是目标是可量化的,非常不认可似是而非的目标定义。
系统性来看, 目标可分为三大类

  • 技术性目标: 非常推荐使用SLA协议去定义技术类指标,明确的表明与二方,三方或者外部系统之间交互的标准。
  • 业务目标: 除了完成既定业务需求以外,关注用户增长率,营收金额(GMV),转化率(漏斗分析),完成一个迭代周期
  • 成员指标: bug率(bug要分级),投诉数量, delay天数、次数

Tips:
SLA常见指标: 可用率, TP99, lantency(延迟,分 read/write, 服务内部,公司其他系统以及外部系统), TPS/QPS
三高: 高可用, 高性能,高并发

领域设计

好吧, 你没有想错, 这里的领域设计指的就是基于DDD(领域驱动设计)的思想对系统进行领域设计。
先粘贴一段Dubbo的话语

1
2
3
4
5
6
7
服务域/实体域/会话域分离
任何框架或组件,总会有核心领域模型,比如:Spring 的 Bean,Struts 的 Action,Dubbo 的 Service,Napoli 的 Queue 等等。这个核心领域模型及其组成部分称为实体域,它代表着我们要操作的目标本身。实体域通常是线程安全的,不管是通过不变类,同步状态,或复制的方式。

服务域也就是行为域,它是组件的功能集,同时也负责实体域和会话域的生命周期管理, 比如 Spring 的 ApplicationContext,Dubbo 的 ServiceManager 等。服务域的对象通常会比较重,而且是线程安全的,并以单一实例服务于所有调用。

什么是会话?就是一次交互过程。会话中重要的概念是上下文,什么是上下文?比如我们说:“老地方见”,这里的“老地方”就是上下文信息。为什么说“老地方”对方会知道,因为我们前面定义了“老地方”的具体内容。所以说,上下文通常持有交互过程中的状态变量等。会话对象通常较轻,每次请求都重新创建实例,请求结束后销毁。简而言之:把元信息交由实体域持有,把一次请求中的临时状态由会话域持有,由服务域贯穿整个过程。

关于DDD,我说下自己的感悟;
限界上下文非常重要, 它明确了我们要处理问题的边界。 核心子域,通用子域,支撑子域都有自己的边界, 根对象是子域交互的核心,值对象保存基本的配置信息和上下文信息。 子域划分的粒度以及边界的确定是DDD最难的一点, 就经验来说,处理好的关键是不断的分析用户故事,看下子域之间的交互频率以及业务运营的主体是不是同一个部门。

领域设计要有产出
图 – 领域边界图以及各个子域的功能图
文档 – 1. 各个子域的API,以及核心的根对象和值对象 2. API说明文档

技术设计

通用设计

单体架构基本被时代淘汰掉,分布式架构(SOA,微服务,微内核, ESB)已经成为事实标准。 包括服务注册发现,网关, 鉴权,链路追踪,线上监控, 消息队列选型,缓存,一致性, 分布式事务, 实时索引,对接三方系统以及数仓,工作流,规则引擎,定时任务,日志服务等。

在设计阶段,很难做到考虑到所有的点,但是遵循可演进,合适,可扩展的基本原则,我们要明确核心的数据流向流程和遵循企业研发规范,在此基础上,就能保证不会偏离主线。螺旋式演进才是王道。

技术设计同样需要产出

  • 架构设计图
  • 核心业务流程图
  • 核心模块组件图
  • 服务划分(基础服务做SASS,做中台,特定业务线的服务做分离,做聚合)说明文档
  • 核心模块交互时序图

技术设计本质上是架构设计, 需要反复推敲和讨论对业务建模,然后针对于不同的场景下的考量做技术选型(tradeoff).

Tips:
常见的几个tradeoff

  • 可扩展性和高性能
  • 可用性和一致性
  • 延迟和吞吐量

核心技术攻关

每个需求都会有自己核心部分技术实现, 比如设计一个短域名服务,社交feed流,IM系统亦或是ailab这样的平台,都会有自己的核心技术难点。所以在技术设计阶段, 充分考虑系统本身带来的技术难点是非常用必要的。 就自己的工作经验来说,我一般会采用以下三种方式来进行贺核心技术攻关

  • 参考既有的开源的工程,模仿实现
  • 独立实现一个最小的MVP工程
  • 寻求团队或者朋友,共同分解问题,
  • 讨论实现

数据库设计

序言:数据库设计不仅限于RDBMS(mysql, posgres),还应包括Nosql(redis,hbase, es),图数据库的设计以及newsql的设计。 凡是涉及到状态存储的都是数据库设计的范畴;因此,我们不仅是要做技术选型还要产出ER或者DDL语句。团队成员内部经过充分讨论和分析最终形成的结果。

这是最重要的一个阶段,如果说前面的阶段是方案设计阶段,那么到这个阶段就是技术落地的阶段。充分考虑到可扩展,单一职责,高效读写的前提原则下,我们对于实体要做细致的划分,考虑到业务实现过程中的一些细节和可扩展性的问题。

数据库设计三范式提供了一个很好的指导原则,但是实际开发中, 不必刻意遵守,如果为了性能和可读性,牺牲一部分的独立性是可以的。

Tips:
存储引擎之间的数据同步和一致性性的问题, 在这个阶段也应该被充分的考虑。 比如商品数据存储在mysql中,但是搜索商品使用的是es的服务,采取何种策略来同步mysql商品数据到es中(采用定时任务或者是CDC方案)是需要团队之间进行考虑的。

代码实现

在前面的充分的准备工作后, 代码实现部分基本遵循小集体(披萨团队)负责一个或者多个模块的原则进行coding. 虽然在之前的工作经验中,我会用比如去掉鉴权的方式来加速前后端联调的速度。但是我认为这不是一个好习惯。 个人比较推崇的是服务会分为基础服务,业务中台服务以及业务线专有服务。而这些服务之间会在某个业务流程中,形成一个DAG的图,因此为了保证效率, 可以由组长或者是业务流程闭环的模块的团队成员规定其他模块的一个deadline. 这样可以保证团队之间的高效协作。

遵循开发规范以及设计原则(SOLID)是coding环节的重要部分。影响的因素主要有两个,其一是接口设计的好坏(顶层), 其二是程序员自身的水平实力。

交付测试&上线

后续的CR, UAT以及deploy的过程,其实已经不算是系统设计的范畴, 更算是软件工程的知识体系。 我这里不再做详细的解释, 只强调一句: 开发要写好单元测试和集成测试。

总结

天下之大事必作于易, 天下之难事必做于细。

系统设计,尤其在设计一个大型的系统的时候是有复杂性的,考虑各个目标之间的一个平衡是门学问。在真正落地的时候,编程的细节对于性能和可扩展性有着重要的作用,这决定了后期系统的可维护性以及升级迭代的效率。

这是一个持续练习和感悟的过程,不要想着可以快速成为系统设计的高手,需要时间,耐心以及努力。