基于.net core 微服务的另类实现
基于.net core 的微服务,网上很多介绍都是千篇一律基于类似webapi,通过http请求形式进行访问,但这并不符合大家使用习惯.如何像形如[ GetService<IOrderService>().SaveOrder(orderInfo)]的方式, 调用远程的服务,如果你正在为此苦恼, 本文或许是一种参考.
- 背景- 原项目基于传统三层模式组织代码逻辑,随着时间的推移,项目内各模块逻辑互相交织,互相依赖,维护起来较为困难.为此我们需要引入一种新的机制来尝试改变这个现状,在考察了 Java spring cloud/doubbo, c# wcf/webapi/asp.net core 等一些微服务框架后,我们最终选择了基于 .net core + Ocelot 微服务方式. 经过讨论大家最终期望的项目结果大致如下所示.  - 但原项目团队成员已经习惯了基于接口服务的这种编码形式, 让大家将需要定义的接口全部以http 接口形式重写定义一遍, 同时客户端调用的时候, 需要将原来熟悉的形如 XXService.YYMethod(args1, args2) 直接使用通过 "."出内部成员,替换为让其直接写 HttpClient.Post("url/XX/YY",”args1=11&args2=22”)的形式访问远程接口,确实是一件十分痛苦的事情. 
- 问题提出- 基于以上, 如何通过一种模式来简化这种调用形式, 继而使大家在调用的时候不需要关心该服务是在本地(本地类库依赖)还是远程, 只需要按照常规方式使用即可, 至于是直接使用本地服务还是通过http发送远程请求,这个都交给框架处理.为了方便叙述, 本文假定以销售订单和用户服务为例. 销售订单服务对外提供一个创建订单的接口.订单创建成功后, 调用用户服务更新用户积分.UML参考如下.  
- 问题转化
- 在客户端,通过微服务对外公开的接口,生成接口代理, 即将接口需要的信息[接口名/方法名及该方法需要的参数]包装成http请求向远程服务发起请求.
- 在微服务http接入段, 我们可以定义一个统一的入口,当服务端收到请求后,解析出接口名/方法名及参数信息,并创建对应的实现类,从而执行接口请求,并将返回值通过http返回给客户端.
- 最后,客户端通过类似 AppRuntims.Instance.GetService<IOrderService>().SaveOrder(orderInfo) 形式访问远程服务创建订单.
- 数据以json格式传输.
- 解决方案及实现
- 为了便于处理,我们定义了一个空接口IApiService,用来标识服务接口. 
- 远程服务客户端代理 - public class RemoteServiceProxy : IApiService 
 {
 public string Address { get; set; } //服务地址private ApiActionResult PostHttpRequest(string interfaceId, string methodId, params object[] p)
 {
 ApiActionResult apiRetult = null;
 using (var httpClient = new HttpClient())
 {
 var param = new ArrayList(); //包装参数 foreach (var t in p)
 {
 if (t == null)
 {
 param.Add(null);
 }
 else
 {
 var ns = t.GetType().Namespace;
 param.Add(ns != null && ns.Equals("System") ? t : JsonConvert.SerializeObject(t));
 }
 }
 var postContentStr = JsonConvert.SerializeObject(param);
 HttpContent httpContent = new StringContent(postContentStr);
 if (CurrentUserId != Guid.Empty)
 {
 httpContent.Headers.Add("UserId", CurrentUserId.ToString());
 }
 httpContent.Headers.Add("EnterpriseId", EnterpriseId.ToString());
 httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var url = Address.TrimEnd('/') + $"/{interfaceId}/{methodId}";
 AppRuntimes.Instance.Loger.Debug($"httpRequest:{url},data:{postContentStr}"); var response = httpClient.PostAsync(url, httpContent).Result; //提交请求 if (!response.IsSuccessStatusCode)
 {
 AppRuntimes.Instance.Loger.Error($"httpRequest error:{url},statuscode:{response.StatusCode}");
 throw new ICVIPException("网络异常或服务响应失败");
 }
 var responseStr = response.Content.ReadAsStringAsync().Result;
 AppRuntimes.Instance.Loger.Debug($"httpRequest response:{responseStr}"); apiRetult = JsonConvert.DeserializeObject<ApiActionResult>(responseStr);
 }
 if (!apiRetult.IsSuccess)
 {
 throw new BusinessException(apiRetult.Message ?? "服务请求失败");
 }
 return apiRetult;
 } //有返回值的方法代理
 public T Invoke<T>(string interfaceId, string methodId, params object[] param)
 {
 T rs = default(T); var apiRetult = PostHttpRequest(interfaceId, methodId, param); try
 {
 if (typeof(T).Namespace == "System")
 {
 rs = (T)TypeConvertUtil.BasicTypeConvert(typeof(T), apiRetult.Data);
 }
 else
 {
 rs = JsonConvert.DeserializeObject<T>(Convert.ToString(apiRetult.Data));
 }
 }
 catch (Exception ex)
 {
 AppRuntimes.Instance.Loger.Error("数据转化失败", ex);
 throw;
 }
 return rs;
 } //没有返回值的代理
 public void InvokeWithoutReturn(string interfaceId, string methodId, params object[] param)
 {
 PostHttpRequest(interfaceId, methodId, param);
 }
 }
