终于,我们跨越了架构设计最艰难的阶段——备选方案的设计与抉择。当最终方案尘埃落定之后,许多架构师会不由自主地松一口气,以为大功即将告成。然而,真正的落地才刚刚开始。
如果说备选方案设计是勾勒宏伟蓝图,那么详细方案设计就是将这幅蓝图一笔一笔地描绘出来,每一个笔触都需要精准到位。这不再是天马行空的创造,而是脚踏实地的工程。架构师需要将方案涉及的关键技术细节确定下来,使得备选方案变成一个可以真正落地的设计方案。这,就是架构设计流程的第 4 步——也是将梦想变为现实的关键一步。
详细方案设计就是将方案涉及的关键技术细节给确定下来。这个阶段需要回答一系列具体的问题:
假如我们确定使用 Elasticsearch 来做全文搜索,那么就需要确定:
假如我们确定使用 MySQL 分库分表,那么就需要确定:
假如我们确定引入 Nginx 来做负载均衡,那么就需要确定:
详细设计方案里面其实也有一些技术点和备选方案类似,但这里的技术方案选择是很轻量级的,我们无须像备选方案阶段那样操作,只需要简单根据这些技术的适用场景选择就可以了。
以 Nginx 的负载均衡策略为例:
| 策略 | 适用场景 |
|---|---|
| 轮询(默认) | 每个请求按时间顺序逐一分配到不同的后端服务器,后端服务器分配的请求数基本一致,如果后端服务器"down 掉",能自动剔除 |
| 加权轮询 | 根据权重来进行轮询,权重高的服务器分配的请求更多,主要适应于后端服务器性能不均的情况 |
| ip_hash | 每个请求按访问 IP 的 hash 结果分配,这样每个访客固定访问一个后端服务器,主要用于解决 session 的问题 |
| fair | 按后端服务器的响应时间来分配请求,响应时间短的优先分配,能够最大化地平衡各后端服务器的压力 |
| url_hash | 按访问 URL 的 hash 结果来分配请求,每个 URL 定向到同一个后端服务器,适用于后端服务器能够将 URL 的响应结果缓存的情况 |
这几个策略的适用场景区别还是比较明显的,根据我们的业务需要,挑选一个合适的即可。
详细设计方案阶段可能遇到的一种极端情况就是在详细设计阶段发现备选方案不可行。一般情况下主要的原因是备选方案设计时遗漏了某个关键技术点或者关键的质量属性。
例如,曾经有一个项目,在备选方案阶段确定是可行的,但在详细方案设计阶段,发现由于细节点太多,方案非常庞大,整个项目可能要开发长达 1 年时间,最后只得废弃原来的备选方案,重新调整项目目标、计划和方案。
这种情况可以通过以下方式有效避免:
架构师不但要进行备选方案设计和选型,还需要对备选方案的关键细节有较深入的理解。架构师选择了某个技术方案,前提必须是架构师自己对它的设计原理有深入的理解。
通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度。方案本身的复杂度越高,某个细节推翻整个方案的可能性就越高。
如果方案本身就很复杂,那就采取设计团队的方式来进行设计。博采众长,汇集大家的智慧和经验,防止只有 1~2 个架构师可能出现的思维盲点或者经验盲区。
让我们以"前浪微博"消息队列系统为例,来看一看备选方案 2 是如何被细化的。
数据库设计两类表:
业务系统发布消息时,首先写入到日志表,日志表写入成功就代表消息写入成功;后台线程再从日志表中读取消息写入记录,将消息内容写入到消息表中。业务系统读取消息时,从消息表中读取。
日志表需要及时清除已经写入消息表的日志数据,消息表最多保存 30 天的消息数据。
直接采用 MySQL 主从复制即可,只复制消息存储表,不复制日志表。
采用 ZooKeeper 来做主备决策,主备服务器都连接到 ZooKeeper 建立自己的节点:
/MQ/server/分区编号/master/MQ/server/分区编号/slave备机监听主机的节点消息,当发现主服务器节点断连后,备服务器修改自己的状态,对外提供消息读取服务。
消息队列系统设计两个角色:生产者和消费者,每个角色都有唯一的名称。
消息队列系统提供 SDK 供各业务系统调用,SDK 从配置中读取所有消息队列系统的服务器信息,SDK 采取轮询算法发起消息写入请求给主服务器。如果某个主服务器无响应或者返回错误,SDK 将发起请求发送到下一台服务器。
SDK 从配置中读取所有消息队列系统的服务器信息,轮流向所有服务器发起消息读取请求。消息队列服务器需要记录每个消费者的消费状态,即当前消费者已经读取到了哪条消息,当收到消息读取请求时,返回下一条未被读取的消息给消费者。
考虑到消息队列系统后续可能会对接多种不同编程语言编写的系统,为了提升兼容性,传输协议用 TCP,数据格式为 ProtocolBuffer。