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

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

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

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

下面是英文解释:

         

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

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

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

实现思路:

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

完整的代码如下:

 // 自定义分发消息检查器
public class ThrottleDispatchMessageInspector : IDispatchMessageInspector
{
//TODO 这两个参数根据系统的配置处理方式存储,作为示例就直接写了
public static int throttleNum = ; // 限流个数
public static int throttleUnit = ; // s CacheItemPolicy policy = new CacheItemPolicy(); //! 过期策略,保证第一个set和之后set的绝对过期时间保持一致 #region implement IDispatchMessageInspector // 此方法的返回值 将作为方法BeforeSendReply的第二个参数 object correlationState传入
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{ // 获取ContractName和OperationName 用来作为缓存键
var context = OperationContext.Current;
string contractName = context.EndpointDispatcher.ContractName;
string operationName = string.Empty;
if (context.IncomingMessageHeaders.Action == null)
{
operationName = request.Properties.Values.LastOrDefault().ToString();
}
else
{
if (context.IncomingMessageHeaders.Action.Contains("/"))
{
operationName = context.IncomingMessageHeaders.Action.Split('/').LastOrDefault();
}
}
string throttleCacheKey = contractName + "_" + operationName;
// 缓存当前请求频率, 以内存缓存System.Runtime.Caching.MemoryCache为例(.net4.0+)
ObjectCache cache = MemoryCache.Default;
var requestCount = cache.Get(throttleCacheKey);
int currRequestCount = ;
if (requestCount != null && int.TryParse(requestCount.ToString(), out currRequestCount))
{
// 访问次数+1
currRequestCount++;
cache.Set(throttleCacheKey, currRequestCount, policy); //必须保证过期策略和第一次set的时候一致,不然过期时间会有问题
}
else
{
policy.AbsoluteExpiration = DateTime.Now.AddSeconds(throttleUnit);
cache.Set(throttleCacheKey, currRequestCount, policy);
} // 如果当前请求数大于阀值,直接关闭
if (currRequestCount > throttleNum)
{
request.Close();
} //作为返回值 传给BeforeSendReply
LogVO log = new LogVO
{
BeginTime = DateTime.Now,
ContractName = contractName,
OperationName = operationName,
Request = this.MessageToString(ref request),
Response = string.Empty
};
return log;
} public void BeforeSendReply(ref Message reply, object correlationState)
{ // 补充AfterReceiveRequest 传递过来的日志实体的属性, 记录
LogVO log = correlationState as LogVO;
log.EndTime = DateTime.Now;
log.Response = this.MessageToString(ref reply);
log.Duration = (log.EndTime - log.BeginTime).TotalMilliseconds; //attention 为不影响接口性能,日志实体push进队列(redis .etc),然后慢慢落地
//TODO 这里直接写文本啦~
try
{
string logPath = "D:\\WcfLog.txt";
if (!File.Exists(logPath))
{
File.Create(logPath);
}
StreamWriter writer = new StreamWriter(logPath, true);
writer.Write(string.Format("at {0} , {1} is called , duration: {2} \r\n", log.BeginTime, log.ContractName + "." + log.OperationName, log.Duration));
writer.Close();
}
catch (Exception ex) { }
}
#endregion
}

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

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

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

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

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

     // 应用自定义服务行为的2中方式:
// 1. 继承Attribute作为特性 服务上打上标示
// 2. 继承BehaviorExtensionElement, 然后修改配置文件
public class ThrottleServiceBehaviorAttribute : Attribute, IServiceBehavior
{
#region implement IServiceBehavior
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{ } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher channelDispather in serviceHostBase.ChannelDispatchers)
{
foreach (var endpoint in channelDispather.Endpoints)
{
// holyshit DispatchRuntime
endpoint.DispatchRuntime.MessageInspectors.Add(new ThrottleDispatchMessageInspector());
}
}
} public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{ }
#endregion #region override BehaviorExtensionElement
//public override Type BehaviorType
//{
// get { return typeof(ThrottleServiceBehavior); }
//} //protected override object CreateBehavior()
//{
// return new ThrottleServiceBehavior();
//}
#endregion
}

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

试验: 阀值 10次每4秒

随便弄个Service

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

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. Java6 WebService的发布

    Java6 WebService的发布   WebService服务发布往往比较混乱,Axis2的发布形式与XFire发布方式差别很大,而Java6 Web服务的发布与Axis2.XFire的Web服 ...

  2. Python 面向对象三(转载)

    来源:Mr.Seven www.cnblogs.com/wupeiqi/p/4766801.html 四.类的特殊成员 上文介绍了Python的类成员以及成员修饰符,从而了解到类中有字段.方法和属性三 ...

  3. 转: NetBean 远程开发的好文2 --> 工欲善其事,必先利其器系列--Netbeans之远程开发

    转自:  http://www.cnblogs.com/zuoca/archive/2012/07/09/Remote_Development_With_Netbeans_origin.html 实践 ...

  4. ITIL,是否已是昨日黄花

    首先声明自己不是ITIL方面的专家,特别是具体的规范细节,后面论述如有不当,请指正.但我为什么会提起它?主要是因为它和运维(IT服务管理)相关性太大了.早起的运维完全就是以ITIL来蓝本构建的,在当时 ...

  5. java int转String全部方式的效率对照与深入解析

    在java中,大家肯定都会遇到int类型转String类型的情形,知其然知其所以然.总结加分析一下,int类型转String类型有下面几种方式: a+"" String.value ...

  6. Solidworks如何为装配体绘制剖面视图

    1 如图所示的工程图来自装配体   2 点击剖面视图,随后绘制一条线(我从正中劈开),弹出对话框,勾选自动打剖面线,确定   3 剖面视图绘制完毕   三个剖视图如下   关于半剖视图,可以这样做.先 ...

  7. 前端性能优化--为什么DOM操作慢? 浅谈DOM的操作以及性能优化问题-重绘重排 为什么要减少DOM操作 为什么要减少操作DOM

    前端性能优化--为什么DOM操作慢?   作为一个前端,不能不考虑性能问题.对于大多数前端来说,性能优化的方法可能包括以下这些: 减少HTTP请求(合并css.js,雪碧图/base64图片) 压缩( ...

  8. JSON Web Token (JWT) 实现与使用方法

    1. JSON Web Token是什么 JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的.自包含的方式,用于作为JSON对象在各方之间安全地传输信息.该 ...

  9. 解决H5在微信浏览器或QQ浏览器修改title的问题

    传送门:http://blog.csdn.net/code_for_free/article/details/51195468 如果是Android,使用 document.title = ‘1231 ...

  10. SpringCloud系列三:将微服务注册到Eureka Server上

    1. 回顾 通过上篇博客的讲解,我们知道硬编码提供者地址的方式有不少问题.要想解决这些问题,服务消费者需要一个强大的服务发现机制,服务消费者使用这种机制获取服务提供者的网络信息.不仅如此,即使服务提供 ...