32、微服务优化实战指南

微服务优化实战指南

1. 微服务性能测试工具

可以通过发送根据脚本规则构建的 HTTP 请求来对微服务进行负载测试,并输出如下报告:

Threads 4
Iterations 5
Rampup 2
Base URL http://localhost:8080
Index Page                http://localhost:8080/ 200 OK 7ms
Index Page                http://localhost:8080/ 200 OK 8ms
...
Index Page                http://localhost:8080/ 200 OK 1ms
Concurrency Level 4
Time taken for tests 0.2 seconds
Total requests 20
Successful requests 20
Failed requests 0
Requests per second 126.01 [#/sec]
Median time per request 1ms
Average time per request 3ms
Sample standard deviation 3ms

有两个工具适合测试微服务性能:
- Welle:若要优化指定处理程序,它适合测量单一请求类型的性能。
- Drill:适合产生复杂负载,以衡量应用程序可服务的用户数量。

2. 性能测量与优化示例

我们将测量一个示例微服务在两种编译选项下的性能:无优化和编译器优化。该微服务会向客户端发送渲染后的索引页面,使用 Welle 工具来测量性能,看是否能提升。

2.1 基础示例

在基于 actix - web 包的新包中创建微服务,在 Cargo.toml 中添加以下依赖:

[dependencies]
actix = "0.7"
actix-web = "0.7"
askama = "0.6"
chrono = "0.4"
env_logger = "0.5"
futures = "0.1"
[build-dependencies]
askama = "0.6"

构建一个异步渲染包含当前时间(精确到分钟)的索引页面的小型服务器,有一个快捷函数:

fn now() -> String {
    Utc::now().to_string()
}

使用 askama 包渲染索引页面模板,并插入从共享状态获取的当前时间。为使用内存堆中的值,时间值使用 String 类型:

#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
    time: String,
}

共享状态使用一个包含 last_minute 值的结构体,该值用 Mutex 包装成 String 类型:

#[derive(Clone)]
struct State {
    last_minute: Arc<Mutex<String>>,
}

索引页面使用以下处理程序:

fn index(req: &HttpRequest<State>) -> HttpResponse {
    let last_minute = req.state().last_minute.lock().unwrap();
    let template = IndexTemplate { time: last_minute.to_owned() };
    let body = template.render().unwrap();
    HttpResponse::Ok().body(body)
}

通过 main 函数启动应用程序,准备 Server 实例并派生一个单独的线程来更新共享状态:

fn main() {
    let sys = actix::System::new("fast-service");
    let value = now();
    let last_minute = Arc::new(Mutex::new(value));
    let last_minute_ref = last_minute.clone();
    thread::spawn(move || {
        loop {
            {
                let mut last_minute = last_minute_ref.lock().unwrap();
                *last_minute = now();
            }
            thread::sleep(Duration::from_secs(3));
        }
    });
    let state = State {
        last_minute,
    };
    server::new(move || {
        App::with_state(state.clone())
           .middleware(middleware::Logger::default())
           .resource("/", |r| r.f(index))
    })
   .bind("127.0.0.1:8080")
   .unwrap()
   .start();
    let _ = sys.run();
}
2.2 性能测试
  • 无优化构建运行 :使用标准命令 cargo run 构建并运行代码,生成包含大量调试信息的二进制文件。使用以下命令进行性能测试:
welle --concurrent-requests 10 --num-requests 100000 http://localhost:8080

