36、异步解耦与容错设计在AWS中的实践

异步解耦与容错设计在AWS中的实践

1. 异步解耦与消息队列

在构建应用程序时,异步解耦是一种重要的设计模式,它允许不同组件之间独立运行,提高系统的可扩展性和容错性。消息队列是实现异步解耦的常用工具,在AWS中,简单队列服务(SQS)是一个强大的消息队列解决方案。

1.1 创建S3存储桶

为了完成URL转PNG的示例,首先需要创建一个启用网站托管的S3存储桶。执行以下命令,将 $yourname 替换为你的姓名或昵称,以避免与其他用户的存储桶名称冲突(S3存储桶名称在所有AWS账户中必须全局唯一):

$ aws s3 mb s3://url2png-$yourname
$ aws s3 website s3://url2png-$yourname --index-document index.html \
  --error-document error.html

网站托管的目的是让用户能够从S3下载生成的PNG图像。

1.2 设置消息队列

创建SQS队列非常简单,只需指定队列名称即可:

$ aws sqs create-queue --queue-name url2png
{
    "QueueUrl": "https://queue.amazonaws.com/878533158213/url2png"
}

请记录返回的 QueueUrl ,后续示例会用到。

1.3 编程式生成消息

现在有了SQS队列,可以向其发送消息。使用Node.js结合AWS SDK向AWS发送请求。以下是使用AWS SDK for Node.js生成消息的示例代码:

const AWS = require('aws-sdk');
const uuid = require('uuid/v4');
const sqs = new AWS.SQS({
    region: 'us-east-1'
});

if (process.argv.length !== 3) {
    console.log('URL missing');
    process.exit(1);
}

const id = uuid();
const body = {
    id: id,
    url: process.argv[2]
};

sqs.sendMessage({
    MessageBody: JSON.stringify(body),
    QueueUrl: '$QueueUrl'
}, (err) => {
    if (err) {
        console.log('error', err);
    } else {
        console.log('PNG will be soon available at ...');
    }
});

在运行脚本之前,需要安装Node.js模块。在终端中运行 npm install 来安装依赖项。还需要修改 config.json 文件,将 QueueUrl 更改为前面创建的队列的URL,将 Bucket 更改为 url2png-$yourname

运行脚本:

$ node index.js "http://aws.amazon.com"

程序应该会返回类似“PNG will be available soon at http://url2png-$yourname.s3-website-us-east-1.amazonaws.com/XYZ.png”的消息。

为了验证消息是否准备好被消费,可以查询队列中的消息数量:

$ aws sqs get-queue-attributes \
  --queue-url "$QueueUrl" \
  --attribute-names ApproximateNumberOfMessages

由于SQS的分布式特性,它只返回消息数量的近似值。如果第一次没有看到消息,可以再次运行命令。

1.4 编程式消费消息

处理SQS消息需要三个步骤:
1. 接收消息
2. 处理消息
3. 确认消息已成功处理

以下是实现这些步骤的代码:

const fs = require('fs');
const AWS = require('aws-sdk');
const webshot = require('webshot');
const sqs = new AWS.SQS({
    region: 'us-east-1'
});
const s3 = new AWS.S3({
    region: 'us-east-1'
});

const receive = (cb) => {
    const params = {
        QueueUrl: '$QueueUrl',
        MaxNumberOfMessages: 1,
        VisibilityTimeout: 120,
        WaitTimeSeconds: 10
    };
    sqs.receiveMessage(params, (err, data) => {
        if (err) {
            cb(err);
        } else {
            if (data.Messages === undefined) {
                cb(null, null);
            } else {
                cb(null, data.Messages[0]);
            }
        }
    });
};

