最近,在使用EventStore的.NET Client API采用大量线程池线程同步写入Event时(用于模拟ASP.NET服务端大并发写入Event的情况),发现EventStore的连接会随机中断,并且在服务端日志中显示客户端连接Heartbeat超时(如果不了解EventStore,请点击传送门)。由于系统中全局共享一个EventStore连接,当连接中断时,会导致所有的写入操作被Block,而EventStore连接的重连速度比较慢(测试机器上重连需要耗费20秒到1分钟),这样会导致比较严重的性能问题、甚至导致客户端请求超时。

示例代码如下:

for( int index=0;index<100;index++)
{
Task.Run( ()=> { connection. AppendToStreamAsync(...).Wait(); } );  
}

也许有人会问,为什么不为每一个请求单独建立一个EventStore连接?好吧,绕开官方文档推荐使用单连接这个问题不谈,似乎为每一个请求建立一个连接是一个好的解决方案,但是实际测试发现这个方案比单链接方案更不靠谱。经过测试发现(使用EventStore版本3.0.5.0测试),为每一个请求单独建立连接会有以下几个方面的问题:

1)每次请求都需要重新连接EventStore Server,耗费的时间比较长,性能比单连接差。

2)重复的建立和断开连接会导致EventStore服务端的TCP端口失去响应(大概30K次连接之后),在这种情况下除非重启EventStore Server,否则客户端再也无法连接上EventStore Server。

3)如果一个连接非正常中断,则所有的连接都会断开重连。

由于多连接存在上面的问题(中途也尝试过连接池,发现也同样存在上述问题),我们只能在单连接这个方向上继续前行,我尝试着将上面的代码修改为如下形式,竟然发现问题神奇的消失了。

for( int index=0;index<100;index++)
{
var tempThread = new Thread(()=>{connection. AppendToStreamAsync(...).Wait();});
tempThread.IsBackground = true;
tempThread.Start();
}

  这两段代码有什么区别呢?有问题的代码是使用线程池的线程来同步写入Event,而没有问题的代码则使用线程同步写入Event,貌似没有什么区别。会是什么问题呢?我又回过头将第一段代码修改为如下形式(将同步写入修改为异步写入,注意:将Wait方法调用去掉),竟然也发现没有问题。

for( int index=0;index<100;index++)
{
Task.Run( ()=> { connection. AppendToStreamAsync(...); } );  
}

  那么问题出在什么地方呢?会不会是EventStore本身的问题呢?由于使用HTTP接口来写入Event非常的稳定,可以基本排除是Event Store Server的问题,那么EentStore .Net Client API有问题的可能性就比较大了。

没有办法,从GIT上下载EventStore的源代码开始调试,最终发现了问题可能是下面的代码引起的:

public void EnqueueMessage(Message message)
{
Ensure.NotNull(message, "message"); _messageQueue.Enqueue(message);
if (Interlocked.CompareExchange(ref _isProcessing, 1, 0) == 0)
ThreadPool.QueueUserWorkItem(ProcessQueue);
} private void ProcessQueue(object state)
{
do
{
Message message; while (_messageQueue.TryDequeue(out message))
{
Action<Message> handler;
if (!_handlers.TryGetValue(message.GetType(), out handler))
throw new Exception(string.Format("No handler registered for message {0}", message.GetType().Name));
handler(message);
} Interlocked.Exchange(ref _isProcessing, 0);
} while (_messageQueue.Count > 0 && Interlocked.CompareExchange(ref _isProcessing, 1, 0) == 0);
}

  这段代码的目的是将客户端写入的自定义事件或者系统产生的事件(无论客户端写入的自定义事件还是系统事件都会被存储到一个唯一的Queue中),顺序的发送到服务端。说到这里就必须提一下EventStore TCP连接的心跳机制,当客户端与EventStore服务端建立连接之后,客户端会定期发送一个Heartbeat事件到服务端,通知服务端客户端还活着,如果在一定的时间内,服务端收不到来自客户端的Heartbeat事件,那么服务端会主动关闭连接,并且在日志中记录一条客户端Heartbeat超时日志。

