栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

.net gRPC Client 集中配置、反射加载

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

.net gRPC Client 集中配置、反射加载

1. 目标

    在调用 gRPC 服务时,避免重复写FromAddress(url)代码,而是通过配置文件反射加载 gRPC 服务。

2. 硬编码写法

    假设我们在 proto 文件中定义了叫 ServiceRPC 的 service,.net gRPC 会自动创建一个 ServiceRPCClient 类型,该类的构造函数中需要一个 channel,如下代码所示。

using Grpc.Net.Client;

namespace XXX
{
    public class Program
    {
        var channel = GrpcChannel.ForAddress("http://localhost:5000");
        var service = new ServiceRPC.ServiceRPCClient(channel);
    }    
}

    上述写法的弊端:

  1.  代码中需要出现url,url即便通过配置文件加载,当服务多起来后也会配置重复化、碎片化;
  2.  代码结构高度一致,冗余度高;
  3.  不容易保证 service 对象是单例的,一个进程中可能出现多处创建代码。
3. 高大上的做法

    当有服务的注册、发现、高可用性、监控等大型应用需要时,可以采用 consul 一类的工具,本文的方法是假设服务均为 .net gRPC,服务的注册通过修改配置文件完成,不涉及服务发现、高可用性、监控等。

4. 服务的可配置加载 4.1 配置对象

    分为 Channel 和 ServiceDef 两层。

    Channel 有 Name 属性(服务的进程名) 和 Address属性(如 http://localhost:5000);

    ServiceDef  有 Name 属性(proto文件中定义的服务名);

    另外,Channel 下包含多个 ServiceDef,Channel缓存了GrpcChannel对象,ServiceDef缓存了Instance实例,此实例为单例,只创建一次。   

    public class ServiceDef
    {
        public ServiceDef(string name, Channel channel)
        {
            Name = name;
            Channel = channel;
        }

        public string Name { get; }

        public Type Type { get; set; }

        public object Instance { get; set; }

        public Channel Channel { get; }
    }

    public class Channel
    {
        public Channel(string name, string address)
        {
            Name = name;
            Address = address;
            Services = new List();
        }

        public string Name { get;  }

        public string Address { get;  }

        public List Services { get; }

        public GrpcChannel GrpcChannel { get; set; }
    }
4.2 配置文件格式

    这里我采用了 xml 文件,用 json 也一样。


  
	
		
		
	
  
4.3 XML配置文件读取 
       private static Dictionary GetChannels(string configFile)
       {
            Dictionary result = new Dictionary();
            string ConfigPath = AppDomain.CurrentDomain.BaseDirectory + configFile;
            if (!File.Exists(ConfigPath))
                ConfigPath = configFile;
            XmlDocument doc = new XmlDocument();
            try
            {
                doc.Load(ConfigPath);
            }
            catch 
            {
                return null;
            }
            XmlNode appnode = doc.SelectSingleNode("configuration/gRPC");
            if (appnode != null)
            {
                foreach (XmlNode channelNode in appnode.ChildNodes)
                {
                    var name = channelNode.Attributes["name"].InnerText;
                    var address = channelNode.Attributes["address"].InnerText;
                    var channel = new Channel(name, address);
                    result[name] = channel;
                    foreach (XmlNode serviceNode in channelNode.ChildNodes)
                    {
                        if (serviceNode.Name == "service")
                        {
                            name = serviceNode.Attributes["name"].InnerText;
                            var serviceDef = new ServiceDef(name, channel);
                            channel.Services.Add(serviceDef);
                        }
                    }
                }
            }

            return result;
        }
4.4 反射创建服务实例
        private static object GetService(ServiceDef def, Type typ)
        {
            if (def.Instance != null) return def.Instance;
            string url = def.Channel.Address;
            if(def.Channel.GrpcChannel == null)
            {
                def.Channel.GrpcChannel = GrpcChannel.ForAddress(url);
            }
            var obj = Activator.CreateInstance(typ, def.Channel.GrpcChannel);
            def.Instance = obj;
            return obj;
        }
4.5 客户端调用的Get方法

    注意,代码中 typename 形如 ServiceRPCClient,Client这6个字符需要去掉才是 proto 文件中定义的 service 的名字。

        public static T Get()
        {
			//创建对象
			var typename = typeof(T).Name;
			var servicename = typename.Remove(typename.Length - 6, 6);//remove "Client"
			ServiceDef def = null;
			if (_ServiceDefs == null)
			{
				throw new Exception("Config file failed to initialze");
			}
			if (_ServiceDefs.ContainsKey(servicename))
			{
				def = _ServiceDefs[servicename];
			}
			if (def != null)
			{
				var obj = GetService(def, typeof(T)); //返回查询到的服务对象
				return (T)obj;
			}
			else
			{
				throw new Exception($"Can not get Service, no service config in config file, type:{typeof(T)}!");
			}
        }
4.6 客户端调用方法

    客户端调用只需要一句话,不涉及url,做到了极简。

    var service = RPCFactory.Get();
5. 总结

    在多 gRPC 服务的场景中,服务的 url 和 实例的创建需要硬编码,或者在调用进程的配置文件中配置 url 带来配置碎片化和重复的问题。本文提供了一种简单的 gRPC 服务集中配置和统一创建的方法,使调用服务的客户端只要一句话。

6.补充:调用不了 gRPC http 服务

gRPC 服务只提供 http 地址,没有提供 https 地址时,调用报错:

Grpc.Core.RpcException: Status(StatusCode="Internal", Detail="Error starting gRPC call. HttpRequestException: An error occurred while sending the request. IOException: The response ended prematurely.", DebugException="System.Net.Http.HttpRequestException: An error occurred while sending the request.
 ---> System.IO.IOException: The response ended prematurely.

根据微软官方文档​​​​​​​,需要在 GetService 之前(如首次调用GetChannels方法时)加上:

// This switch must be set before creating the GrpcChannel/HttpClient.
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/954785.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号