const process = (message, cb) => {
    const body = JSON.parse(message.Body);
    const file = body.id + '.png';
    webshot(body.url, file, (err) => {
        if (err) {
            cb(err);
        } else {
            fs.readFile(file, (err, buf) => {
                if (err) {
                    cb(err);
                } else {
                    const params = {
                        Bucket: 'url2png-$yourname',
                        Key: file,
                        ACL: 'public-read',
                        ContentType: 'image/png',
                        Body: buf
                    };
                    s3.putObject(params, (err) => {
                        if (err) {
                            cb(err);
                        } else {
                            fs.unlink(file, cb);
                        }
                    });
                }
            });
        }
    });
};

const acknowledge = (message, cb) => {
    const params = {
        QueueUrl: '$QueueUrl',
        ReceiptHandle: message.ReceiptHandle
    };
    sqs.deleteMessage(params, cb);
};

const run = () => {
    receive((err, message) => {
        if (err) {
            throw err;
        } else {
            if (message === null) {
                console.log('nothing to do');
                setTimeout(run, 1000);
            } else {
                console.log('process');
                process(message, (err) => {
                    if (err) {
                        throw err;
                    } else {
                        acknowledge(message, (err) => {
                            if (err) {
                                throw err;
                            } else {
                                console.log('done');
                                setTimeout(run, 1000);
                            }
                        });
                    }
                });
            }
        }
    });
};

run();

运行脚本:

$ node worker.js

应该会看到输出显示工作进程正在处理消息,处理完成后显示“done”。几秒钟后,截图应该会上传到S3。

1.5 SQS消息传递的局限性

SQS虽然有很多优点,如可扩展性高、高可用性和按消息付费,但也有一些局限性:
- 不保证消息只传递一次 :消息可能会被多次传递,原因有两个:一是如果在 VisibilityTimeout 内未删除接收到的消息,消息会再次被接收;二是如果 DeleteMessage 操作由于SQS系统中的某个服务器不可用而未能删除所有消息副本。可以通过使消息处理具有幂等性来解决这个问题。
- 不保证消息顺序 :消息的消费顺序可能与生产顺序不同。如果需要严格的顺序,可能需要寻找其他解决方案。
- 不能替代消息代理 :SQS只是一个消息队列,不具备消息路由或消息优先级等功能。

SQS的FIFO队列可以保证消息顺序,并具有检测重复消息的机制,但价格较高,每秒操作数有限制。

2. AWS服务的容错性

在构建系统时,容错性是一个重要的考虑因素。AWS提供了各种服务,这些服务具有不同的故障恢复能力。

2.1 故障恢复能力类型
  • 无保证(单点故障) :发生故障时,不提供任何请求服务。
  • 高可用性 :发生故障时,需要一段时间才能恢复正常服务。
  • 容错性 :发生故障时,服务不受影响,继续正常提供请求服务。
2.2 AWS服务的容错性分类
服务类型 服务名称 说明
单点故障 Amazon Elastic Compute Cloud (EC2) 实例 单个EC2实例可能因硬件故障、网络问题、可用区中断等原因而失败。可使用自动扩展组设置一组EC2实例以实现冗余服务。
单点故障 Amazon Relational Database Service (RDS) 单实例 单个RDS实例可能因与EC2实例相同的原因而失败。使用多可用区模式实现高可用性。
高可用性 Elastic Network Interface (ENI) 网络接口绑定到可用区,该可用区故障时不可用。使用多个不同可用区的子网可消除对单个可用区的依赖。
高可用性 Amazon Virtual Private Cloud (VPC) 子网 VPC子网绑定到可用区,该可用区故障时不可达。
高可用性 Amazon Elastic Block Store (EBS) 卷 EBS卷在可用区内的多台机器上分布数据,但整个可用区故障时不可用(数据不会丢失)。可定期创建EBS快照以便在其他可用区重新创建卷。
高可用性 Amazon Relational Database Service (RDS) 多可用区实例 运行在多可用区模式下,主实例出现问题时,切换到备用实例时会有短暂停机(1分钟)。
容错性 Elastic Load Balancing (ELB),部署到至少两个可用区 作为消费者,不会注意到任何故障。
容错性 Amazon EC2安全组
容错性 Amazon Virtual Private Cloud (VPC) 带有ACL和路由表
容错性 弹性IP地址 (EIP)
容错性 Amazon Simple Storage Service (S3)
容错性 Amazon Elastic Block Store (EBS) 快照
容错性 Amazon DynamoDB
容错性 Amazon CloudWatch
容错性 自动扩展组
容错性 Amazon Simple Queue Service (SQS)
容错性 AWS Elastic Beanstalk(管理服务本身)
容错性 AWS OpsWorks(管理服务本身)
容错性 AWS CloudFormation
容错性 AWS Identity and Access Management (IAM) 不绑定到单个区域,创建的IAM用户在所有区域可用。
2.3 容错设计的重要性

