WebService接口拦截方案

一、背景

       目前考虑到产品基于历史原因,采用的接口webservice+webapi混合,webservice接口涉及产品winform端的业务,尤为重要,经常性出现winform端的一些性能问题,极为苦恼,想在接口端做一些性能监控以辅助分析。

 

二、思路

      产品为单体架构,非平台级,目前能想到的方案就是结合webservice提供的扩展点来做请求拦截处理,分析记录当前调用接口在调用时入参,调用时间点,耗时,调用来自的winform端等信息,记录到客户本地库,再定时将统计信息上传到公司服务器,过程中需考虑到写本地库对客户本地库造成的影响,已经上传公司服务器带来的并发。

      1.  对于写入本地库,默认产品不写入监控数据到本地库即不启用性能监控,只有当需要进行监控时才通过公司平台进行启用,当然这里涉及到的前提是要收集到客户的产品外网地址,结合产品提供的接口来进行启用。

      2.  考虑到接口数量目前6百多,可能要对接口涉及的业务类型进行划分,让自己可以单独启用关注的业务模块接口,或者启动监控所有接口。

      3.  数据写入考虑到数据量对本地数据库的冲击,第一是写入性能数据给接口带来的损耗 第二 不能 影响接口的调用即考虑监控功能的异常处理 第三 数据量的增长,可考虑定时清理,保留1周的数据

      4.  统计数据的上传到公司服务器,考虑当天上传前一天的性能统计数据以减小数据量,考虑到客户体量,上传时间点尽量分散且控制的客户业务较闲时段,服务器接收考虑峰值,可采用MQ来削峰以保证服务器平稳。

 

三、 实现

      webservice 提供的扩展点SoapExtension ,有两种拦截方式,1种是扩展后将扩展点写入web.config文件,但这样的自由度不强,他会拦截所有接口,不法达到只拦截部分业务接口的目标。要达到目标就需要采用第2种方式 结合SoapExtensionAttribute 来对接口打上标签并同时对接口分类,不废话,上代码:

 1 public class TraceAttribute : SoapExtensionAttribute
 2 {
 3     TraceLevel _level = TraceLevel.NONE;
 4     public TraceLevel TraceLevel// 业务类别
 5     {
 6         get { return _level; }
 7         set { _level = value; }
 8     }
 9 
10 
11     int _priority = 1;
12 
13     public override int Priority
14     {
15         get
16         {
17             return _priority;
18         }
19         set
20         {
21             _priority = value;
22         }
23     }
24 
25     public override Type ExtensionType
26     {
27         get { return typeof(WSMonitorExtension); }//定义扩展soap的类型
28     }
29 }

      针对TraceLevel 这里的一个小技巧就是,将其枚举值按2的指数级赋值,这样在接口上打标签时可以多个业务标签同时打,比如: TraceLevel.A | TraceLevel.B

      假设 A业务类型枚举值 2的1次方  即 00000010

             B业务类型枚举值 2的2次方  即  00000100

     则 A | B = 00000010

                      00000100

      即 00000110

      此时在SoapExtension 的扩展中判断是否需要记录监控数据时, 就可以根据设置的监控业务类型来进行 "与" 运算来判断是否需要记录,比如: 设置了监控A 级别业务接口即

       00000110 & 00000010 == 00000010 可以看出需要记录监控数据,此时就把数据异步写入本地库中。

       还要说一个的就是对参数的解析,目前采用解析成json进行存储,这里就涉及到参数对象的转换,尽量考虑全一点,比如 datatable,dataset,枚举,空等情况,借用网上代码,稍作修改,奉上:

       

 1 public static object ConvertToObject(object obj, Type type)
 2     {
 3         if (type == null) return obj;
 4         if (obj == null) return type.IsValueType ? Activator.CreateInstance(type) : null;
 5 
 6         Type underlyingType = Nullable.GetUnderlyingType(type);
 7         if (type.IsAssignableFrom(obj.GetType()))
 8         {
 9             return obj;
10         } else if ((underlyingType ?? type).IsEnum) // 如果待转换的对象的基类型为枚举
11         {
12             if (underlyingType != null && string.IsNullOrEmpty(obj.ToString())) // 如果目标类型为可空枚举,并且待转换对象为null 则直接返回null值
13             {
14                 return null;
15             }
16             else
17             {
18                 return Enum.Parse(underlyingType ?? type, obj.ToString());
19             }
20         }else if (typeof(IConvertible).IsAssignableFrom(underlyingType ?? type)) // 如果目标类型的基类型实现了IConvertible,则直接转换
21         {
22             try
23             {
24                 return Convert.ChangeType(obj, underlyingType ?? type, null);
25             }
26             catch
27             {
28                 return underlyingType == null ? Activator.CreateInstance(type) : null;
29             }
30         }
31         elseelse if (typeof(IConvertible).IsAssignableFrom(underlyingType ?? type)) // 如果目标类型的基类型实现了IConvertible,则直接转换
32         {
33             try
34             {
35                 return Convert.ChangeType(obj, underlyingType ?? type, null);
36             }
37             catch
38             {
39                 return underlyingType == null ? Activator.CreateInstance(type) : null;
40             }
41         }
42         else {
43             TypeConverter converter = TypeDescriptor.GetConverter(type);
44             if (converter.CanConvertFrom(obj.GetType()))
45             {
46                 return converter.ConvertFrom(obj);
47             }
48             ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
49             if (constructor != null)
50             {object o = constructor.Invoke(null);
51                 PropertyInfo[] propertys = type.GetProperties();
52                 Type oldType = obj.GetType();
53                 foreach (PropertyInfo property in propertys)
54                 {
55                     PropertyInfo p = oldType.GetProperty(property.Name);
56                     if (property.CanWrite && p != null && p.CanRead)
57                     {
58                         property.SetValue(o, ConvertToObject(p.GetValue(obj, null), property.PropertyType), null);
59                     }
60                 }
61                 return o;
62             }
63         }
64         return obj;
65     }

 

      对于 统计数据写入公司平台服务器,第一控制 客户端上传的时间点尽量分散以减小并发带来的压力,第二 采用rabbitmq 接受数据,并写入服务器,服务端这边采用站点形式供查询统计数据,接口监控开启设置等,涉及的技术栈: .net core+webapi+elementui+vuejs+rabbitmq 

posted @ 2020-08-21 09:21  这是个坑  阅读(2402)  评论(0编辑  收藏  举报