跳转至

13-架构设计流程:详细方案设计

原文

完成备选方案的设计和选择后,我们终于可以长出一口气,因为整个架构设计最难的一步已经完成了,但整体方案尚未完成,架构师还需继续努力。接下来我们需要再接再励,将最终确定的备选方案进行细化,使得备选方案变成一个可以落地的设计方案。所以今天我来讲讲架构设计流程第 4 步:详细方案设计

架构设计第 4 步:详细方案设计

简单来说,详细方案设计就是将方案涉及的关键技术细节给确定下来。

  • 假如我们确定使用 Elasticsearch 来做全文搜索,那么就需要确定 Elasticsearch 的索引是按照业务划分,还是一个大索引就可以了;副本数量是 2 个、3 个还是 4 个,集群节点数量是 3 个还是 6 个等。
  • 假如我们确定使用 MySQL 分库分表,那么就需要确定哪些表要分库分表,按照什么维度来分库分表,分库分表后联合查询怎么处理等。
  • 假如我们确定引入 Nginx 来做负载均衡,那么 Nginx 的主备怎么做,Nginx 的负载均衡策略用哪个(权重分配?轮询?ip_hash?)等。

可以看到,详细设计方案里面其实也有一些技术点和备选方案类似。例如,Nginx 的负载均衡策略,备选有轮询、权重分配、ip_hash、fair、url_hash 五个,具体选哪个呢?看起来和备选方案阶段面临的问题类似,但实际上这里的技术方案选择是很轻量级的,我们无须像备选方案阶段那样操作,而只需要简单根据这些技术的适用场景选择就可以了。

例如,Nginx 的负载均衡策略,简单按照下面的规则选择就可以了。

  • 轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务器,后端服务器分配的请求数基本一致,如果后端服务器“down 掉”,能自动剔除。
  • 加权轮询:根据权重来进行轮询,权重高的服务器分配的请求更多,主要适应于后端服务器性能不均的情况,如新老服务器混用。
  • ip_hash:每个请求按访问 IP 的 hash 结果分配,这样每个访客固定访问一个后端服务器,主要用于解决 session 的问题,如购物车类的应用。
  • fair:按后端服务器的响应时间来分配请求,响应时间短的优先分配,能够最大化地平衡各后端服务器的压力,可以适用于后端服务器性能不均衡的情况,也可以防止某台后端服务器性能不足的情况下还继续接收同样多的请求从而造成雪崩效应。
  • url_hash:按访问 URL 的 hash 结果来分配请求,每个 URL 定向到同一个后端服务器,适用于后端服务器能够将 URL 的响应结果缓存的情况。

这几个策略的适用场景区别还是比较明显的,根据我们的业务需要,挑选一个合适的即可。例如,比如一个电商架构,由于和 session 比较强相关,因此如果用 Nginx 来做集群负载均衡,那么选择 ip_hash 策略是比较合适的。

详细设计方案阶段可能遇到的一种极端情况就是在详细设计阶段发现备选方案不可行,一般情况下主要的原因是备选方案设计时遗漏了某个关键技术点或者关键的质量属性。例如,我曾经参与过一个项目,在备选方案阶段确定是可行的,但在详细方案设计阶段,发现由于细节点太多,方案非常庞大,整个项目可能要开发长达 1 年时间,最后只得废弃原来的备选方案,重新调整项目目标、计划和方案。这个项目的主要失误就是在备选方案评估时忽略了开发周期这个质量属性。

幸运的是,这种情况可以通过下面方式有效地避免:

  • 架构师不但要进行备选方案设计和选型,还需要对备选方案的关键细节有较深入的理解。例如,架构师选择了 Elasticsearch 作为全文搜索解决方案,前提必须是架构师自己对 Elasticsearch 的设计原理有深入的理解,比如索引、副本、集群等技术点;而不能道听途说 Elasticsearch 很牛,所以选择它,更不能成为把“细节我们不讨论”这句话挂在嘴边的“PPT 架构师”。
  • 通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度,方案本身的复杂度越高,某个细节推翻整个方案的可能性就越高,适当降低复杂性,可以减少这种风险。
  • 如果方案本身就很复杂,那就采取设计团队的方式来进行设计,博采众长,汇集大家的智慧和经验,防止只有 1~2 个架构师可能出现的思维盲点或者经验盲区。

详细方案设计实战

虽然我们上期在“前浪微博”消息队列的架构设计挑选了备选方案 2 作为最终方案,但备选方案设计阶段的方案粒度还比较粗,无法真正指导开发人员进行后续的设计和开发,因此需要在备选方案的基础上进一步细化。

