应用架构

本文总结了架构设计的基本理念。

服务设计理念

软件即服务

应用设计 12 基本准则

比较流程的应用设计 12 基本准则

  • 使用标准化流程自动配置,从而使新的开发者花费最少的学习成本加入这个项目。

  • 和操作系统之间尽可能的划清界限,在各个系统中提供最大的可移植性。

  • 适合部署在现代的云计算平台,从而在服务器和系统管理方面节省资源。

  • 将开发环境和生产环境的差异降至最低,并使用持续交付实施敏捷开发。

  • 可以在工具、架构和开发流程不发生明显变化的前提下实现扩展。

基准代码

一份基准代码,多份部署

  • 一个应用不是分布式系统,仅需一个代码版本控制即可。

  • 所有部署(测试、生产等)对应一份基准代码。

依赖声明

显示声明依赖关系

  • 应用不应该隐式依赖于系统级类库,而是通过类库打包工具显示声明依赖。

  • 同样不应该依赖于系统级工具,而是以类库打包的方式打包进应用。

配置

在环境中存储配置

  • 用于当基准代码能立刻开源,而不暴露任何敏感信息。

  • 环境变量有利于不同的部署,且颗粒度要相对小和独立。

后端服务

将后端服务当作附加资源

  • 这里后端服务为程序运行所需要的通过网络调用的各种服务,将类似的数据库(MySQL、CouchDB),消息 / 队列系统(RabbitMQ),SMTP(邮件发送服务),以及缓存系统(Redis)等配置成可本地及第三方可配置的附加部署资源,使松耦合资源和应用部署,达到最小代码修改的目的。

attached-resources.png.

构建,发布,运行

严格分离构建和运行

  • 基准代码的部署,严格分为构建(仓库代码转为可执行包过程)、发布(将构建结果和发布部署配置结合确定发布版本,并能立即投入运行环境中使用)、运行(指定对应的发布版本,在执行环境中启动一系列应用程序)。

  • 严格区分有利于回退部署,更容易发现问题。

进程

以一个或多个无状态进程运行应用

  • 应用进程必须无状态且无共享,任何需要持久化的数据都要存储在 后端服务 内。

  • 内存区域或磁盘空间可以作为进程在做某种事务型操作时的缓存。

  • 用户数据缓存中的数据应该保存在诸如 Memcached 或 Redis 这样的带有过期时间的缓存中。

端口绑定

通过端口绑定提供服务

  • 互联网应用 通过端口绑定来提供服务 ,并监听发送至该端口的请求。

  • 通常的实现思路是,将网络服务器类库通过 依赖声明 载入应用。例如,Python 的 Tornado。几乎所有服务器软件都可以通过进程绑定端口来等待请求。

并发

通过进程模型进程扩展,任何计算机程序,一旦启动,就会生成一个或多个进程。

  • 进程是一等公民,基于 unix 守护进程模型

  • 将不同的任务分配不同的进程类型调度并发进程模型。

  • 所有应用程序必须可以在多台物理机器间跨进程工作。

  • 基于 进程设计 在系统急需扩展时变得简单及稳妥。

  • 建议使用操作系统进程管理(systemd,分布式进程管理云平台,foreman 等工具),管理输出流,崩溃,重启等请求。

易处理

快速启动和优雅终止可最大化健壮性

  • 应用进程应该时可以瞬间开启或停止,有利于迅速部署变化的 基准代码配置

  • 进程应当最求最小启动时间,提供更敏捷的 发布 及扩展过程。

  • 进程应该在接受到 SIGTERM 优雅的终止。优雅终止是指停止监听服务的端口,即拒绝所有新的请求,并继续执行当前已接收的请求,然后退出。

  • 对于 worker 进程来说,优雅终止是指将当前任务退回队列。例如,RabbitMQ 中,worker 可以发送一个 NACK 信号。此类型的进程所隐含的要求是,任务都应该 可重复执行 , 这主要由将结果包装进事务或是使重复操作 幂等 来实现。

  • 进程还应当在面对突然死亡时保持健壮,例如底层硬件故障。Crash-only design 将这种概念转化为 合乎逻辑的理论

开发环境与线上环境等价

尽可能的保持开发,预发布,线上环境相同

  • 差异体现在,时间差异(编写代码和上线的时间)、人员差异(开发人员、部署运维人员的间代码)、工具差异(本地部署开发环境及线上环境)

  • 做到持续部署,就需要做到本地和线上最小化差异

  • 方法,缩小时间差异:开发人员可以几小时,甚至几分钟就部署代码;缩小人员差异:开发人员不只要编写代码,更应该密切参与部署过程以及代码在线上的表现;缩小工具差异:尽量保证开发环境以及线上环境的一致性。

  • 后端服务 是保持开发与线上等价的重要部分,例如数据库,队列系统,以及缓存。许多语言都提供了简化获取后端服务的类库。如,队列(Python/Django,Celery,RabbitMQ, Beanstalkd, Redis)。

  • 开发人员应该反对在不同环境间使用不同的后端服务,是因为,不同的后端服务意味着会突然出现的不兼容,造成的错误导致持续部署问题的解决需要非常大的代价。

  • 使用类似 ChefPuppet 的声明式配置工具,结合像 Vagrant 这样轻量的虚拟环境就可以使得开发人员的本地环境与线上环境无限接近。

