asyncio aiohttp请求解决API接口耗时问题

本文讲述了如何通过Python的asyncio库和aiohttp实现协程并发请求,从而显著减少大屏API接口的总耗时。原始接口耗时6-7秒,通过并发处理,将耗时降低到约3秒。主要方法是将第二个接口的多次请求转换为并发IO操作,实现了接口调用效率的大幅提升。

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

现象:需要实现一个大屏的API,需要展示50-100份数据列表,单数这些数据的组成部分,由2个http接口数据才能取到,第一个接口:取的是这50-100份数据,另一个接口:根据这50-100份数据中的一部分数据去获取相关的其它数据,所以这是一个两个接口串行,并将第二个接口串行循环完成后的过程。

问题:第一个接口的耗时大概是2-3S,第二个接口的耗时大概是0.3S,这样实现后,实现的这个大屏的API接口,最低耗时也要达到5-6s,经常耗时7-8s,耗时太长,不可接受。

解决方案:第一个接口的耗时没办法再进行缩减,只能从第二接口下手,如果能把第二个接口的时间缩减到一秒内,这样是能够接受的。

采用:python asyncio请求有关的依赖aiohttp。

通过协程的概念,完成IO请求的并发。

@action(methods=['get'], detail=False)
    def list_alarm_limit30(self, request):
        num_objs = KeyValueConfig.objects.filter(key='alarm_number')
        if not num_objs:
            return Response({
                "result": False,
                "code": "NO",
                "message": "请到后台配置告警数量key:alarm_number",
                "data": []
            })
        num_obj = num_objs[0].value
        try:
            num_obj = int(num_obj)
        except Exception as e:
            num_obj = 100
            logger.error('配置告警数量的值错误:{0}'.format(str(e)))
        biz_data = search_business(params={"bk_username": admin_role if admin_role else "admin"})
        biz_dict = {i['bk_biz_id']: i['bk_biz_name'] for i in biz_data}
        alarm_time = time.time()
        data = list_alarm_instance(bk_biz_ids=[i['bk_biz_id'] for i in biz_data], limit=num_obj)['data']
        print('告警列表获取耗时:{0}'.format(time.time() - alarm_time))
        data = sorted(data, key=lambda keys: keys['id'], reverse=True)
        data = data[:num_obj]
        alarm_data = []
        tasks_list = []
        # 创建一个asyncio事件任务循环(协程)
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        for i in data:
            source_start_time = datetime.datetime.strptime(i['begin_time'][:19], '%Y-%m-%d %H:%M:%S')
            source_end_time = source_start_time + datetime.timedelta(minutes=2)
            objs = VoiceMsg.objects.filter(call_time__range=(source_start_time, source_end_time))
            # -1 代表未配置
            # 0  通知失败
            # 1  通知成功
            voice_content = -1
            phone = None
            if objs:
                if objs.filter(status=True):
                    voice_content = 1
                    phone = objs.filter(status=True)[0].phone
                else:
                    voice_content = 0
                    phone = objs.filter(status=False)[0].phone
            end_time = datetime.datetime.strptime(i['end_time'][:19], '%Y-%m-%d %H:%M:%S')
            end_time = i['end_time'][:19] if end_time > source_start_time else '--'

            dimension_dict = i['origin_alarm']['dimension_translation']
            if 'bk_topo_node' in dimension_dict:
                dimension_dict.pop('bk_topo_node')
            if 'bk_target_cloud_id' in dimension_dict:
                dimension_dict.pop('bk_target_cloud_id')
            if 'bk_cloud_id' in dimension_dict:
                dimension_dict.pop('bk_cloud_id')
            dimension_list = ['{0}({1})'.format(i['display_name'], i['display_value'])
                              for i in dimension_dict.values()]
            dimension = ' - '.join(dimension_list)
            content = i['origin_alarm']['anomaly']['{0}'.format(i['level'])]['anomaly_message']
            data_dict = {
                "id": i['id'],
                "bk_biz_name": biz_dict.get(i['bk_biz_id']),
                "source_time": i['begin_time'][:19],
                "end_time": end_time,
                "source_name": i['origin_config']['strategy_name'],
                "alarm_type": LEVEL_TYPE.get(str(i['level'])),
                "alarm_content": {
                    "dimension": "维度信息:{0}".format(dimension),
                    "content": "告警内容:{0}".format(content),
                },

                "source_url": "{0}/o/bk_monitorv3/?bizId={1}#/event-center/detail/{2}".format(BK_PAAS_HOST,
                                                                                              i['bk_biz_id'],
                                                                                              i['id']),
                "voice_status": voice_content,
                'phone': phone
            }
			#  添加IO事件任务到循环列表
            tasks_list.append(asyncio.ensure_future(get_status_log(i['id'])))
            alarm_data.append(data_dict)
        start_time = time.time()
        # 事件任务执行 返回所有结果
        result = asyncio.get_event_loop().run_until_complete(asyncio.gather(*tasks_list))
        api_ids_list = []
        for data_obj in alarm_data:
            for re_obj in result:
                if data_obj['id'] == re_obj['event_id']:
                    data_obj['comment'] = re_obj['comment']
                    api_ids_list.append(re_obj['event_id'])
                    break
        print("日志消耗时间:{0}".format(time.time() - start_time))
        queryset = KeyValueConfig.objects.filter(key='event_ids')
        is_voice = False
        if queryset:
            database_ids_list = [int(i) for i in queryset[0].value.split(',')]
            api_alone_ids = [id_obj for id_obj in api_ids_list if id_obj not in database_ids_list]
            print(api_alone_ids)
            is_voice = True if api_alone_ids else False
        KeyValueConfig.objects.update_or_create(
            defaults={
                "key": "event_ids",
                "value": ','.join([str(i) for i in api_ids_list]),
                "mark": "当前大屏告警的ids"
            },
            key="event_ids"
        )
        flush_objs = KeyValueConfig.objects.filter(key='flush_time')
        flush_time = int(flush_objs[0].value) if flush_objs else '180000'
        return Response({"data": alarm_data, "count": len(alarm_data), "is_voice": is_voice, 'flush_time': flush_time})

