Concurrent connection limit

本文围绕IIS 6上Web Service (WSE 2.0)的性能测试展开,指出因HTTP 1.1规范,客户端与服务器并发连接数受限。为突破限制,介绍两种方法:一是使用AppDomain模拟多个客户端;二是在客户端配置文件中修改连接数量限额,以提高单一测试应用程序的并发量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这几天在做IIS 6上Web Service (WSE 2.0)的性能测试。在这个过程中陆续发现和解决了一些问题。
其中有一个问题比较有意思。我和项目组的同事发现,不论我们用C#写的模拟客户端用多少并发量来连接Web Service,服务器端监测到的并发连接数(性能记数器中的Web Service\Current Connections)总是每客户端最高2个。这使得我们无法查看服务器在大并发量下的真切反应。
那么为什么服务器会对每客户端做出最高2个并发量的限制呢?
通过查找资料,我找到了问题的根源。原来,在HTTP 1.1 Spec中针对Persistent Connections提出了这样的Practical considerations:
Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.
以上内容表明,为了提高HTTP响应时间以及避免产生网络堵塞,HTTP连接中的客户端不应该与服务器端建立超过2个的HTTP连接。如果有更多的请求需要,那么这些请求将被pipeline到这两个HTTP连接之中,并以异步的方式传送给服务器端。举个例子:有上百辆汽车(requests)想从天津开往北京,但是天津与北京之间最多只允许修建两条公路(HTTP connection),因此这些汽车要想从天津驶往北京的话,就只能走这两条公路。
但是,有时的确需要突破这样的限制。比如我一开始提到的性能测试,我需要用尽可能少的客户端程序来模拟尽可能多的用户访问,而不能为了模拟1000个并发量同时使用500台机器来测。那么应该怎样通过一个测试应用程序来产生指定的并发数量呢?
不难看出,为了提高单一测试应用程序所产生的并发量,就应该增加两个指标:网络客户端数量和单一客户端的HTTP连接数量。就我所知,可以通过以下两种方法来分别提高这两个指标。

方法一:使用AppDomain
在. NET中,一个AppDomain就被视为网络连接中的一个客户端,因此如果希望用一个测试应用程序模拟多个客户端,那么只须创建多个AppDomain 即可。需要注意的是,对于每一个AppDomain,最高2个的连接限额仍然存在,不同之处只是我们可以使用一个测试应用程序发送超过2个的并发请求了(现在为了模拟1000个并发量就不需要找500台测试机器了)。请看下面的代码:


AppDomain appDomain = AppDomain.CreateDomain("");
appDomain.ExecuteAssembly(@"TestClient.exe");
AppDomain.Unload(appDomain);

在这里,我通过调用AppDomain的静态方法CreateDomain创建了一个新的应用程序域,并要求该应用程序域执行一个应用程序 TestClient.exe。该应用程序将负责向服务器发送请求(最多只能建立两个连接)。你可以通过多线程的方式来驱动上述代码,使得大量应用程序域在近乎相同的时间里被创建,从而就可以模拟指定数量的客户端,并产生所希望的并发访问量。

方法二:使用配置文件
除了增加客户端数量以外,我们还可以增加单一客户端所能建立的HTTP连接数量。在.NET中实现这一目标非常容易,只需要在客户端(没错,是客户端!)的配置文件中增加以下几行即可:

<system.net>
 <connectionManagement>
  <add address="*" maxconnection="100"/>
 </connectionManagement>
</system.net>

其中,connectionManagement节点负责指定客户端与某一网络主机之间所能建立的最高连接数量。它在Machine.config文件中的默认取值就是2。我们完全可以在应用程序级的配置文件中对这一限额做出更改。address属性表明该连接限额针对的是哪一个网络地址,*表明所有的网络主机;如果写成address="
www.google.com"就表明后面的maxconnection只适用于对google的访问。
好了,现在就可以根据自己的需要来更改配置了。如果你把maxconnection的取值改成了1000,那么你的测试应用程序与服务器之间所能建立的最高连接数量就是测试用应用程序域的数量 * 1000,测吧!emotion-54.gif

转载于:https://www.cnblogs.com/gxh973121/archive/2005/04/18/139484.html

