廬山真面目之二微服務架構NGINX版本實現
一、簡介
在上一篇文章《廬山真面目之微服務的簡介和技術棧》中,我們已經探討了微服務的來龍去脈,也說了想要實現微服務架構所需要的技術棧,今天我們開始實現一個微服務,當然這個實現是簡化版本的,在這個版本里面也不涉及 Docker、K8S等的東西,我們逐步演化,今天這一期只是實現一個NGINX版本的微服務的功能。
1、說明
有關微服務架構所涉及的技術太多,無法再一篇文章內討論完全,所以,我們就分多期來說,每期都遞進相關的技術,然後,一步一步的演化而來。如果您是大牛,就可以直接跳過,因為這些東西相對於您來說,這個太簡單了。特別說明,這裡的所有程式碼都經過測試,所以大家可以放心使用。
2、開發環境
以下就是開發環境,不用多說,都很簡單,一看就知道。
(1)、開發工具:Visual
Studio 2019
(2)、開發語言:C#
(3)、開發平臺:Net
Core3.1,跨平臺。
(4)、閘道器服務:NGINX,專注於高效能閘道器。
3、今天的目標
今天我們的目標就是建立一個基於Nginx閘道器實現的,服務例項沒有任何容器包容,只是獨立程序承載的這麼一個架構實現。
二、微服務架構之NGINX 版本實現
在文章開始之前,我們還是要簡要說明一下。上一篇檔案《廬山真面目之微服務的簡介和技術棧》中我們說過,微服務有三個版本,分別是:單體架構、垂直拆分設計和微服務,基於SOA的我們暫時不討論。其實,第二版本和第一個沒有本質區別,都是單體架構而已,所以,我們今天就簡單實現一下微服務的版本,由於裡面涉及的技術很多,微服務這個版本我又分了多個版本來寫,今天是最簡單。
今天我們主要討論基於NGINX實現的分散式、微服務架構的優缺點,每個專案的程式碼都獨立貼出來,邏輯很簡單,因為我們側重架構嘛,我們開始吧。
1、我們解決方案,包含5個專案。
(1)、PatrickLiu.MicroService.Client(ASP.NET CORE MVC),客戶端應用程式。
這個專案很簡單,幾乎沒有任何修改,只是在這裡訪問服務例項而已。
1)、startup.cs 類中唯一增加的程式碼
1 public class Startup 2 { 3 4 /// <summary> 5 /// 註冊服務到容器中。 6 /// </summary> 7 /// <param name="services"></param> 8 public void ConfigureServices(IServiceCollection services) 9 { 10 services.AddSingleton<IUserService, UserService>(); 11 } 12 }
2)、HomeController.cs 類的程式碼
1 public class HomeController : Controller 2 { 3 private readonly ILogger<HomeController> _logger; 4 private readonly IUserService _userService; 5 private static int index; 6 7 /// <summary> 8 /// 初始化該型別的新例項。 9 /// </summary> 10 /// <param name="logger">注入日誌物件。</param> 11 /// <param name="userService">注入使用者服務物件。</param> 12 public HomeController(ILogger<HomeController> logger, IUserService userService) 13 { 14 _logger = logger; 15 _userService = userService; 16 } 17 18 /// <summary> 19 /// 首頁。 20 /// </summary> 21 /// <returns></returns> 22 public IActionResult Index() 23 { 24 #region 1、單體架構 25 26 //this.ViewBag.Users = _userService.UserAll(); 27 28 #endregion 29 30 #region 2、分散式架構 31 32 #region 2.1、直接訪問服務例項 33 34 //string url = string.Empty; 35 //url = "http://localhost:5726/api/users/all"; 36 //url = "http://localhost:5726/api/users/all"; 37 //url = "http://localhost:5726/api/users/all"; 38 39 #endregion 40 41 #region 通過 Ngnix閘道器訪問服務例項,預設輪訓。 42 43 string url = "http://localhost:8080/api/users/all"; 44 45 #endregion 46 47 string content = InvokeAPI(url); 48 this.ViewBag.Users = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<User>>(content); 49 Console.WriteLine($"This is {url} Invoke."); 50 51 #endregion 52 53 return View(); 54 } 55 56 57 /// <summary> 58 /// 59 /// </summary> 60 /// <param name="url"></param> 61 /// <returns></returns> 62 public static string InvokeAPI(string url) 63 { 64 using (HttpClient client = new HttpClient()) 65 { 66 HttpRequestMessage message = new HttpRequestMessage(); 67 message.Method = HttpMethod.Get; 68 message.RequestUri = new Uri(url); 69 var result = client.SendAsync(message).Result; 70 string conent = result.Content.ReadAsStringAsync().Result; 71 return conent; 72 } 73 } 74 } 75 }
3)、Index.cshtml 檢視的程式碼
1 @{ 2 ViewData["Title"] = "Home Page"; 3 } 4 5 <div class="text-center"> 6 <h1 class="display-4">Welcome</h1> 7 <ul> 8 @{ 9 10 foreach (var item in ViewBag.Users) 11 { 12 <li>@item.ID [email protected] [email protected] </li> 13 } 14 } 15 </ul> 16 <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> 17 </div>
(2)、PatrickLiu.MicroService.Interfaces(NETCORE類庫專案),定義服務介面。
這個專案很簡單,只定義了一個介面型別,名稱:IUserService.cs。
IUserService的程式碼
1 using PatrickLiu.MicroService.Models; 2 using System.Collections.Generic; 3 4 namespace PatrickLiu.MicroService.Interfaces 5 { 6 /// <summary> 7 /// 使用者服務的介面定義。 8 /// </summary> 9 public interface IUserService 10 { 11 /// <summary> 12 /// 查詢指定主鍵的使用者例項物件。 13 /// </summary> 14 /// <param name="id">使用者的主鍵。</param> 15 /// <returns>返回查詢到的使用者例項物件。</returns> 16 User FindUser(int id); 17 18 /// <summary> 19 /// 獲取所有使用者的例項集合。 20 /// </summary> 21 /// <returns>返回所有的使用者例項。</returns> 22 IEnumerable<User> UserAll(); 23 } 24 }
(3)、PatrickLiu.MicroService.Models
(NETCORE類庫專案),定義實體類模型。
這個專案很簡單,只定義了一個實體型別,類名:User.cs。
User.cs 的程式碼
1 using System; 2 3 namespace PatrickLiu.MicroService.Models 4 { 5 /// <summary> 6 /// 使用者模型。 7 /// </summary> 8 public class User 9 { 10 /// <summary> 11 /// 獲取或者設定使用者主鍵。 12 /// </summary> 13 public int ID { get; set; } 14 15 /// <summary> 16 /// 獲取或者設定使用者姓名。 17 /// </summary> 18 public string Name { get; set; } 19 20 /// <summary> 21 /// 獲取或者設定使用者賬號名稱。 22 /// </summary> 23 public string Account { get; set; } 24 25 /// <summary> 26 /// 獲取或者設定使用者密碼。 27 /// </summary> 28 public string Password { get; set; } 29 30 /// <summary> 31 /// 獲取或者設定使用者的電子郵箱地址。 32 /// </summary> 33 public string Email { get; set; } 34 35 /// <summary> 36 /// 獲取或者設定使用者角色。 37 /// </summary> 38 public string Role { get; set; } 39 40 /// <summary> 41 /// 獲取或者設定使用者的登入時間。 42 /// </summary> 43 public DateTime LoginTime { get; set; } 44 } 45 }
(4)、PatrickLiu.MicroService.Services(NetCore 類庫專案),實現介面型別。
這個專案很簡單,只定義了一個型別,實現IUserService介面,類名:UserService.cs。
UserService.cs的程式碼
1 using PatrickLiu.MicroService.Interfaces; 2 using PatrickLiu.MicroService.Models; 3 using System; 4 using System.Collections.Generic; 5 using System.Linq; 6 7 namespace PatrickLiu.MicroService.Services 8 { 9 /// <summary> 10 /// 實現使用者服務介面的實現型別。 11 /// </summary> 12 public class UserService : IUserService 13 { 14 private IList<User> dataList; 15 16 /// <summary> 17 /// 初始化型別的例項 18 /// </summary> 19 public UserService() 20 { 21 dataList = new List<User>() 22 { new User {ID=1,Name="黃飛鴻",Account="HuangFeiHong",Password="HuangFeiHong123456",Email="[email protected]", Role="Admin", LoginTime=DateTime.Now }, 23 new User {ID=2,Name="洪熙官",Account="HongXiGuan",Password="HongXiGuan54667",Email="[email protected]", Role="Admin", LoginTime=DateTime.Now.AddDays(-5) }, 24 new User {ID=3,Name="方世玉",Account="FangShiYu",Password="FangShiYu112233",Email="[email protected]", Role="Admin", LoginTime=DateTime.Now.AddDays(-30) }, 25 new User {ID=4,Name="苗翠花",Account="MiaoCuiHua",Password="MiaoCuiHua887766",Email="[email protected]", Role="Admin", LoginTime=DateTime.Now.AddDays(-90) }, 26 new User {ID=5,Name="嚴詠春",Account="YanYongChun",Password="YanYongChun09392",Email="[email protected]", Role="Admin", LoginTime=DateTime.Now.AddMinutes(-50) }}; 27 } 28 29 /// <summary> 30 /// 查詢指定主鍵的使用者例項物件。 31 /// </summary> 32 /// <param name="id">使用者的主鍵。</param> 33 /// <returns>返回查詢到的使用者例項物件。</returns> 34 public User FindUser(int id) 35 { 36 return dataList.FirstOrDefault(user => user.ID == id); 37 } 38 39 /// <summary> 40 /// 獲取所有使用者的例項集合。 41 /// </summary> 42 /// <returns>返回所有的使用者例項。</returns> 43 public IEnumerable<User> UserAll() 44 { 45 return dataList; 46 } 47 } 48 }
(5)、PatrickLiu.MicroService.ServiceInstance(ASP.NET CORE WEB API專案)。
這個專案很簡單,引用其他三個專案,製作
Restfull API,可以讓客戶端訪問。
引用專案:PatrickLiu.MicroService.Interfaces
PatrickLiu.MicroService.Services
PatrickLiu.MicroService.Models
1)、Startup.cs 的程式碼
1 public class Startup 2 { 3 4 /// <summary> 5 /// 6 /// </summary> 7 /// <param name="services"></param> 8 public void ConfigureServices(IServiceCollection services) 9 { 10 services.AddControllers(); 11 services.AddSingleton<IUserService, UserService>(); 12 } 13 }
2)、Program.cs 的程式碼
1 public class Program 2 { 3 public static void Main(string[] args) 4 { 5 new ConfigurationBuilder() 6 .SetBasePath(Directory.GetCurrentDirectory()) 7 .AddCommandLine(args)//支援命令列 8 .Build(); 9 10 CreateHostBuilder(args).Build().Run(); 11 } 12 13 public static IHostBuilder CreateHostBuilder(string[] args) => 14 Host.CreateDefaultBuilder(args) 15 .ConfigureWebHostDefaults(webBuilder => 16 { 17 webBuilder.UseStartup<Startup>(); 18 }); 19 }
3)、UsersController.cs 的程式碼
1 /// <summary> 2 /// 使用者的 API 型別。 3 /// </summary> 4 [Route("api/[controller]")] 5 [ApiController] 6 public class UsersController : ControllerBase 7 { 8 #region 私有欄位 9 10 private readonly ILogger<UsersController> _logger; 11 private readonly IUserService _userService; 12 private IConfiguration _configuration; 13 14 #endregion 15 16 #region 建構函式 17 18 /// <summary> 19 /// 初始化該型別的新例項。 20 /// </summary> 21 /// <param name="logger">日誌記錄器。</param> 22 /// <param name="userService">使用者服務介面。</param> 23 /// <param name="configuration">配置服務。</param> 24 public UsersController(ILogger<UsersController> logger, IUserService userService, IConfiguration configuration) 25 { 26 _logger = logger; 27 _userService = userService; 28 _configuration = configuration; 29 } 30 31 #endregion 32 33 #region 例項方法 34 35 /// <summary> 36 /// 獲取一條記錄 37 /// </summary> 38 /// <param name="id"></param> 39 /// <returns></returns> 40 [HttpGet] 41 [Route("Get")] 42 public User Get(int id) 43 { 44 return _userService.FindUser(id); 45 } 46 47 /// <summary> 48 /// 獲取所有記錄。 49 /// </summary> 50 /// <returns></returns> 51 [HttpGet] 52 [Route("All")] 53 //[Authorize] 54 public IEnumerable<User> Get() 55 { 56 Console.WriteLine($"This is UsersController {this._configuration["port"]} Invoke"); 57 58 return this._userService.UserAll().Select((user => new User 59 { 60 ID = user.ID, 61 Name = user.Name, 62 Account = user.Account, 63 Password = user.Password, 64 Email = user.Email, 65 Role = $"{this._configuration["ip"]}:{this._configuration["port"]}", 66 LoginTime = user.LoginTime 67 })); ; 68 } 69 70 /// <summary> 71 /// 超時處理 72 /// </summary> 73 /// <returns></returns> 74 [HttpGet] 75 [Route("Timeout")] 76 public IEnumerable<User> Timeout() 77 { 78 Console.WriteLine($"This is Timeout Start"); 79 //超時設定。 80 Thread.Sleep(3000); 81 82 Console.WriteLine($"This is Timeout End"); 83 84 return this._userService.UserAll().Select((user => new User 85 { 86 ID = user.ID, 87 Name = user.Name, 88 Account = user.Account, 89 Password = user.Password, 90 Email = user.Email, 91 Role = $"{this._configuration["ip"]}:{this._configuration["port"]}", 92 LoginTime = user.LoginTime 93 })); ; 94 } 95 96 #endregion 97 }
2、編譯專案,啟動四個服務例項。
這四個服務例項是PatrickLiu.MicroService.ServiceInstance專案的例項,執行 dotnet 命令要在當前目錄下。
我的目錄:E:\Visual Studio 2019\Project\SourceCode\PatrickLiu.MicroService\PatrickLiu.MicroService.ServiceInstance\bin\Debug\netcoreapp3.1
(1)、dotnet
PatrickLiu.MicroService.ServiceInstance.dll --urls="http://*:5726"
--ip="127.0.0.1" --port=5726
可以訪問WebAPI地址,驗證是否成功。
地址:http://localhost:5726/api/users/all
效果如圖:
(2)、dotnet
PatrickLiu.MicroService.ServiceInstance.dll --urls="http://*:5727"
--ip="127.0.0.1" --port=5727
可以訪問WebAPI地址,驗證是否成功。
地址:http://localhost:5727/api/users/all
效果如圖:
(3)、dotnet
PatrickLiu.MicroService.ServiceInstance.dll --urls="http://*:5728"
--ip="127.0.0.1" --port=5728
可以訪問WebAPI地址,驗證是否成功。
地址:http://localhost:5728/api/users/all
效果如圖:
(4)、dotnet PatrickLiu.MicroService.ServiceInstance.dll
--urls="http://*:5729" --ip="127.0.0.1" --port=5729
可以訪問WebAPI地址,驗證是否成功。
地址:http://localhost:5729/api/users/all
效果如圖:
3、下載NGINX 服務元件,預設下載
Windows 版本。
官網: http://nginx.org/en/download.html
4、部署NGINX伺服器。
將下載的檔案拷貝到沒有中文的目錄下,解壓檔案就可以。
我的存放目錄:D:\Programs\MicroServices
5、修改 NGINX.CONF 檔案。
修改該目錄D:\Programs\MicroServices\Nginx-1.18.0\conf
下的配置檔案。檔名:nginx.conf
增加的配置內容如下:
1 upstream MicroService{ 2 server localhost:5726; 3 server localhost:5727; 4 server localhost:5728; 5 server localhost:5729; 6 } 7 8 server { 9 listen 8080; 10 server_name localhost; 11 12 location / { 13 proxy_pass http://MicroService; 14 } 15 }
6、啟動 Nginx 伺服器。
注意:我是在Nginx當前目錄下邊。
Start nginx
NGINX埠預設:80,我改成了8080,沒有提示,一般會啟動正常。
可以訪問NGINX地址,驗證NGINX是否配置並且啟動成功。
地址:http://localhost:8080
效果如圖:
7、開啟瀏覽器,輸入地址:http://localhost:8080/api/users/all,多次重新整理頁面,你就會看到變化。我們已經實現了分散式、負載均衡。
如圖:5726 埠
如圖:5727埠
如圖:5728埠
如圖:5729埠
8、我們測試NGINX的高可用和可擴充套件性,得出以下結論。
(1)、Nginx 客戶端配置很簡單,直接訪問 Nginx 的閘道器地址就會路由到註冊服務例項。
(2)、如果服務例項掉線或者出現異常,可以自動察覺器狀況,下次輪訓可以跳過,不需人為參與。
(3)、如果系統增加了新的服務例項,Nginx 無法自動發現,需要人工修改配置檔案,然後重啟,才可以。
由於第三點的缺點,是我們這個版本的最大的缺點,我們不可能在龐大的系統中總是通過人力來做這些事。無法自動發現服務,我們需要繼續改進,那就是服務註冊發現中心。
三、 結束語
好了,今天就寫到這裡了,這個是介於分散式和微服務之間的一個很簡單的架構實現,雖然很簡單,但是我們所要表達的思想和所要獲取到的東西我們已經得到了。什麼東西都是由簡入繁的,下一期,繼續開始我們的有關微服務的進化吧。努力吧,每天進步一點