技术阅读摘要 - 1.十二要素应用原则

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 十二要素应用原则(The Twelve-Factor App) 在如今的微服务领域非常流行,相信大家或多或少有所耳闻,但了解其中细节的并不多。今天,我将对这12个原则做一个概要分析,结合Go语言中的相关例子,根据开源与大厂的具体实践,和大家一起看看个中究竟。


Go-Framework

概览

十二要素应用原则(The Twelve-Factor App) 在如今的微服务领域非常流行,相信大家或多或少有所耳闻,但了解其中细节的并不多。

今天,我将对这12个原则做一个概要分析,结合Go语言中的相关例子,根据开源与大厂的具体实践,和大家一起看看个中究竟。

官方资料

核心概念

先对12个概念做一个简单的讲解,让不熟悉的朋友心中有个初步概念。

1. Codebase 基准代码 - 一份基准代码,多份部署

示例:一个应用程序的代码,放在一个Git仓库里。

分支算不算一个仓库呢?

这点常有不同的理解。我个人会把一个分支认为是一个轻量级的仓库,每个分支对应一个具体的业务场景。

痛点:在分布式的环境下,保证多个机器上运行的应用程序源代码一致,提升排查问题的效率。

2. Dependencies 依赖 - 显式声明依赖关系

示例:Go语言的go module就显式地声明了项目依赖库与版本,方便维护。

痛点:以前Go vendor模式,将依赖代码都放在本地,没有一个文件集中说明项目的依赖,这就导致想找依赖关系必须去看代码细节。如果某个库的v1.0版本有bug,需要升级到1.1,go module只需查看一个文件,而vendor模式得遍历具体的项目,甚至多次递归。

3. Config 配置 - 在环境中存储配置

示例:将配置信息(端口、数据库、外部服务等)放在环境中,而不要硬编码。

环境定义的扩展:在微服务中,这个环境指的是代码具体的运行环境,包括配置文件、环境变量、配置中心的数据。

值得一提的是,按照这种实践,配置文件不应该和应用程序代码放在一个代码仓库中,而是单独管理。

痛点:保证多机器上运行的代码一致后,把变化点转移到配置中,避免硬编码到代码仓库中、导致更新需要整体升级。

4. Backing Services 后端服务 - 把后端服务当作附加资源

示例:当前的数据库选型为MySQL,有一天突然转变成了阿里云的RDS,只需要修改一个配置。

痛点:这是一种面向标准接口编程的模式,重点是让非业务相关的代码变得灵活可替代,如数据库、缓存、第三方服务。从被调用方的角度来看,过于理想化,很多重量级的组件很难被替代;而从调用方来看,这一切又显得理所当然,谁都希望外部系统的变化是无感知的。

这一点,非常考验每个服务的设计者的能力,尤其是API接口。

5. Build, release, run 构建,发布,运行 - 严格分离构建和运行

示例:Go程序在一个构建机器上用 go build命令生成二进制文件,在发布阶段将这个二进制文件分发到各个待运行的机器上,最后在各个机器上运行这个二进制文件。

痛点:强调了CICD一整套流水线,不要让构建和运行强绑定。举个极端的例子,如果有一台centos和一台ubuntu的机器,我们去两个机器上各自编译+运行,可能因为内核不同而发生奇怪的现象;而如果采用在一台专用的centos机器上编译成统一的可执行文件,再分发到这两台机器上,更加合理。

如果centos上可执行文件无法在ubuntu机器上运行,就在部署阶段报错,提醒我们去修复。我们更应该去把部署的机器改成centos统一管理,而不是去花大量精力兼容ubuntu。

随着Docker技术的推广,大量简化了这一步骤。

6. Processes 进程 - 以一个或多个无状态进程运行应用

示例:程序的内存中不保存具体业务相关的数据,而应该保存在共享存储上。

痛点:程序有状态是横向扩容的一大阻碍。想要完全去掉有状态的数据过于理想化,毕竟放在共享存储上肯定不如本地内存访问快,这对一些性能敏感的应用来说影响很大;但微服务架构强调要面向失败编程,这些有状态的数据在程序崩溃时无法恢复,可能导致级联雪崩的等更可怕情况。这一点,需要服务的负责人仔细权衡。

7. Port Binding 端口绑定 - 通过端口绑定提供服务

示例:通过开放端口、以RPC形式提供服务

痛点:历史上有些程序以unix socket的本地形式提供服务,维护成本比较高。目前微服务以HTTP为代表的RPC方式提供服务,适合横向扩展。

8. Concurrency 并发 - 通过进程模型进行扩展

示例:当并发量达到一定程度,通过横向扩容进程(Docker实例)来满足需求

痛点:在并发度比较小时,最简单的提升方式是纵向扩容 - 即提高机器的配置。但当并发程度达到一定级别后,无法再通过提升硬件配置来解决。如果进程做到了无状态,利用外部的调度平台(如k8s)进行扩缩容,更适合长期发展。