下面我列出一些备选方案 2 典型的需要细化的点供参考,有兴趣的同学可以自己尝试细化更多的设计点。

  1. 细化设计点 1:数据库表如何设计?

  2. 数据库设计两类表,一类是日志表,用于消息写入时快速存储到 MySQL 中;另一类是消息表,每个消息队列一张表。

  3. 业务系统发布消息时,首先写入到日志表,日志表写入成功就代表消息写入成功;后台线程再从日志表中读取消息写入记录,将消息内容写入到消息表中。
  4. 业务系统读取消息时,从消息表中读取。
  5. 日志表表名为 MQ_LOG,包含的字段:日志 ID、发布者信息、发布时间、队列名称、消息内容。
  6. 消息表表名就是队列名称,包含的字段:消息 ID(递增生成)、消息内容、消息发布时间、消息发布者。
  7. 日志表需要及时清除已经写入消息表的日志数据,消息表最多保存 30 天的消息数据。

  8. 细化设计点 2:数据如何复制?

直接采用 MySQL 主从复制即可,只复制消息存储表,不复制日志表。

  1. 细化设计点 3:主备服务器如何倒换?

采用 ZooKeeper 来做主备决策,主备服务器都连接到 ZooKeeper 建立自己的节点,主服务器的路径规则为“/MQ/server/ 分区编号 /master”,备机为“/MQ/server/ 分区编号 /slave”,节点类型为 EPHEMERAL。

备机监听主机的节点消息,当发现主服务器节点断连后,备服务器修改自己的状态,对外提供消息读取服务。

  1. 细化设计点 4:业务服务器如何写入消息?

  2. 消息队列系统设计两个角色:生产者和消费者,每个角色都有唯一的名称。

  3. 消息队列系统提供 SDK 供各业务系统调用,SDK 从配置中读取所有消息队列系统的服务器信息,SDK 采取轮询算法发起消息写入请求给主服务器。如果某个主服务器无响应或者返回错误,SDK 将发起请求发送到下一台服务器。

  4. 细化设计点 5:业务服务器如何读取消息?

  5. 消息队列系统提供 SDK 供各业务系统调用,SDK 从配置中读取所有消息队列系统的服务器信息,轮流向所有服务器发起消息读取请求。

  6. 消息队列服务器需要记录每个消费者的消费状态,即当前消费者已经读取到了哪条消息,当收到消息读取请求时,返回下一条未被读取的消息给消费者。

  7. 细化设计点 6:业务服务器和消息队列服务器之间的通信协议如何设计?

考虑到消息队列系统后续可能会对接多种不同编程语言编写的系统,为了提升兼容性,传输协议用 TCP,数据格式为 ProtocolBuffer。

小结

今天我为你讲了架构设计流程的第四个步骤:详细方案设计,并且基于模拟的“前浪微博”消息队列系统,给出了具体的详细设计示例,希望对你有所帮助。这个示例并不完整,有兴趣的同学可以自己再详细思考一下还有哪些细节可以继续完善。

这就是今天的全部内容,留一道思考题给你吧,你见过“PPT 架构师”么?他们一般都具备什么特点?

读者观点

正是那朵玫瑰: 可以完善的细节: 1、发送端和消费端如何寻址 利用zookeeper做注册中心,把broker的地址注册到zk上,发送端和消费端只要配置注册中心的地址即可获取集群所以broker地址,当有broker下线时,发送端和消费端能及时更新broker地址。 2、发送端消息重试 当发送消息发生网络异常时(不包括超时异常),可以重新选择下一台broker来重试发送,重试策略可以自定义。 3、消息消费采用pull还是push? 考虑push模式会更复杂,故放弃,采用pull模式,消费端主动去拉,为了达到与push模式相同的低延迟效果,可以采用长轮询的方式,消费端轮询拉取消息费,当有消费可消费时,返回消息,如果没有可消费的消息,挂起当前线程,直到超时或者有可消费的消息为止。 4、消息重复问题 消息中间件不解决消息重复的问题,有业务系统自己根据业务的唯一id去重。 5、顺序消息 发送端在发生顺序消息时,只发送到相同broker的相同队列,消费端消费时,顺序消息只能由同一个消费端消息。 6、定时消息 发送端指定消息延时多长时间消费,broker端定时扫描定时消息,达到延时时间的消息加入到消费队列。 7、事务消息 发送端分两步,先预发送消息,broker端只记录消息为预发送状态,再执行本地事务,然后再根据本地事务的成功或者失败发送确认消息(回滚还是提交),这步如果发生异常,broker启动定时任务,把未确认的消息发送给发送端回查事务状态(需要发送端提供回查接口)。

目前就想到这么多。

作者回复: 厉害,基本上重点都涵盖了

