Python 全栈系列271 微服务踩坑记

说明

这个坑花了10个小时才爬出来

碰到一个现象:将微服务改造为并发后,请求最初很快,然后就出现大量的失败,然后过一会又能用。

过去从来没有碰到这个问题,要么是一些比较明显的资源,或者逻辑bug,总之跑断了就明白,非常显眼。然后从接口传入的并发量并不是很大,而且即使初期传入的量大也没见崩。

内容

1 分析

我把日志贴给了chatgpt,然后得到了一些信息。

在压测过程中,出现 “Cannot assign requested address” 错误,通常是由于系统的网络端口资源耗尽或请求连接过于频繁,导致无法分配新的端口。这类问题可能与以下几个因素有关:

  1. 端口耗尽:系统的可用端口数有限,尤其是在并发请求非常高时,操作系统可能耗尽可用的端口资源。这通常会导致类似 “Cannot assign requested address” 的错误。

  2. TIME_WAIT 状态:HTTP 请求结束后,系统会将使用的端口暂时保留在 TIME_WAIT 状态,导致端口无法立即释放和重复使用。如果并发请求较多,TIME_WAIT 状态的端口过多,会导致端口耗尽。

  3. 系统参数限制:一些系统网络参数设置可能过低,例如 net.ipv4.ip_local_port_range(本地可用端口范围)或 net.ipv4.tcp_tw_reuse(TIME_WAIT 状态端口重用),可能会影响并发连接的处理能力。

解决方案建议:

  1. 调整系统端口范围
    你可以通过 sysctl 修改系统可用的端口范围:

    sysctl -w net.ipv4.ip_local_port_range="1024 65535"
    
  2. 启用 TIME_WAIT 重用
    开启 tcp_tw_reuse 允许系统重用 TIME_WAIT 状态的端口:

    sysctl -w net.ipv4.tcp_tw_reuse=1
    
  3. 增加文件句柄和连接数
    提升系统最大文件句柄和连接数限制:

    ulimit -n 65536
    sysctl -w fs.file-max=1000000
    
  4. 合理配置负载均衡
    如果你使用了 Nginx 作为负载均衡器,确保 Nginx 配置了足够的连接处理能力,例如增加 worker_connections

这些设置可以帮助你在高并发压测环境中优化连接资源,减少端口耗尽的情况。

文件句柄数我先看了,非常大100多万。然后我看到fastapi在疯狂的刷端口,一个容器内所有的端口都给这个服务,所以说端口不够用也不对。

看到TIME_WAIT我突然有感觉了,应该是这个问题。

由于这个微服务实际上是第二级的,负责处理大量的规则。每一个entity都会执行一次规则。没执行一次都发起一次调用。然后按照TCP的规范,握手释放的时间至少要10秒,保守是30秒。默认情况下是2Min。

在这里插入图片描述
从这里也可以推算,假设有5万个端口分配给微服务,每120秒的冷却时间,那么TPS大约是400,如果改为30秒,那么是1200。还有就是通过长连接 + 批量发送来实质性的扩大TPS。

最初我的主web框架是flask和tornado。

本来这次没有意外的话,我应该使用Tornado搭建服务的,也许也就不会有这个问题。最初是看到FastAPI的一些优点:例如更高的json序列化效率,结合pydantic的写法,的确是更好的,所以也冒了一点险来使用。

然后在优化的过程中了解到了Gunicorn和Uvicorn的差别,也更明确了FastAPI适用的场景。例如,使用FastAPI进行异步数据库查询应该会比Tornado更合适 。反正想要做的更好就必须去冒险,踩坑可能是一种无法避免的事,当我再从坑里爬出来,就离目标更近了一点。

我也和Chat确认了一下:

是的,TornadoFastAPI 确实有不同之处,尤其是在长连接(如 WebSocket、长轮询)方面。

1. Tornado 的长连接能力

Tornado 是一个异步的 Python web 框架,最早是为实时 Web 应用设计的。它天然支持长连接,适合处理WebSocket长轮询、**服务器推送(Server-Sent Events, SSE)**等应用场景。它的异步 IO 模型基于 tornado.ioloop.IOLoop,让 Tornado 可以高效地处理并发连接和长时间保持的连接。

2. FastAPI 与长连接

