前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >即时通信IM-使用连接池降低IM后台API访问时延

即时通信IM-使用连接池降低IM后台API访问时延

原创
作者头像
yiwei
修改2020-11-13 17:04:46
1.8K0
修改2020-11-13 17:04:46
举报
文章被收录于专栏:腾讯云云通信专家服务

背景

腾讯云服务团队曾收到客户反馈:在使用SDK时,调用IM后台接口偶现超时的现象。在经过腾讯云客服人员排查后,发现部分用户请求并未到达IM后台。此时往往会引导从客户侧网络开始排查,其中有一项优化措施是建议客户端使用长连接+连接池的调用方式。本文会详细介绍连接池技术的原理和使用方式,并通过实验验证在调用REST API时的优化效果。

分析

在即时通信IM官方文档REST API 简介中介绍,当出现REST API请求概率性超时时,有以下解决方案:

官网文档描述
官网文档描述

其中,2、3、4项均为本地网络环境的测试和检查,配置不得当往往会直接导致访问完全不通。而第五项的优化会影响远程调用效率和时延,间接影响服务质量,下文将重点针对长连接+连接池进行阐述。

长连接简介

客户端发起HTTP请求的大致步骤如下:

HTTP请求流程
HTTP请求流程

HTTP1.0中任何一次请求都需要单独建立连接,即使请求量很小,也涉及到上图中的全部流程,这样导致有大量的请求时延耗费在了建立和关闭TCP连接的操作上。1999年发布的HTTP1.1协议针对以上问题作出了改进,他支持在HTTP header中传入一个字段:Connection: keep-alive,以告诉服务器需要保持连接,以达到在一个TCP连接上传输多个HTTP请求和响应的目的,减少了用在建立和释放连接上的耗时。

长连接提升性能的因素

除了上述所说的省去了多次请求间频繁的新建和释放连接的流程,长连接在其他协议栈也减少了流程从而达到减少时延的目的,如:

(1)避免半连接/全连接队列溢出丢包

在Linux内核协议栈中,TCP包传入应用层前会经过两个队列:

  • 半连接队列:未完成三次握手的连接
  • 全连接队列:已完成三次握手,但未被应用层accept

三次握手中,在第一步服务器在收到客户端的syn包后,会把相关信息放到半连接队列中,同时回复syn+ack给客户端。之后在服务器收到客户端的ack包,如果这时全连接队列没满,那么从半连接队列拿出相关信息放入到全连接队列中,否则按tcp_abort_on_overflow指示的执行。如果不使用长连接,大量的连接重新开始握手,导致全连接队列溢出,会直接促使TCP三次握手的第一次到半连接队列的包丢弃。

(2)TLS层优化

目前即时通讯IM的REST API请求均需要通过HTTPS协议进行加密传输,以保证数据传输的安全性。HTTPS便是基于TLS协议对HTTP的明文信息进行加密传输的。这里不对TLS的原理做详细介绍,大致流程是证书交换-->服务端加密-->客户端解密的过程,整个过程中反复地客户端和服务端之间的数据交换是一个耗时的过程,且数据的加解密是一个计算密集型的操作消耗CPU资源,因此如果相同的请求能省去加解密流程就能在HTTPS协议下对整个性能有很大提升了。实际上这种优化是有的,即基于长连接的会话复用技术:对于已建立的TLS会话,使用session id为key,主密钥为value组成一对键值对保存在服务端和客户端的本地。当第二次握手时,客户端如果想复用会话,则发起的Client Hello中带上session id,服务端收到这个session id检查本地是否存在,有则允许会话复用,进行后续操作。

连接池

连接池是用来分配、管理、释放长连接的技术。当客户端需要发起访问时,从连接池中分配一条长连接发送请求,处理完请求响应后便释放回连接池。主流编程语言均提供成熟的连接池工具,比如python中的urllib3,Java的HttpClient等。

连接池使用试验

试验环境

  • 请求接口:v4/group_open_http_svc/get_group_info(获取群组详细资料),该接口请求方式可参考https://cloud.tencent.com/document/product/269/1616
  • 客户端:一台腾讯云加拿大的虚拟机(用以模拟高延时环境)
  • 相关程序库:python urllib3

