ASP.Net Core的基本配置
.在VS中调试的时候有很多修改Web应用运行端口的方法。但是在开发、调试微服务应用的时候可能需要同时在不同端口上开启多个服务器的实例,因此下面主要看看如何通过命令行指定Web应用的端口(默认5000)
可以通过设置临时环境变量ASPNETCORE URLS来改变默认的端口、域名,也就是执行 dotnet xxx.dll之前执行set ASPNETCORE_URLS=http://127.0.0.1:5001来设置环境变量。
如果需要在程序中读取端口、域名(后续服务治理会用到) ,用ASPNETCORE URLS环境变量就不太方便,可以自定义配置文件, 自己读取设置。
修改Program.cs
public static IWebHost BuildWebHost(string[] args) { var config = new ConfigurationBuilder() .AddCommandLine(args) .Build(); String ip = config["ip"]; String port = config["port"]; return WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseUrls($"http://{ip}:{port}") .Build(); }
然后启动的时候:
dotnet WebApplication5.dll--ip 127.0.0.1-port 8889
.Net Core因为跨平台,所以可以不依赖于IIS运行了。可以用.Net Core内置的kestrel服务器运行网站,当然真正面对终端用户访问的时候一般通过Nginx等做反向代理。
Consul服务治理发现
Consul是注册中心,服务提供者、服务消费者等都要注册到Consul中,这样就可以实, ,现服务提供者、服务消费者的隔离。
除了Consul之外,还有Eureka,Zookeeper等类似软件。
Consul服务安装
consul下载地址https://www.consul.io/
运行
consul.exe agent -dev
这是开发环境测试,生产环境要建集群,要至少一台Server,多台Agent consul
监控页面http://127.0.0.1:8500/consult
主要做三件事:提供服务到ip地址的注册;提供服务到ip地址列表的查询;对提供服务方的健康检查(HealthCheck) ;
.Net Core连接Consul
新建Asp.Net Core WebAPI项目WebApplication4,安装Consul nuget包
Install-Package Consul
Rest服务的准备
先使用使用默认生成的ValuesController做测试
再提供一个HealthController.cs
[Route("api/Health")] public class HealthController : Controller { [HttpGet] public IActionResult Get() { return Ok("ok"); } }
服务器从命令行中读取ip和端口
让Rest服务注册到Consul中
Startup.cs:
using Consul; public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //... ... app.UseMvc(); String ip = Configuration["ip"];//部署到不同服务器的时候不能写成127.0.0.1或者0.0.0.0,因为这是让服务消费者调用的地址 Int32 port = Int32.Parse(Configuration["port"]); //向consul注册服务 ConsulClient client = new ConsulClient(ConfigurationOverview); Task<WriteResult> result= client.Agent.ServiceRegister(new AgentServiceRegistration() { ID = "apiservice1" + Guid.NewGuid(),//服务编号,不能重复,用Guid最简单 Name = "apiservice1",//服务的名字 Address = ip,//我的ip地址(可以被其他应用访问的地址,本地测试可以用127.0.0.1,机房环境中一定要写自己的内网ip地址) Port = port,//我的端口 Check = new AgentServiceCheck() { DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务停止多久后反注册 Interval =TimeSpan.FromSeconds(10),//健康检查时间间隔,或者称为心跳间隔 HTTP =$"http://{ip}:{port}/api/health",//健康检查地址, Timeout =TimeSpan.FromSeconds(5) } }); } private static void ConfigurationOverview(ConsulClientConfiguration obj) { obj.Address = new Uri("http://127.0.0.1:8500"); obj.Datacenter = "dc1"; }
注意不同实例一定要用不同的Id,即使是相同服务的不同实例也要用不同的ld,上面的代码用Guid做Id,确保不重复。相同的服务用相同的Name. Address、 Port是供服务消 "费者访问的服务器地址(或者IP地址)及端口号。Check则是做服务健康检查的(解释一下)。
在注册服务的时候还可以通过AgentServiceRegistration的Tags属性设置额外的标签。
通过命令行启动两个实例
dotnet WebApplication4.dll --ip 127.0.0.1 --port 5001 dotnet WebApplication4.dll --ip 127.0.0.1 --port 5002
应用停止的时候反注册。
服务查询
新建控制台项目queryconsul1,并引用nuget包
using Consul; static void Main(string[] args) { using (ConsulClient consulClient = new ConsulClient(c=>c.Address=new Uri("http://127.0.0.1:8500"))) { //consulClient.Agent.Services()获取consul中注册的所有的服务 Dictionary<String,AgentService> services = consulClient.Agent.Services().Result.Response; foreach (KeyValuePair<String, AgentService> kv in services) { Console.WriteLine($"key={kv.Key},{kv.Value.Address},{kv.Value.ID},{kv.Value.Service},{kv.Value.Port}"); } //获取所有服务名字是"apiservice1"所有的服务 var agentServices = services.Where(s => s.Value.Service.Equals("apiservice1", StringComparison.CurrentCultureIgnoreCase)) .Select(s => s.Value); //根据当前TickCount对服务器个数取模,“随机”取一个机器出来,避免“轮询”的负载均衡策略需要计数加锁问题 var agentService = agentServices.ElementAt(Environment.TickCount%agentServices.Count()); Console.WriteLine($"{agentService.Address},{agentService.ID},{agentService.Service},{agentService.Port}"); } Console.ReadKey(); }
编写服务消费者
创建类库RestTools
添加Consul nuget包引用
Install-Package Consul
Install-Package Newtonsoft.Json
创建消息返回类ResponseEntity.cs
public class ResponseEntity<T> { /// <summary> /// 返回状态码 /// </summary> public HttpStatusCode StatusCode { get; set; } /// <summary> /// 返回的json反序列化出来的对象 /// </summary> public T Body { get; set; } /// <summary> /// 响应的报文头 /// </summary> public HttpResponseHeader Headers { get; set; } }
创建转发消息类RestTemplate.cs
public class RestTemplate { private String consulServerUrl; public RestTemplate(String consulServerUrl) { this.consulServerUrl = consulServerUrl; } /// <summary> /// 获取服务的一个IP地址 /// </summary> /// <param name="serviceName">consul服务IP</param> /// <returns></returns> private async Task<String> ResolveRootUrlAsync(String serviceName) { using (var consulClient = new ConsulClient(c => c.Address = new Uri(consulServerUrl))) { var services = (await consulClient.Agent.Services()).Response; var agentServices = services.Where(s => s.Value.Service.Equals(serviceName, StringComparison.InvariantCultureIgnoreCase)).Select(s => s.Value); //TODO:注入负载均衡策略 var agentService = agentServices.ElementAt(Environment.TickCount % agentServices.Count()); //根据当前TickCount对服务器个数取模,“随机”取一个机器出来,避免“轮询”的负载均衡策略需要计数加锁问题 return agentService.Address + ":" + agentService.Port; } } /// <summary> /// //把http://apiservice1/api/values转换为http://192.168.1.1:5000/api/values /// </summary> private async Task<String> ResolveUrlAsync(String url) { Uri uri = new Uri(url); String serviceName = uri.Host;//apiservice1 String realRootUrl = await ResolveRootUrlAsync(serviceName); return uri.Scheme + "://" + realRootUrl + uri.PathAndQuery; } /// <summary> /// Get请求转换 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="url">请求地址</param> /// <param name="requestHeaders">请求头</param> /// <returns></returns> public async Task<ResponseEntity<T>> GetForEntityAsync<T>(String url, HttpRequestHeaders requestHeaders = null) { using (HttpClient httpClient=new HttpClient()) { HttpRequestMessage requestMsg = new HttpRequestMessage(); if (requestHeaders!=null) { foreach (var header in requestHeaders) { httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); } } requestMsg.Method = HttpMethod.Get; //http://apiservice1/api/values转换为http://192.168.1.1:5000/api/values requestMsg.RequestUri = new Uri(await ResolveUrlAsync(url)); var result = await httpClient.SendAsync(requestMsg); ResponseEntity<T> responseEntity = new ResponseEntity<T>(); responseEntity.StatusCode = result.StatusCode; String bodyStr = await result.Content.ReadAsStringAsync(); responseEntity.Body = JsonConvert.DeserializeObject<T>(bodyStr); responseEntity.Headers = responseEntity.Headers; return responseEntity; } } }