FastAPI 虽然不是为长连接特别设计的,但它基于 Starlette 框架,同样支持 WebSocket、SSE 等长连接类型的通信。FastAPI 的优势在于它的性能和现代设计,包括使用异步支持(async/await),并且在许多情况下性能可以媲美 Tornado。

FastAPI 使用 WebSocket 的例子:

from fastapi import FastAPI, WebSocket

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Message received: {data}")

3. Tornado 和 FastAPI 的区别

  • 长连接支持:Tornado 原生支持长连接,适合实时通讯类应用,例如聊天服务器、实时数据流等。FastAPI 也支持长连接,但需要通过 WebSocket 等机制来实现,底层是通过 Starlette 提供的异步支持。

  • 并发模型:Tornado 使用自己的事件循环 (IOLoop) 和异步编程模型,比较轻量且高效。而 FastAPI 基于 Python 的 asyncio 事件循环,允许更现代的异步编程(async/await),并且和 Starlette、Uvicorn 结合提供高性能的异步 API。

  • 实时性和延迟:Tornado 被设计为应对高并发的长连接场景,在处理实时应用时,性能和低延迟方面表现突出。FastAPI 也表现不错,但对于长连接,可能在某些特定情况下性能不如 Tornado 稳定。

4. 长连接适用场景

  • Tornado:适合构建实时交互应用,如聊天系统、股票行情推送系统、游戏服务器等需要保持长时间的连接或高频率通讯的场景。

  • FastAPI:适合 RESTful API 服务,同时也可以处理长连接需求,如 WebSocket,但它的设计初衷是高性能的 API,而非长连接为主的应用场景。

总结:

  • Tornado:更适合专注于长连接实时通信的场景,原生支持这些特性。
  • FastAPI:更适合快速构建高性能API,也可以支持长连接,但需要依赖底层的异步框架。

小结论

到这里可以有一些小结论:

  • 1 在大量的密集处理任务上,tornado可能会更好一些;而fastapi更适合零散的异步任务。400TPS-1200TPS是一个天然限制。
  • 2 大量的规则不可以作为API,而是应该封装为函数式。事实上,我估算了一下产生的二级请求是一个比较夸张的量。能勉强顶住,FastAPI已经算不错的了。
  • 3 度量是否要API也要看计算/传输比。在简单规则中,这个值太低了,完全划不来。

2 解决

既然是“端口相对不足”的问题,那么就做相应的调整。

the_port=34009
docker run -d \
 --name=short_name_query_server_${the_port} \
 -v /etc/localtime:/etc/localtime \
 -v /etc/timezone:/etc/timezone \
 -v /etc/hostname:/etc/hostname \
 -p ${the_port}:8000 \
 -e "LANG=C.UTF-8" \
 --sysctl net.ipv4.tcp_fin_timeout=30 \
 --sysctl net.ipv4.tcp_tw_reuse=1 \
 --sysctl net.ipv4.ip_local_port_range="10000 65535" \
 -w /workspace \
 IMAGE \
 sh -c "uvicorn fast_server:app --host 0.0.0.0 --port 8000 --workers=5 

在docker启动时增加配置项,反正顾名思义吧

然后可以切入容器检查

cat /proc/sys/net/ipv4/tcp_fin_timeout
cat /proc/sys/net/ipv4/tcp_tw_reuse
cat /proc/sys/net/ipv4/ip_local_port_range

改完后实测下来是在足够大的批量里跑数都是0错误了,当然只能是可怜的并发2,而且服务内部我还不敢去并发执行规则。

3 Next

规则执行只会有 get、pass、reject、error四个状态

目前规则的样式

# reject
@app.post("/r000/")
async def r000(justent:JustEnt):
    the_ent = justent.some_ent
    the_result = RuleResult()
    try:
        if judge_existence(the_ent, word_list=r0_exe_clude_list):
            the_result.status = 'reject'
        else:
            the_result.status = 'pass'
        return the_result.dict()
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

在上层调用的样式

