一、简介
在上一篇文章《庐山真面目之一微服务的简介和技术栈》中,我们已经探讨了微服务的来龙去脉,也说了想要实现微服务架构所需要的技术栈,今天我们开始实现一个微服务,当然这个实现是简化版本的,在这个版本里面也不涉及 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 无法自动发现,需要人工修改配置文件,然后重启,才可以。
由于第三点的缺点,是我们这个版本的最大的缺点,我们不可能在庞大的系统中总是通过人力来做这些事。无法自动发现服务,我们需要继续改进,那就是服务注册发现中心。
三、 结束语
好了,今天就写到这里了,这个是介于分布式和微服务之间的一个很简单的架构实现,虽然很简单,但是我们所要表达的思想和所要获取到的东西我们已经得到了。什么东西都是由简入繁的,下一期,继续开始我们的有关微服务的进化吧。努力吧,每天进步一点点。