如何做好架构

"架构类文章小抄"

Posted by gambol on May 2, 2016

写在前面的话

记录看到不错的架构文章.加上自己的思考. 不定期更新. 现在包含了两篇文章

  1. 软件设计杂谈
  2. Martin Fowler阐述“牺牲的架构”

软件设计杂谈

url: https://zhuanlan.zhihu.com/p/20005177 这篇文章很赞, 里面的许多观点我非常赞同.

前提

  1. 搞懂需求. 分析需求, 弄清楚需求的底层原因和想要解决的问题
  2. 设计多个解决方案
    • 外包
    • 利用开源系统
    • 自己重写 选择一个适合自己的方案. 工程师现在越来越贵,能用合理的价格搞定的功能,就不该雇人去打理(自己打脸)。一个产品,最核心的部分不超过整个系统的20%,把人力资源铺在核心的部分,才是软件设计之道。

软件设计

一. 分解问题

软件设计是一个把大的问题不断分解,直至原子级的小问题,然后再不断组合的过程. 分解和组合在软件设计中的作用如此重要,以至于一个系统如果合理分解,那么日后维护的代价就要小得多。

注: 这里所说的分解, 不一定是分解系统, 而是分解问题. 把问题分成有机的小问题(小模块), 然后再将小模块细分.

分解的太细, 给自己的维护带来麻烦. 不做分解, 将来不好扩展. 一个比较好方法是: 不要考虑太远, 考虑半年. 如果你觉得半年之内, 可能需要改动, 就预留出扩展.

二 . 总线

定义系统的整个总线(框架). 总线把生产者和消费者分离,让彼此互不依赖。心脏往外供血时,把血压入动脉血管就是了。它并不需要知道谁是接收者。

在web项目里, spring通常是这个大的总线. 分拆到各个小模块里, 总线可能是一个 责任链, 或者某个消息队列

三.路由

路由包括外部路由和内部路由 外部路由处理输入,把不同的输入dispatch到系统里不同的组件。做web app的,可能没有意识到,但其实每个web framework,最关键的组件之一就是url dispatch。 内部路由则需工程师考虑。service级别的路由(数据流由哪个service处理)可以用consul等service discovery组件,service内部的路由(数据流到达后怎么处理)则需要自己完成。路由的具体方式有很多种,pattern matching最为常见。

看懂了他文字, 手上没有特别好的例子来解释. — 我也没有弄明白他说的真实含义

四. 队列

仔细想想,队列其实就是总线+路由(可选)+存储的一个特殊版本。一般而言,system bus之上是系统的各个service,每个service再用service bus(或者queue)把micro service chain起来,然后每个micro service内部的组件间,再用queue连接起来。

补充说明: 消息机制. 消息机制也是一个很好的解耦方案. 缺点是: 让系统复杂性变高. 在合适的地方用消息. 消息驱动看上去很美, 对开发同学要求比较高.

五. 协议(protocol)

一旦我们把系统分解成一个个service,service再分解成micro service,彼此之间互不依赖,仅仅通过总线或者队列来通讯,那么,我们就需要协议来定义彼此的行为。协议听起来很高大上,其实不然。我们写下的每个function(或者每个class),其实就是在定义一个不成文的协议:function的arity是什么,接受什么参数,返回什么结果。调用者需严格按照协议调用方能得到正确的结果。

六. 代谢

软件系统也是如此。日志会把硬盘写满,软件会失常,硬件会失效,网络会拥塞等等。一个好的软件系统需要一个好的代谢系统:出现异常的服务会被关闭,同样的服务会被重新启动,恢复运行。

时刻关注系统, 最好能未雨绸缪,发现系统的瓶颈. 不停的解决系统目前的短板. 这个短板可能是性能短板, 也可能是开发效率上的短板

七. 高可用

大部分软件系统里的各种服务也需要高可用性:除非完全无状态的服务,且服务重启时间在ms级。服务的高可用性和路由是息息相关的:高可用性往往意味着同一服务的冗余,同时也意味着负载分担。好的路由系统(如consul)能够对路由至同一服务的数据在多个冗余服务间进行负载分担,同时在检测出某个失效服务后,将数据路只由至正常运作的服务。

  • 高可用性还意味着非关键服务,即便不可恢复,也只会导致系统降级,而不会让整个系统无法访问。就像壁虎的尾巴断了不妨碍壁虎逃命,人摔伤了手臂还能吃饭一样,一个软件系统里统计模块的异常不该让用户无法访问他的个人页面。*

说了两件事情: 1. 任何service, 都至少有两台机器组成集群

  1. 有办法及时关闭不可用的servce, 保证整个系统的主要业务正常 通常在实现时, 有几种解决方法: a. 给业务增加开关. b.减少进来的用户, 把压力反馈在源头

八. 安全性

在业务较小时, 开发效率比安全重要. 业务有一定规模时, 安全比开发效率重要

九. 落实你的设计

在进入开发之前, 将设计方案概括出来, 写在纸上, 并且和人说清楚.

十. 谦虚 && 接受别人的意见

设计错了没有关系, 反思并改正. when the facts change, I change my mind.

Martin Fowler阐述“牺牲的架构”

第二篇文章, url地址是: http://www.infoq.com/cn/news/2014/11/sacrificial-architecture

文章里提到了一个名词, 叫做牺牲的架构

很多代码, 是为了解决目前的问题而产生的, 所以, 可以考虑在几年后, 完全抛弃掉这部分代码. Martin举了一个eBay的例子,他们的做法与牺牲的架构是一致的,他们在一段时间之后把perl脚本移植成了c++代码,又在一段时间之后移植成了java代码。能够支撑1996年ebay的架构不会成为能够支撑2006年ebay的架构。1996年的这一版不能处理2006年的负载压力,但是2006年的版本对于构建和维护来讲太复杂了,而且是针对1996版之后的需求逐步演变而来的。

为了在合适的时机重新构建而构建。它就像抛弃原型,只是代码已经投入使用罢了。当你的业务增长的时候,你可能不得不抛弃之前的一些或者全部的代码库(就像eBay的做法,第二次提到了)。这并不意味着之前的解决方案不好:一点也不,在当时的情况下它们非常恰当。

“性能就是一种特性”。所以团队可以把性能特性和其他的特性一起来排序。最初它的优先级不高,但是开发阶段后期就要提高它的优先级了。