我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

导读

首先,我们都清楚一个现状,Rust 的价值不是“快”,而是把正确性前置,让“能跑”与“跑得对”成为一件事。本文通过两个具体项目(高并发 Web 服务 + 并行扫描 CLI 工具),系统讲清 Rust 的所有权/生命周期/并发模型如何落地到Tokio + Actix Web + sqlx + tracing的工程实践,并附上压测、优化、发布的全链路清单,干货满满,绝不会让大家看无所获。
建议收藏,拿它当工程脚手架使用。

目录

  • 一、为什么是 Rust:从口号到度量

  • 二、从 0 到 1:双项目目标与边界约束

  • 三、语言内核要点:所有权、生命周期、并发与异步

  • 四、项目 A:高并发 Web 服务(Tokio + Actix Web + sqlx)

    • 4.1 领域建模与错误边界
    • 4.2 异步数据库访问与连接管理
    • 4.3 中间件:限流 / 超时 / 重试 / 观测
    • 4.4 可观测性:tracing + 指标 + 请求关联
    • 4.5 压测方法与优化策略
  • 五、项目 B:系统工具 CLI(并行扫描 + 异步推送)

    • 5.1 命令行 UX 与参数约束
    • 5.2 Rayon 并行 + Tokio 异步的协作原则
    • 5.3 FFI 边界与 unsafe 最小化
  • 六、工程化:工作区、测试金字塔、CI/CD、发布与回滚

  • 七、性能优化清单:数据结构、内存、锁、I/O、调度与编译

  • 八、常见坑与心智模型:Send/Sync、Pin、自引用与生命周期“倒推”

  • 九、团队落地方法论:从基线到守门

  • 十、附:可复用脚手架与配置片段(可直接粘贴)

  • 结语:把小确定性叠成团队的生产力曲线

一、为什么是 Rust:从口号到度量

  • 三个工程承诺:高性能(零成本抽象)、内存安全(编译期保证)、并发安全(无数据竞争)。

  • 度量视角

    1. 延迟分布看 P50/P90/P99,而非平均值;
    2. 资源曲线看吞吐/CPU/内存/FD 随负载变化的斜率;
    3. 可观测性看结构化日志密度、Trace 覆盖率、指标可回归性。
  • 与传统栈差异:Rust 在“高吞吐 + 低延迟 + 可控内存 + 无 GC 抖动”的场景里让“正确性与性能兼得”,更适合边缘节点/高并发服务/系统工具/嵌入式

二、从 0 到 1:双项目目标与边界约束

业务背景

  • 项目 A(Web):规则查询与评估服务,读多写少;目标 P99 < 50ms,支持灰度发布和追踪。
  • 项目 B(CLI):跨平台磁盘扫描与聚合工具,支持并行统计与结果异步推送。

非功能目标

  • 统一 Tokio 运行时与连接管理;
  • 结构化日志、指标、分布式 Trace 贯通;
  • 镜像体积 < 30MB,支持 x86_64/ARM 静态发布;
  • 建立可回归的压测与性能守门

技术边界

  • 优先纯 Rust;FFI 仅在性能必要或生态缺口时启用;unsafe 有严格封装与 Code Review 清单。

三、语言内核要点:所有权、生命周期、并发与异步

3.1 所有权与借用:把错误挡在编译期

  • 单一所有权:一个值同时只有一个“拥有者”,转移所有权后原持有者不可再用。
  • 借用规则:任意时刻要么多个不可变借用,要么一个可变借用。这在编译期消灭了数据竞争。

简例(错误示例对照有助记忆):

fn mutate_twice(v: &mut Vec<i32>) {
    let a = &mut *v;
    let b = &mut *v; // ❌ 同时持有两个可变借用,编译失败
    a.push(1);
    b.push(2);
}

设计提示:以不可变共享 + 原子更新/细粒度锁替代“大锁 + 多可变”。

3.2 生命周期:接口契约的可视化

生命周期并非“语法噪音”,而是数据活期的显式契约。实践中推荐“倒推法”:
从函数调用点推断被借用值的活期,把生命周期留在边界,在实现细节里尽量缩短作用域,减少显式标注。

3.3 并发与 Send/Sync:跨线程的合法性

  • Send:值能否跨线程移动Sync&T 能否多线程共享
  • 触发编译报错时先问:内部是否藏了 Rc<RefCell<T>>单线程容器?如需并发共享,改用 Arc<Mutex<T>> 或无锁结构。

