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
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步