一、開場白
我是一名程式設計師,是基於 NET 框架的跨平臺開發的程式設計師。現在的業務系統,不論大小都開始實現了微服務,不管合不合適,最起碼說起來挺牛氣的。我做一位程式設計師,當然也不能落後了。微服務是為了滿足高並行、高可用和高擴充套件特性進化出來的一個架構模式。一個微服務架構中,為了解決其中一個效能問題,可能就會有很多技術方案。我今天就和大家天天為了應付高並行可以使用的快取和動態分離的設計,聽起來挺高大上的,其實實現起來也沒有那麼複雜。
開發環境我簡單的介紹一下。
開發語言:C#
開發工具:Visual Studio 2022
開發框架:ASP.NET WEB MVC
負載均衡:Nginx(Windows版本)。
作業系統:Windows10 專業版
二、程式碼實現
程式碼實現分為兩個部分,第一個部分是有關Nginx的設定,第二個部分是C#程式碼擴充套件的中介軟體的實現。
1、Nginx設定的實現。
說明一下,Nginx的組態檔裡包含了很多其他部分,我都放在一起了,便於以後自己查用,如果使用,需要過濾一下。裡面都有備註,說的很清楚。
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #快取設定 proxy_cache_path /MicroService\MicroServiceCode\NginxCache\Data levels=1:2 keys_zone=web_cache:50m inactive=1m max_size=1g; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; #叢集地址 #dotnet run PatrickLiu.MicroServices.Webs.exe --urls="http://*:5678" --ip="127.0.0.1" --port="5678" #dotnet run PatrickLiu.MicroServices.Webs.exe --urls="http://*:5679" --ip="127.0.0.1" --port="5679" #dotnet run PatrickLiu.MicroServices.Webs.exe --urls="http://*:5680" --ip="127.0.0.1" --port="5680"
#負載均衡策略
#1)、輪訓
#2)、權重 server 127.0.0.1:5678 weight=1;
#3)、url_hash:hash $request_uri
#4)、ip_hash:ip_hash
#5)、least_conn:最少連線數
#6)、fair:
upstream MicroServices{ #ip_hash; server 127.0.0.1:5678 weight=1; server 127.0.0.1:5679 weight=15; server 127.0.0.1:5680 weight=10; } server { listen 8086; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; #單對單請求轉發 #location / { # proxy_pass http://127.0.0.1:7152; #} #單對多叢集設定 location / { proxy_pass http://MicroServices; } #快取設定 location /third/{ proxy_store off; proxy_redirect off; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_pass http://127.0.0.1:7152/third/; proxy_cache web_cache; proxy_cache_valid 200 304 2m; proxy_cache_key $scheme$proxy_host$request_uri; } #動靜分離 location /item/{ alias E:\MicroService\MicroServiceCode\NginxHtmls/;#root的話需要拼接地址 if (!-f $request_filename){#如果檔案不存在直接轉發到伺服器生成 proxy_pass http://MicroServices; break; } } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # #server { # listen 443 ssl; # server_name localhost; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #} }
2、中介軟體擴充套件實現。
程式碼也很簡單,我是直接擴充套件的MVC的中介軟體實現的靜態化。廢話不多說,直接上程式碼。
1 namespace PatrickLiu.MicroServices.Webs.Extensions 2 { 3 /// <summary> 4 /// 自定義擴充套件靜態中介軟體實現。 5 /// </summary> 6 public static class StaticPageMiddlewareExtensions 7 { 8 /// <summary> 9 /// 10 /// </summary> 11 /// <param name="app"></param> 12 /// <param name="path"></param> 13 /// <param name="isDelete"></param> 14 /// <param name="isWarnup"></param> 15 /// <returns></returns> 16 public static IApplicationBuilder UseStaticPage(this IApplicationBuilder app,string path,bool isDelete,bool isWarnup) 17 { 18 return app.UseMiddleware<StaticPageMiddleware>(path,isDelete,isWarnup); 19 } 20 } 21 }
1 namespace PatrickLiu.MicroServices.Webs.Extensions 2 { 3 /// <summary> 4 /// 支援在返回HTML時,將返回的Stream儲存到指定目錄。 5 /// </summary> 6 public sealed class StaticPageMiddleware 7 { 8 private readonly RequestDelegate _next; 9 private string _directoryPath; 10 private bool _supportDelete; 11 private bool _supportWarmup; 12 13 /// <summary> 14 /// 構造範例。 15 /// </summary> 16 /// <param name="next"></param> 17 /// <param name="directoryPath"></param> 18 /// <param name="supportDelete"></param> 19 /// <param name="supportWarmup"></param> 20 public StaticPageMiddleware(RequestDelegate next, string directoryPath = "", bool supportDelete = false, bool supportWarmup = false) 21 { 22 _next = next; 23 _directoryPath = directoryPath; 24 _supportDelete = supportDelete; 25 _supportWarmup = supportWarmup; 26 } 27 28 /// <summary> 29 /// 30 /// </summary> 31 /// <param name="context"></param> 32 /// <returns></returns> 33 public async Task InvokeAsync(HttpContext context) 34 { 35 if (context.Request.Headers.ContainsKey("XmlHttpRequest")) 36 { 37 await _next(context); 38 } 39 else if (_supportDelete && "Delete".Equals(context.Request.Query["ActionHeader"])) 40 { 41 DeleteHtml(context.Request.Path.Value); 42 context.Response.StatusCode = 200; 43 } 44 else if (_supportWarmup && "ClearAll".Equals(context.Request.Query["ActionHeader"])) 45 { 46 ClearDirectory(10);//考慮數量級 47 context.Response.StatusCode = 200; 48 } 49 else if (context.Request.Path.Value.Contains("/item/", StringComparison.OrdinalIgnoreCase)) 50 { 51 var originalStream = context.Response.Body; 52 using (var copyStream = new MemoryStream()) 53 { 54 context.Response.Body = copyStream; 55 await _next(context); 56 57 copyStream.Position = 0; 58 var reader = new StreamReader(copyStream); 59 var content = await reader.ReadToEndAsync(); 60 string url = context.Request.Path.Value; 61 await SaveHtml(url, content); 62 63 copyStream.Position = 0; 64 await copyStream.CopyToAsync(originalStream); 65 context.Response.Body = originalStream; 66 } 67 } 68 else 69 { 70 await _next(context); 71 } 72 } 73 74 75 /// <summary> 76 /// 77 /// </summary> 78 /// <param name="v"></param> 79 private void ClearDirectory(int index) 80 { 81 if (index > 0) 82 { 83 try 84 { 85 var files = Directory.GetFiles(_directoryPath); 86 foreach (var file in files) 87 { 88 File.Delete(file); 89 } 90 } 91 catch (Exception ex) 92 { 93 Console.WriteLine($"Clear Directory failed {ex.Message}"); 94 ClearDirectory(index--); 95 } 96 } 97 } 98 99 /// <summary> 100 /// 可以指定策略刪除Html檔案。 101 /// </summary> 102 /// <param name="url"></param> 103 private void DeleteHtml(string url) 104 { 105 if (string.IsNullOrEmpty(url)) 106 { 107 return; 108 } 109 try 110 { 111 if (!url.EndsWith(".html", StringComparison.OrdinalIgnoreCase)) 112 { 113 return; 114 } 115 var fullPath = Path.Combine(_directoryPath,url.Split("/").Last()); 116 File.Delete(fullPath); 117 } 118 catch (Exception ex) 119 { 120 Console.WriteLine($"Delete {url} 異常,{ex.Message}"); 121 } 122 } 123 124 /// <summary> 125 /// 將 HTML 內容寫到伺服器上。 126 /// </summary> 127 /// <param name="url"></param> 128 /// <param name="content"></param> 129 /// <exception cref="NotImplementedException"></exception> 130 private async Task SaveHtml(string url, string content) 131 { 132 if (string.IsNullOrEmpty(content) || string.IsNullOrEmpty(url)) 133 { 134 return; 135 } 136 137 if (!url.EndsWith(".html", StringComparison.OrdinalIgnoreCase)) 138 { 139 return; 140 } 141 142 try 143 { 144 145 if (!Directory.Exists(_directoryPath)) 146 { 147 Directory.CreateDirectory(_directoryPath); 148 } 149 150 var fullPath = Path.Combine(_directoryPath, url.Split("/").Last()); 151 await File.WriteAllTextAsync(fullPath, content); 152 } 153 catch (Exception ex) 154 { 155 Console.WriteLine(ex.Message); 156 } 157 } 158 } 159 }
1 using PatrickLiu.MicroServices.Interfaces; 2 using PatrickLiu.MicroServices.Models; 3 using PatrickLiu.MicroServices.Services; 4 using PatrickLiu.MicroServices.Webs.Extensions; 5 6 var builder = WebApplication.CreateBuilder(args); 7 8 // Add services to the container. 9 builder.Services.AddControllersWithViews(); 10 11 #region Options 12 13 string configurationDateTime = DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss fff"); 14 15 builder.Services.Configure<EmailOption>(op => op.Title = $"services.Configure<EmailOption>--DefaultName {configurationDateTime}--{DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss fff")}"); 16 17 builder.Services.Configure<EmailOption>("FromMemory", op => op.Title = "services.Configure<EmailOption>----FromMemory"); 18 19 builder.Services.Configure<EmailOption>("FromConfiguration", builder.Configuration.GetSection("Email")); 20 21 builder.Services.Configure<EmailNewOption>("FromConfigurationNew", builder.Configuration.GetSection("EmailNew")); 22 23 builder.Services.AddOptions<EmailOption>("AddOption").Configure(op => op.Title = "AddOtpion Title-- DefaultName"); 24 25 builder.Services.Configure<EmailOption>(null, op => op.From = "services.Configure<EmailOption>--Name-null--Same with ConfigureAll"); 26 27 builder.Services.PostConfigure<EmailOption>(null, op => op.Body = "services.PostConfigure<EmailOption>--Name null---Same with PostConfigureAll"); 28 29 #endregion 30 31 //builder.Services.AddSingleton<OrderServiceOption>(); 32 //builder.Services.AddScoped<IOrderServiceForOptions, MyOrderServiceForOptions>();//Scoped<==>IOptionSnapshot 33 builder.Services.AddSingleton<IOrderServiceForOptions, MyOrderServiceForOptions>();//AddSingleton<==>IOptionMonitor 34 builder.Services.Configure<OrderServiceOption>(builder.Configuration.GetSection("MyCount")); 35 builder.Services.PostConfigure<OrderServiceOption>(p => { 36 p.MaxOrderCount += 1212121; 37 }); 38 39 40 var app = builder.Build(); 41 42 // Configure the HTTP request pipeline. 43 if (!app.Environment.IsDevelopment()) 44 { 45 app.UseExceptionHandler("/Home/Error"); 46 } 47 app.UseStaticFiles(); 48 49 app.UseStaticPage(@"E:\MicroService\MicroServiceCode\NginxHtmls", false, false);靜態中介軟體使用方法。 50 51 app.UseRouting(); 52 53 app.UseAuthorization(); 54 55 app.MapControllerRoute( 56 name: "default", 57 pattern: "{controller=Home}/{action=Index}/{id?}"); 58 59 app.Run();
3、實現效果。
1】、快取的目錄生成。
2】、靜態檔案的生成
三、結束
好了,今天就到這裡了,有了新的積累在儲存起來。程式設計師這個行業需要持續學習,其實,不光是程式設計師這個行業,很多行業都是需要持續學習的,只不過我們持續學習的內容不同而已。老天不會辜負努力的人,我們堅持自己,努力在學習中改變自己,在提升自己的同時可以讓自己更融合,發現我們的目標。技術好是一個追求,也是一個承諾,對人對己都是一樣。努力前進,不負韶華。