`

一个局部变量导致的血案

    博客分类:
  • Java
 
阅读更多





今天接到一个临时任务,排查一个网站的诡异问题,是这样的,这个网站访问量很大,上了一个模块,在页面服务端发出一个http请求,读取另一个java网站提供的数据,上线之后发现一旦存在并发,或是比较多的访问,http请求就会失败,甚至在服务器上不能打开任何页面,但是服务器可以被ping通,也可以ping通其它网址(我没有看到真实的情况,只是听说有这样一个情况)。



对于一个高并发网站的服务端发起一个http请求我总是觉得很恐怖,在以前的项目中也从来没这么做过(宁愿让客户端ajax方式请求),凭感觉一开始我就怀疑是线程池的问题。因为我们知道,asp.net的请求由线程池中的工作线程处理,如果在这个工作线程中同步发起http请求,那么就好比把一个内存级的处理速度一下子拉到了网络级的处理速度(因为需要同步等待网络),我想会不会是网站本身访问量就很大了(比如线程池最大允许工作线程800,当前的并发已经需要占用200),那么这个速度下来之后(比如从100毫秒处理时间到1秒),一下子线程池就爆满了(200×10=1000>800)。于是,我建议开发使用异步页和异步的HttpWebRequest,最简单的方式是使用WebClient基于事件的异步模式DownloadStringAync()。在开发人员按照我的建议修改代码上线之后,问题还是没有解决。我也一时很迷茫,想不出还会是哪些原因(我自己在线下测试了一下,使用这种方式即使很大的请求量始终占用一个工作线程,而IOCP则占用了比较多)。



在拿到了线上服务器调试的机会之后,修改页面代码植入了定时器,定时输出线程池的可用工作线程和可用IOCP,结果请求过来之后也只占用了30个工作线程(我们的网站30个并发左右),IOCP不占用(因为后来代码回滚了,没有使用异步的Http请求)。后来我想看一下tcp连接情况吧,输入netstat –s后我傻了,看到4000多个tcp连接,当时第一反映就是访问量太大了。在之前写程序的时候记得好像要通过注册表修改才能为windows server启用6万多个tcp端口(默认端口范围好象是1000多到5000,正好符合之前看到的4000多个连接),还记得tcp的端口关闭之后默认需要等待4分钟才能继续使用,查了一下是MaxUserPort和TcpTimedWaitDelay两个参数,根据msdn把这2个值修改为最大的65534和最小的30(秒)。算了一下,如果每个访问网站的请求都是新连接,每个请求又要建立一次http请求,那么一次请求也就占用2个端口最多了,算它最大能建立6万个端口,30秒全部释放,也就是30秒可以处理3万个请求,那么我们的处理能力是1000请求/秒,但是我们的网站一般每台服务器的并发不超过50,远远够用了。修改了注册表重启机器之后发现,果然修改生效了,不过建立的tcp连接在压力上来之后很快达到了6万多个,服务器又出现不能访问外网的问题了,此时断定根源问题就是tcp连接过多,已经不能再建立连接了(当然也就不能建立http连接),但是已经打开的页面确实可以打开,新的页面不能打开,又忽然想到了http的keep alive。



有了这个方向之后修改代码,为httpwebrequest设置了keep alive(为了确认是不是加上了connection:keep alive的http头又费了不少周折),为目标的web服务器也启用了keep alive,乱七八糟设置了一堆ServicePointManager的属性,经过几个小时百般尝试之后还是不行,总是发现服务器很容易达到6万个连接(也就1分钟不到吧),cpu狂飙,然后就像断网一样,一直纠结在为什么没有keep alive,为什么没有重用tcp连接。后来又单独制作了一个网站,发现并没有占用这么多端口(难道有其它服务或是模块开了很多tcp端口?)。



突然想到,为什么不看看到底建立了哪些连接呢?输入netstat –an –p tcp > c:\a.txt,之后打开a.txt一看瞬间傻眼,99%的连接都是到memcached的服务器。第一反应,代码里大量用到了memcached?查了代码之后发现,虽然一个请求可能确实进行了10次左右的memcache存取(不能说少,但是也不至于并发大到这个程度),但是我们用的客户端有连接池,不可能并发大到一下子建立这么多连接,而且我们的连接池最大的tcp连接也就是500。于是继续查看代码,在看到MemcachedClient的初始化代码后我瞬间石化,居然是每次都声明一个局部变量,然后初始化MemcachedClient类,而不是使用static变量来保存MemcachedClient的实例。



在之前调研memcache客户端连接池bug问题的时候了解到,每一次实例化一个新的MemcachedClient对象都会新建一套连接池,所谓池就是具有一个最小连接,也就是在初始化池的时候就会建立这些最小连接,达到比较好的初始性能,这个值我们配置的是10(每一次请求都要新建10个tcp连接??)。写了一段循环测试了一下才运行100次循环,建立的tcp连接就到了1000(并且每一次初始化连接池都耗时300毫秒以上,cpu始终是100%,可见创建这么多连接很耗性能),和猜想完全一致,也想到当时发现页面刷新一下连接就加了十几个是这样原因啊。在把局部变量改为全局的静态变量之后,网站在100个并发的情况下依然运行良好,远远大于原先的期望(50个并发)。



从线程池怀疑到端口不够用,再怀疑到keep alive问题,最后定位最终的原因(和新加的httpwebrequest模块没关系),费了不少周折。那么之后的解决方案很简单了,排查项目中其它memcache的使用方式,然后修改memcache客户端(我们使用的是Enyim.Caching),修改MemcachedClient的构造方法为private的,并且提供singleton入口即可。



对于性能优化我的体会是,往往大多数的性能问题来自一到两个根源问题,如果能找到并解决那么或许可以有很大的效果。希望此文对大家有帮助。
分享到:
评论

相关推荐

    VB 局部变量举例

    在VB(Visual Basic)编程语言中,局部变量是程序中非常重要的一个概念,它们用于存储临时数据,只在声明它们的代码块内有效。本文将深入探讨VB中的局部变量,通过具体的例子来帮助理解其使用方式和作用。 一、什么...

    C语言全局/局部变量

    然后在某个函数内部定义一个局部变量: ```c void func(void) { int localVar; // 局部变量,只在func函数内部可见 localVar = 10; // 使用localVar进行计算... } ``` 为了减少全局变量的使用,我们还可以利用...

    全局变量、局部变量、静态全局变量、静态局部变量的区别

    与普通局部变量不同,静态局部变量在函数第一次调用时初始化,之后的每次调用都会保留上次的值,直到程序结束。静态局部变量的生命周期为整个程序运行期间,但其作用域仍限于声明它的函数内部。 ### 关键区别 1. *...

    局部变量 与 全局变量

    通过代码的运行,并理解代码,明白C中的局部变量与本地变量的区别

    c语言全局变量和局部变量问题汇总

    假设我们有一个简单的示例来说明全局变量和局部变量的使用: ```c #include int global_var = 10; void modify_global() { global_var++; // 修改全局变量 } void modify_local() { int local_var = 5; // ...

    成员变量和局部变量

    局部变量:在方法内或者方法声明处 在内存中的位置不同 成员变量:在堆内存中 局部变量:在栈内存中 初始化值不同 成员变量:有默认的初始化值 局部变量:没有初始化值,必须手动初始化 生命周期不同 成员...

    VB6.0中静态变量和局部变量的区别

    在这个例子中,`clickCount`就是一个静态变量,每次点击按钮触发`CountClicks`子程序,`clickCount`的值都会累加,不会重置为0。 ### 2. 局部变量(Local Variable) 局部变量是在函数或过程内部声明的,它的生命...

    Python语言基础:局部变量和全局变量.pptx

    当函数被调用时,它会创建一个新的局部作用域,在这个作用域内声明的变量只存在于函数执行的过程中,一旦函数执行完毕,这些局部变量就会被销毁。因此,局部变量不能在函数外部被访问。在上面的实例中,`sum`函数内...

    静态全局变量,静态局部变量,全局变量,局部变量

    - **作用域**: 具有全局作用域,意味着一旦在一个源文件中定义了全局变量,它就可以在整个程序中被访问。 - **存储位置**: 存储在静态存储区中。 - **生命周期**: 生命周期贯穿整个程序的运行过程。 - **可见性**: ...

    [面试/笔试系列3]局部变量能否和全局变量重名

    - **行为:** 当在一个函数或块中定义了一个局部变量,并且它的名字与全局变量相同,那么在该函数或块的范围内,对该变量的引用将指向局部变量而不是全局变量。这意味着局部变量“隐藏”了全局变量。 - **解除隐藏:...

    C语言中局部变量、全局变量.pdf

    C语言中局部变量、全局变量.pdf

    static静态局部变量的妙用

    本文将深入探讨`static`静态局部变量的一个特别应用——如何通过其保持变量状态的特性来实现计数器或其他类似功能,从而提高程序的性能和可维护性。 #### 二、`static`关键字简介 在C/C++等编程语言中,`static`...

    C语言面试题大汇总之华为面试题:1、局部变量能否和全局变量重名;2、如何引用一个已经定义过的全局变量;3、全局变量可不可以定义在可被多个.C文件包含的头文件中 为什么;4、语句for( ;1 ;)有什么问题 它是什么意思……

    在同一个函数内可以定义多个同名的局部变量,每个局部变量的作用域就在那个循环体内。 二、extern关键字 extern关键字用于引用已经定义过的全局变量。如果用引用头文件方式来引用某个在头文件中声明的全局变理,...

    局部变量和全局变量总汇

    - **答案**:可以,但是需要注意的是,虽然可以在不同的`.c`文件中声明同名的全局变量,但必须确保只有一个`.c`文件中对此变量进行初始化,否则会导致链接错误。例如: ```c // global.h extern int global_var;...

    Labview应用技术 局部变量写计数器(课堂实训).docx

    本篇实训将关注LabVIEW中的一个重要概念——局部变量,以及如何利用它来实现一个简单的计数器功能。 局部变量在LabVIEW中是用于在程序框图内部存储和传递数据的临时容器。与全局变量不同,局部变量仅在其创建的函数...

    如何在IAR中通过Watch窗口观察局部变量的值

    特别是当涉及到嵌入式系统开发,比如针对MSP430单片机进行项目开发时,IAR Embedded Workbench是一个常用的开发和调试工具。但是,在使用该工具时,开发者可能会遇到无法通过Watch窗口观察到局部变量值的问题。以下...

    全局变量、局部变量、静态变量即内存管理

    变量类型是编程语言中非常重要的一个概念,全局变量、局部变量、静态变量都是程序员经常使用的变量类型,但是这些变量类型之间的区别和联系却经常让人感到困惑。今天我们将详细地探讨这些变量类型之间的区别和联系,...

    详解Vue 全局变量,局部变量

    主要介绍了Vue全局变量局部变量,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    解析python的局部变量和全局变量

    a = 300 # 定义一个局部变量a,并初始化300 print(--test1--修改前:a=%s % a) a = 200 # 给变量a重新赋值200 print(--test1--修改后:a=%s % a) def test2(): a = 400 # 定义另一个局部变量a,并初始化400 ...

    C代码之局部变量(数据结构)

    `sub`函数接收两个参数`a`和`b`,这两个参数是`main`函数中`a`和`b`的副本,它们在`sub`函数内部创建了自己的局部变量`a`和`b`,以及一个额外的局部变量`c`。在`sub`函数中,`a`、`b`和`c`的值被改变,但这些改变...

Global site tag (gtag.js) - Google Analytics