一般对于提供出来的接口,虽然知道在哪些业务场景下才会被调用,但是不知道什么时候被调用、调用的频率、接口性能,当出现问题的时候也不容易重现请求;为了追踪这些内容就需要把每次接口的调用信息给完整的记录下来,也就是记录日志。日志中可以把调用方ip、服务器ip、调用时间点、时长、输入输出都给完整的记录下来,有了这些数据,排查问题、重现异常、性能瓶颈都能准确的找到切入点。

这种功能,当然没人想要去在每个Operation里边插入一段代码,如果有类似AOP的玩意就再好不过了。

wcf中有IDispatchMessageInspector分发消息检查器这么个玩意,

  1. namespace System.ServiceModel.Dispatcher
  2. {
  3. using System;
  4. using System.ServiceModel;
  5. using System.ServiceModel.Channels;
  6.  
  7. public interface IDispatchMessageInspector
  8. {
          // 接收请求后触发, 此方法的返回值将作为BeforeSendReply的第二个参数correlcationState传入
  9. object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext);
          // 在输出相应触发
  10. void BeforeSendReply(ref Message reply, object correlationState);
  11. }
  12. }

下面是英文解释:

         

亦即允许我们对进出服务的消息进行检查和修改,这看起来有点像mvc的过滤器

执行过程:  AfterReceiveRequest  ->  wcf操作 ->  BeforeSendReply

切入点找到了,需要做的就是实现这个接口方法,记录日志,还可以计算一段时间内访问的次数并设置相应的阀值也就是可以实现并发限流,限流的目的主要是防止恶意调用维持己方服务器的稳定。

实现思路:

  1. public class ThrottleDispatchMessageInspector : IDispatchMessageInspector
  2. {
  3. public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
  4. {
  5.        // [并发限流]
  6.        ContractName+OperationName 作为MemoryCache的键,值为调用次数; 设置绝对过期时间
  7.        if(次数 > 设定的阀值)
  8.           request.Close(); 直接关闭请求
  9.         
  10.        // [log] 记录请求输入消息、开始时间、服务器ip、客户端ip、 访问的wcf契约和方法...
  11.        LogVO log
  12.        // [log] 将日志实体作为返回值
  13.        return log;
  14.  
  15. }
  16.    public void BeforeSendReply(ref Message reply, object correlationState) {
  17.         //[log]补充完整log的属性: 请求结束时间, 调用时长... 然后将log丢入队列(不直接插数据库,防止日志记录影响接口性能) 慢慢落地rds中
  18.         var log = correlationState as LogVO;
  19.         log => Queue
  20.      }    
  21.   }

完整的代码如下:

  1. // 自定义分发消息检查器
  2. public class ThrottleDispatchMessageInspector : IDispatchMessageInspector
  3. {
  4. //TODO 这两个参数根据系统的配置处理方式存储,作为示例就直接写了
  5. public static int throttleNum = ; // 限流个数
  6. public static int throttleUnit = ; // s
  7.  
  8. CacheItemPolicy policy = new CacheItemPolicy(); //! 过期策略,保证第一个set和之后set的绝对过期时间保持一致
  9.  
  10. #region implement IDispatchMessageInspector
  11.  
  12. // 此方法的返回值 将作为方法BeforeSendReply的第二个参数 object correlationState传入
  13. public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
  14. {
  15.  
  16. // 获取ContractName和OperationName 用来作为缓存键
  17. var context = OperationContext.Current;
  18. string contractName = context.EndpointDispatcher.ContractName;
  19. string operationName = string.Empty;
  20. if (context.IncomingMessageHeaders.Action == null)
  21. {
  22. operationName = request.Properties.Values.LastOrDefault().ToString();
  23. }
  24. else
  25. {
  26. if (context.IncomingMessageHeaders.Action.Contains("/"))
  27. {
  28. operationName = context.IncomingMessageHeaders.Action.Split('/').LastOrDefault();
  29. }
  30. }
  31. string throttleCacheKey = contractName + "_" + operationName;
  32. // 缓存当前请求频率, 以内存缓存System.Runtime.Caching.MemoryCache为例(.net4.0+)
  33. ObjectCache cache = MemoryCache.Default;
  34. var requestCount = cache.Get(throttleCacheKey);
  35. int currRequestCount = ;
  36. if (requestCount != null && int.TryParse(requestCount.ToString(), out currRequestCount))
  37. {
  38. // 访问次数+1
  39. currRequestCount++;
  40. cache.Set(throttleCacheKey, currRequestCount, policy); //必须保证过期策略和第一次set的时候一致,不然过期时间会有问题
  41. }
  42. else
  43. {
  44. policy.AbsoluteExpiration = DateTime.Now.AddSeconds(throttleUnit);
  45. cache.Set(throttleCacheKey, currRequestCount, policy);
  46. }
  47.  
  48. // 如果当前请求数大于阀值,直接关闭
  49. if (currRequestCount > throttleNum)
  50. {
  51. request.Close();
  52. }
  53.  
  54. //作为返回值 传给BeforeSendReply
  55. LogVO log = new LogVO
  56. {
  57. BeginTime = DateTime.Now,
  58. ContractName = contractName,
  59. OperationName = operationName,
  60. Request = this.MessageToString(ref request),
  61. Response = string.Empty
  62. };
  63. return log;
  64. }
  65.  
  66. public void BeforeSendReply(ref Message reply, object correlationState)
  67. {
  68.  
  69. // 补充AfterReceiveRequest 传递过来的日志实体的属性, 记录
  70. LogVO log = correlationState as LogVO;
  71. log.EndTime = DateTime.Now;
  72. log.Response = this.MessageToString(ref reply);
  73. log.Duration = (log.EndTime - log.BeginTime).TotalMilliseconds;
  74.  
  75. //attention 为不影响接口性能,日志实体push进队列(redis .etc),然后慢慢落地
  76. //TODO 这里直接写文本啦~
  77. try
  78. {
  79. string logPath = "D:\\WcfLog.txt";
  80. if (!File.Exists(logPath))
  81. {
  82. File.Create(logPath);
  83. }
  84. StreamWriter writer = new StreamWriter(logPath, true);
  85. writer.Write(string.Format("at {0} , {1} is called , duration: {2} \r\n", log.BeginTime, log.ContractName + "." + log.OperationName, log.Duration));
  86. writer.Close();
  87. }
  88. catch (Exception ex) { }
  89. }
  90. #endregion
  91. }