# 接口返回数据模型  v {status: pass/reject/get , data:None 或者匹配全称}
# mapping_list 仅用于本次,不是通用设计
# raw 也是如此
import time 
def waterfall_api_mode(last_fall, rule_name ,reject_list = None, get_list = None, mappling_list = None, raw = None , base_url = None):
    next_fall = []
    last_ent_list = last_fall 
    pure_rule_url = rule_name + '/'

    if len(last_ent_list):
        rule_url = base_url + pure_rule_url
        # api mode
        tick1 = time.time()
        task_list = []
        for ent in last_ent_list:
            tem_dict = {}
            tem_dict['task_id'] =  ent 
            tem_dict['url'] = rule_url
            if raw is None :
                tem_dict['json_params'] = {'some_ent':ent}
            else:
                tem_dict['json_params'] = {'some_ent':ent,'raw':raw}
            task_list.append(tem_dict)
        rule_res = asyncio.run(json_player(task_list, concurrent = 10))
        # 解析结果,保留pass
        for tem_res in rule_res:
            for k,v in tem_res.items():
                # print(k,v)
                if v['status'] == 'pass':
                    next_fall.append(k)
                elif v['status'] == 'get':
                    if get_list is not None :
                        get_list.append(v['data'])
                    if mappling_list is not None :
                        mappling_list.append({'ent':k,'mapping_ent': v['data']})
                elif v['status'] == 'reject':
                    if reject_list is not None :
                        reject_list.append(k)

        tick2 = time.time()
        print('takes %.2f ' %(tick2-tick1))
    return next_fall

可以把输入的实体列表作为一个series,然后去apply就好了。根据每次apply的结果,分为四个类型:

  • 1 get : 附加到返回部分
  • 2 reject : 目前可以直接扔掉(如果是学习和分析)
  • 3 pass : 没有获取也没有抛弃,传入下一步处理。如果没有pass,那么处理结束。
  • 4 error:发生错误的部分,可以发往kafka

然后做一个简单的程序流就可以取代目前的微服务了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/877639.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Python生成多种不同类型的Excel图表

目录 一、使用工具 二、生成Excel图表的基本步骤 三、使用Python创建Excel图表 柱形图饼图折线图条形图散点图面积图组合图瀑布图树形图箱线图旭日图漏斗图直方图不使用工作表数据生成图表 四、总结 Excel图表是数据可视化的重要工具,它通过直观的方式将数字信…

CesiumJS+SuperMap3D.js混用实现可视域分析 S3M图层加载 裁剪区域绘制

版本简介: cesium:1.99;Supermap3D:SuperMap iClient JavaScript 11i(2023); 官方下载文档链家:SuperMap技术资源中心|为您提供全面的在线技术服务 示例参考:support.supermap.com.cn:8090/w…

嵌入式鸿蒙系统开发语言与开发方法分析

大家好,今天主要给大家分享一下,HarmonyOS系统的主力开发语言ArkTS语言开发方法,它是基于TypeScript(简称TS)语言扩展而来。 第一:ArkTS语言基本特性 目的:声明式UI,让开发者以更简洁,更自然的方式开发高性能应用。 声明式 UI基本特性: 基本UI描述:ArkTS定义了各种装饰…

Docker-compose:管理多个容器

Docker-Compose 是 Docker 公司推出的一个开源工具软件,可以管理多个 Docker 容器组成一个应用。用户需要定义一个 YAML 格式的配置文件 docker-compose.yml,写好多个容器之间的调用关系。然后,只要一个命令,就能同时启动/关闭这些…

Jenkins部署若依项目

一、配置环境 机器 jenkins机器 用途:自动化部署前端后端,前后端自动化构建需要配置发送SSH的秘钥和公钥,同时jenkins要有nodejs工具来进行前端打包,maven工具进行后端的打包。 gitlab机器 用途:远程代码仓库拉取和…

HTML5超酷炫的水果蔬菜在线商城网站源码系列模板1

文章目录 1.设计来源1.1 主界面1.2 商品列表界面1.3 商品详情界面1.4 其他界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/142059238 HTML5超酷炫的水果蔬菜在线商城网…

动手学习RAG: moka-ai/m3e 模型微调deepspeed与对比学习

动手学习RAG: 向量模型动手学习RAG: moka-ai/m3e 模型微调deepspeed与对比学习动手学习RAG:迟交互模型colbert微调实践 bge-m3 1. 环境准备 pip install transformers pip install open-retrievals注意安装时是pip install open-retrievals,但调用时只…

【Mac】系统环境配置

常用工具 Navicat PJ版本:this Host切换器 SwitchHosts termius 一款好用的Linux服务器连接工具: termius 小飞机 dddd:🪜 Git mac安装git有好多种方式,自带的xcode或者通过Homebrew来安装,本文的…

