本文是“.NET Conf China 2022”上我的一個分享,這里更細化的分享出來。
分享分為四個部分:
制定指示
設計應用
正確測試
性能優化
高性能:不一定是架構出來的,但一定是優化出來的。
制定指標-收集
首先把項目中的熱路徑API和核心API找出來,然后分析每個API是CPU密集型的,還是內存密集型的,以供在后面測試參考或對API的判斷。
制定指標-制定
TPS=并發線程*1000/ART,這是TPS和平均響應時間的公式。這里的表格相對完整,作為開發的性能測試,有時也可以只要求響應時間和TPS。或要求TPS和P95,P99。有時成功率很敏感,不管發性能怎么樣,成功率必須100%,這是根據業務的類型要求的,特別是和錢有關的請求,要求都比較高。
某API指標
指標名稱
指標值
業務指標
TPS(2C2G)
2000
響應時間
ART
10
P95
12
P99
15
成功率
100%
并發線程
20
穩定性指標
壓力持續時間
>=8h
壓力閾值
CPU <80%,TPS≈2000
內存泄露
無
TPS波動
<5%
應用資源(2C2G)
?MEM
<2G
CPU最大使用率
<90%
DB資源(8C8G)
?MEM
<8G
CPU最大使用率
<90%
緩存(1C1G)
MEM
<80%
CPU最大使用率
<90%
設計應用-通用設計
開發人員要了解通用狀態下的設備情況,比如CPU的緩存,內存,硬盤之間的關系,比如速度是在依次減少,成本在降低。還要了解各種網絡協速度,I/O的速度,以及使用各種數據庫的速度通常的閾值是什么樣的。
提升性能的兩個法寶:用緩存和用Queue,緩存可以讓使用數據速度更快,Queue可以分隔復雜業務,讓吞吐量更高,合理有效的使用兩種技術可以很大的提升性能。
設計應用-.NET體系
使用異步:Demo
//同步方法
app.MapGet("/sync", () =>
{using (var con = new MySqlConnection(connectionString)){var result = con.Query<int>("select sleep(6)");Console.WriteLine($"sync:{DateTime.Now}");return result;}
});
//異步方法,沒有CancellationToken
app.MapGet("/async", async () =>
{using (var con = new MySqlConnection(connectionString)){var result = await con.QueryAsync<int>("select sleep(6)");Console.WriteLine($"sync:{DateTime.Now}");return result;}
});
//異步,有CancellationToken
app.MapGet("/asyncwithtoken", async (CancellationToken token) =>
{using (var con = new MySqlConnection(connectionString)){var result = await con.QueryAsync<int>(new CommandDefinition("select sleep(6)", cancellationToken: token));Console.WriteLine($"sync:{DateTime.Now}");return result;}
});
通過postman請求三個api,然后取消,會發現前兩個api始終會執行完成,并輸出時間;第三個會在取消后停止響應。
正確使用異步,可以有效地提升服務端的資源使用狀況。
謹防阻塞:
記一次性能故障排查
桂素偉,公眾號:桂跡記一次性能故障排查
大集合化整為零:用最正確的集合來處理數據,不要在集中存放大量數據,這樣不管對內存或之后的運算,造成負擔。同時要從業務層次評估計集合的最大上限。?
避免在Host生成文件
再記一次Memory?Leak分析
桂素偉,公眾號:桂跡再記一次Memory Leak分析
復雜方法要比對:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using PerformanceDemo;
using System.Collections.Generic;
using System.Xml.Linq;var summary = BenchmarkRunner.Run(typeof(CustomTypeTest));public class CustomTypeTest
{ [Benchmark][Arguments(2000)]public Dictionary<string, SBook> GetBook1(int count){var sbookDic = new Dictionary<string, SBook>();for (var i = 0; i <= count; i++){sbookDic.Add($"張{i}豐", new SBook(){Id = i,Name = "C#從入門到精通",Author = $"張{i}豐",Title = "C#1.0",Description = "這是一本書"});}return sbookDic;}[Benchmark][Arguments(2000)]public Dictionary<string, Book> GetBook2(int count){var bookDic = new Dictionary<string, Book>();for (var i = 0; i <= count; i++){bookDic.Add($"張{i}豐", new Book(){Id = i,Name = "C#從入門到精通",Author = $"張{i}豐",Title = "C#1.0",Description = "這是一本書"});}return bookDic;}[Benchmark][Arguments(2000)]public List<Book> GetBook3(int count){var bookList = new List<Book>();for (var i = 0; i <= count; i++){bookList.Add(new Book{Id = i,Name = "C#從入門到精通",Author = $"張{i}豐",Title = "C#1.0",Description = "這是一本書"});}return bookList;}[Benchmark][Arguments(2000)]public List<SBook> GetBook4(int count){var sbookList = new List<SBook>();for (var i = 0; i <= count; i++){sbookList.Add(new SBook{Id = i,Name = "C#從入門到精通",Author = $"張{i}豐",Title = "C#1.0",Description = "這是一本書"});}return sbookList;}
}
public class Book
{public int Id { get; set; }public string Name { get; set; }public string Description { get; set; }public string Title { get; set; }public string Author { get; set; }
}
public struct SBook
{public int Id { get; set; }public string Name { get; set; }public string Description { get; set; }public string Title { get; set; }public string Author { get; set; }
}
下面是比較結果:
讓每個API輕巧快速
有一顆追求性能的心——關注.NET版本
關于在開發層次的性能注意事項有很多,這是根據不同的項目,使用不同的類庫決定的,上面只是我性能化化中的幾個代表性場景。
設計應用-發布
配置:GC方式 工作站方式,服務器方式:CPU使用率比內存更重要,服務器GC性能更好;內存利用率較高而 CPU 使用率相對較低,工作站 GC 性能更高。
發布方式:默認和R2R首次請求時間和體積不同
普通模式 | R2R模式 | AOT模式 | |
大小 | 29.8 MB | 62.2 MB | 19.5M |
首次請求用時 | 360ms | 90ms | 20ms |
正確測試
測性能的正確姿勢
盡量與生產環境一致
要有監控,通過監控數據對比發現問題
不要打滿資源:CPU<90%,內存少于最大值
讓子彈多飛會兒,觀察內存是有什么不一樣
遇到問題借助工具
dotnet-dump
dotnet-counters
前人經驗也很寶貴
https://learn.microsoft.com/zh-cn/dotnet/core/diagnostics/
性能優化
減少響應時間:
優化&簡化流程
優化調用鏈路上的函數
把關系數據庫操作轉成緩存操作
用BenchmarkDotNet
提升TPS:
優先降低性能測中的較高資源
讓應用性能是線性的,可以輕松地通過擴容來提升TPS
經過上面的分享,可以得出:
高性能:一定不是架構出來的,但一定是優化出來的。