ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

.net gRPC 服务的配置、加载

2021-07-22 10:57:33  阅读:198  来源: 互联网

标签:name gRPC string Channel var net public def 加载


目标

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

硬编码写法

    假设我们在 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 对象是单例的,一个进程中可能出现多处创建代码。

高大上的做法

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

服务的可配置加载

配置对象

    分为 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<ServiceDef>();
        }

        public string Name { get;  }

        public string Address { get;  }

        public List<ServiceDef> Services { get; }

        public GrpcChannel GrpcChannel { get; set; }
    }

配置文件格式

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

<?xml version="1.0" encoding="utf-8"?>
  <gRPC>
	<channel name = "Process" address="http://localhost:5000">
		<service name="ServiceRPC"/>
		<service name="ServiceRPC2"/>
	</channel>
  </gRPC>
</configuration>

XML配置文件读取 

       private static Dictionary<string, Channel> GetChannels(string configFile)
       {
            Dictionary<string, Channel> result = new Dictionary<string, Channel>();
            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;
        }

反射创建服务实例

        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;
        }

客户端调用的Get方法

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

        public static T Get<T>()
        {
			//创建对象
			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)}!");
			}
        }

客户端调用方法

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

    var service = RPCFactory.Get<ServiceRPC.ServiceRPCClient>();

总结

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

标签:name,gRPC,string,Channel,var,net,public,def,加载
来源: https://blog.csdn.net/xhydongda/article/details/118992627

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有