2. 服务间的通讯—DispatchProxy 实现 RESTful API 客户端

服务间的通讯—DispatchProxy 实现 RESTful API 客户端

在微服务架构中,很多时候需要调用很多服务才能完成一项功能。服务之间如何互相调用就变成微服务架构中的一个关键问题。微服务内部间的调用有多种方式,包括最基本的 RESTful Api,远程过程调用 RPC、基于消息队列的事件驱动。

在工作实践中,我们微服务间调用最基本的方式还是 RESTful Api,而这时就会发现如果每次请求都需要自己构建 HttpClient,硬编码去调用 api,会带来非常多的重复性的工作,而且对业务代码的侵入也非常严重。这就需要一个声明式 REST 客户端来解决问题。

我们通过 DispatchProxy 动态代理的方式模仿 Feign 实现这样一个声明式 REST 客户端,结合服务发现,通过定义接口,打上特性的方式实现异步调用 Http api。

1. DispatchProxy

DispatchProxy是一个抽象类,是 .Net Core下的一个动态代理的类。它提供了实例化代理对象和处理器方法分发的机制。DispatchProxy 类的典型用法如下:

var proxy = DispatchProxy.Create<IInterface, Proxy>();

DispatchProxy 的强大功能:它允许我们创建一个 Proxy 类型,该类型可以像 IInterface 一样被使用,且不需要真正"实现它"。

具体示例如下:

首先定义一个接口

public interface IService
{
	public Task<string> GetAsync(string name);
}

通过继承 DispatchProxy 实现一个代理类

public class ServiceDispatchProxy : DispatchProxy
{
	protected override object Invoke(MethodInfo targetMethod, object[] args)
	{
	    return Task.FromResult($"{targetMethod.Name}: {args[0]}");
	}
}

通过 DispatchProxy.Create<T, TProxy>() 创建代理

var service = DispatchProxy.Create<IService, ServiceDispatchProxy>();
var result = service.GetAsync("test").Result;
Console.WriteLine(result);
Console.ReadKey();

可以看到:
在这里插入图片描述
这里调用 IService 的 GetAsync 方法时,实际上是执行了 ServiceDispatchProxy 中的 Invoke,在没有实现IService 的情况下动态的实现了 GetAsync 中的逻辑。

2. 声明式 REST 客户端实现

  1. 代码解析

在这里插入图片描述
AbstactFeignProxy<T> 实现 DispatchProxy 抽象类,在 Invoke 中通过反射执行调用 api 接口的逻辑,这里因为需要兼容异步和同步方法,所以对于 Get 和 Post 都区分了两种方式(由于生产场景中 Rest api 调用只使用了Get和 Post 两种方式,这里只针对两种方式提供代理)。

protected override object Invoke(MethodInfo targetMethod, object[] args)
{
     var method = GetOrCacheMethod(targetMethod);

     //代理请求
     if (method.Method == HttpMethod.Get)
     {
         var methodName = method.IsAsync ? "GetAsync" : "Get";
         // 反射获取方法Get方法,注意这里需要指定AbstactHystrixFeignProxy的泛型类型
         var getMethod = typeof(AbstactFeignProxy<>).MakeGenericType(typeof(T))
             .GetMethod(methodName);
         var curMethod = getMethod.MakeGenericMethod(method.ReturnType);
         var result = curMethod.Invoke(this, new object[] { method, args });
         return result;
     }
     else
     {
         var methodName = method.IsAsync ? "PostAsync" : "Post";
         var postMethod = typeof(AbstactFeignProxy<>).MakeGenericType(typeof(T))
             .GetMethod(methodName);
         var curMethod = postMethod.MakeGenericMethod(method.ReturnType);
         if (args != null && args.Length == 1)
         {
             var res = curMethod.Invoke(this, new object[] { method, args[0] });
             return res;
         }
         var result = curMethod.Invoke(this, new object[] { method, null });
         return result;
     }
 }

其中,GetOrCacheMethod 方法是对代理接口方法的解析,包括通过自定义的标签判断接口的代理方式,并且从中提取接口调用必须的参数