人工智能开发实战matplotlib库应用基础

内容导读 matplotlib简介绘制直方图绘制撒点图 一、matplotlib简介 matplotlib是一个Python 2D绘图库,它以多种硬拷贝格式和跨平台的交互式环境生成高质量的图形。 matplotlib 尝试使容易的事情变得更容易,使困难的事情变得可能。 我们只需几行代码…

Qt ORM模块使用说明

附源码:QxOrm是一个C库资源-CSDN文库 使用说明 把QyOrm文件夹拷贝到自己的工程项目下, 在自己项目里的Pro文件里添加include($$PWD/QyOrm/QyOrm.pri)就能使用了 示例test_qyorm.h写了表的定义,Test_QyOrm_Main.cpp中写了所有支持的功能的例子: 通过自动表单添加…

C++ ——string的模拟实现

目录 前言 浅记 1. reserve(扩容) 2. push_back(尾插) 3. iterator(迭代器) 4. append(尾插一个字符串) 5. insert 5.1 按pos位插入一个字符 5.2 按pos位插入一个字符串 …

CleanClip for Mac 剪切板 粘贴工具 历史记录 安装(保姆级教程,新手小白轻松上手)

CleanClip:革新macOS剪贴板管理体验 目录 功能概览 多格式历史记录保存智能搜索功能快速复制操作拖拽功能 安装指南 前期准备安装步骤 配置与使用 功能概览 多格式历史记录保存 CleanClip支持保存文本、图片、文件等多种格式的复制历史记录,为用户提…

C语言 | Leetcode C语言接雨水II

题目: 题解: typedef struct{int row;int column;int height; } Element;struct Pri_Queue; typedef struct Pri_Queue *P_Pri_Queue; typedef Element Datatype;struct Pri_Queue{int n;Datatype *pri_qu; };/*优先队列插入*/ P_Pri_Queue add_pri_que…

通信工程学习:什么是SNI业务节点接口

SNI:业务节点接口 SNI业务节点接口,全称Service Node Interface,是接入网(AN)和一个业务节点(SN)之间的接口,位于接入网的业务侧。这一接口在通信网络中扮演着重要的角色&#xff0c…

几种手段mfc140u.dll丢失的解决方法,了解mfc140u.dll

在使用Windows操作系统时,许多用户可能会遇到“找不到mfc140u.dll”或“mfc140u.dll未找到”的错误提示。这个错误通常是由于该文件丢失或损坏所致。本文将详细介绍mfc140u.dll文件的作用、丢失的原因及其解决方法,帮助您快速恢复系统的正常运行。 一、m…

2024年华为9月4日秋招笔试真题题解

2024年华为0904秋招笔试真题 二叉树消消乐好友推荐系统维修工力扣上类似的题--K站中转内最便宜的航班 二叉树消消乐 题目描述 给定原始二叉树和参照二叉树(输入的二叉树均为满二叉树,二叉树节点的值范围为[1,1000],二叉树的深度不超过1000)&#xff0c…

Maven 解析:打造高效、可靠的软件工程

Apache Maven【简称maven】 是一个用于 Java 项目的构建自动化工具, 通过提供一组规则来管理项目的构建、依赖关系和文档。 1.Pre-预备知识: 1.1.Maven是什么? [by Maven是什么?有什么作用?Maven的核心内容简述_ma…

简化登录流程,助力应用建立用户体系

随着智能手机和移动应用的普及,用户需要在不同的应用中注册和登录账号,传统的账号注册和登录流程需要用户输入用户名和密码,这不仅繁琐而且容易造成用户流失。 华为账号服务(Account Kit)提供简单、快速、安全的登录功能,让用户快…

Zabbix自定义监控项与触发器

当我们需要获取某台主机上的数据时,直接利用 zabbix 提供的模板可以很方便的获得需要的数据,但是有些特别的数据,利用这些现有的模板或监控项是无法实现的,例如网站状态信息的监控、mysql数据库主从状态等信息。这是就需要自己定义键值和监控…

Java许可政策再变,Oracle JDK 17 免费期将结束!

原文地址:https://www.infoworld.com/article/3478122/get-ready-for-more-java-licensing-changes.html Oracle JDK 17的许可协议将于9月变更回Oracle Technology Network License Agreement,这将迫使用户重新评估他们的使用策略。 有句老话说&#xf…