1. 程式人生 > >廬山真面目之二微服務架構NGINX版本實現

廬山真面目之二微服務架構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 無法自動發現,需要人工修改配置檔案,然後重啟,才可以。
      由於第三點的缺點,是我們這個版本的最大的缺點,我們不可能在龐大的系統中總是通過人力來做這些事。無法自動發現服務,我們需要繼續改進,那就是服務註冊發現中心。

三、  結束語
          好了,今天就寫到這裡了,這個是介於分散式和微服務之間的一個很簡單的架構實現,雖然很簡單,但是我們所要表達的思想和所要獲取到的東西我們已經得到了。什麼東西都是由簡入繁的,下一期,繼續開始我們的有關微服務的進化吧。努力吧,每天進步一點