protected FeignMethodInfo GetOrCacheMethod(MethodInfo targetMethod)
{
    // 同一个接口的不同参数进行缓存
    return cachedMethod.GetOrAdd(targetMethod.Name + targetMethod.GetParameters()
        .Select(p => p.ParameterType.Name).JoinAsString("_"), 
        () =>
        {
            var attr = targetMethod.GetCustomAttribute<MappingAttribute>(true);
            if (attr == null)
            {
                throw new InvalidOperationException("请设置代理方式!");
            }

            var classAttr = typeof(T).GetCustomAttribute<FeignClientAttribute>();
            var fallback = classAttr?.FallbackImpl;
            var serviceName = classAttr?.Name;
            var url = attr.Route;
            var urlTemplate = "";
            for (int i = 0; i < targetMethod.GetParameters().Length; i++)
            {
                urlTemplate += targetMethod.GetParameters()[i].Name + "={" + i + "}&";
            }
            urlTemplate = "?" + urlTemplate.Trim('&');
            return new FeignMethodInfo()
            {
                Url = url,
                ServiceName = serviceName,
                Method = attr is PostMappingAttribute ? HttpMethod.Post : HttpMethod.Get,
                ReturnType = targetMethod.ReturnType.BaseType == typeof(Task) ?
                targetMethod.ReturnType.GetGenericArguments().FirstOrDefault()
                : targetMethod.ReturnType,
                IsAsync = targetMethod.ReturnType.BaseType == typeof(Task),
                Fallback = fallback,
                UrlTemplate = urlTemplate,
                Parameters = targetMethod.GetParameters()
            };
        });
}

在 Get 和 Post 方法中,只进行了请求参数的统一处理,又通过抽象方法将具体的请求操作交给具体的实现类实现,如图中的 HttpClientProxy,这里是因为考虑到普通 api 和微服务间的 api 在请求时是有区别的,微服务间的 api 请求需要使用服务名称通过服务发现获取服务地址。同样的,对于 api 结果的处理也会不一样,这里提供了 IApiResultProcessor 接口,可以通过实现它,将默认结果处理器替换掉,自行提供结果处理逻辑。

public async Task<TResult> GetAsync<TResult>(FeignMethodInfo targetMethod, object[] args)
{
    var urlParam = string.Format(targetMethod.UrlTemplate, args);
    if (targetMethod.Parameters.Length == 1 && !targetMethod.Parameters[0].ParameterType.IsPrimitive
        && targetMethod.Parameters[0].ParameterType != typeof(string))
    {
        urlParam = ModelToUriParam(args[0]);
    }
    var url = targetMethod.Url + urlParam;
    var httpRes = await GetRequestAsync(targetMethod.ServiceName, url, targetMethod);
    return await ApiResultProcessor.Process<TResult>(httpRes);
}

而后,提供 IFeignProxyFactory 代理工厂接口,具体使用时可以通过替换工厂实现类,创建不同的代理类。工厂类创建代理的时候,需要接口一些自定义的标签,如 FeignClientAttribute,通过这些标签来标识哪些类是需要创建代理的。

public class DefaultFeignProxyFactory : IFeignProxyFactory
{
    private IProxyConfiguration _proxyConfiguration;
    public DefaultFeignProxyFactory(IProxyConfiguration proxyConfiguration)
    {
        _proxyConfiguration = proxyConfiguration;
    }

    /// <summary>
    /// 创建代理
    /// </summary>
    /// <returns></returns>
    public virtual T GetProxy<T>()
    {
        Type type = typeof(T);
        if (type.GetCustomAttributes(typeof(FeignClientAttribute), true).Any())
        {
            var proxy = DispatchProxy.Create<T, HttpClientProxy<T>>();
            (proxy as AbstactFeignProxy<T>).ProxyConfiguration = _proxyConfiguration;
            return proxy;
        }
        return default;
    }

    /// <summary>
    /// 创建断路器代理
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public virtual T GetHytrixProxy<T>()
    {
        Type type = typeof(T);
        if (type.GetCustomAttributes(typeof(FeignClientAttribute), true).Any())
        {
            var proxy = DispatchProxy.Create<T, HttpClientHystrixProxy<T>>();
            (proxy as AbstactFeignProxy<T>).ProxyConfiguration = _proxyConfiguration;
            return proxy;
        }
        return default;
    }
}

