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

原文地址:https://www.cnblogs.com/liujiabing/p/13819676.html