设计具有容错性的系统可以为最终用户提供高质量的服务。无论系统中发生什么故障,用户都不会受到影响,可以继续消费娱乐内容、购买商品和服务或与朋友交流。在AWS中,构建容错系统变得越来越经济实惠,但仍然是云计算中的一项高级技能,一开始可能具有挑战性。

以下是消息处理的流程图:

graph LR
    A[生产消息] --> B[发送到SQS队列]
    B --> C[接收消息]
    C --> D[处理消息]
    D --> E[上传到S3]
    E --> F[确认消息]
    F --> G[删除消息]

通过以上步骤和分析,我们可以看到如何在AWS中使用消息队列实现异步解耦,并了解不同AWS服务的容错性,从而构建出可扩展、容错的系统。

3. 实现容错系统的策略

为了构建一个具有容错性的系统,我们可以采用以下几种策略。

3.1 引入冗余

引入冗余是消除单点故障的关键。当系统中某个组件出现故障时,冗余的组件可以继续提供服务,确保系统的正常运行。例如,在使用 Amazon EC2 实例时,可以使用自动扩展组来创建多个实例,这些实例可以分布在不同的可用区。这样,即使某个可用区出现故障,其他可用区的实例仍然可以处理请求。

以下是一个简单的步骤说明:
1. 创建一个启动配置,定义 EC2 实例的基本设置,如 AMI、实例类型等。
2. 创建一个自动扩展组,指定启动配置、最小实例数、最大实例数和期望实例数。
3. 将自动扩展组关联到 Elastic Load Balancing (ELB),以便将请求均匀地分配到多个实例上。

# 创建启动配置
aws autoscaling create-launch-configuration \
  --launch-configuration-name my-launch-config \
  --image-id ami-12345678 \
  --instance-type t2.micro

# 创建自动扩展组
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name my-auto-scaling-group \
  --launch-configuration-name my-launch-config \
  --min-size 2 \
  --max-size 10 \
  --desired-capacity 2 \
  --availability-zones us-east-1a us-east-1b

# 关联到 ELB
aws autoscaling attach-load-balancers \
  --auto-scaling-group-name my-auto-scaling-group \
  --load-balancer-names my-load-balancer
3.2 失败重试

当系统中的某个操作失败时,可以通过重试机制来尝试再次执行该操作。在使用 AWS 服务时,很多服务都支持重试机制。例如,在使用 SQS 时,如果接收消息或删除消息的操作失败,可以在一定的时间间隔后重试。

以下是一个简单的重试代码示例:

const MAX_RETRIES = 3;
const RETRY_DELAY = 1000; // 1 秒

const sendMessageWithRetry = (sqs, params, retries = 0) => {
    sqs.sendMessage(params, (err) => {
        if (err) {
            if (retries < MAX_RETRIES) {
                console.log(`Retry attempt ${retries + 1} after ${RETRY_DELAY}ms`);
                setTimeout(() => sendMessageWithRetry(sqs, params, retries + 1), RETRY_DELAY);
            } else {
                console.log('Max retries reached. Giving up.');
            }
        } else {
            console.log('Message sent successfully');
        }
    });
};
3.3 使用幂等操作

