<commit_before>---

layout: post
title: 系统设计的一些体会
category: 架构
tags: Architecture
keywords: 系统设计

---

## 简介

* TOC
{:toc}

![](/public/upload/architecture/system_design.png)

1. 几种“架构”不可以混淆
1. 业务设计中，"数据匹配"可能只是一句话，而在工程实现中则是一个很复杂的项目。

## 笔者的历程

1. 觉得代码实现是难点
2. 觉得数据库设计是难点。因为数据库设计有了，代码也就定了。一个学生信息管理系统，数据库设计自然没啥。但对于一个权限管理系统、审批流系统，数据库设计就很有含量了
3. 觉得业务抽象是难点。比如一个配置中心系统、ABTest 系统有哪些基本抽象，如何入手。此时是不是用数据库存储都是一个问题，比如还可以存在zk上。
4. 实现一个有闭环的平台类系统，比如风控系统等。

	* 首先它的输入是各种各样，输出也是各种各样的。
	* 各种技术的结合。java 业务处理、spark数据处理等
	* 性能要求带来的复杂度

对于第4点，也可以换个角度看，笔者曾经实现一个推送系统，各阶段如下：

1. 可送达，将业务方的消息送达到客户端。这中间涉及到推送的基本概念等
2. 可调控，比如频控，限制某个业务方的消息，以防止推送过多对用户的打扰。此外，动态调度，保证推送服务器的负载相对均衡
3. 推送系统与业务方的关系问题。比如一个业务的推送点击率很低，那么如何提高？是推送系统做还是业务方来做？这里面就需要你自己形成一套方法论，这个方法论的形成需要你的实践与沟通。沟通可不简单啊，你找人家，人家也得有时间，或者过几天他想法又变了
4. 推送系统达到一个均衡。老板比较关心点击率，业务方却只想要更多的推送机会，你担心会对用户形成打扰。推送与业务方的接口，数据报告（用数据而不是感觉说话）。这个过程中，你的人力有限，先做什么后做什么。

## 业务系统设计

1. 定义项目解决的真正问题。很多时候，“有一个需求，我们做个系统吧”，然而系统做着做着，就会跑偏掉，要么自己想一堆伪需求，要么做出来的系统用处不大或只解决了部分问题。
1. 解决有什么抽象，设定几个概念术语来描述系统、向别人解释系统
2. 基本理念，比如推送系统，宁愿不推也不要打扰用户。基本理念很重要，要害就在于影响了很多决策

	* 假设你的算法 有一点错误率，那错误率 让哪些用户来扛
	* 有个新需求很紧急，你干不干，一个新feature 你接不接，风险点是否跟基本理念冲突
	* 有很多事可以你的项目做，也可以别人的项目做，到底是你做还是别人做
	
4. 画后台界面原型图（如果有的话），我发现这是一个很有含量的东西

	* 充分反应了你对系统的认识，如果你认识不到位，这个界面根本画不出来，画出来用户也不知道所以。
	* 逼着你思考：一共要做哪些事儿？用户入口是什么？哪些让用户看到（易变的部分），哪些不让（不变的、可以自动化的部分）？
	* 和用户最佳的沟通工具

5. 迭代优于一步到位


### 如何分析一个业务

先功能后性能

1. 找到所有相关人，摸清需求，罗列123
2. 梳理需求，摸清楚需求背后的需求，汇总需求，将需求归类，总结为一两个核心点
3. 列出所有可能的方案，针对每一个方案，**先深度优先遍历**，即搞清楚其所有关键点。比如方案一 关键是 技术1和技术2，技术1关键的技术3和技术4。然后，根据需求、已有资源、未来规划等对方案进行“剪枝”，最终得到一个看起来可行的方案
4. 群策群力，将方案抛出来，接受各方的challenge。很多时候技术并非只有一条道能达成结果，但是如果多个研发有不同的思路，大部分情况下都会想法设法去证明基于思路的方案才是对的，同时陷入对其它方法的批判中，事实上双方的方案可能只是取舍方式不一，并无绝对对错。

这要求：

1. 工作所需的组件会用，会基本的排查，最好读过源码
2. 实现过 相当复杂度的组件
3. 形成一种常识与判断。比如开车，你左右转向要提前观察左右后视镜、超车时要关注对向来车、前面的车什么行为什么意图。