3.4 异步模型(Tokio):把等待让给调度器

  • Rust 的 async/await 会生成状态机,Tokio 提供多线程调度与 I/O 驱动。

  • 关键三点:

    1. 不可阻塞运行时:阻塞任务放 spawn_blocking
    2. 超时与取消内建:tokio::time::timeoutselect!
    3. 背压与限流:在入口处就控制并发度,避免雪崩。

四、项目 A:高并发 Web 服务(Tokio + Actix Web + sqlx)

4.1 领域建模与错误边界:把耦合留给基础设施

分层建议

domain/    # 纯业务语义,不依赖框架与外设
infra/     # db、cache、http client、telemetry 等
web/       # 协议、路由、中间件、DI 装配

错误分类

  • 领域错误(业务可预期):如 RuleNotFound
  • 基础设施错误(I/O/超时/连接):如 sqlx::Error、缓存超时。
// src/error.rs
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DomainError {
    #[error("rule not found: {0}")]
    NotFound(String),
    #[error("invalid input: {0}")]
    Invalid(String),
}
#[derive(Error, Debug)]
pub enum InfraError {
    #[error("db: {0}")]
    Db(#[from] sqlx::Error),
    #[error("timeout")]
    Timeout,
}
pub type DResult<T> = Result<T, DomainError>;
pub type IResult<T> = Result<T, InfraError>;

4.2 异步数据库访问与连接管理:编译期校验让查询更稳

// infra/db.rs (Postgres)
use sqlx::{PgPool, FromRow};
#[derive(Debug, FromRow, Clone)]
pub struct Rule { pub id: String, pub version: i32, pub expr: String }

pub async fn get_rule(pool: &PgPool, id: &str) -> IResult<Option<Rule>> {
    let r = sqlx::query_as!(
        Rule,
        r#"SELECT id, version, expr FROM rules WHERE id = $1"#, id
    )
    .fetch_optional(pool)
    .await?;
    Ok(r)
}

实践建议

  • 开启 sqlx 编译期查询校验(需要 DATABASE_URL);
  • 连接池尺寸与数据库最大连接数联动,防止过度排队;
  • 回传结构尽量引用/切片替代多余拷贝。

4.3 中间件:限流 / 超时 / 重试 / 观测

  • 限流:入口限并发(令牌桶)+ 应用内轻量限流;
  • 超时:对 DB/Cache/下游 HTTP 严格设置硬超时;
  • 重试:只对幂等操作,指数退避 + 上限次数。
// web/middleware/timeout.rs
use actix_web::dev::*;
use actix_web::{Error, ResponseError};
use tokio::time::{timeout, Duration};

pub struct TimeoutLayer;
pub struct TimeoutMiddleware<S> { inner: S }

impl<S, B> Transform<S, ServiceRequest> for TimeoutLayer
where S: Service<ServiceRequest, Response=ServiceResponse<B>, Error=Error> {
    type Transform = TimeoutMiddleware<S>;
    type Response = ServiceResponse<B>; type Error = Error; 
    type InitError = (); type Future = std::future::Ready<Result<Self::Transform, Self::InitError>>;
    fn new_transform(&self, service: S) -> Self::Future {
        std::future::ready(Ok(TimeoutMiddleware { inner: service }))
    }
}
impl<S, B> Service<ServiceRequest> for TimeoutMiddleware<S>
where S: Service<ServiceRequest, Response=ServiceResponse<B>, Error=Error> {
    type Response = ServiceResponse<B>; type Error = Error;
    type Future = Pin<Box<dyn Future<Output=Result<Self::Response, Self::Error>>>>;
    fn call(&self, req: ServiceRequest) -> Self::Future {
        let fut = self.inner.call(req);
        Box::pin(async move {
            match timeout(Duration::from_millis(300), fut).await {
                Ok(r) => r,
                Err(_) => Err(actix_web::error::ErrorGatewayTimeout("timeout")),
            }
        })
    }
}

4.4 可观测性:tracing + 指标 + 请求关联

// infra/telemetry.rs
use tracing::{info, instrument};
use metrics::{counter, histogram};

#[instrument(name="rule.query", skip(rule_id))]
pub async fn record(rule_id: &str, took_ms: u128) {
    histogram!("rules.query.latency_ms").record(took_ms as f64);
    counter!("rules.query.count").increment(1);
    info!(%rule_id, took_ms, "query_ok");
}

落地要点

  • 统一 request_id,贯穿日志/指标/Trace;
  • 关键路径必须有 span,并附业务标签(如 rule_id)。

4.5 压测方法与优化策略:以“基线—单变量—回归”推进