ant: 很有幸我们现在的架构师就是PPT架构师,我觉得他的优点就是懂了很多的概念,能说话到,可以忽悠住老板。缺点也很明显,就是他知道的都不是很深,比如曾经我们的搜索引擎原型,他并不能说出ES和solr的优缺点(当然我也不知道,平时用solr多点),最后我们选了ES,他给的原因就是朋友说的ES比solr好,后面搜索这里就交给我来搞了。我们是互联网项目,在重构的项目的时候他选择了jpa,这就导致变化需求的时候,查询这块比较麻烦,不灵活。 其实就像前面说的,每个技术存在就是合理的,只是每个有每个技术的使用点,架构师应该对常见的技术栈原理非常清楚,知道什么时候应该使用什么技术。 我理解的PPT架构师的特点就是知识点多,知道概念,能虎住老板,缺点就是技术栈不咋滴,特别是细节上。我觉得架构师应该帮助员工成长,而不是遇到问题就说这个问题我没遇到过,你上网搜索下解决方案。 初次留言,欢迎板砖

作者回复: 架构师确实需要技术面宽,但别只知道技术名词,基本使用,关键原理,优缺点都需要了解

蜗牛: 就一些新的技术引入,架构师需要做哪些技术验证,或者研究到什么深度以后,才认为该技术适合呢?

作者回复: 基本原理,优点缺点,关键设计点,架构师至少要安装过,编写demo体验过,确定选型后,要进行性能和可用性测试例如es的索性设计就是关键设计点

稳健的少年: PPT架构师以脱离一线时间较久的领导居多吧。很多技术他们没有真正使用过,只是从各种途径得知很强大,其他公司(BAT)都在用,于是就在PPT中写入这种技术。缺点就是很容易做出貌似可行,深究其细节全是坑的设计。

作者回复: BAT三个字是设计捷径,但也很多坑

Nick: 好吧,我就是ppt架构师😓 架构的层级看,有企业架构、业务架构、IT架构等,EA分业务、应用、技术、数据、基础设施等,不同的架构对架构师要求不一样,这个教程主要是讲技术架构,要求架构师对技术细节掌握较深,其他几种架构师虽然在写ppt但是真得不简单呢,知识面广度,行业理解,客户需求,抽象思维,心理揣摩,产品知识样样都要掌握

作者回复: 你这种叫“系统分析师”或者“解决方案架构师”更贴切😄

crazyone: 华哥,"日志表是尾部追加,性能高",这个具体实施细节能否讲解下。

作者回复: 其实高性能的很多存储方案都是这样设计的,MySQL有Binlog,HBase有HLog,道理都是相通的。 在这个备选方案中,我们设计一个日志表,假设名称叫MQ_LOG,包含ID, time, queueName, message, publisher等几个字段,每次收到消息发布请求时先写这个表,每次都是表尾部追加。 如果不用自己设计的日志表,mysql的binlog也类似尾部追加,性能也不错,缺点就是没法自己灵活实现各种刷盘机制了。

piboye: 一个这么低量级的mq,消息格式为什么选择tcp和pb?调试和测试,压测工具单独开发的工作量呢?http协议多简单,http可以轻松做到10wqps,难道是Java的netty性能不行? 作者回复: 单机http性能做不到10w。我们测试过16核和32核的机器,单机大约在1.5~1.8万左右

李雷: 若没有日志表,直接使用消息表,同步写入性能低,评论中您写到多个消息表的寻址性能比较低,日志表作为缓冲表,尾部写入性能高,再加上异步写入消息表,总体比单消息表设计性能高,我的理解对吗?谢纠正

作者回复: 理解正确👍

MavenTalker: 架构师对个人的知识点、知识面、知识体系要求很高,也就是技术宽度与深度的问题。

平常说打杂也不为过,什么都要懂,但不一定都得上手做,要能指引他人按着蓝图去实施,当然碰到棘手问题,还是要深入进去解决。

架构师是体现了一个团队的技术强度,PPT架构师有时候还是需要做一做,但不能只停留在PPT里,落地实战同样不能逊色。

作者回复: 我的习惯是最好安装运行,然后写demo体验一下,单纯看文档理解还是不够

小龙: 架构师需要把框架搭起来吗?还是只是以文档形式把方案写出来,团队根据文档搭框架写代码?现在的专栏技术都是讲解互联网电商行业的技术及架构,您专栏讲的架构知识是否适用于工业领域?比如自动化码头调度监控所有自动化设备的软件系统。

作者回复: 1. 架构师不需要搭框架,但要保证方案可行 2. 这套架构理论不局限于互联网软件系统,各行业都可以适应

王天明: 之前有个所谓的架构师同事,拒绝落地任何项目,他到网上找到一个技术demo程序给同事演示一遍后他就撒手不管了,拒绝做进一步的指导。口头禅是“我是架构师”潜台词是,我负责搭一个理论架子领你们入门就够了,其他具体的细节不是架构师的事。

作者回复: 菊花厂某些架构师:细节我们不考虑😄

我的观点