Apache架构师总结的30条设计原则

与个人的一些小心得

Posted by Haiming on June 9, 2020

不想当架构师的后端不是好程序员 :)

看到了一篇文章,之中讲述了一个Apache的架构师提出的30条架构原则。看了一下, 发现对于架构设计,特别是分布式的架构设计很有帮助,在这里对于每一条,进行一下自己的梳理,希望可以有所收获。

转载文章地址

一. 基本原则

1. KISS(Keep it simple, stupid)

保持每件事情尽可能的简单,用最简单的解决方案来解决问题。

如果能使用简单的方法去解决问题,就不要将方案搞复杂。同样的功能,显然简单的方式更容易维护和重构。不要给团队挖坑谢谢。

2. YAGNI(You aren’t gonna need it)

不要去搞不必要的东西,需要时候再搞。

不知道为啥外国人总喜欢搞这种迷之缩写……但是理论是可以理解的,有些程序员自认为很有用的未来会被产品提出来的功能,实际上可能很久都不会被用到,反而造成了代码方面的冗余。

有人会觉得冗余怎么了?我多写几行代码,多一些功能,不好么?我自己是这样理解的:

  1. 这种冗余很难有一个“标准”,就比如怎么知道“多几行代码”,“多几个功能”这个的边界呢?开发目前没有需要的功能不是浪费资源吗?
  2. 对于这些代码,测试要怎么玩?如果不测,那么因为这些多余的代码,程序出现了问题,算谁的责任?如果测了,那就是不止开发的资源被浪费,测试的资源也被浪费了。

3. 爬,走,跑

程序的第一步要求是先跑起来,然后再优化使其变得更好,最后继续优化。迭代着做事情。