import logging import requests import pymysql from typing import List, Tuple, Optional from concurrent.futures import ThreadPoolExecutor import time from dbutils.pooled_db import PooledDB # 配置常量 MAX_WORKERS = 10 # 最大线程数 def query_movie(imdb_id, vod_id): url = f"https://imdb236.p.rapidapi.com/api/imdb/{imdb_id}" print(url) headers = { "x-rapidapi-key": "40eb93fa97mshba6ed80cd703eadp105b67jsn0fb3c77e09ea", "x-rapidapi-host": "imdb236.p.rapidapi.com" } try: response = requests.get(url, headers=headers) response.raise_for_status() data = response.json() if not data: logging.warning(f"未找到IMDB编号: {imdb_id}") return False originalTitle = data.get("originalTitle", "") releaseDate = data.get("releaseDate", "") description = data.get("description", "") averageRating = data.get("averageRating", "") if not any([originalTitle, releaseDate, description, averageRating]): logging.warning(f"关键字段全空{data.get('imdbId', '未知ID')}") return False with get_db_connection() as connection: with connection.cursor() as cursor: sql = "UPDATE mac_vod SET vod_sub = %s, vod_year = %s, vod_vod_blurb_en = %s, imdb_score = %s WHERE vod_id = %s" cursor.execute(sql, (originalTitle, releaseDate, description, averageRating, vod_id)) connection.commit() logging.info(f"Updated ID:{vod_id} Title:{originalTitle}") return True except Exception as e: logging.error(f"处理ID:{imdb_id}时出错: {str(e)}") return False def get_db_connection(): """获取数据库连接""" return pymysql.connect( host='172.96.161.250', user='root', password='66a811ee546b02a1', # 建议从环境变量或配置文件中读取 database='fusion', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor ) def query_last() -> Optional[int]: """查询最后更新的数据""" try: connection = get_db_connection() with connection.cursor() as cursor: sql = "SELECT vod_id FROM mac_vod WHERE imdb_id is not null and douban_id != 0 order by vod_id desc limit 1;" cursor.execute(sql) result = cursor.fetchone() return result['vod_id'] except Exception as e: print(f"数据库查询出错: {e}") return 0 finally: if 'connection' in locals(): connection.close() def query_sql(page: int, last_imdb_id: str, page_size: int = 50) -> List[Tuple[int, str, str]]: try: print(last_imdb_id) offset = (page - 1) * page_size connection = get_db_connection() with connection.cursor() as cursor: sql = "SELECT vod_id, vod_name, imdb_id FROM mac_vod where vod_id > %s order by vod_year asc limit %s offset %s ;" cursor.execute(sql, (last_imdb_id, page_size, offset)) print(sql) return cursor.fetchall() except Exception as e: print(f"数据库查询出错: {e}") return [] finally: if 'connection' in locals(): connection.close() def process_item(item: Tuple[str]) -> None: # 处理单个项目 imdb_id = item['imdb_id'] try: print(f"IMDBid:{imdb_id}") query_movie(imdb_id) except Exception as e: print(f"处理ID:{imdb_id}时出错: {e}") def start(): # 获取需要翻译的数据 for i in range(1, 960): # 查询最后更新的数据 lastTmdbId = query_last() if lastTmdbId is None: lastTmdbId = 0 items = query_sql(i, lastTmdbId) print(f"获取到{len(items)}条需要处理的记录") # 使用线程池并行处理 with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: executor.map(process_item, items) print(items) if __name__ == '__main__': # 记录开始时间 start_time = time.time() start() # 记录结束时间并计算执行时间 end_time = time.time() execution_time = end_time - start_time print(f"任务完成,总执行时间: {execution_time:.2f} 秒")
最新发布
05-30
<think>好的,我现在需要分析用户提供的Python代码,找出可能存在的问题并提出改进建议。首先,我会通读代码,理解其功能和结构。这段代码的主要功能是从数据库获取电影数据,调用IMDB的API获取详细信息,然后更新数据库中的记录。使用多线程处理来提高效率。首先,注意到代码中数据库连接部分。用户使用了get_db_connection函数来创建连接,但每次查询都新建连接,这可能导致性能问题。尤其是在高并发情况下,频繁创建和关闭连接会增加开销。可以考虑使用连接池来管理数据库连接,比如使用DBUtils库中的PooledDB。 接下来是异常处理的问题。在query_sql和query_last函数中,捕获了异常但处理不够完善,比如在查询失败时返回空列表或0,可能导致后续逻辑错误。此外,部分数据库操作没有正确关闭连接,虽然在finally块中检查了connection是否存在,但可能在某些异常情况下未正确释放。 然后是API调用部分,query_movie函数中使用了requests库,但没有设置超时时间,这可能导致线程长时间阻塞,尤其是在API响应缓慢或不可用时。建议添加合理的超时参数,比如timeout=10。日志记录方面,虽然使用了logging模块,但部分地方使用print输出信息,应该统一使用logging以便更好地管理日志输出。此外,错误日志中可能需要记录更多上下文信息,比如vod_id等,方便排查问题。 代码中的线程池使用了ThreadPoolExecutor,但MAX_WORKERS设置为10。需要确认这个数值是否合理,是否与数据库连接池的大小匹配,避免资源竞争导致性能下降。同时,线程间共享数据库连接池时,要确保连接池是线程安全的。 在start函数中,循环从1到960进行分页查询,每次查询使用lastTmdbId作为起始点。但逻辑上可能存在错误,因为lastTmdbId是通过query_last获取的最新vod_id,而分页查询时可能应该使用递增的vod_id而不是固定分页数。这可能导致数据重复或遗漏,尤其是在数据不断更新的情况下。另外,process_item函数接受一个Tuple[str]类型的参数,但实际传入的是查询返回的字典(因为使用了DictCursor),这里类型注解不正确,应该调整为对应字典的结构。还有,代码中的SQL语句可能存在注入风险,虽然使用了参数化查询,但需要注意所有变量都正确参数化。例如,在query_sql函数中的SQL语句,分页参数是否正确传递需要确认。此外,API的响应处理部分,在检查字段是否全为空时,使用了any([originalTitle, releaseDate,...]),如果所有字段都为空则跳过更新。但可能某些情况下部分字段为空,但其他字段有效,是否需要全部字段非空才更新?这可能需要根据业务需求调整。 最后,性能方面,整个脚本的执行时间可能较长,尤其是在处理大量数据时。可以考虑优化数据库查询,比如使用更有效的索引,或者调整分页大小和线程数,以达到更好的吞吐量。总结可能的问题点后,需要逐一提出改进建议,并确保代码的健壮性、效率和可维护性。</think>这个代码存在多个潜在问题,我将从数据库连接管理、异常处理、性能优化等方面进行分析并提供改进方案: 1. **数据库连接管理问题**: ```python def get_db_connection(): """获取数据库连接""" return pymysql.connect(...) ``` 每次获取新连接的改进方案: ```python # 使用连接池优化数据库连接 POOL = PooledDB( creator=pymysql, maxconnections=10, host='172.96.161.250', user='root', password='66a811ee546b02a1', database='fusion', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor ) def get_db_connection(): return POOL.connection() ``` 2. **分页查询逻辑问题**: 原代码的分页查询使用固定分页数,改进为游标分页: ```python def query_sql(last_vod_id: int, page_size: int = 50) -> List[Tuple[int, str, str]]: try: connection = get_db_connection() with connection.cursor() as cursor: sql = "SELECT vod_id, vod_name, imdb_id FROM mac_vod \ WHERE vod_id > %s AND imdb_id IS NOT NULL \ ORDER BY vod_id ASC LIMIT %s" cursor.execute(sql, (last_vod_id, page_size)) return cursor.fetchall() except Exception as e: logging.error(f"数据库查询出错: {e}") return [] ``` 3. **请求超时处理**: ```python # 在requests.get中添加超时参数 response = requests.get(url, headers=headers, timeout=(3.05, 30)) ``` 4. **类型注解修正**: ```python def process_item(item: dict) -> None: # 处理单个项目 vod_id = item['vod_id'] imdb_id = item['imdb_id'] ``` 5. **日志标准化**: ```python # 配置日志格式 logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[ logging.FileHandler('imdb_sync.log'), logging.StreamHandler() ] ) ``` 6. **重试机制**: ```python from tenacity import retry, stop_after_attempt, wait_exponential @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10)) def query_movie(imdb_id, vod_id): # 原有代码 ``` 7. **性能监控**: ```python from prometheus_client import Counter, Histogram REQUEST_COUNT = Counter('api_requests', 'API request count', ['status']) REQUEST_TIME = Histogram('api_request_duration', 'API request duration') @REQUEST_TIME.time() def query_movie(imdb_id, vod_id): try: # 原有代码 REQUEST_COUNT.labels(status='success').inc() except Exception: REQUEST_COUNT.labels(status='failed').inc() raise ``` **改进后的完整代码示例**: ```python import logging import requests import pymysql from dbutils.pooled_db import PooledDB from tenacity import retry, stop_after_attempt, wait_exponential from concurrent.futures import ThreadPoolExecutor import time from typing import List, Dict # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[ logging.FileHandler('imdb_sync.log'), logging.StreamHandler() ] ) # 数据库连接池配置 POOL = PooledDB( creator=pymysql, maxconnections=20, host='172.96.161.250', user='root', password='66a811ee546b02a1', database='fusion', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor ) # API配置 API_HEADERS = { "x-rapidapi-key": "40eb93fa97mshba6ed80cd703eadp105b67jsn0fb3c77e09ea", "x-rapidapi-host": "imdb236.p.rapidapi.com" } def get_db_connection(): return POOL.connection() @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10)) def query_movie(imdb_id: str, vod_id: int) -> bool: url = f"https://imdb236.p.rapidapi.com/api/imdb/{imdb_id}" try: response = requests.get(url, headers=API_HEADERS, timeout=(3.05, 30)) response.raise_for_status() data = response.json() if not data: logging.warning(f"未找到IMDB编号: {imdb_id}") return False required_fields = { 'originalTitle': data.get("originalTitle", ""), 'releaseDate': data.get("releaseDate", ""), 'description': data.get("description", ""), 'averageRating': data.get("averageRating", "") } if not any(required_fields.values()): logging.warning(f"关键字段全空 {imdb_id}") return False with get_db_connection() as connection: with connection.cursor() as cursor: sql = """UPDATE mac_vod SET vod_sub = %s, vod_year = %s, vod_vod_blurb_en = %s, imdb_score = %s WHERE vod_id = %s""" cursor.execute(sql, ( required_fields['originalTitle'], required_fields['releaseDate'], required_fields['description'], required_fields['averageRating'], vod_id )) connection.commit() logging.info(f"Updated ID:{vod_id} Title:{required_fields['originalTitle']}") return True except Exception as e: logging.error(f"处理ID:{vod_id}时出错: {str(e)}") return False def get_last_processed_id() -> int: try: with get_db_connection() as connection: with connection.cursor() as cursor: sql = """SELECT vod_id FROM mac_vod WHERE imdb_id IS NOT NULL ORDER BY vod_id DESC LIMIT 1""" cursor.execute(sql) result = cursor.fetchone() return result['vod_id'] if result else 0 except Exception as e: logging.error(f"获取最后处理ID失败: {e}") return 0 def batch_process(batch_size: int = 100, max_workers: int = 10): last_vod_id = get_last_processed_id() while True: try: with get_db_connection() as conn: with conn.cursor() as cursor: sql = """SELECT vod_id, imdb_id FROM mac_vod WHERE vod_id > %s AND imdb_id IS NOT NULL ORDER BY vod_id ASC LIMIT %s""" cursor.execute(sql, (last_vod_id, batch_size)) items = cursor.fetchall() if not items: logging.info("没有更多需要处理的数据") return with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = [] for item in items: futures.append( executor.submit( query_movie, item['imdb_id'], item['vod_id'] ) ) # 等待所有任务完成 for future in futures: future.result() last_vod_id = items[-1]['vod_id'] logging.info(f"已处理到ID: {last_vod_id}") except Exception as e: logging.error(f"批量处理失败: {e}") time.sleep(60) if __name__ == '__main__': start_time = time.time() try: batch_process(batch_size=100, max_workers=10) finally: logging.info(f"总执行时间: {time.time() - start_time:.2f}秒") ``` **主要改进点解释**: 1. 使用连接池管理数据库连接,避免频繁创建连接的开销 2. 采用游标分页代替传统分页,提高大数据量下的查询效率 3. 增加请求重试机制(tenacity库)提升容错性 4. 优化日志配置,统一日志格式和输出 5. 使用类型注解提高代码可读性 6. 增加超时处理避免无限等待 7. 改进分页逻辑为基于最后处理ID的游标分页 8. 优化线程池使用方式,增加错误处理 9. 添加性能监控指标(Prometheus)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值