那么这段代码与连接中断有什么关系呢?注意,客户端的Heartbeat事件也是通过这段代码发送到服务端的。如果,我说如果,由于什么原因导致Heartbeat事件不能及时的发送到服务端,会不会导致连接中断呢?答案是肯定的。

那么这段怎么看都没有问题的代码为什么在使用线程池线程并发写入的情况下会导致Heartbeat事件发送不及时呢?是不是这句话有问题?难道在大并发的情况下QueueUserWorkItem会导致ProcessQueue的调用会被延迟?

ThreadPool.QueueUserWorkItem(ProcessQueue);

  为了验证我的想法,我创建了一个System.Threading.Thread.Timer,定时100毫秒,在Callback中输出两次Callback之间的时间差,当大量的使用ThreadPool的线程时,发现两次Callback之间的时间差慢慢的从200毫秒,越变越大,直到到好几秒。问题到这里就已经很清楚了,当ThreadPool的线程被大量占用时,通过QueueUserWorkItem注册的回调方法会有一定的延迟,具体的延迟时间与ThreadPool的线程被占用的时间和数量有关系,对于实时性要求比较高的任务,比如EventStore的ProcessQueue来说,是不适合使用QueueUserWorkItem来注册回调的。

最后,我将EventStore代码中的QueueWorkItem调用替换为线程调用,发现问题解决。

后续,为了说明这个问题,我在EventStore的Group中发布了一个帖子说明这个问题,EventStore的作者认为Task.Run不能真实的模拟ASP.NET的情况,建议我到真实环境中测试。为此我创建了一个简单的ASP.NET MVC项目和一个简单的客户端,模拟大压力下两种实现的差别。通过测试发现,使用原版的API,随着并发线程数量的增长,Event的写入速度越来越慢,而使用修改后的API,则发现随着并发线程数量的增长,Event的写入速度变化不明显,基本上没有太大的差别,理论上来说在某一个并发下应该会导致Heartbeat超时。由于IIS默认并发访问数量的限制,又懒得去调整服务器,并且由于IIS本身又管理了一套连接池,想压出Heartbeat超时比较困难,就没有做Heartbeat超时的压力测试。

EventStore .NET API Client在使用线程池线程同步写入Event导致EventStore连接中断的问题研究的更多相关文章

  1. Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)

    多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线 ...

  2. Java多线程面试题:线程锁+线程池+线程同步等

    1.并发编程三要素? 1)原子性 原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行. 2)可见性 可见性指多个线程操作一个共享变量时,其中一个线程对变量 ...

  3. 运用JAVA的concurrent.ExecutorService线程池实现socket的TCP和UDP连接

    运用JAVA的concurrent.ExecutorService线程池实现socket的TCP和UDP连接 最近在项目中可能要用到socket相关的东西来发送消息,所以初步研究了下socket的TC ...

  4. android线程与线程池-----线程池(二)《android开发艺术与探索》

    android 中的线程池 线程池的优点: 1 重用线程池中的线程,避免了线程的创建和销毁带来的性能开销 2 能有效的控制最大并发数,避免大量线程之间因为喜欢抢资源而导致阻塞 3 能够对线程进行简单的 ...

  5. C# 显式创建线程 or 使用线程池线程--new Thread() or ThreadPool.QueueUserWorkItem()

    在C#多线程编程中,关于是使用自己创建的线程(Thread)还是使用线程池(ThreadPool)线程,一直很困惑,知道看了Jeffrey Richter的相关介绍才明白,记录如下: 当满足一下任何条 ...

  6. ThreadPool.QueueUserWorkItem引发的血案,线程池异步非正确姿势导致程序闪退的问题

    ThreadPool是.net System.Threading命名空间下的线程池对象.使用QueueUserWorkItem实现对异步委托的先进先出有序的回调.如果在回调的方法里面发生异常则应用程序 ...

  7. java 线程池线程忙碌且阻塞队列也满了时给一个拒接的详细报告

    线程池线程忙碌且阻塞队列也满了时给一个拒接的详细报告.下面是一个自定义的终止策略类,继承了ThreadPoolExecutor.AbortPolicy类并覆盖了rejectedExecution方法把 ...

  8. 线程池线程数与(CPU密集型任务和I/O密集型任务)的关系

    近期看了一些JVM和并发编程的专栏,结合自身理解,来做一个关于(线程池线程数与(CPU密集型任务和I/O密集型任务)的关系)的总结: 1.任务类型举例: 1.1: CPU密集型: 例如,一般我们系统的 ...

  9. 线程池+同步io和异步io(浅谈)

    线程池+同步io和异步io(浅谈) 来自于知乎大佬的一个评论 我们的系统代码从同步方式+线程池改成异步化之后压测发现性能提高了一倍,不再有大量的空闲线程,但是CPU的消耗太大,几乎打满,后来改成协程化 ...