日志

把日志当作事件流,日志使得应用程序运行的动作变得透明。

  • 日志应该是 事件流 的汇总。

  • 应用本身从不考虑存储自己的输出流。每一个运行的进程都会直接的标准输出(stdout)事件流。

  • 在预发布或线上部署中,每个进程的输出流由运行环境截获,并将其他输出流整理在一起,然后一并发送给一个或多个最终的处理程序,用于查看或是长期存档。这些存档路径对于应用来说不可见也不可配置,而是完全交给程序的运行环境管理。类似 LogplexFluentd 的开源工具可以达到这个目的。

  • 输出流可以发送到 Splunk 这样的日志索引及分析系统,或 Hadoop/Hive 这样的通用数据存储系统。这些系统为查看应用的历史活动提供了强大而灵活的功能。

管理进程

后台管理任务当作一次性进程运行,区别于处理应用的常规业务任务进程。

  • 一次性管理进程应该和正常的 常驻进程 使用同样的环境。

  • 随基准代码使用相同的配置、发布,避免同步问题。

  • 所有进程类型应该使用同样的 依赖隔离 技术。

微服务

架构考虑

  • 业务需求,系统实现功能。

  • 技术栈,公司员工擅长的技术栈。

  • 成本,人力财力支持。

  • 组织架构,部分小组支持。

  • 可扩展性,软件设计需求变化,业务变更。

  • 可维护性,系统学习成本,bug 修复成本。

架构

单体架构

功能、业务集成在发布中,部署运行在同一个进程中。

  • 一层架构:业务逻辑、数据库混合等。

  • MVC 模式:Model、View、Controller,简单来说分层设计。

  • 前后端:单体架构分离。

单体架构特征及难易:

  • 易于开发,开发工具等

  • 易于测试,测试框架等

  • 易于部署,一份代码发布部署

  • 易于水平伸缩,代码的 copy。

  • 难于维护,代码膨胀。

  • 难于构建、测试、部署成本大。

  • 难于上手,接手困难。

  • 难于创新,框架固定,升级风险。

  • 难于扩展,水平(需求逻辑)、垂直(物理硬件)扩展。

微服务架构

使用一套小服务开发单个应用,每个服务运行在单独的进程中,采取轻量级的通信机制互联,并且每个服务都可以自动化部署

微服务架构特征:

  • 单一职责,紧密相关业务放在一起。

  • 轻量级通信,http/grpc 平台无关,语言无关。

  • 隔离性,单独进程隔离。

  • 数据隔离,降低实现复杂性,方便微服务间实现无状态通信及不断添加微服务。

  • 技术多样性,技术栈多样性。

第二代微服务:k8s, service mesh, serverless。

  • k8s: 云平台。

  • service mesh:服务同行。

  • serverless:无服务无需关注底层。

微服务优势

  • 围绕业务构建团队,根据康威定律,团队决定成果。

  • 去中心化数据管理,以业务区分将数据存储

    • 团队,内聚,独立开发业务,没有依赖,沟通成本低。
    • 产品,服务彼此独立部署,没有依赖,充分利用资源(频繁使用的服务可以部署多份)。

[!IMPORTANT]
微服务不是银弹,没有任何一种技术和管理上的进步,可以极大的提升生产效率。在微服务实践中需要做相应的取舍。

微服务劣势

网络通信需要做极大的优化,基本上等于分布式痛点,现有的比较流行的开源解决方式是使用 istio。

  • 服务注册 / 发现

  • 路由,流量转移

  • 弹性,熔断,超时,重试

  • 安全

  • 可观测性

分布式

分布式八大难点

  • 网络,需要做数据消息缓存。

  • 延时,单个调用从单体调用 ns 到 ms。

  • 带宽,考虑同一时间的负载,主要和延时相关。

  • 安全,通信必须考虑安全加密、类库漏洞等。

  • 网络拓扑,k8s、服务网格处理单点故障、自动伸缩、服务迁移影响。

  • 服务器管理,资源管理配置及依赖。

  • 监控和可观测性(新增),系统存在问题需能告警及跟踪。

  • 消息传输成本,grpc/json 通信协议。

  • 网络同质,不同客户端及环境使用的网络不同质需要做针对化处理。

系统设计

系统设计尽量遵循 MVP 原则.

[!IMPORTANT]
MVP (Minimum viable Product) 以最小的代价提供一个可测试交付的版本,然后再后续开发中直到一个稳定的状态。

TDD

Test Driven Development

以下是对 TDD (Test Driven Development) 的归纳总结

  • TDD 是一种开发方法论,它要求在编写实际代码之前先编写测试代码。

  • TDD 的基本原则是:先写一个失败的测试,然后编写足够的代码使其通过,最后进行重构。

  • TDD 的好处包括:提高代码质量、减少 bug、提高代码可维护性、加快开发速度等。

  • TDD 的核心是测试驱动,通过编写测试用例来驱动代码的开发。

  • TDD 的流程包括:编写测试、运行测试、编写代码、运行测试、重构代码。

  • TDD 的关键是编写简单、清晰、可测试的代码。

  • TDD 适用于各种编程语言和项目类型。

简历

  • 项目

    • 规模
    • 背景
    • 价值
    • 困难
    • 成果
    • 技术栈
  • 教育

    • 专业
    • 学历
    • 竞赛
    • 证书