可以看到这里提供了两种代理类的创建方式,其中的一个代理类是普通的 api 代理,另一个代理类实现了熔断降级功能。

类图中没有反应出来的一个点是,REST 客户端通过容器扩展提供快捷注入的方式,不用调用工厂类自行进行代理类创建。

public static class HttpProxyServiceCollectionExtensions
{
    /// <summary>
    /// 添加 HttpClientProxy 必要依赖
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddHttpClientProxyFactory(this IServiceCollection services)
    {
        services.AddSingleton<IProxyConfiguration, ProxyConfiguration>();
        services.AddSingleton<IFeignProxyFactory, DefaultFeignProxyFactory>();
        services.AddSingleton<IApiResultProcessor, DefaultApiResultProcessor>();
        return services;
    }

    /// <summary>
    /// 注册 HttpClient 代理
    /// </summary>
    /// <typeparam name="T">代理接口类型</typeparam>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddHttpClientProxy<T>(this IServiceCollection services) where T : class
    {
        //注册fallback
        var attr = typeof(T).GetCustomAttributes(typeof(FeignClientAttribute), true).FirstOrDefault();
        if (attr != null && (attr as FeignClientAttribute).FallbackImpl != null)
        {
            services.AddSingleton((attr as FeignClientAttribute).FallbackImpl);
        }
        //注册代理
        services.AddSingleton(serviceProvider =>
        {
            var proxyFactory = serviceProvider.GetService<IFeignProxyFactory>();
            var proxy = proxyFactory.GetHytrixProxy<T>();
            return proxy;
        });
        return services;
    }
}

这里是默认的依赖注入,若是有和其他情况的代理方式需要实现,替换掉几个默认接口即可,如以下与 Nacos结合的服务间调用的代理。

public static class NacosProxyServiceCollectionExtensions
{
    /// <summary>
    /// 添加 Nacos Http 请求代理必要依赖
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddNacosHttpProxyFactory(this IServiceCollection services)
    {
        var configuration = services.GetConfiguration();
        services.AddNacosAspNet(configuration, "nacos");
        services.AddHttpClientProxyFactory();
        services.Replace(new ServiceDescriptor(typeof(IFeignProxyFactory), typeof(NacosFeignProxyFactory), ServiceLifetime.Singleton));
        services.Replace(new ServiceDescriptor(typeof(IApiResultProcessor), typeof(MicroServiceApiResultProcessor), ServiceLifetime.Singleton));
        return services;
    }
}
  1. 如何使用

声明式REST API客户端的使用方式如下:

(1)普通的 api 代理

   1)编写代理接口

	// api接口基地址
	[FeignClient(Name = "http://loalhost:5000")]
	 public interface IService5
	 {
	 	 // api接口路由
	     [GetMapping(Route = "/service1")]
	     Task<string> GetAsync(string name);
	 }

   2)向容器添加依赖注入

public void ConfigeService(IServiceCollection services)
{
	services.AddHttpClientProxyFactory();
	services.AddHttpClientProxy<IService5>();
}

   3)在类中注入使用

public class HomeController
{
	public HomeController(IService5 service)
	{}
}

(2)结合 Nacos 的服务间 api 代理

   1)编写代理接口,注意需要使用 NacosFeignClient 特性

[NacosFeignClient(Name = "App1")]
public interface IService7
{
    [GetMapping(Route = "/Service")]
    Task<string> GetAsync();
}

   2)向容器添加依赖注入

public void ConfigeService(IServiceCollection services)
{
	services.AddNacosHttpProxyFactory();
	services.AddHttpClientProxy<IService5>();
}

对于本文中响应式 REST Api 客户端实现的源码已经上传到 github,仓库地址如下:
https://github.com/25lonelyboy/HttpApiClient

欢迎有兴趣的朋友去看看,希望能给大家提供一些参考。

微服务系列文章:
上一篇:服务发现—Asp.net core 结合consul实现服务发现
下一篇:服务间的通讯-WebApiClient

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值