具体第二个接口请求封装成IO请求

async def get_status_log(event_id):
    params = {
        'bk_app_code': settings.APP_CODE,
        'bk_app_secret': settings.SECRET_KEY,
        "bk_username": admin_role if admin_role else "admin",
        "id": event_id,
    }
    session = aiohttp.ClientSession()
    session.headers.update({'X-DATA-REQUEST-ID': get_request_id()})
    session.headers.update({'Content-Type': 'application/json; chartset=utf-8'})
    async with session.request(
            method="POST",
            url=BK_PAAS_HOST + '/api/c/compapi/v2/monitor_v3/get_event_log/',
            data=json.dumps(params),
            verify_ssl=False
    ) as response:
        content = json.loads(await response.content.read())
        # 关闭IO请求
        await session.close()
        comment = "--"
        for status_obj in content.get('data', []):
            if status_obj['operate'] == 'ANOMALY_NOTICE':
                comment = "已通知" if status_obj['status'] == 'SUCCESS' else '通知失败'
                break
        return {
            "event_id": event_id,
            "comment": comment
        }

效果:原本大屏API要展示100条数据,第二个接口耗时3-4S,API总耗时6-7S,现在通过协程实现,第二个接口耗时不到一秒,现在大屏API总耗时三秒左右。
100次接口的耗时和一次接口的耗时差不多。

### 解决OKX API接口请求超时问题的方法 对于API接口请求超时的问题,通常可以从多个角度来优化和解决问题。针对OKX API的具体情况,以下是一些可行的策略: #### 增加超时设置时间 如果默认的超时时间较短,则可以通过调整HTTP客户端配置中的timeout参数延长等待响应的时间长度。这有助于在网络状况不佳的情况下获得完整的服务器回应[^1]。 ```javascript const axios = require('axios'); // 设置更长的超时时间(单位毫秒) axios.defaults.timeout = 5000; // 设定为5秒钟 ``` #### 实施重试机制 实现自动化的重试逻辑可以在初次尝试失败后再次发起相同的请求直到成功为止或达到最大重试次数限制。这种方式能够有效应对临时性的网络波动所引起的连接中断现象[^2]。 ```javascript function retryRequest(config, retriesLeft) { return new Promise((resolve, reject) => { axios.request(config).then(response => resolve(response)).catch(error => { if (retriesLeft > 0 && error.code === 'ECONNABORTED') { // 超时时触发重试 console.log(`Retrying... (${retriesLeft} attempts left)`); setTimeout(() => { retryRequest(config, retriesLeft - 1).then(resolve).catch(reject); }, 1000 * Math.random()); // 随机延迟再试以减轻负载压力 } else { reject(error); } }); }); } ``` #### 使用异步非阻塞模式 采用异步编程模型而非同步方式发送HTTP请求可防止主线程被长时间占用从而提高程序的整体性能表现并减少因单次操作耗时过久而导致整个应用卡顿的风险[^3]。 ```python import asyncio import aiohttp async def fetch_data(url): async with aiohttp.ClientSession() as session: try: response = await session.get(url, timeout=aiohttp.ClientTimeout(total=60)) data = await response.json() return data except Exception as e: print(f"Error fetching from {url}: ", str(e)) loop = asyncio.get_event_loop() result = loop.run_until_complete(fetch_data("https://www.okx.com/api/v5/market/ticker?instId=BTC-USDT")) print(result) ``` #### 合理规划并发量控制 适当降低同时发出大量请求的数量可以避免因为瞬间流量过大给目标服务端带来巨大负担进而引发其主动关闭部分未完成的任务链接的情况发生;同时也应该注意遵循API提供商给出的相关速率限制指南以免触犯规定造成不必要的麻烦[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值