Dotnet微服务:使用HttpclientFactory实现服务之间的通信
一,为什么要使用IHttpclientFactory
在项目实施过程中,不可避免地需要与其它服务或第三方服务通信,主要方式有二种Http和Rpc。第三方服务一般是以Web Api的方式提供http访问接口,微服务之间通信的话Spring cloud是使用http,框架为feign。而dubbo是使用rpc方式。steeltoe是基于spring cloud的,所以推荐使用http方式。在java技术栈有feign框架可以使用,不用每次请求都去构造请求实例和内容,可以实现像调用本地方法一样调用其它服务,简化了调用流程。.net则可以使用IHttpclientFactory,通过依赖注入简化调用流程,并且IHttpclientFactory启用了Httpclient实例池,不会每次调用都实例化一个Httpclient实例,提高了通讯效率。
二,IHttpclientFactory的四种使用方法
准备:新建两个web api项目模拟两个服务,一个用户管理服务(监听端口8013)和一个订单管理服务(监听端口8014)。
订单管理服务新建一个名为OrderController的api控制器,并新建一个名用getOrder的接口:
[Route("api/[controller]")] [ApiController] public class OrderController : ControllerBase { [HttpPost("GetOrder")] public string GetOrder([FromBody] string name) { return $"get order' ${name} 'from UserService "; } }
下面使用IHttpclientFactory的四种方法在用户管理服务去访问订单管理服务。
1,基本使用方法。
1),在用户管理服务中新建一个名为UserController的api控制器,并新建一个名用getOrder的接口:
[Route("api/[controller]")] [ApiController] public class UserController : ControllerBase { [HttpGet("GetOrder")] public string GetOrder() { return ""; } }
2),Startup中添加Httpclient依赖
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
3),修改Getorder接口
private readonly IHttpClientFactory _clientFactory; public UserController(IHttpClientFactory clientFactory) { this._clientFactory = clientFactory; } [HttpGet("GetOrder")] public async Task<string> GetOrderAsync() { var request = new HttpRequestMessage(HttpMethod.Post,"http://localhost:8014/api/order/getorder"); request.Content = new StringContent(JsonConvert.SerializeObject("test"), System.Text.Encoding.UTF8, "application/json"); using var ret = await _clientFactory.CreateClient().SendAsync(request); ret.EnsureSuccessStatusCode(); return await ret.Content.ReadAsStringAsync(); }
4),访问用户管理服务的getOrder接口
这个使用方法并没有体现出IHttpClientFactory的简便性,主要是为了方便重构。
2,命名客户端
如果其它服务提供了多个接口,并且不同的服务需要不同的配置,如http头,认证方式等,可以使用命令客户端的方式。
1),Startup.ConfigService
public void ConfigureServices(IServiceCollection services) { services.AddDiscoveryClient(Configuration); services.AddHttpClient("orderService", config => { config.BaseAddress = new Uri("http://localhost:8014"); config.DefaultRequestHeaders.Add("OrderHeader", "test"); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
2),修改Getorder接口
[HttpGet("GetOrder")] public async Task<string> GetOrderAsync() { var request = new HttpRequestMessage(HttpMethod.Post,"api/order/getorder"); request.Content = new StringContent(JsonConvert.SerializeObject("test"), System.Text.Encoding.UTF8, "application/json"); using var ret = await _clientFactory.CreateClient("orderService").SendAsync(request); ret.EnsureSuccessStatusCode(); return await ret.Content.ReadAsStringAsync(); }
3,类型化客户端
命名客户端调用时不够优雅,类型化客户端方式使用接口实现,更接近于feign的代码风格
1),新建一个名为OrderService的类
public class OrderServiceRemoting { public HttpClient client { get; } public OrderServiceRemoting(HttpClient client) { client.BaseAddress = new Uri("http://localhost:8014"); client.DefaultRequestHeaders.Add("OrderServiceHeader", "test"); this.client = client; } public async Task<string> GetOrderAsync(string name) { var request = new HttpRequestMessage(HttpMethod.Post, "api/order/getorder"); request.Content = new StringContent(JsonConvert.SerializeObject("test"), System.Text.Encoding.UTF8, "application/json"); using var ret =await client.SendAsync(request); ret.EnsureSuccessStatusCode(); return await ret.Content.ReadAsStringAsync(); } }
2),Startup.ConfigureService
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient<OrderServiceRemoting>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
3),修改Getorder接口
[Route("api/[controller]")] [ApiController] public class UserController : ControllerBase { private readonly OrderServiceRemoting orderServiceRemoting; public UserController(OrderServiceRemoting orderServiceRemoting) { this.orderServiceRemoting = orderServiceRemoting; } [HttpGet("GetOrder")] public async Task<string> GetOrderAsync() { return await orderServiceRemoting.GetOrderAsync("test"); } }
清爽很多了
4,生成的客户端
配合第三方库Refit使用, 将REST API 转换为实时接口。refit库开源地址:https://github.com/reactiveui/refit
1,新建业务接口:ITypedOrderServiceRemoting,并对IServiceCollection进行扩写
public interface ITypedOrderServiceRemoting { [Post("/api/order/getorder")] Task<string> GetOrderAsync([Body(BodySerializationMethod.Serialized)] string name); } public static class TypedOrderServiceRemotingExtention { public static IServiceCollection AddOrderServiceHttpClient( this IServiceCollection serivce) { serivce.AddHttpClient("OrderService", configureClient => { configureClient.BaseAddress = new Uri("http://localhost:8014"); configureClient.DefaultRequestHeaders.Add("OrderServiceHeader", "test"); }).AddTypedClient(C=>Refit.RestService.For<ITypedOrderServiceRemoting>(C)); return serivce; } }
2,Startup中引用TypedOrderServiceRemotingExtention类,调用AddOrderServiceHttpClient
public void ConfigureServices(IServiceCollection services) { services.AddOrderServiceHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
3,修改getOrder接口
[Route("api/[controller]")] [ApiController] public class UserController : ControllerBase { private readonly ITypedOrderServiceRemoting orderServiceRemoting; public UserController(ITypedOrderServiceRemoting orderServiceRemoting) { this.orderServiceRemoting = orderServiceRemoting; } [HttpGet("GetOrder")] public async Task<string> GetOrderAsync() { return await orderServiceRemoting.GetOrderAsync("test"); } }
三,配置Eureka,服务之间使用IHttpclientFactory进行通信
上述的方法是指定了请求地址,如果地址改变就需要重新修改请求代码。Eureka保存有各个服务的注册地址及端口,可以利用这个特点与上述请求方式相结合,而且还有个很大的优点:负载均衡。如果有多个相同功能的服务注册到Eureka,调用方可以指定负载方案(默认为随机调用方案)调用这个服务的接口。
1,将上述两个服务注册到Eureka,暗体方法见前文:Steeltoe集成Eureka
2,以上述的“类型化客户端”方法举例,修改OrderServiceRemoting中client的baseaddress
public OrderServiceRemoting(HttpClient client) { client.BaseAddress = new Uri("http://eureka-order-service"); client.DefaultRequestHeaders.Add("OrderServiceHeader", "test"); this.client = client; }
其中eureka-order-service是订单服务注册到eureka的实例名:见第一步中的附图。
3,Startup.cs
public void ConfigureServices(IServiceCollection services) { services.AddDiscoveryClient(Configuration); services.AddTransient<DiscoveryHttpMessageHandler>(); services.AddHttpClient("orderservice").AddServiceDiscovery().AddTypedClient<OrderServiceRemoting>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
4,修改用户管理服务的getOrder接口改为“类型化客户端”方式调用
private readonly OrderServiceRemoting orderServiceRemoting; public UserController(OrderServiceRemoting orderServiceRemoting) { this.orderServiceRemoting = orderServiceRemoting; } [HttpGet("GetOrder")] public async Task<string> GetOrderAsync() { return await orderServiceRemoting.GetOrderAsync("test"); }
5,负载均衡测试,再新建一个web api项目监听端口8015并注册到Eureka,注册配置信息除eureka.instance.port和eureka.instance.instanceId外与eureka-order-service相同,eureka.instance.port值为本服务监听端口:8015。eureka.instance.instanceId为:eureka-order-service-2。并与订单管理服务一样新建getOrder接口
[HttpPost("GetOrder")] public string GetOrder([FromBody] string name) { return $"get order' ${name} 'from UserService 2"; }
6,请求用户管理服务的getOrder接口,可以看到负载均衡已经生效。