构建容错Web应用:原理与实践
1. 使用冗余EC2实例提高可用性
虚拟机器可能出现故障的原因有多个方面,主要分为硬件和软件两部分:
-
硬件方面
:
- 若主机硬件发生故障,将无法继续承载其上的虚拟机器。
- 主机的网络连接中断时,虚拟机器会失去网络通信能力。
- 主机系统断电,虚拟机器也会随之故障。
-
软件方面
:
- 应用程序存在内存泄漏问题,最终会耗尽内存导致故障。
- 应用程序不断向磁盘写入数据且不删除,会导致磁盘空间耗尽,应用程序失败。
- 应用程序对边界情况处理不当,可能会意外崩溃。
由于上述原因,单个EC2实例是单点故障。若仅依赖单个EC2实例,系统迟早会崩溃。
1.1 冗余可消除单点故障
以制作蓬松云派的生产线为例,原本的单条生产线存在问题,只要其中一个步骤崩溃,整个生产线就必须停止。比如冷却派皮的步骤出现故障,后续步骤因无法获得冷却的派皮而无法工作。
若采用多条生产线,例如三条,当其中一条生产线出现故障时,另外两条仍可继续为客户生产蓬松云派。虽然需要三倍数量的机器,但系统稳定性大大提高。
将此例子应用到EC2实例,不使用单个EC2实例运行应用程序,而是使用三个。若其中一个实例发生故障,另外两个仍能处理传入的请求。并且可以选择三个小的EC2实例来替代一个大的实例,以降低成本。
对于动态EC2实例池,可通过解耦的方式实现与实例的通信,即在EC2实例和请求者之间放置负载均衡器或消息队列。
1.2 冗余需要解耦
通过冗余和同步解耦可使EC2实例具备容错能力。若某个EC2实例崩溃,弹性负载均衡器(ELB)会停止向该实例路由请求,自动扩展组会在几分钟内替换崩溃的实例,然后ELB开始向新实例路由请求。
以下是系统中冗余部分的说明:
| 冗余部分 | 说明 |
| ---- | ---- |
| 可用区(AZs) | 使用两个可用区,若其中一个发生故障,另一个仍有实例运行 |
| 子网 | 子网与可用区紧密耦合,每个可用区需要一个子网 |
| EC2实例 | 在一个子网(可用区)中有多个实例,且在两个子网(可用区)中都有实例 |
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
Internet --> LoadBalancer(Load balancer):::process
LoadBalancer --> AZA(Availability zone A):::process
LoadBalancer --> AZB(Availability zone B):::process
AZA --> SubnetA(10.0.1.0/24):::process
AZB --> SubnetB(10.0.2.0/24):::process
SubnetA --> WebServersA(Web servers):::process
SubnetB --> WebServersB(Web servers):::process
WebServersA --> AutoScalingA(Auto - scaling group):::process
WebServersB --> AutoScalingB(Auto - scaling group):::process
2. 使代码具备容错性的考虑因素
若要实现容错,需相应地构建应用程序,可参考以下两个建议。
2.1 允许崩溃,但也要重试
Erlang编程语言以“让它崩溃”的概念而闻名,即程序遇到无法处理的情况时崩溃,由其他部分处理崩溃。但人们常忽略Erlang也以重试而闻名。只崩溃不重试是无用的,因为若无法从崩溃中恢复,系统将停机。
“让它崩溃”(或“快速失败”)可应用于同步和异步解耦场景:
-
同步解耦场景
:请求发送者必须实现重试逻辑。若在一定时间内未收到响应或收到错误,发送者会再次发送相同请求。
-
异步解耦场景
:若消息在一定时间内被消费但未得到确认,它会返回队列,下一个消费者会再次处理该消息。异步系统默认具备重试机制。
不过,“让它崩溃”并非适用于所有情况。若请求内容无效,让服务器崩溃并不能解决问题;但若服务器无法访问数据库,重试是有意义的,因为数据库可能在几秒后恢复正常。
重试也存在问题,例如重试创建博客文章时,会在数据库中创建多个重复条目。可通过幂等重试来解决此问题。
2.2 幂等重试使容错成为可能
为防止因重试导致博客文章多次添加到数据库,可采用简单方法,如使用标题作为主键。若主键已存在,则跳过插入步骤,使博客文章的插入操作具有幂等性,即无论操作执行多少次,结果都相同。
插入博客文章的实际过程更复杂,可分为以下步骤:
1.
在数据库中创建博客文章条目
:使用通用唯一标识符(UUID)作为主键。客户端生成UUID,若请求包含的UUID在数据库中不存在,则创建新记录;若存在,则继续插入流程。
2.
使缓存失效
:向缓存层发送失效消息。多次使缓存失效不会造成太大影响,最坏情况是需要多查询几次数据库。
3.
在博客的Twitter动态中发布链接
:由于Twitter不支持幂等操作,无法保证只发布一次状态更新。可采用以下两种方法:
- 向Twitter API查询最新状态更新,若与要发布的内容匹配,则跳过该步骤。但Twitter是最终一致性系统,可能会导致多次发布。
- 在数据库中记录是否已发布状态更新。但可能出现数据库记录已发布,而实际未发布的情况。需要根据业务需求选择容忍缺失状态更新还是容忍多次发布。
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
Start(Start):::process --> CheckUUID{Is the UUID already in the database?}:::process
CheckUUID -- Yes --> End(End):::process
CheckUUID -- No --> CreateEntry(Create database entry):::process
CreateEntry --> End
构建容错Web应用:原理与实践
3. 构建容错Web应用:Imagery案例
在设计Imagery应用时,最初的流程是用户上传图像,应用对图像应用滤镜,然后返回处理后的图像。但这个过程是同步的,存在问题:若Web服务器在请求和响应期间崩溃,用户的图像将无法处理;当大量用户使用该应用时,系统会变得繁忙,可能会变慢或停止工作。因此,需要将其转换为异步过程。
3.1 异步处理流程
当用户想要上传图像时,首先创建一个处理流程,系统会返回一个唯一ID。用户使用该ID上传图像,上传完成后,工作者会在后台处理图像。用户可以随时使用处理ID查询处理状态,在图像处理期间,用户无法看到处理后的图像,处理完成后,查询将返回处理后的图像。
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
User(User):::process --> CreateProcess(Create an image process):::process
CreateProcess --> GetID(Get's back an ID):::process
GetID --> UploadImage(Upload an image to the process identified by the ID):::process
UploadImage --> Worker(Worker picks up the job to process the image):::process
Worker --> ApplyFilter(Apply the sepia filter to the image):::process
ApplyFilter --> Wait(Wait until image is processed asynchronously):::process
Wait --> ViewImage(View the sepia image by the ID):::process
3.2 映射到AWS服务
将异步处理流程映射到AWS服务,大部分AWS服务默认具有容错能力,因此尽可能选择这些服务。具体步骤如下:
1.
创建处理流程
:用户创建一个带有唯一ID的处理流程,该流程存储在DynamoDB中。
2.
上传图像
:用户使用处理ID将图像上传到S3,S3键与新的处理状态“已上传”一起持久化到DynamoDB,并生成一个SQS消息以触发处理。
3.
处理图像
:SQS消息被EC2实例消费,原始图像从S3下载,处理后将处理后的图像上传到S3,DynamoDB中的处理状态更新为“已处理”,并记录处理后图像的S3键。
4.
查看图像
:用户等待处理状态变为“已处理”,然后通过DynamoDB知道处理后图像的S3键,从而查看图像。
| 步骤 | 操作 | 涉及服务 |
|---|---|---|
| 1. 创建处理流程 | 用户创建带有唯一ID的处理流程,存储在DynamoDB | DynamoDB |
| 2. 上传图像 | 用户使用ID上传图像到S3,S3键和状态存到DynamoDB,生成SQS消息 | S3, DynamoDB, SQS |
| 3. 处理图像 | EC2实例消费SQS消息,处理图像并上传到S3,更新DynamoDB状态 | EC2, S3, DynamoDB |
| 4. 查看图像 | 用户等待状态更新,通过DynamoDB获取S3键查看图像 | DynamoDB, S3 |
3.3 幂等状态机
幂等状态机是Imagery应用的核心。有限状态机有至少一个起始状态和一个结束状态,状态之间有定义的转换。Imagery状态机如下:
(Created) -> (Uploaded) -> (Processed)
- Created -> Uploaded :此转换由函数uploaded(s3Key)定义,需要上传的原始图像的S3键。
- Uploaded -> Processed :此转换由函数processed(s3Key)定义,需要处理后的图像的S3键。
需要注意的是,状态机只关注操作的结果,不跟踪操作的进度。
整个示例在AWS免费套餐范围内,只要在几天内完成示例,并且是为该项目创建的全新AWS账户且无其他操作,就无需支付费用。完成示例后,需要清理账户。
超级会员免费看
176万+

被折叠的 条评论
为什么被折叠?