测试报告如下:
| 指标 | 值 |
| ---- | ---- |
| 总请求数 | 100000 |
| 并发数 | 10 |
| 完成请求数 | 100000 |
| 错误请求数 | 0 |
| 5XX 请求数 | 0 |
| 总耗时 | 29.883248121s |
| 平均耗时 | 298.832µs |
| 飞行总时间 | 287.14008722s |
| 平均飞行时间 | 2.8714ms |
| 50%请求响应时间 | 3.347297ms |
| 66%请求响应时间 | 4.487828ms |
| 75%请求响应时间 | 5.456439ms |
| 80%请求响应时间 | 6.15643ms |
| 90%请求响应时间 | 8.40495ms |
| 95%请求响应时间 | 10.27307ms |
| 99%请求响应时间 | 14.99426ms |
| 100%请求响应时间 | 144.630208ms |

  • 优化构建运行 :使用 cargo run --release 命令重新编译,该命令会向 rustc 编译器传递 -C opt - level = 3 优化标志。再次使用相同参数的 Welle 工具测试,报告如下:
    | 指标 | 值 |
    | ---- | ---- |
    | 总请求数 | 100000 |
    | 并发数 | 10 |
    | 完成请求数 | 100000 |
    | 错误请求数 | 0 |
    | 5XX 请求数 | 0 |
    | 总耗时 | 8.010280915s |
    | 平均耗时 | 80.102µs |
    | 飞行总时间 | 63.961189338s |
    | 平均飞行时间 | 639.611µs |
    | 50%请求响应时间 | 806.717µs |
    | 66%请求响应时间 | 983.35µs |
    | 75%请求响应时间 | 1.118933ms |
    | 80%请求响应时间 | 1.215726ms |
    | 90%请求响应时间 | 1.557405ms |
    | 95%请求响应时间 | 1.972497ms |
    | 99%请求响应时间 | 3.500056ms |
    | 100%请求响应时间 | 37.844721ms |

可以看到,请求平均耗时减少了 70% 以上。

3. 代码优化

尝试对代码应用以下三种优化:
- 减少共享状态的阻塞。
- 通过引用重用状态中的值。
- 添加响应缓存。

Cargo.toml 文件中有如下特性配置:

[features]
default = []
cache = []
rwlock = []
borrow = []
fast = ["cache", "rwlock", "borrow"]
  • cache :激活请求缓存。
  • rwlock :对 State 使用 RwLock 而非 Mutex
  • borrow :通过引用重用值。
3.1 无阻塞状态共享

Mutex 替换为 RwLock ,因为 Mutex 在读写时都会锁定,而 RwLock 允许单个写入者或多个读取者。修改 State 结构体:

#[derive(Clone)]
struct State {
    last_minute: Arc<RwLock<String>>,
}

修改 last_minute 引用计数器的创建:

let last_minute = Arc::new(RwLock::new(value));

在工作线程中使用 write 方法锁定写入:

let mut last_minute = last_minute_ref.write().unwrap();

在处理程序中使用 read 方法锁定读取:

let last_minute = req.state().last_minute.read().unwrap();
3.2 通过引用重用值

修改 IndexTemplate 结构体:

struct IndexTemplate<'a> {
    time: &'a str,
}

使用引用:

let template = IndexTemplate { time: &last_minute };
3.3 缓存

State 结构体中添加新字段:

cached: Arc<RwLock<Option<String>>>

初始化:

let cached = Arc::new(RwLock::new(None));
let state = State {
    last_minute,
    cached,
};

修改索引处理程序:

let cached = req.state().cached.read().unwrap();
if let Some(ref body) = *cached {
    return HttpResponse::Ok().body(body.to_owned());
}
let mut cached = req.state().cached.write().unwrap();
*cached = Some(body.clone());
3.4 优化编译与测试
  • 部分优化编译 :使用 cargo run --release --features rwlock,borrow 编译代码并测试,报告如下:
    | 指标 | 值 |
    | ---- | ---- |
    | 总请求数 | 100000 |
    | 并发数 | 10 |
    | 完成请求数 | 100000 |
    | 错误请求数 | 0 |
    | 5XX 请求数 | 0 |
    | 总耗时 | 7.94342667s |
    | 平均耗时 | 79.434µs |
    | 飞行总时间 | 64.120106299s |
    | 平均飞行时间 | 641.201µs |
    | 50%请求响应时间 | 791.554µs |
    | 66%请求响应时间 | 976.074µs |
    | 75%请求响应时间 | 1.120545ms |
    | 80%请求响应时间 | 1.225029ms |
    | 90%请求响应时间 | 1.585564ms |
    | 95%请求响应时间 | 2.049917ms |
    | 99%请求响应时间 | 3.749288ms |
    | 100%请求响应时间 | 13.867011ms |
  • 全部优化编译 :使用 cargo run --release --features fast 编译代码并测试,报告如下:
    | 指标 | 值 |
    | ---- | ---- |
    | 总请求数 | 100000 |
    | 并发数 | 10 |
    | 完成请求数 | 100000 |
    | 错误请求数 | 0 |
    | 5XX 请求数 | 0 |
    | 总耗时 | 7.820692644s |
    | 平均耗时 | 78.206µs |
    | 飞行总时间 | 62.359549787s |
    | 平均飞行时间 | 623.595µs |
    | 50%请求响应时间 | 787.329µs |
    | 66%请求响应时间 | 963.956µs |
    | 75%请求响应时间 | 1.099572ms |
    | 80%请求响应时间 | 1.199914ms |
    | 90%请求响应时间 | 1.530326ms |
    | 95%请求响应时间 | 1.939557ms |
    | 99%请求响应时间 | 3.410659ms |
    | 100%请求响应时间 | 10.272402ms |

