zoukankan      html  css  js  c++  java
  • 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接口,可以看到负载均衡已经生效。

  • 相关阅读:
    ZooKeeper简介
    Kylin简介
    MVC之Ajax
    mvc扩展HtmlHelper功能
    history.js 一个无刷新就可改变浏览器栏地址的插件(不依赖jquery)
    在ASP.NET MVC中,使用Bundle来打包压缩js和css
    OAuth的MVC实现(微软)
    自定义Exception:MVC抛出自定义异常,并以Json方式返回
    Ubuntu 下常用命令
    科技界、IT届的外号
  • 原文地址:https://www.cnblogs.com/liujiabing/p/13819676.html
Copyright © 2011-2022 走看看