- 远程服务端http接入段统一入口 - [Route("api/svc/{interfaceId}/{methodId}"), Produces("application/json")]
 public async Task<ApiActionResult> Process(string interfaceId, string methodId)
 {
 Stopwatch stopwatch = new Stopwatch();
 stopwatch.Start();
 ApiActionResult result = null;
 string reqParam = string.Empty;
 try
 {
 using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
 {
 reqParam = await reader.ReadToEndAsync();
 }
 AppRuntimes.Instance.Loger.Debug($"recive client request:api/svc/{interfaceId}/{methodId},data:{reqParam}"); ArrayList param = null;
 if (!string.IsNullOrWhiteSpace(reqParam))
 {
 //解析参数
 param = JsonConvert.DeserializeObject<ArrayList>(reqParam);
 }
 //转交本地服务处理中心处理
 var data = LocalServiceExector.Exec(interfaceId, methodId, param);
 result = ApiActionResult.Success(data);
 }
 catch BusinessException ex) //业务异常
 {
 result = ApiActionResult.Error(ex.Message);
 }
 catch (Exception ex)
 {
 //业务异常
 if (ex.InnerException is BusinessException)
 {
 result = ApiActionResult.Error(ex.InnerException.Message);
 }
 else
 {
 AppRuntimes.Instance.Loger.Error($"调用服务发生异常{interfaceId}-{methodId},data:{reqParam}", ex);
 result = ApiActionResult.Fail("服务发生异常");
 }
 }
 finally
 {
 stopwatch.Stop();
 AppRuntimes.Instance.Loger.Debug($"process client request end:api/svc/{interfaceId}/{methodId},耗时[ {stopwatch.ElapsedMilliseconds} ]毫秒");
 }
 //result.Message = AppRuntimes.Instance.GetCfgVal("ServerName") + " " + result.Message;
 result.Message = result.Message;
 return result;
 }