幂等操作是指无论执行多少次,其结果都是相同的操作。在实现失败重试机制时,使用幂等操作可以避免重复操作带来的问题。例如,在使用 S3 上传文件时,可以使用文件的哈希值作为对象的键,这样即使多次上传相同的文件,也不会产生额外的影响。

以下是一个使用幂等操作上传文件到 S3 的示例:

const AWS = require('aws-sdk');
const crypto = require('crypto');
const fs = require('fs');

const s3 = new AWS.S3();

const uploadFile = (filePath) => {
    fs.readFile(filePath, (err, data) => {
        if (err) {
            console.log('Error reading file:', err);
        } else {
            const hash = crypto.createHash('sha256').update(data).digest('hex');
            const params = {
                Bucket: 'my-bucket',
                Key: hash,
                Body: data
            };
            s3.putObject(params, (err) => {
                if (err) {
                    console.log('Error uploading file:', err);
                } else {
                    console.log('File uploaded successfully');
                }
            });
        }
    });
};
4. 总结与展望

通过使用消息队列实现异步解耦和构建容错系统,我们可以提高系统的可扩展性和可靠性。异步解耦允许不同组件独立运行,当系统面临高并发时,可以通过增加工作进程来处理更多的消息。而容错设计则可以确保系统在面对各种故障时仍然能够正常运行。

在未来的系统设计中,我们可以进一步优化异步解耦和容错策略。例如,使用更高效的消息队列服务,或者结合更多的 AWS 服务来提高系统的性能和可靠性。同时,不断监控系统的运行状态,及时发现并解决潜在的问题,确保系统始终保持稳定运行。

以下是一个系统架构的流程图,展示了异步解耦和容错设计的整体流程:

graph LR
    A[用户请求] --> B[消息生产]
    B --> C[SQS 队列]
    C --> D[工作进程]
    D --> E[处理任务]
    E --> F[结果存储到 S3]
    F --> G[返回结果给用户]
    H[故障检测] --> I[自动扩展组调整]
    I --> D
    J[失败重试] --> D

在这个流程图中,用户请求首先被转换为消息并发送到 SQS 队列。工作进程从队列中获取消息并处理任务,将结果存储到 S3 并返回给用户。同时,系统会进行故障检测,当检测到故障时,自动扩展组会进行调整,增加或减少工作进程的数量。对于失败的操作,会进行重试,确保任务最终能够完成。

通过以上的策略和设计,我们可以构建一个具有高可扩展性和容错性的系统,为用户提供更加稳定和可靠的服务。

提供了基于BP(Back Propagation)神经网络结合PID(比例-积分-微分)控制策略的Simulink仿真模型。该模型旨在实现对杨艺所著论文《基于S函数的BP神经网络PID控制器及Simulink仿真》中的理论进行实践验证。在Matlab 2016b环境下开发,经过测试,确保能够正常运行,适合学习和研究神经网络在控制系统中的应用。 特点 集成BP神经网络:模型中集成了BP神经网络用于提升PID控制器的性能,使之能更好地适应复杂控制环境。 PID控制优化:利用神经网络的自学习能力,对传统的PID控制算法进行了智能调整,提高控制精度和稳定性。 S函数应用:展示了如何在Simulink中通过S函数嵌入MATLAB代码,实现BP神经网络的定制化逻辑。 兼容性说明:虽然开发于Matlab 2016b,但理论上兼容后续版本,可能会需要调整少量配置以适配不同版本的Matlab。 使用指南 环境要求:确保你的电脑上安装有Matlab 2016b或更高版本。 模型加载: 下载本仓库到本地。 在Matlab中打开.slx文件。 运行仿真: 调整模型参数前,请先熟悉各模块功能和输入输出设置。 运行整个模型,观察控制效果。 参数调整: 用户可以自由调节神经网络的层数、节点数以及PID控制器的参数,探索不同的控制性能。 学习和修改: 通过阅读模型中的注释和查阅相关文献,加深对BP神经网络PID控制结合的理解。 如需修改S函数内的MATLAB代码,建议有一定的MATLAB编程基础。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值