寫這篇博客主要目的是記錄一下本身的學習過程,只能是簡單入門級別的,由於水平有限就寫到哪算哪吧,寫的不對之處歡迎指正。
代碼放在:https://github.com/xiajingren/NetCoreMicroserviceDemohtml
關於微服務的概念解釋網上有不少...
我的理解,微服務是一種系統架構模式,它和語言無關,和框架無關,和工具無關,和服務器環境無關...
微服務思想是將傳統的單體系統按照業務拆分紅多個職責單1、且可獨立運行的接口服務,各個服務之間不耦合。至於服務如何拆分,沒有明確的定義。
幾乎任何後端語言都能作微服務開發。
微服務也並非天衣無縫的,微服務架構會帶來更多的問題,增長系統的複雜度,引入更多的技術棧...nginx
一個客戶端,一個產品服務,一個訂單服務。3個項目都是asp.net core web應用程序。建立項目的時候記得啓用一下Docker支持,或者後面添加也行。git
爲產品、訂單服務添加一些基礎代碼,就簡單的返回一下 服務名稱,當前時間,服務的ip、端口。
github
爲了方便,我使用Docker來運行服務,不用Docker也行,關於docker的安裝及基本使用就不介紹了。web
在項目根目錄打開PowerShell窗口執行:docker build -t productapi -f ./Product.API/Dockerfile .
Successfully表明build成功了。docker
執行:docker run -d -p 9050:80 --name productservice productapi
數據庫
執行:docker ps
查看運行的容器:
小程序
沒問題,使用瀏覽器訪問一下接口:
也沒問題,其中的ip端口是Docker容器內部的ip端口,因此端口是80,這個無所謂。後端
build鏡像:docker build -t orderapi -f ./Order.API/Dockerfile .
運行容器:docker run -d -p 9060:80 --name orderservice orderapi
瀏覽器訪問一下:
OK,訂單服務也部署完成了。api
客戶端我這裏只作了一個web客戶端,實際多是各類業務系統、什麼PC端、手機端、小程序。。。這個明白就好,爲了簡單就不搞那麼多了。
由於客戶端須要http請求服務端接口,因此須要一個http請求客戶端,我我的比較習慣RestSharp,安利一波:https://github.com/restsharp/RestSharp
添加基礎代碼:
IServiceHelper.cs:
public interface IServiceHelper { /// <summary> /// 獲取產品數據 /// </summary> /// <returns></returns> Task<string> GetProduct(); /// <summary> /// 獲取訂單數據 /// </summary> /// <returns></returns> Task<string> GetOrder(); }
ServiceHelper.cs:
public class ServiceHelper : IServiceHelper { public async Task<string> GetOrder() { string serviceUrl = "http://localhost:9060";//訂單服務的地址,能夠放在配置文件或者數據庫等等... var Client = new RestClient(serviceUrl); var request = new RestRequest("/orders", Method.GET); var response = await Client.ExecuteAsync(request); return response.Content; } public async Task<string> GetProduct() { string serviceUrl = "http://localhost:9050";//產品服務的地址,能夠放在配置文件或者數據庫等等... var Client = new RestClient(serviceUrl); var request = new RestRequest("/products", Method.GET); var response = await Client.ExecuteAsync(request); return response.Content; } }
Startup.cs:
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); //注入IServiceHelper services.AddSingleton<IServiceHelper, ServiceHelper>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } }
HomeController.cs:
public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private readonly IServiceHelper _serviceHelper; public HomeController(ILogger<HomeController> logger, IServiceHelper serviceHelper) { _logger = logger; _serviceHelper = serviceHelper; } public async Task<IActionResult> Index() { ViewBag.OrderData = await _serviceHelper.GetOrder(); ViewBag.ProductData = await _serviceHelper.GetProduct(); return View(); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } }
Index.cshtml:
@{ ViewData["Title"] = "Home Page"; } <div class="text-center"> <h1 class="display-4">Welcome</h1> <p> @ViewBag.OrderData </p> <p> @ViewBag.ProductData </p> </div>
代碼比較簡單,這裏就不用docker了,直接控制檯啓動,使用瀏覽器訪問:
中止一下訂單服務:docker stop orderservice
訂單服務中止,致使客戶端業務系統沒法獲取訂單數據。
要解決這個問題,很容易想到:集羣。
既然單個服務實例有掛掉的風險,那麼部署多個服務實例就行了嘛,只要你們不一樣時全掛就行。
docker run -d -p 9061:80 --name orderservice1 orderapi
docker run -d -p 9062:80 --name orderservice2 orderapi
docker run -d -p 9051:80 --name productservice1 productapi
docker run -d -p 9052:80 --name productservice2 productapi
如今訂單服務和產品服務都增長到3個服務實例。
public class ServiceHelper : IServiceHelper { public async Task<string> GetOrder() { string[] serviceUrls = { "http://localhost:9060", "http://localhost:9061", "http://localhost:9062" };//訂單服務的地址,能夠放在配置文件或者數據庫等等... //每次隨機訪問一個服務實例 var Client = new RestClient(serviceUrls[new Random().Next(0, 3)]); var request = new RestRequest("/orders", Method.GET); var response = await Client.ExecuteAsync(request); return response.Content; } public async Task<string> GetProduct() { string[] serviceUrls = { "http://localhost:9050", "http://localhost:9051", "http://localhost:9052" };//產品服務的地址,能夠放在配置文件或者數據庫等等... //每次隨機訪問一個服務實例 var Client = new RestClient(serviceUrls[new Random().Next(0, 3)]); var request = new RestRequest("/products", Method.GET); var response = await Client.ExecuteAsync(request); return response.Content; } }
固然拿到這些服務地址能夠本身作複雜的負載均衡策略,好比輪詢,隨機,權重等等 都行,甚至在中間弄個nginx也能夠。這些不是重點,因此就簡單作一個隨機吧,每次請求來了隨便訪問一個服務實例。
瀏覽器測試一下:
能夠看到請求被隨機分配了。可是這種作法依然不安全,若是隨機訪問到的實例恰好掛掉,那麼業務系統依然會出問題。
簡單處理思路是:
1.若是某個地址請求失敗了,那麼換一個地址接着執行。
2.若是某個地址的請求連續屢次失敗了,那麼就移除這個地址,下次就不會訪問到它了。
。。。。。。
業務系統實現以上邏輯,基本上風險就很低了,也算是大大增長了系統可用性了。
而後思考另外一個問題:
實際應用中,上層的業務系統可能很是多,爲了保證可用性,每一個業務系統都去考慮服務實例掛沒掛掉嗎?
並且實際應用中服務實例的數量或者地址大可能是不固定的,例如雙十一來了,流量大了,增長了一堆服務實例,這時候每一個業務系統再去配置文件裏配置一下這些地址嗎?雙十一過了又去把配置刪掉嗎?顯然是不現實的,服務必需要作到可靈活伸縮。
未完待續...