写点什么

简单软件架构的一些好处

  • 2022-03-29
  • 本文字数:3806 字

    阅读完需:约 12 分钟

简单软件架构的一些好处

本文最初发表于 Wave 官网,经原作者 Dan Luu 授权,InfoQ 中文站翻译并分享。

 

Wave 是一家价值 17 亿美元的公司,拥有 70 名工程师,该公司的产品是一款加减数字的 CRUD 应用程序。为了与此保持一致,我们的架构是一种标准的 CRUD 应用架构,基于 Postgres 的 Python 单体架构。先从一个简单的架构入手,然后尽量用最简单的方式来解决问题,这使得我们的业务范围能够扩大到这种规模,而工程师们大多专注于为用户提供价值的工作。

 

Stackoverflow 扩大了单体的规模,取得了良好的效果(2013 年的架构/2016 年的架构),最后以 18 亿美元的价格被收购。如果我们关注的是流量而非市场市值,那么 Stackoverflow 就是互联网上流量最高的前 100 个网站之一(关于其他许多建立在单体之上的有价值的公司的案例,请参考这条 Twitter 主题的回复。我们没有很多网络流量,因为我们是一个移动应用,但 Alexa 还是将我们的网站列在了前 75000 名,尽管我们的网站基本上只是人们查找 APP 的一种途径,而大部分人并没有从我们的网站中获得这些 APP)。

 

有些应用的要求,使得在一个枯燥的数据库中构建出一个简单的单体应用是不可能的,但对大部分应用来说,即便是在前 100 个网站的流量水平上,计算机的运行速度也足以满足使用简单的架构来提供服务,通常创建简单的架构比复杂的架构更便宜、更容易。

 

尽管简单的架构具有不合理的有效性,但是大部分的新闻报道都是围绕着复杂的架构展开的。举例来说,在最新的通用技术会议上,就有六场演讲讨论了怎样构建或处理基于微服务的复杂结构的负面影响,却没有一场演讲讨论如何构建简单的单体。甚至关于量子计算的演讲也有一场。更大规模的会议也一样;最近旧金山的一次以企业为导向的会议上,关于处理复杂架构的演讲,场次就高达两位数;却没有一场关于如何构建简单的单体的演讲。我上次去的那次会议给我留下了很深的印象,就是许多公司的员工,他们的应用程序规模很小,本来可以用简单的架构就能完成,但是他们使用的都是会议圈子和网络上流行的最新、最复杂的技术。

 

我们的架构是如此简单,以至于我都懒得去做一个架构图。我会讨论我们所做的使一切乏味的事。

 

我们目前使用的是乏味的、同步的 Python,这意味着,当我们的服务器进程在等待 I/O 时被阻塞,比如网络请求。我们之前尝试过 Eventlet,这是一种理论上能使我们从 Python 中获得更高效率的异步框架,但是我们碰到了大量的 Bug,我们觉得,等待事件的 CPU 和延迟成本,都不值得我们为处理 Eventlent 问题而承担操作上的痛苦。其他知名的 Python 框架也有类似的情况,但是大规模使用它们的用户往往也会报告大规模使用这些框架带来的严重后果。使用同步的 Python 代价很高,因为我们需要支付 CPU 的费用,而在网络请求期间,CPU 除了等待之外什么都不做,但是,现在,我们每个月只能处理几十亿个请求,因此,即便是使用 Python 这种缓慢的语言,也要支付公共云的零售费用,这样的成本也很低。我们工程团队的成本完全决定了我们所运营的系统的成本。

 

我们将长时间运行的任务(我们不想让响应阻塞)分配到一个队列中,而不是承担使我们的单体异步的复杂性。

 

我们不能像我们想的那样无聊的地方,就是我们的内部数据中心。当我们只在塞内加尔和科特迪瓦运营时,我们完全是在云端中运营,但是,随着我们的业务范围扩大到乌干达(以及未来更多的国家/地区),我们不得不拆分后端,部署到当地的内部数据中心,以遵守当地的数据存储法律和法规。这并非一项简单的操作,但正如那些在面向服务的复杂架构中做过相同工作的人所知道的那样,这种操作要比使用复杂的服务导向的架构要简单得多。

 

另外一个方面是我们必须研发的软件,而非购买。刚起步时,我们强烈地倾向于购买软件,而非研发软件,因为一个由少数工程师组成的团队无法承担研发软件的时间成本。虽然“购买”这一选项,通常会给你提供一些无效的工具,但这在那个时候是正确的选择。如果我们不能说服供应商修复 Showstopper 错误,而这个错误对我们至关重要,那么在这种情况下,构建更多的自己的工具,并且在更多的方面保留内部的专业知识,这的确是很有意义的,但这与公司应只选择“构建”其核心能力的标准建议相悖。这种复杂性的大部分都是我们不愿意承担的,但是对于某些类别的产品,即便是进行了相当广泛的研究,我们仍然找不到供应商能够提供适合我们的产品。公平地说,我们的供应商需要解决的问题比我们需要解决的问题复杂得多,因为我们的供应商承担着为每个客户解决问题的复杂性,而我们只需要为一个客户解决问题,那就是我们自己。

 