这边需要注意 : 1. 类似于Web上的HttpContext.Current.Cache 这边使用的是相应的内存缓存 MemoryCache,在更新缓存值的时候 过期策略要保持和初次设置的时候一致,如果没传入则缓存将不过期;

         2. 并发限制的配置根据自身系统框架来设定,不要写死

         3. 日志记录不要直接落rds,不然并发高了rds的连接数很容易爆,而且影响api的处理速度(可以push to redis, job/service land data)

         4. 读取wcf的消息载体Message的数据后,要重新写入(这个方法是直接用老外的)

接着,只要自定义服务行为在ApplyDispatchBehavior方法里将自定义的分发消息检查器给注入到分发运行时就可以了,直接贴代码:

  1. // 应用自定义服务行为的2中方式:
  2. // 1. 继承Attribute作为特性 服务上打上标示
  3. // 2. 继承BehaviorExtensionElement, 然后修改配置文件
  4. public class ThrottleServiceBehaviorAttribute : Attribute, IServiceBehavior
  5. {
  6. #region implement IServiceBehavior
  7. public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
  8. {
  9.  
  10. }
  11.  
  12. public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
  13. {
  14. foreach (ChannelDispatcher channelDispather in serviceHostBase.ChannelDispatchers)
  15. {
  16. foreach (var endpoint in channelDispather.Endpoints)
  17. {
  18. // holyshit DispatchRuntime
  19. endpoint.DispatchRuntime.MessageInspectors.Add(new ThrottleDispatchMessageInspector());
  20. }
  21. }
  22. }
  23.  
  24. public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
  25. {
  26.  
  27. }
  28. #endregion
  29.  
  30. #region override BehaviorExtensionElement
  31. //public override Type BehaviorType
  32. //{
  33. // get { return typeof(ThrottleServiceBehavior); }
  34. //}
  35.  
  36. //protected override object CreateBehavior()
  37. //{
  38. // return new ThrottleServiceBehavior();
  39. //}
  40. #endregion
  41. }

这边,由于本人比较懒,直接就继承Attribute后 将服务行为贴在服务上;更好的做法是 继承 BehaviorExtensionElement , 然后在配置文件里边注册自定义行为让所有的接口走自定义检查器的逻辑。

试验: 阀值 10次每4秒

随便弄个Service

  1. [ThrottleServiceBehavior]
  2. public class Service1 : IService1
  3. {
  4. public string GetData()
  5. {
  6. object num = MemoryCache.Default.Get("IService1_GetData") ?? "";
  7.  
  8. return string.Format("already request {0} times, Throttle is : {1} per {2} seconds", num, WcfDispatchMessageInspector.ThrottleDispatchMessageInspector.throttleNum, WcfDispatchMessageInspector.ThrottleDispatchMessageInspector.throttleUnit);
  9. }
  10. }

1. 四秒内刷5次:  

2. 超过10次的时候: 

直接就断开了~

完整的代码