  • 基线:禁用缓存/默认连接池/无特殊优化,记录 P50/P90/P99。
  • 单变量:一次只引入一个改动(如 SQL 索引/批量/序列化方式)。
  • 回归守门:CI 触发小规模压测,超阈值则告警。

k6 片段:

import http from 'k6/http'; import { check } from 'k6';
export let options = { vus: 200, duration: '60s' };
export default function () {
  const r = http.get(__ENV.BASE + '/api/rule/risk_001');
  check(r, { '200 ok': res => res.status === 200 });
}

五、项目 B:系统工具 CLI(并行扫描 + 异步推送)

5.1 命令行 UX 与参数约束

use clap::Parser;
#[derive(Parser, Debug)]
#[command(name="mydu", about="Fast disk usage analyzer")]
pub struct Args {
    #[arg(long, default_value=".")] pub path: String,
    #[arg(long, default_value="67108864")] pub min_size: u64, // 64MB
    #[arg(long)] pub json: bool,
    #[arg(long)] pub push: Option<String>,
}

用户故事

  • 快速扫描:mydu --path /data --min-size 64MB --json
  • 推送结果:mydu --path /data --push http://collector/api/ingest

5.2 Rayon 并行 + Tokio 异步的协作原则

  • CPU 密集(遍历、聚合、排序)→ Rayon;
  • I/O/网络(读取、推送)→ Tokio;
  • 不要互相阻塞:CPU 重活用 Rayon;Tokio 任务不要塞长计算。
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let args = Args::parse();
    let (big_files, stats) = rayon::join(
        || scan_big_files(&args.path, args.min_size),
        || calc_stats(&args.path),
    );
    if let Some(url) = args.push.as_ref() {
        push_result_async(&big_files, url).await?;
    }
    Ok(())
}

5.3 FFI 边界与 unsafe 最小化

  • 仅在生态缺口或性能硬性需求时引入 C 库;
  • unsafe最小闭包包裹,并在安全接口外层保证所有权与释放规则;
  • 任何 unsafe 代码都需专门 Review 清单与测试用例。

六、工程化:工作区、测试金字塔、CI/CD、发布与回滚

6.1 工作区与目录约定

workspace/
├─ rulesvc/       # Web 服务
├─ mydu/          # CLI 工具
└─ common/        # 共享模型、工具、telemetry 初始化

Cargo.toml(节选):

[workspace]
members = ["rulesvc", "mydu", "common"]

[profile.release]
lto = true
codegen-units = 1
panic = "abort"

6.2 测试金字塔:单元/集成/端到端

  • 单元测试:纯逻辑与变换;
  • 集成测试tests/ 目录对外接口做黑盒验证;
  • 端到端:使用容器化依赖(DB/Cache),模拟真实 HTTP 与 CLI。
// rulesvc/tests/e2e.rs
#[tokio::test]
async fn query_rule_ok() {
    let app = spawn_test_app().await;
    let url = format!("{}/api/rule/{}", app.addr, "risk_001");
    let res = reqwest::get(url).await.unwrap();
    assert!(res.status().is_success());
}

6.3 CI/CD 与安全守门

  • 流水线:Lint(clippy)→ 格式化 → 单元/集成 → 安全扫描(cargo-audit)→ 轻量基准 → 构建工件 → 发布。
  • 回滚策略:预编译多版本工件,流量开关受控,异常回到前一稳定版本。

6.4 跨平台发布与最小镜像

  • 目标:x86_64-unknown-linux-musl 静态链接;
  • 运行镜像使用 scratch/distroless,减小攻击面;
  • 通过 cross/多阶段 Docker 在 CI 内产出多架构工件。
# build
FROM rust:1.82 as build
WORKDIR /app
COPY . .
RUN apt-get update && apt-get install -y musl-tools && \
    rustup target add x86_64-unknown-linux-musl && \
    cargo build --release --target x86_64-unknown-linux-musl

# runtime
FROM scratch
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/rulesvc /rulesvc
ENTRYPOINT ["/rulesvc"]

