一、简介
         在上一篇文章《庐山真面目之一微服务的简介和技术栈》中,我们已经探讨了微服务的来龙去脉,也说了想要实现微服务架构所需要的技术栈,今天我们开始实现一个微服务,当然这个实现是简化版本的,在这个版本里面也不涉及 Docker、K8S等的东西,我们逐步演化,今天这一期只是实现一个NGINX版本的微服务的功能。

       1、说明
             有关微服务架构所涉及的技术太多,无法再一篇文章内讨论完全,所以,我们就分多期来说,每期都递进相关的技术,然后,一步一步的演化而来。如果您是大牛,就可以直接跳过,因为这些东西相对于您来说,这个太简单了。特别说明,这里的所有代码都经过测试,所以大家可以放心使用。

       2、开发环境
            
以下就是开发环境,不用多说,都很简单,一看就知道。
            (1)、开发工具:Visual Studio 2019
            (2)、开发语言:C#
            (3)、开发平台:Net Core3.1,跨平台。
            (4)、网关服务:NGINX,专注于高性能网关。
            (5)、操作系统:Windows 10,64 bit。

        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 --@item.Name  --@item.Role </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="huangFeiHong@sina.com", Role="Admin", LoginTime=DateTime.Now },
23             new User {ID=2,Name="洪熙官",Account="HongXiGuan",Password="HongXiGuan54667",Email="HongXiGuan@sina.com", Role="Admin", LoginTime=DateTime.Now.AddDays(-5) },
24             new User {ID=3,Name="方世玉",Account="FangShiYu",Password="FangShiYu112233",Email="fangShiYu@163.com", Role="Admin", LoginTime=DateTime.Now.AddDays(-30) },
25             new User {ID=4,Name="苗翠花",Account="MiaoCuiHua",Password="MiaoCuiHua887766",Email="miaoCuiHua@sohu.com", Role="Admin", LoginTime=DateTime.Now.AddDays(-90) },
26             new User {ID=5,Name="严咏春",Account="YanYongChun",Password="YanYongChun09392",Email="yanYongChun@263.com", 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 无法自动发现,需要人工修改配置文件,然后重启,才可以。
      由于第三点的缺点,是我们这个版本的最大的缺点,我们不可能在庞大的系统中总是通过人力来做这些事。无法自动发现服务,我们需要继续改进,那就是服务注册发现中心。

三、  结束语
   
      好了,今天就写到这里了,这个是介于分布式和微服务之间的一个很简单的架构实现,虽然很简单,但是我们所要表达的思想和所要获取到的东西我们已经得到了。什么东西都是由简入繁的,下一期,继续开始我们的有关微服务的进化吧。努力吧,每天进步一点点。

posted on 2020-11-07 10:59  可均可可  阅读(2038)  评论(2编辑  收藏  举报