构建一个rust生产应用读书笔记四(实战5)

时间:2024-12-19 07:12:43

到目前为止我们完成了邮件订阅的第一个迭代,但是我们目前还没有创建一个包含HTML表单的网页来实际测试端到端的流程。不过,我们已经完成了一些黑盒集成测试,这些测试主要关注两个基本场景:

  • 当提交有效的表单数据时(如提供姓名和电子邮件),数据会被正确地保存到数据库中。
  • 如果表单数据不完整(例如,缺少电子邮件、姓名或两者皆无),API会返回400状态码。

尽管如此,我们尚未准备好让软件在生产环境中稳定运行。实际上,我们还处于‘盲目’的状态:一方面,应用程序本身还未经过全面的检测;另一方面,我们也没有实施任何遥测数据的收集机制,这意味着我们对应用可能面临的未知风险缺乏了解。

为了应对由未知未知引起的故障或错误,我们可以采取哪些措施?

生产级应用需要考虑的问题

1. 监控和日志记录

  • 实施全面监控:使用监控工具跟踪应用程序的健康和性能,包括系统指标、数据库查询和网络流量。这有助于早期发现异常。
  • 详细日志记录:确保应用程序记录详细的运行信息,特别是关键路径和错误处理。日志应包括时间戳、用户ID、请求详情和错误消息。

2. 错误处理和弹性设计

  • 优雅降级:设计应用程序以优雅地处理故障。例如,如果数据库连接丢失,应用程序应能够重试连接或向用户提供有意义的错误消息。
  • 断路器:实现断路器以防止级联故障。如果服务开始失败,断路器可以暂时停止向其发送请求,允许其恢复。
  • 速率限制和节流:实施速率限制以保护应用程序免受突然的流量高峰影响,防止资源过载。

3. 测试和验证

  • 负载测试:定期进行负载测试,模拟高流量场景,识别潜在瓶颈。
  • 模糊测试:使用模糊测试通过发送意外或格式错误的输入来识别漏洞。
  • 混沌工程:通过有意引入故障来练习混沌工程,测试系统的弹性。工具如Chaos Monkey可以帮助实现这一点。

4. 应急响应计划

  • 制定应急响应计划:制定明确简洁的应急响应计划,包括角色和责任、沟通协议和诊断及解决问题的步骤。
  • 事后分析:在事件发生后进行事后分析,了解问题所在及如何防止类似问题再次发生。与团队分享发现,提高集体知识水平。

5. 持续学习和改进

  • 保持信息更新:及时了解最新安全补丁、最佳实践和您技术栈中的新兴威胁。
  • 定期代码审查:定期进行代码审查,提前发现潜在问题,确保代码质量。
  • 安全审计:定期进行安全审计,识别并修复漏洞。

6. 文档和培训

  • 维护关键系统文档:维护系统的最新文档,包括架构图、部署流程和故障排除指南
  • 培训和演练:培训团队成员熟悉应急响应计划,并定期进行演练,确保每个人在故障发生时知道自己的职责。

7. 社区和外部资源

  • 利用社区知识:参与开发者社区和论坛,学习他人的经验和最佳实践。
  • 咨询外部专家:考虑咨询外部专家或使用托管服务,以获得额外的见解和支持。

通过实施这些策略,您可以更好地准备您的团队和系统,以应对未知未知,减少故障和错误的发生概率和影响。

日志

在 Rust 中,最常用的日志库是 loglog 提供了五个宏:tracedebuginfowarn 和 error。这些宏都做同样的事情——生成一条日志记录,但每个宏使用不同的日志级别,正如其名称所暗示的那样。

  • trace 是最低级别的日志:跟踪级别的日志通常非常冗长,信噪比较低(例如,每当 Web 服务器接收到一个 TCP 数据包时,生成一条跟踪级别的日志记录)。
  • 接下来按严重程度递增顺序依次是 debuginfowarn 和 error
  • error 级别的日志用于报告可能影响用户的严重故障(例如,处理传入请求失败或数据库查询超时)。

让我们来看一个简单的使用示例:

rust深色版本
use log::{trace, debug, info, warn, error};

fn main() {
    // 初始化日志记录器
    env_logger::init();

    // 示例日志记录
    trace!("This is a trace-level log message");
    debug!("This is a debug-level log message");
    info!("This is an info-level log message");
    warn!("This is a warn-level log message");
    error!("This is an error-level log message");
}

在这个示例中,我们使用 env_logger 来初始化日志记录器。env_logger 是一个流行的日志记录后端,可以根据环境变量配置日志级别。默认情况下,env_logger 会打印 info 及以上级别的日志消息。

在main.rs 里面添加日志

Cargo.toml 添加依赖

[dependencies]
env_logger = "0.9"

使用"info"作为默认值作为日志级别,并启动日志记录功能

//!main.rs
#[tokio::main]
async fn main() -> std::io::Result<()> {    
    env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
   //...
}

日志数据结果

#[2024-11-23T00:30:29Z INFO  actix_server::builder] starting 8 workers
[2024-11-23T00:30:29Z INFO  actix_server::server] Tokio runtime found; starting in existing Tokio runtime
[2024-11-23T00:30:29Z INFO  actix_server::server] starting service: "actix-web-service-127.0.0.1:8000", workers: 8, listening on: 127.0.0.1:8000

给subscription.rs 也添加上日志

use core::result::Result::{Err, Ok};

use actix_web::{web, HttpResponse};
use chrono::Utc;
use sqlx::PgPool;
use uuid::Uuid;

#[derive(serde::Deserialize)]
pub struct FormData {
    email: String,
    name: String,
}

pub async fn subscribe(form: web::Form<FormData>, pool: web::Data<PgPool>) -> HttpResponse {
    log::info!("[subscriptions].subscribe() save new details in the database");
    match sqlx::query!(
        r#"insert into subscriptions (id,email,name,subscribed_at) values($1,$2,$3,$4)"#,
        Uuid::new_v4(),
        form.email,
        form.name,
        Utc::now()
    )
    .execute(pool.as_ref())
    .await
    {
        Ok(_) => {
           use core::result::Result::{Err, Ok};

use actix_web::{web, HttpResponse};
use chrono::Utc;
use sqlx::PgPool;
use uuid::Uuid;

#[derive(serde::Deserialize)]
pub struct FormData {
    email: String,
    name: String,
}

pub async fn subscribe(form: web::Form<FormData>, pool: web::Data<PgPool>) -> HttpResponse {
    log::info!("[subscriptions].subscribe() save new details in the database");
    match sqlx::query!(
        r#"insert into subscriptions (id,email,name,subscribed_at) values($1,$2,$3,$4)"#,
        Uuid::new_v4(),
        form.email,
        form.name,
        Utc::now()
    )
    .execute(pool.as_ref())
    .await
    {
        Ok(_) => {
            log::info!("[subscriptions].subscribe() new subscriptions has been saved");
            HttpResponse::Ok().finish()
        }
        Err(e) => {
            print!("Failed to execute query:{}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

            HttpResponse::Ok().finish()
        }
        Err(e) => {
            print!("Failed to execute query:{}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

总结:

这一节比较简单,主要学习了日志的级别,和项目中如何使用,日志在整个项目的开发周期都起着至关重要的作用,同时也希望在学习和工作当中把重要的信息都记录到日志

注:各位亲爱的小伙伴们,今年是我从事软件行业的第20年,通过博客记录的方式将我知道的、理解的、有帮助的都分享给大家。同时,也提供就业指导,专业简历优化服务。你们的支持是我最大的动力