随机推荐

  1. Mybatis基础及入门案例

    这几天正在对SSM框架的知识进行一个回顾加深,有很多东西学的囫囵吞枣,所以利用一些时间进一步的学习.首先大概了解一下mybatis的使用,再通过一个案例来学习它. 什么是MyBatis Mybatis ...

  2. [VBA]批量替换PPT里的字体颜色

    不知道为什么计组老师的大量课件字体是伤害视力的亮蓝色……看久了眼睛疼,想把颜色替换成保护视力一点的灰色,但是找了N久也没找到在图形界面上直接操作的方法,于是在MSDN上晃了晃,Google了一下,写了 ...

  3. vs2013设置语言

    设置语言格式 [工具]-[选项]-[国际化]

  4. 使用Unity解耦你的系统—PART4——Unity&PIAB

    在前面几篇有关Unity学习的文章中,我对Unity的一些常用功能进行介绍,包括:Unity的基本知识.管理对象之间的关系.生命周期.依赖注入等,今天则是要介绍Unity的另外一个重要功能——拦截(I ...

  5. linux中使用rm命令将文件移到回收站的方法

    今天在终端下,看到我的用户目录下有个-的文件夹(maven生成),相要删除收回点空间,习惯性的用命令 rm -rf ~ ,一回车,猛然想起的时候已经来不及了,世界一下子清静了,想死的心都有了! 没错, ...

  6. thinkphp5.0环境变量配置

    允许使用环境变量配置,并且优先级别比在配置文件中要高,因为在读取配置参数的时候,首先会判断环境变量中是否存在该配置. 在开发过程中,可以在应用根目录下面的.env来模拟环境变量配置,.env文件中的配 ...

  7. 使用 Eigen 3.3.3 进行矩阵运算

    Eigen是一个能够进行线性代数运算的C++开源软件包,包含矩阵和矢量操作,Matlab中对矩阵的大多数操作都可以在Eigen中找到. 最近需要计算厄米特矩阵的逆,基于LLT分解和LDLT分解,自己写 ...

  8. 【LeetCode】32. Longest Valid Parentheses

    Given a string containing just the characters '(' and ')', find the length of the longest valid (wel ...

  9. Redis_NoSql分布式数据库CAP原理

    前文简单介绍了NoSql数据库的四大分类以及常用的数据库技术,本文简单介绍分布式数据库CAP原理. 一.传统的CAID是什么 1. A(Atomicity)原子性:事务里的所有操作要么全部做完,要么都 ...

  10. [BZOJ2427][HAOI2010]软件安装(Tarjan+DP)

    2427: [HAOI2010]软件安装 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1987  Solved: 791[Submit][Statu ...