当前位置: 移动技术网 > IT编程>开发语言>.net > 基于.net core微服务的另一种实现方法

基于.net core微服务的另一种实现方法

2018年08月08日  | 移动技术网IT编程  | 我要评论

不再流泪的女人,卡布西游宿命对决,site:0773.cm

前言

基于.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 = 0; 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 == 0)
 {
 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[0].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(0, file.name.length - 4))
   .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接口动态的生成这个客户端代理呢? 答案是肯定的,因本文较长了,留在下篇再续

附上动态编译文章链接:

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网