9. Disposability 易处理 - 快速启动和优雅终止可最大化健壮性

示例:程序能够快速启动,遇到严重异常时也能尽可能地完成现有的任务,优雅终止。

痛点:启动时间少能让程序遇到问题后快速恢复,也可以更快速地横向扩容;而优雅终止更多地是为了保障数据的一致性。

10. Dev/prod parity 开发环境与线上环境等价 - 尽可能的保持开发,预发布,线上环境相同

示例:开发、预发布、线上等环境上部署的代码、配置、第三方服务等,尽可能地保证一致,让故障提前在测试环境发生。

痛点:尽可能地保证线上环境的稳定性,让问题尽可能地在开发环境中暴露出来,结合CICD工作保障环境的一致性。

11. Logs 日志 - 把日志当作事件流

示例:在微服务架构中,通过日志将一个事件(如一个RPC请求)串联起来。

痛点:利用格式化的日志+ELK,将日志收集到统一的管理平台,用于分析数据。

12. Admin processes 管理进程 - 后台管理任务当作一次性进程运行

示例:将脚本、定时任务等,也作为一个应用程序提交,由k8s等调度平台执行

痛点:手动执行脚本或任务往往会有两个严重问题:误操作与可追溯性差。更合理的方式是走一个正式的发布流程,方便管理。

案例串讲

开发阶段

你(研发人员)收到了一个需求,要实现一个分布式的订单服务。于是,你新建了一个git仓库(1.基准代码),准备用go语言编写,部署在多个机器上(8.并发)。开发期间引用了大量的开源库,用Go Module将这些依赖都管理了起来(2.依赖)。

开发过程中,由于测试和线上的数据库地址不同,所以你把这些信息放在了配置中心(3.配置),如Etcd、k8s的ConfigMap中。

由于外部服务会依赖这个订单服务,所以要求研发提供的API接口必须向前兼容,所以选用了经典的RESTful风格的HTTP接口作为对外的协议(7.端口绑定)。这样,即便订单服务后续不断迭代,对外部来说也只是修改一个调用地址即可(4.后端服务)。

上线阶段

在测试、预发布环境,为了开发与测试人员更好地验证功能,绝大部分的配置参数都保证与线上一致(10.开发环境与线上环境等价)。

整个上线流程包括了测试、预发布、线上三个环境的部署和验证,统一采用CICD的技术自动化执行(5.构建,发布,运行)。

维护阶段

订单服务很复杂,经常会发生崩溃,导致很多数据恢复不了。于是,研发组长决定把关键的数据都放在Redis集群中(6.进程),保证应用的无状态化。

数据不丢了,但程序崩溃后恢复很慢,发现花大量的时间在加载初始数据上。于是又做了一次优化,利用Lazy Load的思路,按需加载(9.易处理)。

程序正常运行了,但用户反馈有个请求经常失败。这时,你利用ELK收集上的日志,排查了整个链路中的信息(11.日志),发现某处代码逻辑搞混了。当前的问题修复了,你还需要去数据库修复历史数据,就又写了个sql脚本提交到专门用来维护修改数据的代码仓库,提交给平台自动运行(12.管理进程)。

总结

业界对这12个原则的理解并不完全一致,这也是因为不同角色处于不同的阶段所导致的视野差异性

总体来说,这12个原则强调了简单性、可维护性,我们可以把它们作为微服务架构设计的指导性原则。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
存储 安全 编译器
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(下)
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(下)
|
6月前
|
SEO
技术写作:漏斗内容策略、认知博客、支柱内容、研究报告、通用门控内容、电子书和教程
顶部漏斗是指客户旅程中的认知阶段,他们第一次接触到企业或产品。在这个阶段,他们意识到自己存在问题,并开始寻找信息或解决方案。此阶段的内容旨在通过提供与他们的问题相关的解决方案或有价值的信息来吸引潜在客户的注意力和兴趣。这种内容通常是广泛而丰富的,而不是针对产品的。其目的是在建立信任和品牌权威的同时,告知和教育受众。
102 5
|
存储 分布式计算 Hadoop
了解基础讨论数据
了解基础讨论数据
44 0
|
安全 Java C++
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(上)
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计
|
测试技术
【解决方案 二十一】系统专业名词梳理及释义
【解决方案 二十一】系统专业名词梳理及释义
121 0
|
缓存 前端开发 NoSQL
笔记整理:技术架构涵盖内容和演变过程总结
单体架构 2. 应用与数据库分离 3. 使用缓存抗量 4. 多应用部署和Nginx反向代理 5. 数据库读写分离 6. 应用分组部署 7. 应用分库设计 8. RPC 分布式部署 9. 应用细分和网关引入 10. 低代码编程和可复用
330 0
笔记整理:技术架构涵盖内容和演变过程总结
|
存储 Web App开发 缓存
Librdkafka使用要点
欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接和图片上传 LaT...
1807 0