不知道是不是由于本人junior,我一直都是按照这个标准进行软件开发的(写的时候先不想怎么优化哈哈哈。

我认为这一点在目前的软件行业是非常需要的:迅速拿出可以跑的初级版本。因为目前的软件开发是以Agile为主流,那么往往每个迭代会新增或者修改一些功能点,从最坏的角度打算,可能这个版本的修改在下个版本就会被PM割掉或者要求修改。而且“小步快跑”本身就是和“一步到位”矛盾的。因此不要妄想一次性直接做到完美。

4. 自动化测试

创建稳定、高质量的产品的唯一方法就是自动化测试。所有的都可以自动化,当你设计时,不妨想想这一点。

自动化测试一定是不可缺少的,尤其是当产品已经足够大,代码足够复杂的时候,只靠人力去“回归”是一定不行的。但是是否只有自动化测试就ok呢?显然不是,比如Win10的bug简直像山一样多,就是因为其开除了手动测试团队。

5. 时刻要想投入产出比(ROI)

就是划得来不。

每次说到这个,我都想起当时问TL的一个问题:为什么我们不也搭一套完整的微服务框架呢?比如dubbo+zookeeper,redis搞个集群啥的。

人家一句话就给我怼回来了:你维护么?

哈哈哈

6. 了解你的用户

不要花了几个月时间做了一个 devops 用户界面,最后你发现那些人只喜欢命令行。

这部分,如果有问题,那么一般是产品的锅。当然,程序员本身如果接触客户的话,更需要去了解自己的用户。没看清题目就答题,是没有好下场的。

7. 尽可能的独立去设计和测试一个功能

当你做设计时,应该想想这一条。从长远来看这能给你解决很多问题,否则你的功能只能等待系统其他所有的功能都就绪了才能测试,这显然很不好。有了这个原则, 你的版本将会更加的顺畅。

作者是从架构师的角度来考虑,那么一个很依赖于其他模块的功能,本质上就是不好去测试和处理的。

如果出于代码维护的角度,独立的功能也应该尽量的拆出来,这样的话,在对功能进行修改或者拓展的时候,可以尽量的保证其他代码的功能不受影响,易于维护。

8. 不要玩花活

当你做设计时,应该想想这一条。从长远来看这能给你解决很多问题,否则你的功能只能等待系统其他所有的功能都就绪了才能测试,这显然很不好。有了这个原则, 你的版本将会更加的顺畅。

个人觉得和原则1相呼应,除非真的为了解决具体的问题,否则不要为了花哨而花哨,为了“高端”而高端

二. 功能选择

9. 拥抱MVP(Minimal Viable Product,最小可运行版本)

不可能预测到用户将会如何使用我们的产品。所以要拥抱 MVP(Minimal Viable Product),最小可运行版本。这个观点主要思想就是你挑几个很少的使用场景,然后把它搞出来,然后发布上线让用户使用,然后基于体验和用户反馈再决定下一步要做什么。

不要闭门造车,不要想着一开始就搞个大新闻。让客户去定夺他们想要什么,而非是我们自己臆想他们的需求

10. 尽可能的做较少的功能 / 11. 等到有人提出再说

当有疑问的时候,就不要去做,甚至干掉。很多功能从来不会被使用。最多留个扩展点就够了。

可扩展的代码是很好的代码,但是不要一开始就直接搞上去多余的功能。和原则2呼应

12. 有勇气和客户说不

这时候你需要找到一个更好的解决方案来去解决。记住亨利福特曾经说过的 :”如果我问人们他们需要什么,他们会说我需要一匹速度更快的马”。记住:你是那个专家,你要去引导和领导。要去做正确的事情,而不是流行的事情。最终用户会感谢你为他们提供了汽车。

不是只满足对方的要求,而是使用自己的专业素养,给对方介绍我们认为更好的方案。当然,最后定夺权还在客户。

三、服务端设计和并发

13. 要知道一个Server是如何运行

要知道一个 server 是如何运行的,从硬件到操作系统,直到编程语言。优化 IO 调用的数量是你通往最好架构的首选之路。

做好架构就要从头知道到尾,这话果真不假。而在网络应用这种架构之中,IO调用的数量的确是系统对外提供服务的瓶颈,究竟如何才能最大化的利用系统的能力,是一个比较难的问题。

14. 要了解 Amdhal 同步定律

Amdahl’s_law

这个定律主要是说并行多任务不可能无穷无尽的提高任务效率,因为总有一些部分是无法并行完成的。

在线程之间共享可变数据会让你的程序变慢。只在必要的时候才去使用并发的数据结构,只在必须使用同步(synchronization)的时候才去使用同步。如果要用锁,也要确保尽可能少的时间去 hold 住锁。如果要在加锁后做一些事情,要确保自己在锁内会做哪些事情。

尽量少的使用并发的数据结构,尽量少的使用同步,尽量少的使用加锁时间。 例如Redis就是用单线程来避免了这些无谓的开销。

15. 无阻塞且事件驱动

如果你的设计是一个无阻塞且事件驱动的架构,那么千万不要阻塞线程或者在这些线程中做一些 IO 操作,如果你做了,你的系统会慢的像骡子一样。

看看,为啥NIO要阻塞线程呢?而且事件驱动,意味着其会接受中断的信号,比如网络IO这种硬中断,那么人家都给你数据搞完了,为啥要自己在线程之中做IO呢?不可理喻。

四、分布式系统

16. 尽量做无扩展的系统

无状态的系统的是可扩展的和直接的。任何时候都要考虑这一点,不要搞个不可扩展的,有状态的东东出来,这是起码的。

这一点实际上不能一概而论。我在和楠哥聊天的时候,对整个server set的更新方式表示了好奇:为啥要按序更新。他的回答是我们的服务是有状态的,一次性直接搞完的话状态无法保存,那某些服务就会有问题。

他又举例,比如Jira,就是有状态的服务,因为其内部数据关联项太多,如果使用数据库的查询,可能没办法做到。那么就要将数据先缓存到内存之中,那内存之中都有数据了,一定是有状态的。这就是在性能和成本之间做取舍。

17. 保证消息只被传递一次(Exactly-once)

保证消息只被传递一次,不管失败,这很难,除非你要在客户端和服务端都做控制。试着让你的系统更轻便(使用原则 18)。你要知道大部分的承诺 exactly-once-delivery 的系统都是做了精简的。

在之后的博文之中我会专门写一下《流计算之中的Exactly Once特性》。这部分我认为意思是要尽量满足Exactly-once 的特性,具体的话新文章会讲

18. 尽可能设计操作幂等

实现一个操作尽可能的幂等。这样的话就比较好恢复,而且你还处于至少一次传递(at least once delivery)的状态。

这里面的“at least once delivery” 和上面的“只传递一次”是相似的概念。但是看字面意思也能看到,其意义就是可能整个系统不止一次的提交,那么在这种情况下,幂等就可以保证系统的数据是稳定的,且非重复的。

19. CAP理论

知道 CAP 理论。可扩展的事务(分布式事务)是很难的。如果可能的的话,尽可能的使用补偿机制。RDBMS 事务是无法扩展的。

CAP理论:对于一个分布式计算系统而言,不可能同时满足以上三点:

  1. 一致性Consistency:所有节点上面的数据副本是一致的

  2. 可用性Availability:每次请求都能获得非错响应,但是不保证是最近写入的数据

  3. 分区容错性Partition tolerance:系统如果不能再时限之内达到数据一致性,就意味着发生了分区的情况,必须在C,保证节点上面副本一致和A,保证获得响应之间抉择。

    这个的理解还有另外一个例子:CAP理论中的P到底是个什么意思? - 邬江的回答 - 知乎 https://www.zhihu.com/question/54105974/answer/139037688

    当系统之中的节点不再联通的时候,整个系统就分成了几个区。如果一个数据项只在一个节点之中保存,那么分区之后其他节点就访问不到这个数据了,这个时候就是分区不容错的。

    那么如何避免?就是要将一个数据项复制到多个节点上面,那么出现分区之后,这个数据项可能分布到各个区之中,这个时候就是分区容错的。

    但是分区容错会有另外的一致性问题:多点上面的数据可能在同一时刻不一致。要保证一致性C,那么就会等待全部写节点成功,这个时候就没有A。

尽可能的使用补偿机制,也就是在数据和自己预想的情况不一致的时候,使用某些手段来进行补偿,而非是直接分布式事务回滚。

20. /21. 分布式一致性的局限

分布式一致性无法扩展,也无法进行组通信,也无法进行集群范围内的可靠通信。理想情况下最大的节点限制为 8 个节点。

在分布式系统中,你永远无法避免延迟和失败。

同上

五、用户体验

22. Know your customer

要了解你的用户和清楚他们的目标。他们是新手、专家还是偶然的用户?他们了解计算机科学的程度。极客喜欢扩展点,开发者喜欢示例和脚本,而普通人则喜欢 UI。

23. 最好的产品是不需要产品手册的

24. 不要推卸选择

当你无法在两个选择中做决定的时候,请不要直接把这个问题通过提供配置选项的方式传递给用户。这样只能让用户更加的发懵。如果连你这个专家都无法选择的情况下,交给一个比你了解的还少的人这样合适吗?最好的做法的是每次都找到一个可行的选项;次好的做法是自动的给出选项,第三好的做法是增加一个配置参数,然后设置一个合理的默认值。

按照作者的想法,对于比较棘手的选择,不要指望让客户去弄懂它,而是最好能够找到一个可行的普适的选项。实在找不到的话,可以使用系统来给出一个建议的值或者选项。

25. / 26. 总是要为配置设置一个合理的默认值。

设计不良的配置会造成一些困扰。应该总是为配置提供一些示例值。

27. 配置值要易于填写

配置值必须是用户能够理解和直接填写的。比如:不能让用户填写最大缓存条目的数量,而是应该让用户填写可被用于缓存的最大内存。

28. 如果输入了未知的配置要抛出错误

永远不要悄悄的忽略。悄悄的忽略配置错误往往是找 bug 花了数小时的罪魁祸首。

这一点我自觉做得非常好,因为我的log规则就是能多打绝对不少打,哈哈哈哈

六、艰难的问题

29. 更换语言并非万能药

梦想着新的编程语言就会变得简单和明了,但往往要想真正掌握会很难。不要轻易的去换编程语言。

这一点倒是觉得很有趣……我认为除非架构方面有问题,否则更换语言绝对是有弊无利。语言和语言之间,乍看只是语法的差别,实则整个生态都不同。妄想着几天精通,无异于痴人说梦。

30. 复杂的拖拉拽的界面是艰难的

复杂的拖拉拽的界面是艰难的,不要去尝试这样的效果,除非你准备好了 10 人年的团队。

七、最后

最后,说一个我的感受。在一个理想的世界里,一个平台应该是有多个正交组件组成-每个组件都负责一个方面(比如,security,messaging,registry,mdidation,analytics)。好像一个系统构建成这样才是完美的。

但不幸的是,现实中我们很难达到这样的状态。因为在项目初始状态时,很多事情是不确定的,你无法做到这样的独立性,现在我更倾向于在开始的时候适当的重复是必要的,当你尝试铲除他们的时候,你会发现引入了新的复杂性,分布本身就意味着复杂。有时候治愈的过程要比疾病本身更加的糟糕。