可以看到,应用全部优化后,比无优化版本快了 2% 以上。

4. 其他优化技术
4.1 链接时优化

Cargo.toml 文件中添加以下内容激活链接时优化(LTO):

[profile.release]
lto = true
4.2 用中止代替恐慌

Cargo.toml 文件中添加:

[profile.release]
panic = "abort"
4.3 减小二进制文件大小

使用 strip 命令:

strip <path_to_your_binary>

优化流程 mermaid 图

graph LR
    A[创建微服务] --> B[无优化性能测试]
    B --> C[优化编译]
    C --> D[优化性能测试]
    D --> E[应用代码优化]
    E --> F[部分优化编译测试]
    F --> G[全部优化编译测试]
    G --> H[其他优化技术应用]

综上所述,微服务优化是一个逐步推进的过程,通过合理运用各种优化技术,可以显著提升微服务的性能和效率。但也要注意避免过度优化,以免增加开发难度和影响代码可维护性。

微服务优化实战指南

5. 优化技术的综合考量

在进行微服务优化时,需要综合考虑各种优化技术的优缺点和适用场景。

5.1 链接时优化的权衡

链接时优化(LTO)虽然可以帮助减小二进制文件的大小,但编译时间会显著增加。而且,并非在所有情况下都能提升性能。例如,在一些简单的微服务场景中,激活 LTO 可能并不会带来明显的性能提升,反而会因为编译时间的增加而影响开发效率。因此,在决定是否使用 LTO 时,需要进行充分的测试和比较,权衡编译时间和性能提升之间的关系。

5.2 中止代替恐慌的风险

使用中止(abort)代替 Rust 的恐慌(panic)可以减少错误处理代码的空间占用,从而可能提升性能。但这种方式也存在很大的风险,因为程序在中止时无法正确记录日志或传递跟踪信息。对于微服务来说,这可能会导致在出现问题时难以进行故障排查。因此,在使用这种优化技术时,需要确保有其他机制来处理日志记录和跟踪信息,例如创建一个单独的线程来处理跟踪信息。

5.3 减小二进制文件大小的适用场景

减小二进制文件大小的优化技术(如使用 strip 命令)通常适用于分布式应用中硬件空间有限的场景。但需要注意的是,剥离调试信息后,将无法对二进制文件进行调试。因此,在开发和测试阶段,不建议使用该优化技术,而应该在生产环境中,当性能和空间占用成为关键因素时再考虑使用。

6. 优化的注意事项

在进行微服务优化时,还需要注意以下几点:

6.1 避免过度优化

过度优化可能会使代码变得复杂,增加开发和维护的难度。例如,在一些简单的微服务中,为了追求微小的性能提升而进行复杂的优化,可能会导致代码难以理解和修改。因此,应该根据实际需求进行优化,避免为了不必要的性能提升而投入过多的时间和精力。

6.2 性能测试的重要性

在进行任何优化之前和之后,都需要进行充分的性能测试,以确保优化确实带来了性能提升。不同的优化技术可能会相互影响,因此需要对各种优化组合进行测试,找到最适合的优化方案。例如,在应用链接时优化和代码优化的同时,需要测试不同组合下的性能,以确定最佳的配置。