七、性能优化清单:从数据结构到编译器

  1. 数据结构

    • 读多写少:DashMap/RwLock 组合,或无锁结构;
    • 减少 String 分配,优先 &strCow<'_, str>Bytes
    • 小数组:SmallVec<[T; N]> 降低堆分配。
  2. 内存与复制

    • 复用缓冲区、避免隐式拷贝;
    • 序列化热点考虑 simd-json 或二进制协议(bincode/postcard)。
  3. 锁与并发

    • 能无锁则无锁;必要时 parking_lot 提升锁实现质量;
    • 细粒度拆分锁,避免全局“大锁”。
  4. I/O 与批处理

    • 把 N 次小 I/O 合并为 1 次大 I/O;
    • DB 层使用批量与索引优化。
  5. 调度

    • Tokio 内避免 CPU 重活;CPU 任务交给 Rayon;
    • 控制 spawn_blocking 并发上限。
  6. 系统参数(谨慎实验):

    • 文件描述符上限、TCP backlog;
    • 压测下观测曲线再决定是否上线。
  7. 编译优化

    • lto=truecodegen-units=1
    • 可接受时 panic="abort"
    • 基准数据而非“想象”决定是否启用。
  8. 回归监控

    • 任何优化都对应基准表项;
    • 报警建立在“基线 ± 容忍区间”上。

八、常见坑与心智模型:Send/Sync、Pin、自引用与生命周期

  • Send/Sync

    • 错误信息通常指向“跨线程移动/共享不安全”;
    • 自查:是否包含 Rc/RefCell?改用 Arc/Mutex 或不可变共享。
  • Pin

    • 用于固定 Future 或自引用结构,一旦 Pin 就不再移动
    • 多数框架已封装,手写时严格遵守不动点规则。
  • 生命周期“倒推”

    • 调用点先画出数据的活期,再反推实现中的借用范围;
    • 不要为“省一次克隆”把接口搞成迷宫;清晰优先
  • async + trait

    • 如果遇到异步 trait 限制,考虑 async_trait 或以返回 impl Future 的方式改写接口边界。

九、团队落地方法论:从基线到守门

  1. 观测优先:Trace + 指标 + 结构化日志在 MVP 之前就落地。
  2. 以基线为锚:每次改动记录“前后对照表”,只改一处,避免“优化雾里看花”。
  3. 分层内聚:domain 不依赖框架,infra 可替换,web 只做协议与装配。
  4. 守门与回滚:性能守门阈值 + 一键回滚是“可持续优化”的底线保障。
  5. 知识沉淀:把目录约定、脚手架、压测脚本、发布清单写成团队可执行文档,新人 0.5 天能跑通端到端。

十、附:可复用脚手架与配置片段(可直接粘贴)

10.1 common 库:统一类型与 telemetry

// common/src/lib.rs
pub mod types {
    use serde::{Serialize, Deserialize};
    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct Rule { pub id: String, pub version: i32, pub expr: String }
}
pub mod prelude {
    pub use anyhow::{Result, Context};
    pub use tracing::{info, error, instrument, warn};
}
pub mod telemetry {
    use tracing_subscriber::{fmt, EnvFilter};
    pub fn init() {
        let filter = EnvFilter::try_from_default_env()
            .unwrap_or_else(|_| EnvFilter::new("info"));
        fmt().with_env_filter(filter).json().init();
    }
}

10.2 Web 入口:装配与启动

// rulesvc/src/main.rs
use actix_web::{App, HttpServer, web};
#[tokio::main]
async fn main() -> std::io::Result<()> {
    common::telemetry::init();
    let state = build_state().await.expect("init state");
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(state.clone()))
            .wrap(timeout_layer()) // 超时
            .service(web::resource("/api/rule/{id}").to(get_rule_handler))
    })
    .workers(num_cpus::get())
    .bind(("0.0.0.0", 8080))?
    .run().await
}

10.3 端到端压测脚本(k6)

import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = { vus: 150, duration: '90s' };
export default function () {
  const r = http.get(`${__ENV.BASE}/api/rule/risk_001`);
  check(r, { 'status 200': (res) => res.status === 200 });
  sleep(0.1);
}

10.4 发布检查脚本

#!/usr/bin/env bash
set -euo pipefail
BIN=${1:-rulesvc}
echo "[info] file info:" && file $BIN
echo "[info] ldd (may fail for static):" && ldd $BIN || true
./$BIN --version || true

结语:把小确定性叠成团队的生产力曲线 ✨

Rust 不是“银弹”,但它给了我们一种工程秩序

  • 用所有权与生命周期,把内存与并发错误挡在编译期;
  • 用 Tokio 的异步与 Rayon 的并行,把硬件吃满而无 GC 抖动;
  • 用结构化观测、基线压测与守门阈值,把“快与稳”变成团队资产。

所以说,当你把这些“微小但确定”的动作日更到项目里,性能与稳定性不会靠撞大运,而会成为可复现的输出。愿大家都能在实践里把 Rust 玩成“能跑、跑得对、还跑得久”的那类系统,一起加油!冲冲冲🚀🚀🚀。

(未完待续)

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