试验过程

先使用单个连接的模式访问,代码如下,已隐去秘钥和用户id等信息:

代码语言:python
代码运行次数:0
复制
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json, urllib2
import time

USERSIG = 'xxxxx'
IDENTIFIER='administrator'
SDKAPPID=00000000

url = 'https://console.tim.qq.com/v4/group_open_http_svc/get_group_info' \
      '?usersig={}&identifier={}&sdkappid={}&contenttype=json'.format(USERSIG,IDENTIFIER,SDKAPPID)
data = {"GroupIdList": ["@TGS#2LSF4FZGA"]}

def sendHttpGetReq(url, headers={}):
    try:
        req = urllib2.Request(url, headers=headers, data=json.dumps(data))  # 新建连接
        initial_time = time.time()
        resp = urllib2.urlopen(req)  #发送请求
        ending_time = time.time()
        elapsed_time = str((ending_time - initial_time)*1000)
        print elapsed_time
    except urllib2.URLError, error:
        print(error)
        resp = False
    return resp

def sendReq(url):
    request_times = 5
    while request_times > 0:
        request_times -= 1
        resp = sendHttpGetReq(url)    # 每次访问均新建一个连接
        respContent = json.loads(resp.read())
    return respContent

def get_res():

    respContent = sendReq(url)
    return respContent

if __name__ == '__main__':
    res = get_res()

运行结果如下,单次请求耗时为600ms ~700ms之间:

代码语言:python
代码运行次数:0
复制
672.338962555
705.717086792
698.77910614
687.951087952
663.526058197

下面使用连接池访问相同接口,这里由于只使用单进程访问单独接口,因此连接池中连接数量设置为1即可(maxsize=1)

代码语言:python
代码运行次数:0
复制
import urllib3
import json
import time

USERSIG = 'xxxxxxxx'
IDENTIFIER='administrator'
SDKAPPID=00000000

url = 'https://console.tim.qq.com/v4/group_open_http_svc/get_group_info' \
      '?usersig={}&identifier={}&sdkappid={}&contenttype=json'.format(USERSIG,IDENTIFIER,SDKAPPID)
data = {"GroupIdList": ["@TGS#2LSF4FZGA"]}

pool = urllib3.PoolManager(retries=False, timeout=3, num_pools=1, maxsize=1)   # 新建连接池,连接池数量一个,缓冲池内维护一条长连接
def send_http():
    for _ in range(5):
        initial_time = time.time()
        r = pool.request('POST', url ,body=json.dumps(data) , headers={'Content-Type': 'application/json'})
        ending_time = time.time()  # Time when acknowledged the request
        elapsed_time = str((ending_time - initial_time)*1000)
        print elapsed_time
        # print(r.status)

if __name__ == '__main__':
    send_http()

运行结果如下,可见除了第一条请求耗时600ms以上,后续所有请求均仅耗时二百多毫秒,时延降低明显:

代码语言:python
代码运行次数:0
复制
673.004150391
234.243869781
270.310878754
273.617982864
231.528997421

试验结论

由此可见,在访问即时通信IM REST API时,使用连接池技术可以有效的降低访问时延。实际业务中,会存在访问多个不同的组合接口,线上量级也会远比试验中要大,再加上客户端所在网络环境的复杂多变,连接池技术可以产生更明显的优化效果,用户可以根据业务实际需求设置连接池数和长连接数量,以达到耗费计算资源和提高请求效率之间的平衡。

总结

实际生产环境中,接口调用超时可能是多方面原因引起的,本文仅从HTTP长连接的角度给出客户端优化建议,希望能给到部分用户帮助。如有任何问题,欢迎联系腾讯云服务团队(提交工单)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 分析
    • 长连接简介
      • 长连接提升性能的因素
        • 连接池
        • 连接池使用试验
          • 试验环境
            • 试验过程
              • 试验结论
              • 总结
              相关产品与服务
              SSL 证书
              腾讯云 SSL 证书(SSL Certificates)为您提供 SSL 证书的申请、管理、部署等服务,为您提供一站式 HTTPS 解决方案。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档