今天,我们正处于互联网飞速发展的时代。每个人的生活都离不开网络。互联网已经影响到每个人生活的方方面面。我们购物用淘宝和京东,交流用微信,美图秀秀美图等等。在这每一个操作之下,都离不开一个技术概念HTTP(HypertextTransferProtocol,超文本传输??协议)。例如:栗子:,我们打开京东APP,首先进入开机画面页面,然后进入首页。开屏一般是广告,而首页是内容相关的,包括闪购、商品推荐、每个标签页,每个标签也有对应的内容。当我们进入开屏或者开屏之后(这个要看各个app的技术实现),我们都会向后台服务发送一个http请求。这个请求会带上页面广告位的信息,向后台请求内容。后台根据广告位的配置,选择合适的广告或推荐商品,返回给APP进行展示。这里为了描述方便,将后台作为一个简单的整体。其实后台会有非常复杂的业务调度,比如获取用户画像、广告定向、获取素材、计算坐标、返回APP等。APP端使用坐标信息,下载素材,然后进行渲染,从而在客户端进行展示。这些都是秒级甚至毫秒级的响应。高效的HTTP客户端在这里尤为重要。本文主要从业务场景来分析,如何实现一个高效的HTTPClient。一、概念当我们需要模拟发送http请求时,往往有两种方式:1、通过浏览器2、通过curl命令发送请求。如果我们在一个大规模的高并发业务中,如果我们使用curl来进行http请求,其效果和性能是不能满足业务需求的,这就引入了另外一个概念libcurl。2.实现在开始实现客户端发送http请求之前,我们先了解两个概念:同步请求和异步请求服务器完成处理,通知客户端事件已经完成,客户端收到通知后将服务器处理的结果返回给调用者。通过这两个概念,我们可以看出异步的实现要比同步复杂的多。同步,也就是我们简单的等待处理结果,等处理结果完成后返回给调用者。对于异步,在实现上往往需要各种回调机制和通知机制,即处理完成后,需要知道是哪个任务完成了,从而通知客户端去处理任务完成后的剩余逻辑完成了。接下来,我们将从代码实现的角度进一步了解libcurl的同步和异步请求操作的区别,从而更加异步的理解同步和异步的实现原理。同步使用libcurl完成同步http请求。原理和代码都比较简单。主要步骤如下:1.初始化简易手柄2.设置简易手柄的相关参数。本例中主要有以下几个参数CURLOPT_URL即请求的urlCURLOPT_WRITEFUNCTION即回调函数,将http服务器返回的数据写入对应的地方CURLOPT_FOLLOWLOCATION是否获取302跳转后的内容CURLOPT_POSTFIELDSIZE,本次发送的数据大小CURLOPT_POSTFIELDS,本次发送的数据内容较多参数设置参考libcurl官网3.curl_easy_perform,调用该函数发送http请求,等待同步返回结果4.curl_easy_cleanup,释放步骤1编译的easyhandle资源代码实现(easy_curl.cc)异步访问网络编程读者,或多或少了解多路复用的原理。IO多路复用在Linux下包括三种,select、poll、epoll。从抽象的角度来看,它们的功能是相似的,但具体的细节是不同的:首先,会为相关的事件注册一组文件描述符。然后阻塞并等待某个事件发生或等待超时。在使用Libcurl进行异步请求时,从上层结构来看,简单来说就是easyhandle和multiinterface的结合。其中easyhandle底层也是socket和multiinterface,其底层实现也是使用epoll,那么我们如何使用easyhandle和multiinterface实现一个高性能的异步http请求客户端呢?下面我们将以代码的形式,让读者进一步了解其实现机制。multi接口的使用是在easy接口的基础上,把easyhandle放在一个队列中(multihandle),然后并发发送请求。与easy接口相比,multi接口是一种异步、非阻塞的传输方式。multi接口的使用主要有以下步骤:curl_multi_init初始化一个multihandler对象初始化多个easyhandler对象,使用curl_easy_setopt进行相关设置。调用curl_multi_add_handle将easyhandler添加到multicurl对象。添加完成后,执行curl_multi_perform方法。并发访问后,curl_multi_remove_handle移除相关的easycurl对象,先用curl_easy_cleanup清除easyhandler对象,最后用curl_multi_cleanup清除multihandler对象。http_request.hhttp_request.ccmain.cc至此,我们可以使用libcurl并发发送http请求了。当然,这只是一个简单的异步实现功能。更多的功能需要读者使用libcurl中的其他功能来实现。这里给各位读者留个问题(这个问题也是笔者项目中用到的功能,项目已经稳定在线4年,日请求量20E)。由于业务需要,需要同时向指定的几个公司发送某个请求。即需要同时向多个http服务器发送请求,并在特定的超时时间内获取这些http服务器的返回内容,并进行处理,那么这个功能应该如何使用libcurl来实现呢?根据披露,可以使用libcurl的另一个参数CURLOPT_PRIVATE。3、性能对比至此,我们基本完成了高性能http并发功能的设计,那么性能如何呢?笔者从以下几个角度做了测试:1.串行发送同步请求2.多线程情况下,发送同步请求(这里有4个线程,我测试的服务器是4C)3.使用多接口4、使用multiinterface,并复用其对应的easyhandle5,使用dns缓存(easyhandle设置CURLOPT_DNS_CACHE_TIMEOUT),即不需要每次都进行dns解析平均耗时(ms)最大耗时(ms)串口同步21.38130.617多线程同步4.33116.751多接口1.37611.974多接口连接复用0.3520.748多接口使用dns缓存0.3810.7314.一点经验libcurl是高性能是的,比较好用的HTTP客户端,直接使用的时候,一定要详细了解它的接口功能,不然很容易掉坑,还记得18年中期,当一个启动某功能,偶尔会出现coredump(上线前也进行了大量的性能测试,有一次没有coredump)。为了分析这个原因,作者将服务的代码保持精简精简,然后模拟测试,缩小coredump定位的范围。最后发现只有超时的时候才会导致coredump,这就解释了为什么测试环境没有coredump,但是线上会生成coredump。这是因为线路的超时时间设置为5ms,而测试环境的超时时间为20ms,基本上就把原因定位到了超时导致的coredump。然后分析libcurl源码发现,libcurl的一个参数设置导致了发送时的coredump。至此,笔者用了23小时解决了这个问题。