译注:Showtopper 错误是导致执行停止并基本上变得无用的硬件或软件错误。必须修复此严重错误,以使开发过程进一步进行。

 

我们在运营的头几个月里,就犯了一个错误,就是没有仔细地界定数据库事务的边界,这在今天已经付出了一定的代价。在 Wave 的代码库中,SQLAlchemy 数据库会话是一个请求全局变量;在任何时候访问 DB 对象的属性时,它都隐含地开始一个新的数据库事务,并且 Wave 代码库中的任何函数都可以在会话上调用 commit,使其提交所有挂起、的更新。这使得我们很难控制数据库更新发生的时间,从而增加了出现微妙的数据完整性错误的概率,并且很难依靠数据库来构建类似于幂等键(idempotency key)或事务性暂存的作业流失。这样做还会增加我们意外地持有打开的长时间运行的数据库事务的风险,这可能使模式迁移操作变得困难

 

一些我们不确定的选择(因为我们在考虑更改,或建议其他从零起步的团队考虑另一种方式)有:使用 RabbitMQ(就我们的目的而言,Redis 可能同样适用于任务队列,只需要 Redis 就可以减轻操作负担);使用 Celery(这对于我们的用例来说过于复杂,并且已经出现了好几次故障,比如在版本升级过程中出现了向后兼容性问题);使用 SQLAlchemy(它使开发人员难以理解自己的代码将会产生怎样的数据库查询,从而导致各种难以调试的情况,同时也带来了不必要的操作痛苦,尤其是与上面提到的数据库事务边界的观点有关);以及使用 Python(由于我们的创始 CTO 的技术背景,这是最初的正确选择,但其并发支持、性能和广泛的动态性使我们质疑它是否是大规模后端代码库的正确选择)。以上所有这些都不是主要的错误,而且对于一些(例如 Python) 来说,缺陷已经很少了,因此,与投资到理论上更好的迁移相比,我们将花费更少的费用去进行更多的维护,但如果我们现在就从头编写一套类似的代码库,那么我们就会认真考虑,它们是否正确的选择。

 

在某些方面,我们很满意能做出这样的选择,虽然这些听上去并不像是最简单可行的解决方案,比如我们的 API,我们使用 GraphQL;我们的传输协议,我们有一段时间使用自定义协议;还有我们的主机管理,我们使用 Kubernetes。对于我们的传输协议,我们曾经使用了一种基于 UDP 的自定义协议,并带有 SMS 和 USSD 后备功能,这也是这场讲座所提到的性能理由。在 HTTP/3 发布后,我们已经能够用 HTTP/3 来替代我们的自定义协议,通常我们只需要 USSD 就可以解决像最近在马里发生的互联网关闭这样的事件)。

 

对于 GraphQL 的使用,我们相信其优点多于缺点:

 

优点:

 

  • 精确返回类型的自文档化;

  • 精确返回类型的代码生成使得客户端更加安全;

  • GraphiQL 交互式探索器是生产力的一个胜利;

  • 我们的各种应用(用户应用、支持应用、Wave agent 应用等)大多可以共享一个 API,从而减少复杂性。

  • 可组合的查询语言允许客户端在一次数据包往返中准确获取它们需要的数据,而无需建立大量的特殊用途的端点;

  • 避免了对什么算作 RESTful API 的无意义争论。

 

缺点:

 

  • 当我们采用 GraphQL 时,GraphQL 库并不是很好(基本的 Python 库是从 JavaScript 库中移植过来的,因此不是 Python 化的,Graphene 需要大量的模板,Apollo-Android 生成的优化代码非常烂)。

  • 默认的 GQL 编码是冗余的,而且由于很多客户端的带宽较低,所以我们非常关心限制大小。

 

对于 Kubernetes,我们之所以选择 Kubernetes,是因为我们清楚,如果业务成功(确实如此),而且我们不断扩张,我们最终会扩张到那些要求我们在该国内运营服务的国家。各国之间的具体规定各不相同,但是我们在非洲的主要市场上拓展了业务,这就要求我们在该国运营我们的 “主要数据中心”,还有其他一些规定,例如,要求我们能够将故障转移到该国的数据中心。

 