7. 优化效果总结

为了更直观地展示不同优化阶段的效果,我们将之前的测试结果汇总成以下表格:

优化阶段 总耗时(s) 平均耗时(µs) 50%请求响应时间(µs) 99%请求响应时间(ms)
无优化 29.883248121 298.832 3347.297 14.99426
编译器优化(–release) 8.010280915 80.102 806.717 3.500056
部分代码优化(rwlock, borrow) 7.94342667 79.434 791.554 3.749288
全部代码优化(fast) 7.820692644 78.206 787.329 3.410659

从表格中可以看出,通过逐步应用各种优化技术,微服务的性能得到了显著提升。特别是在应用全部代码优化后,请求平均耗时进一步降低,响应时间也更加稳定。

8. 优化流程回顾

为了更好地理解微服务优化的整个流程,我们再次回顾之前的 mermaid 图:

graph LR
    A[创建微服务] --> B[无优化性能测试]
    B --> C[优化编译]
    C --> D[优化性能测试]
    D --> E[应用代码优化]
    E --> F[部分优化编译测试]
    F --> G[全部优化编译测试]
    G --> H[其他优化技术应用]

这个流程清晰地展示了微服务优化的步骤:从创建微服务开始,进行无优化性能测试,然后进行编译器优化和性能测试,接着应用代码优化并进行部分和全部优化编译测试,最后考虑应用其他优化技术。每个步骤都需要进行性能测试,以确保优化的有效性。

9. 总结与建议

微服务优化是一个复杂而又重要的过程,需要综合运用各种优化技术,并根据实际情况进行权衡和选择。以下是一些总结和建议:

  • 逐步优化 :从简单的优化开始,如编译器优化,然后逐步应用代码优化和其他优化技术。这样可以更好地控制优化的效果和风险。
  • 性能测试贯穿始终 :在每个优化阶段都进行性能测试,确保优化确实带来了性能提升。同时,通过测试可以发现不同优化技术之间的相互影响。
  • 避免过度优化 :根据实际需求进行优化,避免为了微小的性能提升而使代码变得复杂。保持代码的简洁和可维护性是非常重要的。
  • 综合考虑各种因素 :在选择优化技术时,需要综合考虑性能提升、编译时间、开发难度、空间占用等因素,找到最适合的优化方案。

通过遵循这些建议,可以有效地提升微服务的性能和效率,同时保持代码的质量和可维护性。

内容概要:本文详细介绍了“秒杀商城”微服务架构的设计与实战全过程,涵盖系统从需求分析、服务拆分、技术选型到核心功能开发、分布式事务处理、容器化部署及监控链路追踪的完整流程。重点解决了高并发场景下的超卖问题,采用Redis预减库存、消息队列削峰、数据库乐观锁等手段保障数据一致性,并通过Nacos实现服务注册发现与配置管理,利用Seata处理跨服务分布式事务,结合RabbitMQ实现异步下单,提升系统吞吐能力。同时,项目支持Docker Compose快速部署和Kubernetes生产级编排,集成Sleuth+Zipkin链路追踪与Prometheus+Grafana监控体系,构建可观测性强的微服务系统。; 适合人群:具备Java基础和Spring Boot开发经验,熟悉微服务基本概念的中高级研发人员,尤其是希望深入理解高并发系统设计、分布式事务、服务治理等核心技术的开发者;适合工作2-5年、有志于转型微服务或提升架构能力的工程师; 使用场景及目标:①学习如何基于Spring Cloud Alibaba构建完整的微服务项目;②掌握秒杀场景下高并发、超卖控制、异步化、削峰填谷等关键技术方案;③实践分布式事务(Seata)、服务熔断降级、链路追踪、统一配置中心等企业级中间件的应用;④完成从本地开发到容器化部署的全流程落地; 阅读建议:建议按照文档提供的七个阶段循序渐进地动手实践,重点关注秒杀流程设计、服务间通信机制、分布式事务实现和系统性能优化部分,结合代码调试与监控工具深入理解各组件协作原理,真正掌握高并发微服务系统的构建能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值