- 本地服务中心通过接口名和方法名,找出具体的实现类的方法,并使用传递的参数执行,ps:因为涉及到反射获取具体的方法,暂不支持相同参数个数的方法重载.仅支持不同参数个数的方法重载. - public static object Exec(string interfaceId, string methodId, ArrayList param) 
 {
 var svcMethodInfo = GetInstanceAndMethod(interfaceId, methodId, param.Count);
 var currentMethodParameters = new ArrayList(); for (var i = ; i < svcMethodInfo.Paramters.Length; i++)
 {
 var tempParamter = svcMethodInfo.Paramters[i]; if (param[i] == null)
 {
 currentMethodParameters.Add(null);
 }
 else
 {
 if (!tempParamter.ParameterType.Namespace.Equals("System") || tempParamter.ParameterType.Name == "Byte[]")
 {
 currentMethodParameters.Add(JsonConvert.DeserializeObject(Convert.ToString(param[i]), tempParamter.ParameterType)
 }
 else
 {
 currentMethodParameters.Add(TypeConvertUtil.BasicTypeConvert(tempParamter.ParameterType, param[i]));
 }
 }
 } return svcMethodInfo.Invoke(currentMethodParameters.ToArray());
 } private static InstanceMethodInfo GetInstanceAndMethod(string interfaceId, string methodId, int paramCount)
 {
 var methodKey = $"{interfaceId}_{methodId}_{paramCount}";
 if (methodCache.ContainsKey(methodKey))
 {
 return methodCache[methodKey];
 }
 InstanceMethodInfo temp = null; var svcType = ServiceFactory.GetSvcType(interfaceId, true);
 if (svcType == null)
 {
 throw new ICVIPException($"找不到API接口的服务实现:{interfaceId}");
 }
 var methods = svcType.GetMethods().Where(t => t.Name == methodId).ToList();
 if (methods.IsNullEmpty())
 {
 throw new BusinessException($"在API接口[{interfaceId}]的服务实现中[{svcType.FullName}]找不到指定的方法:{methodId}");
 }
 var method = methods.FirstOrDefault(t => t.GetParameters().Length == paramCount);
 if (method == null)
 {
 throw new ICVIPException($"在API接口中[{interfaceId}]的服务实现[{svcType.FullName}]中,方法[{methodId}]参数个数不匹配");
 }
 var paramtersTypes = method.GetParameters(); object instance = null;
 try
 {
 instance = Activator.CreateInstance(svcType);
 }
 catch (Exception ex)
 {
 throw new BusinessException($"在实例化服务[{svcType}]发生异常,请确认其是否包含一个无参的构造函数", ex);
 }
 temp = new InstanceMethodInfo()
 {
 Instance = instance,
 InstanceType = svcType,
 Key = methodKey,
 Method = method,
 Paramters = paramtersTypes
 };
 if (!methodCache.ContainsKey(methodKey))
 {
 lock (_syncAddMethodCacheLocker)
 {
 if (!methodCache.ContainsKey(methodKey))
 {
 methodCache.Add(methodKey, temp);
 }
 }
 }
 return temp;
- 服务配置,指示具体的服务的远程地址,当未配置的服务默认为本地服务. - [ 
 {
 "ServiceId": "XZL.Api.IOrderService",
 "Address": "http://localhost:8801/api/svc"
 },
 {
 "ServiceId": "XZL.Api.IUserService",
 "Address": "http://localhost:8802/api/svc"
 }
 ]
- AppRuntime.Instance.GetService<TService>()的实现. - private static List<(string typeName, Type svcType)> svcTypeDic; 
 private static ConcurrentDictionary<string, Object> svcInstance = new ConcurrentDictionary<string, object>(); public static TService GetService<TService>()
 {
 var serviceId = typeof(TService).FullName; //读取服务配置
 var serviceInfo = ServiceConfonfig.Instance.GetServiceInfo(serviceId);
 if (serviceInfo == null)
 {
 return (TService)Activator.CreateInstance(GetSvcType(serviceId));
 }
 else
 {
 var rs = GetService<TService>(serviceId + (serviceInfo.IsRemote ? "|Remote" : ""), serviceInfo.IsSingle);
 if (rs != null && rs is RemoteServiceProxy)
 {
 var temp = rs as RemoteServiceProxy;
 temp.Address = serviceInfo.Address; //指定服务地址
 }
 return rs;
 }
 }
 public static TService GetService<TService>(string interfaceId, bool isSingle)
 {
 //服务非单例模式
 if (!isSingle)
 {
 return (TService)Activator.CreateInstance(GetSvcType(interfaceId));
 } object obj = null;
 if (svcInstance.TryGetValue(interfaceId, out obj) && obj != null)
 {
 return (TService)obj;
 }
 var svcType = GetSvcType(interfaceId); if (svcType == null)
 {
 throw new ICVIPException($"系统中未找到[{interfaceId}]的代理类");
 }
 obj = Activator.CreateInstance(svcType); svcInstance.TryAdd(interfaceId, obj);
 return (TService)obj;
 } //获取服务的实现类
 public static Type GetSvcType(string interfaceId, bool? isLocal = null)
 {
 if (!_loaded)
 {
 LoadServiceType();
 }
 Type rs = null;
 var tempKey = interfaceId; var temp = svcTypeDic.Where(x => x.typeName == tempKey).ToList(); if (temp == null || temp.Count == )
 {
 return rs;
 } if (isLocal.HasValue)
 {
 if (isLocal.Value)
 {
 rs = temp.FirstOrDefault(t => !typeof(RemoteServiceProxy).IsAssignableFrom(t.svcType)).svcType;
 }
 else
 {
 rs = temp.FirstOrDefault(t => typeof(RemoteServiceProxy).IsAssignableFrom(t.svcType)).svcType;
 }
 }
 else
 {
 rs = temp[].svcType;
 }
 return rs;
 }
- 为了性能影响,我们在程序启动的时候可以将当前所有的ApiService类型缓存. - public static void LoadServiceType() 
 {
 if (_loaded)
 {
 return;
 }
 lock (_sync)
 {
 if (_loaded)
 {
 return;
 }
 try
 {
 svcTypeDic = new List<(string typeName, Type svcType)>();
 var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
 var dir = new DirectoryInfo(path);
 var files = dir.GetFiles("XZL*.dll");
 foreach (var file in files)
 {
 var types = LoadAssemblyFromFile(file);
 svcTypeDic.AddRange(types);
 }
 _loaded = true;
 }
 catch
 {
 _loaded = false;
 }
 }
 } //加载指定文件中的ApiService实现
 private static List<(string typeName, Type svcType)> LoadAssemblyFromFile(FileInfo file)
 {
 var lst = new List<(string typeName, Type svcType)>();
 if (file.Extension != ".dll" && file.Extension != ".exe")
 {
 return lst;
 }
 try
 {
 var types = Assembly.Load(file.Name.Substring(, file.Name.Length - ))
 .GetTypes()
 .Where(c => c.IsClass && !c.IsAbstract && c.IsPublic);
 foreach (Type type in types)
 {
 //客户端代理基类
 if (type == typeof(RemoteServiceProxy))
 {
 continue;
 } if (!typeof(IApiService).IsAssignableFrom(type))
 {
 continue;
 } //绑定现类
 lst.Add((type.FullName, type)); foreach (var interfaceType in type.GetInterfaces())
 {
 if (!typeof(IApiService).IsAssignableFrom(interfaceType))
 {
 continue;
 }
 //绑定接口与实际实现类
 lst.Add((interfaceType.FullName, type));
 }
 }
 }
 catch
 {
 } return lst;
 }
- 具体api远程服务代理示例
public class UserServiceProxy : RemoteServiceProxy, IUserService 
 {
 private string serviceId = typeof(IUserService).FullName; public void IncreaseScore(int userId,int score)
 {
 return InvokeWithoutReturn(serviceId, nameof(IncreaseScore), userId,score);
 }
 public UserInfo GetUserById(int userId)
 {
 return Invoke<UserInfo >(serviceId, nameof(GetUserById), userId);
 }
 }
- 结语- 经过以上改造后, 我们便可很方便的通过形如 AppRuntime.Instance.GetService<TService>().MethodXX()无感的访问远程服务, 服务是部署在远程还是在本地以dll依赖形式存在,这个便对调用者透明了.无缝的对接上了大家固有习惯. 
 PS: 但是此番改造后, 遗留下来了另外一个问题: 客户端调用远程服务,需要手动创建一个服务代理( 从 RemoteServiceProxy 继承),虽然每个代理很方便写,只是文中提到的简单两句话,但终究显得繁琐, 是否有一种方式能够根据远程api接口动态的生成这个客户端代理呢? 答案是肯定的,因本文较长了,留在下篇再续
 附上动态编译文章链接. https://www.cnblogs.com/xie-zhonglai/p/dynamic_compilation_netstandard.html.
基于.net core 微服务的另类实现的更多相关文章
- 基于.NET CORE微服务框架 -surging的介绍和简单示例 (开源)
		一.前言 至今为止编程开发已经11个年头,从 VB6.0,ASP时代到ASP.NET再到MVC, 从中见证了.NET技术发展,从无畏无知的懵懂少年,到现在的中年大叔,从中的酸甜苦辣也只有本人自知.随着 ... 
- 基于.NET CORE微服务框架 -谈谈surging API网关
		1.前言 对于最近surging更新的API 网关大家也有所关注,也收到了不少反馈提出是否能介绍下Api网关,那么我们将在此篇文章中剥析下surging的Api 网关 开源地址:https://git ... 
- 基于.net core微服务(Consul、Ocelot、Docker、App.Metrics+InfluxDB+Grafana、Exceptionless、数据一致性、Jenkins)
		1.微服务简介 一种架构模式,提倡将单一应用程序划分成一组小的服务,服务之间互相协调.互相配合,为用户提供最终价值.每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(RESTfu ... 
- 基于.NET CORE微服务框架 -谈谈surging的服务容错降级
		一.前言 对于不久开源的surging受到不少.net同学的青睐,也受到.net core学习小组的关注,邀请加入.NET China Foundation以方便国内.net core开源项目的推广, ... 
- 基于.NET CORE微服务框架 -浅析如何使用surging
		1.前言 surging受到大家这么强烈的关注,我感到非常意外,比如有同僚在公司的分享会上分享surging, 还有在博客拿其它的RPC框架,微服务做对比等等,这些举动都让我感觉压力很大,毕竟作为个人 ... 
- 基于.NET CORE微服务框架 -谈谈Cache中间件和缓存降级
		1.前言 surging受到不少.net同学的青睐,也提了不少问题,提的最多的是什么时候集成API 网关,在这里回答大家最近已经开始着手研发,应该在1,2个月内会有个初版API网关,其它像Token身 ... 
- 基于.NET CORE微服务框架 -Api网关服务管理
		1.前言 经过10多天的努力,surging 网关已经有了大致的雏形,后面还会持续更新完善,请大家持续关注研发的动态 最近也更新了surging新的版本 更新内容: 1. 扩展Zookeeper封装2 ... 
- 基于.NET CORE微服务框架 -surging 基于messagepack、protobuffer、json.net 性能对比
		1.前言 surging内部使用的是高性能RPC远程服务调用,如果用json.net序列化肯定性能上达不到最优,所以后面扩展了protobuf,messagepack序列化组件,以支持RPC二进制传输 ... 
- 基于.NET CORE微服务框架 -谈谈surging 的messagepack、protobuffer、json.net 序列化
		1.前言 surging内部使用的是高性能RPC远程服务调用,如果用json.net序列化肯定性能上达不到最优,所以后面扩展了protobuf,messagepack序列化组件,以支持RPC二进制传输 ... 
随机推荐
- 线性规划费用流解法(Bzoj1061: [Noi2008]志愿者招募)
			题面 传送门 Sol 线性规划费用流解法用与求解未知数为非负数的问题 这道题可以列出一堆形如 \(x[i]+x[j]+x[k]+...>=a[p]\) 的不等式 我们强行给每个式子减去一个东西, ... 
- JQuery UI完成自动匹配的的下拉列表步骤
			1.先引入jquery ui相关的js,如:jquery-ui-1.10.4.js 2.写js <script type="text/javascript"> $(fu ... 
- JavaScript的进阶之路(四)理解对象2
			对象的三个属性 原型属性 1.var v={}的原型是Object.prototype;继承了一个constructor属性指代Object()构造函数,实际的原型是constructor.proto ... 
- 《Linux命令行与Shell脚本编程大全第2版》读书笔记
			公司说不准用云笔记了,吓得我赶紧把笔记贴到博客上先..... 近3年前的了,只有一半的章节,后面的没空记录了.... 第1章 可以cat /proc/meminfo文件来观察Linux系统上虚拟内存的 ... 
- 远景WEBGIS平台实现客户端SHP文件加载
			远景WEBGIS平台的研发目前取得新进展,实现客户端shp文件的加载,可以不经过PC上的数据转换工具转换. 远景WEBGIS平台(RemoteGIS)是基于HTML5自主研发的新一代WEBGIS基础平 ... 
- error C3861: “getpid”: 找不到标识符
			原文:http://blog.csdn.net/woniu199166/article/details/52471242 这种错误一般就是没有对应的函数或者对应的头文件 旧版的vs添加#include ... 
- No toolchains found in the NDK toolchains folder for ABI with prefix: arm-linux-androideabi
			产生背景最近把Android Studio更新到3.0,更新之后出现了build错误:No toolchains found in the NDK toolchains folder for ABI ... 
- C/C++内存管理详解 ZZ
			内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的 检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存 ... 
- VS2013个版本密钥(亲测可用)
			Visual Studio Ultimate 2013 KEY(密钥):BWG7X-J98B3-W34RT-33B3R-JVYW9 Visual Studio Premium 2013 KEY(密钥) ... 
- 在NGUI中高效优化UIScrollView之UIWrapContent的简介以及使用
			前言: 1.我使用的NGUI版本为 v3.7.5,不知道老版的NGUI是否有UIWrapContent 这个脚本. 2.本文讲解主要以图片显示的例子为主,本文例子UIScrollView是水平方向,一 ... 
