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接口,可以看到负载均衡已经生效。


浙公网安备 33010602011771号