wcf利用IDispatchMessageInspector实现接口监控日志记录和并发限流的更多相关文章

  1. C# 面向切面编程--监控日志记录方案

    背景:现在公司整体在做监控平台,要求把各个部分的细节都记录下来,在前台页面上有所显示,所以现在需要做的就是一个监控日志的记录工作,今天讲的就是渲染监控日志的例子. 现状:当前的渲染程序没有为监控日志记 ...

  2. 循序渐进nginx(三):日志管理、http限流、https配置,http_rewrite模块,第三方模块安装,结语

    目录 日志管理 access_log error_log 日志文件切割 自定义错误页 http访问限流 限制请求数 语法 使用 限制连接数 语法 测试 补充: https配置 使用 生成证书 配置ng ...

  3. 利用AOP与ToStringBuilder简化日志记录

    刚学spring的时候书上就强调spring的核心就是ioc和aop blablabla...... IOC到处都能看到...AOP么刚开始接触的时候使用在声明式事务上面..当时书上还提到一个用到ao ...

  4. 如何利用redis来进行分布式集群系统的限流设计

    在很多高并发请求的情况下,我们经常需要对系统进行限流,而且需要对应用集群进行全局的限流,那么我们如何类实现呢. 我们可以利用redis的缓存来进行实现,并且结合mysql数据库一起,先来看一个流程图. ...

  5. 高并发之 API 接口,分布式,防刷限流,如何做?

    在开发分布式高并发系统时有三把利器用来保护系统:缓存.降级.限流 缓存 缓存的目的是提升系统访问速度和增大系统处理容量 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解 ...

  6. 实例:接口并发限流RateLimiter

    需求:接口每秒最多只能相应1个请求 1.创建 全局类对象 import com.google.common.util.concurrent.RateLimiter; import org.spring ...

  7. springcloud zuulfilter 实现get,post请求日志记录功能

    import com.alibaba.fastjson.JSONObject; import com.idoipo.infras.gateway.open.model.InvokeLogModel; ...

  8. 【高并发】亿级流量场景下如何为HTTP接口限流?看完我懂了!!

    写在前面 在互联网应用中,高并发系统会面临一个重大的挑战,那就是大量流高并发访问,比如:天猫的双十一.京东618.秒杀.抢购促销等,这些都是典型的大流量高并发场景.关于秒杀,小伙伴们可以参见我的另一篇 ...

  9. 利用 Nginx 实现限流

    在当下互联网高并发时代中,项目往往会遇到需要限制客户端连接的需求.我们熟知的 Nginx 就提供了有这样的功能,可以简单的实现对客户端请求频率,并发连接和传输速度的限制…. Nginx 限流 Ngin ...

随机推荐

  1. 文本文件打印类库(C#)

    我写了一个打印文本文件的类库,功能包含:打印预览.打印.打印时能够选择打印机.能够指定页码范围. 调用方法很easy: TextFilePrinter p = new TextFilePrinter( ...

  2. 怎么将JSP页面的ID值传给Action进行更新和删除

    这里只是单纯的SH整合. JSP页面代码 <!-- value=action中数据库的User对象集合list必须和action定义的名字一样, 且为了在这里能够访问,需要生成get/set方法 ...

  3. [转载]LoadRunner如何处理AJAX异步请求

    最近在网上经常有人问“LoadRunner脚本回放成功,但数据没有写入数据库,这是什么原因”,记得以前的同事也遇到过相同的问题,再次将解决方法贴出来,希望能帮助大家. 相信大家在做测试的过程中,特别是 ...

  4. scrollTop clientTop offsetTop scrollHeight clientHeight clientWidth的差别及使用方法

    这几个属性做滚动时会经经常使用到.现总例如以下: 首先定义一个div.样式例如以下: <style> *{ margin:0px; padding:0px;} body{ margin:0 ...

  5. LBS 附近的人

    1 http://www.infoq.com/cn/articles/depth-study-of-Symfony2 2 http://lbsyun.baidu.com/

  6. CNN 防止过拟合的方法

    CNN 防止过拟合的方法 因为数据量的限制以及训练参数的增多,几乎所有大型卷积神经网络都面临着过拟合的问题,目前常用的防止过拟合的方法有下面几种:      1. data augmentation: ...

  7. 【Linux 驱动】设备驱动程序再理解

    学习设备驱动编程也有一段时间了,也写过了几个驱动程序,因此有对设备驱动程序有了一些新的理解和认识,总结一下.学习设备驱动编程也有一段时间了,也写过了几个驱动程序.因此有对设备驱动程序有了一些新的理解和 ...

  8. IIS8应用池重启脚本

    重启 IIS8 应用程序池的批处理 批处理很简单:c:\windows\system32\inetsrv\AppCmd.exe stop apppool /apppool.name:"ASP ...

  9. Java编程之路相关书籍(三个维度)

    一.关于Java的技术学习.能够依照以下分三个维度进行学习 : (1)向下发展,也就是底层的方向 建议看<深入Java虚拟机>.<Java虚拟机规范>.<Thinking ...

  10. iOS左滑手势失效

    iOS7之后,苹果优化了一个小功能,就是对于UINavagationController堆栈里的UIViewController,只要轻轻在视图控制器的左边缘右滑一下,该视图控制器就会pop出栈(前提 ...