电信集成是我们无法回避的复杂性的一个方面。从理论上讲,我们将使用 SaaS SMS 提供商来完成这一切,但是,非洲各大 SaaS SMS 提供商的业务并没有遍及整个非洲,因此在那里使用这些服务的费用都让人望而却步。如果我们利用 SaaS SMS 提供商来解决我们所有的短信需求,那么以前的那些说工程师的薪酬成本如何主导我们系统成本的说法是站不住脚的;提供电信集成服务的团队为此付出了数倍的代价。

 

通过将应用架构尽量简化,我们就可以将复杂性(以及人力)预算用于有利于业务发展的领域。如果没有足够的理由去提高复杂性,那么我们就可以基于尽可能简单地做事的这一想法,用少量的工程师,创建一个规模不小的业务,虽然我们所经营的非洲金融业务通常被视为难以涉足的业务,我们会在以后的文章中谈到(我们的早期和最有帮助的咨询顾问之一,他给我们提出的建议,对于 Wave 的成功非常关键,起初提出 Wave 是一个糟糕的商业点子,而创始人应该选择另一个,因为他预见到了许多潜在的困难)。

 

原文链接:

https://www.wave.com/en/blog/simple-architecture/index.html

2022-03-29 14:504367

评论 2 条评论

发布
用户头像
机翻的吧,一点都读不通顺
2022-03-30 11:43
回复
用户头像

微服务架构趋势下如何处理存量系统
https://xie.infoq.cn/article/3f9e2ea9e02ef60a90f7dac3d
2022-03-30 08:46
回复
没有更多了
发现更多内容

聊聊 C 语言和 ABAP 这两门编程语言的关系

汪子熙

c++ 编程语言 C语言 abap 3月月更

HertzBeat赫兹节拍 v1.0.beta.6 发布,Linux监控来啦

TanCloud探云

开源 APM 监控 告警

WebAssembly技术_JS调用C函数示例_传递参数、方法导出

DS小龙哥

webassembly 3月月更

昇腾CANN论文上榜CVPR,全景图像生成算法交互性再增强!

华为云开发者联盟

CVPR CANN 图像生成 昇腾AI处理器 图像布局

presto是如何保证作业内存不会发生冲突和溢出

华为云开发者联盟

内存 presto 内存计算引擎 System Pool general Pool

科幻变现实:喷下即疗愈,生物3D打印绘就生命密码图

脑极体

743 网络延迟时间

好吃不贵

Redis集群架构剖析(3):集群处理redis-cli指令

非晓为骁

redis 架构 分布式 redis cluster

小程序电商微服务设计

唐尤华

架构实战营

5G 和 Wi-Fi 市场与技术的一些思考系列之二

李伟-晨泳

Jetpack Compose之 在Compose中使用Navigation导航

黄林晴

android Compose JetPack

计算机编码规则之:Base64编码

程序那些事

Java base64 nio 程序那些事 3月月更

FAQ(常见问题)页面的编写技巧

小炮

企业 常见问题 客户服务

一文搞定 Flutter 底部弹窗实现

岛上码农

flutter 跨平台 ios开发 Android开发 3月月更

深入浅出 Vue3 核心知识点

百瓶技术

Vue 前端 VUE 3.0 源码 vue3.0

云效DevOps全家桶评测征集令重磅来袭!免费使用云效全套功能

阿里云云效

云计算 阿里云 DevOps 云原生

IOS技术分享| ARCallPlus 开源项目(一)

anyRTC开发者

ios 移动开发 语音通话 视频通话 呼叫邀请

如何在新公司快速落地

Hockor

Redis:在windows环境安装Redis

喀拉峻

redis

☕️从Java8到Java17的新特性(一):Java8 的新特性

看山

Java java8

公有云RDS太贵?基于ECS构建的多云RDS服务可降低近半成本

沃趣科技

数据库 公有云 RDS 云数据库RDS for MySQL 云数据库Redis

RocketMQ的tag还有这个“坑”!

中间件兴趣圈

RocketMQ tag 消息过滤

你了解部署流水线吗?

华为云开发者联盟

自动化 软件开发 devcloud 部署流水线 流水线

Python迎来31岁生日,蝉联年度编程语言排行榜冠军

Python猫

Python

最好的 6 个 React Table 组件详细亲测推荐

蒋川

React

如何消除代码山中那一大坨参数列表

华为云开发者联盟

代码 软件设计 参数 全局变量 参数列表

Kubernetes API规范:为optional的字段使用pointer

工程师薛昭君

API Kubernetes 集群

《软件开发的201个原则》思考:4. 高质量软件是可以实现的

非晓为骁

个人成长 软件工程 软件开发

Rust的Cow类型

Shine

rust cow

大数据培训十大Hive调优技巧

@零度

大数据 hive调优

java培训Redis高频面试考点

@零度

Java redis

简单软件架构的一些好处_大数据_Dan Luu_InfoQ精选文章