wcf利用IDispatchMessageInspector实现接口监控日志记录和并发限流
一般对于提供出来的接口,虽然知道在哪些业务场景下才会被调用,但是不知道什么时候被调用、调用的频率、接口性能,当出现问题的时候也不容易重现请求;为了追踪这些内容就需要把每次接口的调用信息给完整的记录下来,也就是记录日志。日志中可以把调用方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实现接口监控日志记录和并发限流的更多相关文章
- C# 面向切面编程--监控日志记录方案
背景:现在公司整体在做监控平台,要求把各个部分的细节都记录下来,在前台页面上有所显示,所以现在需要做的就是一个监控日志的记录工作,今天讲的就是渲染监控日志的例子. 现状:当前的渲染程序没有为监控日志记 ...
- 循序渐进nginx(三):日志管理、http限流、https配置,http_rewrite模块,第三方模块安装,结语
目录 日志管理 access_log error_log 日志文件切割 自定义错误页 http访问限流 限制请求数 语法 使用 限制连接数 语法 测试 补充: https配置 使用 生成证书 配置ng ...
- 利用AOP与ToStringBuilder简化日志记录
刚学spring的时候书上就强调spring的核心就是ioc和aop blablabla...... IOC到处都能看到...AOP么刚开始接触的时候使用在声明式事务上面..当时书上还提到一个用到ao ...
- 如何利用redis来进行分布式集群系统的限流设计
在很多高并发请求的情况下,我们经常需要对系统进行限流,而且需要对应用集群进行全局的限流,那么我们如何类实现呢. 我们可以利用redis的缓存来进行实现,并且结合mysql数据库一起,先来看一个流程图. ...
- 高并发之 API 接口,分布式,防刷限流,如何做?
在开发分布式高并发系统时有三把利器用来保护系统:缓存.降级.限流 缓存 缓存的目的是提升系统访问速度和增大系统处理容量 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解 ...
- 实例:接口并发限流RateLimiter
需求:接口每秒最多只能相应1个请求 1.创建 全局类对象 import com.google.common.util.concurrent.RateLimiter; import org.spring ...
- springcloud zuulfilter 实现get,post请求日志记录功能
import com.alibaba.fastjson.JSONObject; import com.idoipo.infras.gateway.open.model.InvokeLogModel; ...
- 【高并发】亿级流量场景下如何为HTTP接口限流?看完我懂了!!
写在前面 在互联网应用中,高并发系统会面临一个重大的挑战,那就是大量流高并发访问,比如:天猫的双十一.京东618.秒杀.抢购促销等,这些都是典型的大流量高并发场景.关于秒杀,小伙伴们可以参见我的另一篇 ...
- 利用 Nginx 实现限流
在当下互联网高并发时代中,项目往往会遇到需要限制客户端连接的需求.我们熟知的 Nginx 就提供了有这样的功能,可以简单的实现对客户端请求频率,并发连接和传输速度的限制…. Nginx 限流 Ngin ...
随机推荐
- 天地图应用ArcGIS发布的服务(转)
天地图应用ArcGIS发布的服务 本文包含三个部分:利用ArcMap将Excel的数据转化为ArcGIS MXD文件.利用ArcMap发布服务.天地图添加ArcGIS发布的服务. 一 MXD文件的生成 ...
- JS方面重点摘要(一)
1.获取样式(1)style只能获取到行间样式的属性(2)火狐getComputedStyle(obj,null)[attr],IE:obj.currentStyle[attr] 2.ready.on ...
- kata-container环境搭建
一.安装qemu 注意,目前kata-container所要求的qemu最低版本是v2.7.0.在笔者的环境下(Ubuntu16.04 VM),apt-get官方源的最高版本是v2.5.0.所以不要用 ...
- ZOJ1157, POJ1087,UVA 753 A Plug for UNIX (最大流)
链接 : http://acm.hust.edu.cn/vjudge/problem/viewProblem.action? id=26746 题目意思有点儿难描写叙述 用一个别人描写叙述好的. 我的 ...
- EffectiveJava(19)导出常量的几种方式 - - 接口只用于定义类型
package com.classinteface.finalinterface; /** * 常量接口模式 java.io.ObjectStreamConstants * 这种模式会导致实现其的类将 ...
- Win7旗舰版+IIS7没有错误提示怎么办
在IIS Manger中将ASP的调试属性修改默认值,启用服务端调试和客户端调试都改为True,重启后生效.
- cordova与ios native code交互的原理
非常早曾经写了一篇博客,总结cordova插件怎么调用到原生代码:cordova调用过程,只是写得太水.基本没有提到原理.近期加深了一点理解,又一次补充说明一下 js调用native 以下是我们产品中 ...
- css 中 important 的用法
css 中 important 的如何使用? important对 一个良好(或者是标准)的浏览器来说,不仅仅是从顺序上提升代码的优先级,还可以用来提升class的优先级(比如firefox), ...
- DNS 取得授权
1.阿里云上cnroot.cn申请DNS解析服务器 也就是cnroot.cn下的子域名都从这个DNS上获取. 如www.cnroot.cn 如 handle.cnroot.cn 2.vi /home/ ...
- 早来的圣诞礼物!--android 逆向菜鸟速參手冊完蛋版
我的说明: 让老皮特整理了这么长时间这个手冊,心里挺过意不去的,回头我去深圳带着他女儿去游乐场玩玩得了,辛苦了.peter! 太多的话语,也描写叙述不出这样的感觉了,得找个时间.不醉不归... 注:下 ...