[技术攻关：从零到精通](http://zhangtielei.com/posts/blog-zero-to-professional.html)当为一个新系统编写代码的时候，代码应该从接口设计开始（我们平常做业务开发，在大部分情况下，都不用自己设计接口。比如做客户端开发，各种MVC, MVP模式已经把代码框架都定义好了，我们只用往接口实现里填东西。PS：但用不上不代表不重要）。先用代码定义出各层的接口（包括回调接口）(PS：第一次看到将回调接口 提升到接口设计的范畴)，没有实现，只是能够编译通过。有了这些接口，就可以拿它们与同事进行非常细节的讨论了。应该先把接口讨论得足够清楚，再进行下一步的具体实现。这也是一个比较痛苦的过程，我们需要反复抉择，而通常「选择」就意味着痛苦。

阅读技术文章来「循证」的做法。很多个人博主和团队博客会在网上发表他们自己系统的实现过程，以及系统前后版本的演进过程。如果我们恰好找到相关的类似这样的文章，那么它们就有很大的参考价值。我们从别人分享的技术方案中获得一个印证，确保自己的想法没有走向极端，或者漏掉了什么重要的东西。

### 业务架构

[到底什么才是业务架构？](https://www.chainnews.com/articles/535023035422.htm) 文章讲的比较虚，但把“业务架构” 这个词给体现出来了。

结合笔者的个人体验，以一个音乐app为例，其主干架构如下

![](/public/upload/architecture/system_design_demo.png)

1. 包括基本的服务 歌曲、专辑、艺人、用户
2. 从业务上说，这是一个读多写少的业务，你需要有一个框架无缝的处理cache、db和业务的关系
3. 存储是按照领域进行的，但用户的需求不是，两者在web 服务这一层发生碰撞，最复杂的就是web 服务层。
4. 那么web 服务层如何简化，能减少业务程序猿的负担？
5. 最常见的负担是什么？根据歌曲找专辑、根据专辑找艺人，根据专辑找声音等，也就是按照需求构建各个domain的关系，笔者曾经试想过：提供一个统一的关系服务，比如根据歌曲id，即可一次性直接找到专辑id、艺人id、相关歌曲id 等。

说到业务架构，wiki上各种高大上，按笔者在实际开发过程的中的理解，就是：

1. 尽量发现小伙伴们写代码的重复的、臃肿的地方，提高效率
2. 什么是团队代码中容易出bug的部分，规范它们（通常通过框架约束），减少个人易出错的概率。
3. [可能是讲分布式系统最到位的一篇文章](http://www.10tiao.com/html/46/201811/2651011019/1.html) 提到中间件的作用：中间件为了在软件系统的迭代过程中，避免将精力过多地花费在某个子功能下众多差异不大的选项中。在现实中，这点更多时候出现在技术层面的中间件里，因为与业务相比，技术层面“稳定”多了，所以做标准化更有价值，更能获得长期收益。但“稳定”是相对的，哪怕单纯在业务层面也存在相对稳定的部分。

## 误区

### 过多过早的以技术实现来干扰设计

api 设计原则

**高层API以操作意图为基础设计**。如何能够设计好API，跟如何能用面向对象的方法设计好应用系统有相通的地方，**高层设计一定是从业务出发，而不是过早的从技术实现出发**。低层API根据高层API的控制需要设计。**设计实现低层API的目的，是为了被高层API使用，考虑减少冗余、提高重用性的目的，低层API的设计也要以需求为基础，要尽量抵抗受技术实现影响的诱惑。**

api 包括

1. http api
2. rpc api
3. rabbitmq，consumer 向 producer 提供一个api，这个api 在producer 可以提供的数据 中选择consumer 需要的数据，producer 无需了解 consumer的细节

大到架构设计，小到接口设计，若是换个编程语言、存储，设计、代码感觉、接口就变了，一定说明设计有问题。

### 有什么、会怎么就怎么做 ==> 理论上应该怎么做，就怎么做

透过表象看本质，抓住本质找规律，运用规律改变世界。

我们先要想，这个系统应该是什么样子，而不是怎么样做写代码最简单。

很多时候，发现问题、描述问题，比解决问题更重要。一个很重要的问题是，你要对烂的代码、设计容忍度比较低，不能一边抱怨一边让代码更复杂。

### 对一些原则的误解

[对一些架构设计原则的反思](https://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&mid=2651011100&idx=2&sn=483a3534be06932788bd512a8b93c2a0&chksm=bdbec04f8ac949598182b41a00275f120fa0c3c34e3c589cd82b6bfb1113b2892db6786d4c04&scene=27#wechat_redirect)

1. KISS(Keep It Simple, Stupid) 。怎样才算是“Simple and Stupid”︖这是这个原则中最让⼈迷惑的地⽅。只有通过对⽤户的**业务⽣命周期、访问⽣命周期**进⾏分析，根据流量的压⼒不同，进⾏合理的**树状拆分**，也因此形成不同的系统，那么这些所形成的系统⼀定是内聚的，边界⼀定是清晰的，也⼀定是“Simple and Stupid”。也就是说，只有从业务上去分析、去拆分，才能够得到⼀个“Simple and Stupid”的结构，这是⼀个副产品，⽽不是⽬标。
2. Single responsibility principle，这句话很对，但没没什么用。This principle was described in the work of <NAME> and <NAME> . They called it cohesion（内聚）。什么是内聚？A class should have only one reason to change。为什么要单一？"Because each responsibility is an axis of change”，意思是“因为每个职责是⼀个变化的维度”。**“单一职责” 远不如 “A class should have only one reason to change” 更能指导人的实践**。
3. 原本现实⽣活中打保龄球，可以⾃⼰算分，也可以让别⼈帮忙算。为什么可以拆分开来，这是因为打保龄球的核⼼⽣命周期是打球，算分只是⼀个游戏规则，没有这个规则，保龄球也可以打的，因此这个分数计算规则可以拆分出来。并且保龄球游戏产⽣的结果是计算分数的输入，这两个步骤是打保龄球游戏的两个连续的⽣命周期活动，因此非核⼼⽣命周期可以拆分出去，形成树状结构。**Game 的原本功能没变，只不过其中⼀个步骤的实现分离出去，通过⽅法调⽤的⽅式回归了⽽已**。这样 Game 的职责更专注，分数计算也更专注，修改时可以互不影响，确实叫“内聚”比较好。<commit_msg>[fix]fix doc<commit_after>---

layout: post
title: 系统设计的一些体会
category: 架构
tags: Architecture
keywords: 系统设计

---

## 简介

* TOC
{:toc}

![](/public/upload/architecture/system_design.png)

1. 几种“架构”不可以混淆
1. 业务设计中，"数据匹配"可能只是一句话，而在工程实现中则是一个很复杂的项目。

## 笔者的历程

1. 觉得代码实现是难点
2. 觉得数据库设计是难点。因为数据库设计有了，代码也就定了。一个学生信息管理系统，数据库设计自然没啥。但对于一个权限管理系统、审批流系统，数据库设计就很有含量了
3. 觉得业务抽象是难点。比如一个配置中心系统、ABTest 系统有哪些基本抽象，如何入手。此时是不是用数据库存储都是一个问题，比如还可以存在zk上。
4. 实现一个有闭环的平台类系统，比如风控系统等。

	* 首先它的输入是各种各样，输出也是各种各样的。
	* 各种技术的结合。java 业务处理、spark数据处理等
	* 性能要求带来的复杂度

对于第4点，也可以换个角度看，笔者曾经实现一个推送系统，各阶段如下：

1. 可送达，将业务方的消息送达到客户端。这中间涉及到推送的基本概念等
2. 可调控，比如频控，限制某个业务方的消息，以防止推送过多对用户的打扰。此外，动态调度，保证推送服务器的负载相对均衡
3. 推送系统与业务方的关系问题。比如一个业务的推送点击率很低，那么如何提高？是推送系统做还是业务方来做？这里面就需要你自己形成一套方法论，这个方法论的形成需要你的实践与沟通。沟通可不简单啊，你找人家，人家也得有时间，或者过几天他想法又变了
4. 推送系统达到一个均衡。老板比较关心点击率，业务方却只想要更多的推送机会，你担心会对用户形成打扰。推送与业务方的接口，数据报告（用数据而不是感觉说话）。这个过程中，你的人力有限，先做什么后做什么。

## 业务系统设计

1. 定义项目解决的真正问题。很多时候，“有一个需求，我们做个系统吧”，然而系统做着做着，就会跑偏掉，要么自己想一堆伪需求，要么做出来的系统用处不大或只解决了部分问题。
1. 解决有什么抽象，设定几个概念术语来描述系统、向别人解释系统
2. 基本理念，比如推送系统，宁愿不推也不要打扰用户。基本理念很重要，要害就在于影响了很多决策

	* 假设你的算法 有一点错误率，那错误率 让哪些用户来扛
	* 有个新需求很紧急，你干不干，一个新feature 你接不接，风险点是否跟基本理念冲突
	* 有很多事可以你的项目做，也可以别人的项目做，到底是你做还是别人做
	
4. 画后台界面原型图（如果有的话），我发现这是一个很有含量的东西

	* 充分反应了你对系统的认识，如果你认识不到位，这个界面根本画不出来，画出来用户也不知道所以。
	* 逼着你思考：一共要做哪些事儿？用户入口是什么？哪些让用户看到（易变的部分），哪些不让（不变的、可以自动化的部分）？
	* 和用户最佳的沟通工具

5. 迭代优于一步到位


### 如何分析一个业务

先功能后性能

1. 找到所有相关人，摸清需求，罗列123
2. 梳理需求，摸清楚需求背后的需求，汇总需求，将需求归类，总结为一两个核心点
3. 列出所有可能的方案，针对每一个方案，**先深度优先遍历**，即搞清楚其所有关键点。比如方案一 关键是 技术1和技术2，技术1关键的技术3和技术4。然后，根据需求、已有资源、未来规划等对方案进行“剪枝”，最终得到一个看起来可行的方案
4. 群策群力，将方案抛出来，接受各方的challenge。很多时候技术并非只有一条道能达成结果，但是如果多个研发有不同的思路，大部分情况下都会想法设法去证明基于思路的方案才是对的，同时陷入对其它方法的批判中，事实上双方的方案可能只是取舍方式不一，并无绝对对错。

这要求：

1. 工作所需的组件会用，会基本的排查，最好读过源码
2. 实现过 相当复杂度的组件
3. 形成一种常识与判断。比如开车，你左右转向要提前观察左右后视镜、超车时要关注对向来车、前面的车什么行为什么意图。

[技术攻关：从零到精通](http://zhangtielei.com/posts/blog-zero-to-professional.html)当为一个新系统编写代码的时候，代码应该从接口设计开始（我们平常做业务开发，在大部分情况下，都不用自己设计接口。比如做客户端开发，各种MVC, MVP模式已经把代码框架都定义好了，我们只用往接口实现里填东西。PS：但用不上不代表不重要）。先用代码定义出各层的接口（包括回调接口）(PS：第一次看到将回调接口 提升到接口设计的范畴)，没有实现，只是能够编译通过。有了这些接口，就可以拿它们与同事进行非常细节的讨论了。应该先把接口讨论得足够清楚，再进行下一步的具体实现。这也是一个比较痛苦的过程，我们需要反复抉择，而通常「选择」就意味着痛苦。

阅读技术文章来「循证」的做法。很多个人博主和团队博客会在网上发表他们自己系统的实现过程，以及系统前后版本的演进过程。如果我们恰好找到相关的类似这样的文章，那么它们就有很大的参考价值。我们从别人分享的技术方案中获得一个印证，确保自己的想法没有走向极端，或者漏掉了什么重要的东西。

### 业务架构

[到底什么才是业务架构？](https://www.chainnews.com/articles/535023035422.htm) 文章讲的比较虚，但把“业务架构” 这个词给体现出来了。

结合笔者的个人体验，以一个音乐app为例，其主干架构如下

![](/public/upload/architecture/system_design_demo.png)

1. 包括基本的服务 歌曲、专辑、艺人、用户
2. 从业务上说，这是一个读多写少的业务，你需要有一个框架无缝的处理cache、db和业务的关系
3. 存储是按照领域进行的，但用户的需求不是，两者在web 服务这一层发生碰撞，最复杂的就是web 服务层。
4. 那么web 服务层如何简化，能减少业务程序猿的负担？
5. 最常见的负担是什么？根据歌曲找专辑、根据专辑找艺人，根据专辑找声音等，也就是按照需求构建各个domain的关系，笔者曾经试想过：提供一个统一的关系服务，比如根据歌曲id，即可一次性直接找到专辑id、艺人id、相关歌曲id 等。

说到业务架构，wiki上各种高大上，按笔者在实际开发过程的中的理解，就是：

1. 尽量发现小伙伴们写代码的重复的、臃肿的地方，提高效率
2. 什么是团队代码中容易出bug的部分，规范它们（通常通过框架约束），减少个人易出错的概率。
3. [可能是讲分布式系统最到位的一篇文章](http://www.10tiao.com/html/46/201811/2651011019/1.html) 提到中间件的作用：中间件为了在软件系统的迭代过程中，避免将精力过多地花费在某个子功能下众多差异不大的选项中。在现实中，这点更多时候出现在技术层面的中间件里，因为与业务相比，技术层面“稳定”多了，所以做标准化更有价值，更能获得长期收益。但“稳定”是相对的，哪怕单纯在业务层面也存在相对稳定的部分。

## 误区

### 过多过早的以技术实现来干扰设计

api 设计原则

**高层API以操作意图为基础设计**。如何能够设计好API，跟如何能用面向对象的方法设计好应用系统有相通的地方，**高层设计一定是从业务出发，而不是过早的从技术实现出发**。低层API根据高层API的控制需要设计。**设计实现低层API的目的，是为了被高层API使用，考虑减少冗余、提高重用性的目的，低层API的设计也要以需求为基础，要尽量抵抗受技术实现影响的诱惑。**

api 包括

1. http api
2. rpc api
3. rabbitmq，consumer 向 producer 提供一个api，这个api 在producer 可以提供的数据 中选择consumer 需要的数据，producer 无需了解 consumer的细节

大到架构设计，小到接口设计，若是换个编程语言、存储，设计、代码感觉、接口就变了，一定说明设计有问题。

### 有什么、会怎么就怎么做 ==> 理论上应该怎么做，就怎么做

透过表象看本质，抓住本质找规律，运用规律改变世界。

我们先要想，这个系统应该是什么样子，而不是怎么样做写代码最简单。

很多时候，**发现问题、描述问题，比解决问题更重要。一个很重要的要求是你要对烂的代码、设计容忍度比较低，不能一边抱怨一边让代码更复杂**。

### 对一些原则的误解

[对一些架构设计原则的反思](https://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&mid=2651011100&idx=2&sn=483a3534be06932788bd512a8b93c2a0&chksm=bdbec04f8ac949598182b41a00275f120fa0c3c34e3c589cd82b6bfb1113b2892db6786d4c04&scene=27#wechat_redirect)

1. KISS(Keep It Simple, Stupid) 。怎样才算是“Simple and Stupid”︖这是这个原则中最让⼈迷惑的地⽅。只有通过对⽤户的**业务⽣命周期、访问⽣命周期**进⾏分析，根据流量的压⼒不同，进⾏合理的**树状拆分**，也因此形成不同的系统，那么这些所形成的系统⼀定是内聚的，边界⼀定是清晰的，也⼀定是“Simple and Stupid”。也就是说，只有从业务上去分析、去拆分，才能够得到⼀个“Simple and Stupid”的结构，这是⼀个副产品，⽽不是⽬标。
2. Single responsibility principle，这句话很对，但没没什么用。This principle was described in the work of <NAME> and <NAME> . They called it cohesion（内聚）。什么是内聚？A class should have only one reason to change。为什么要单一？"Because each responsibility is an axis of change”，意思是“因为每个职责是⼀个变化的维度”。**“单一职责” 远不如 “A class should have only one reason to change” 更能指导人的实践**。
3. 原本现实⽣活中打保龄球，可以⾃⼰算分，也可以让别⼈帮忙算。为什么可以拆分开来，这是因为打保龄球的核⼼⽣命周期是打球，算分只是⼀个游戏规则，没有这个规则，保龄球也可以打的，因此这个分数计算规则可以拆分出来。并且保龄球游戏产⽣的结果是计算分数的输入，这两个步骤是打保龄球游戏的两个连续的⽣命周期活动，因此非核⼼⽣命周期可以拆分出去，形成树状结构。**Game 的原本功能没变，只不过其中⼀个步骤的实现分离出去，通过⽅法调⽤的⽅式回归了⽽已**。这样 Game 的职责更专注，分数计算也更专注，修改时可以互不影响，确实叫“内聚”比较好。
{'fraction_non_alphanumeric': 0.12181095775826015, 'fraction_numerical': 0.03450439146800502, 'mean_word_length': 11.890835579514825, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 4, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7479185', 'n_tokens_mistral': 8532, 'n_tokens_neox': 9899, 'n_words': 416}