关于API请求优化与缓存的问题

最近在做一个项目,是一个STEAM信息拉取的项目。在这个项目中,我需要使用http://store.steampowered.com/api/appdetails/?appids={appid}&lang={lang} 这个steam的接口进行获取游戏的相关信息。

但是我们这时候就遇到了问题,拉取一个游戏的信息时候,这个API返回了太大量的信息

在我们的对API接口的测试中,API接口返回的信息可以达到41.2KB。可能相对于我们个人用户,41.2KB的信息并不大,但是对于服务器请求与处理来说,41.2KB已经非常的大了。甚者,我们一个页面中可能包含了10个这样的游戏,这意味着我们需要对这个API接口请求10次,每次返回不同游戏的信息,即我们需要调取400多KB的信息,400多KB显然还不算多。但是我的客户提供的服务器配置仅为1H1G1M的配置。

显然,每次访问页面都请求一次原API接口的想法绝对不可行,这不仅使用了服务器的大多数网络资源,而且返回给客户端的时候极其容易超时。

假设一个用户访问了这个页面,服务器向这个STEAM的API请求400KB的数据,而此时我们服务器的访问网络仅仅只有100KB/s,这时候就非常尴尬了,我们使用同步调取API的时间都需要用到4秒,更不计入PHP后端处理数据、返回客户端的时间了,而这也就意味了用户访问这个页面所需要等待白屏的时间在4秒以上,而这都在网络达到“理想状态”的时候,当我们的服务器访问API的网络不稳定的时候,php后端就会返回408:Request Timeout。而且在我的测试中,处理超时的情况几乎每次都会出现,换句话说,如果站点就这样上线,用户访问的成功率会很低,极大影响客户的体验。

相信我,没人想访问这样的网站,而我的客户也绝对是拒绝的。

 

而此时,我就萌生了一个想法,使用AJAX异步加载steam的API信息。

具体实现方法是:当鼠标悬浮于一个游戏信息上面时候,就会通过这个标签中带有的id属性往PHP后端异步请求API数据。

(效果如图)

那么,这时候,我们尚且使用异步加载的方式简单地解决了API带来的页面访问过长而造成访问超时的问题。

但是,在随后的测试中,我发现在请求游戏信息的时候,仍然会存在API调取超时的问题。

而且有时候,我们将鼠标悬浮于游戏信息上面欲呼出详细信息框的时候,经常需要等待超过3秒,而在这3秒内,无任何信息返回给用户,此时用户可能就会尝试去请求其他游戏的信息,这时候,AJAX的请求并未停止,而是一个新的AJAX请求开始向STEAM的API请求信息。这就对服务器网络资源造成了更加严重的堵塞。而这种种原因,都会对用户体验造成极大的影响。

而更大的问题在于,以上的情景仅仅设立在只用1人访问的情况下,而该网站的并发量的峰值可能会到达10-20。这就意味这在现行的网络状态与请求策略的情况下,肯定是无法应对的。

 

面对这个问题,我只能往缓存的方向去考虑了。

我首先考虑到的是将AJAX请求返回的内容进行PHP的文件缓存的方式。

而此种方式也解决了缓解请求堵塞的问题,但在之后我们便发现了其他问题,例如这种缓存方式不够灵活,不方便数据的再处理。并且在缓存返回信息的时候就会因为API或者网络堵塞的问题造成API返回空信息或者返回403 Forbiden而造成缓存了错误的信息,而造成在之后的其他用户请求的时候获取到错误的数据。而我们在对缓存进行检测的时候也造成大量的不便

 

而在这之后,我想了很久也没想到其他更好的方法,只能在不断优化AJAX请求方式,例如在页面生成的时候便一次调用AJAX获取相关信息,然后使用hide的方式不显示出来,等待用户鼠标悬停到相关地方在进行显示。(但是很可惜的是,这种方法会在访问时进行并发的AJAX请求,造成部分请求堵塞超时)

 

在想(玩)了(ow)很久之后突然想起一种类似于镜像存储的方式来对API返回的信息缓存在本地,再在请求时使用文件读取的方式进行输出,这就大大避免了因为网络资源不足导致的超时等错误。

而在使用这种方法后,服务器网络资源不足的缺陷就差不多都解决了,并且我将AJAX请求的策略调整会最初始的方法确保返回到的信息的“正确率”。

于是,在这种方法下,我们的请求大部分都会走文件访问这种方式而不经过HTTP,那么我们就对网络资源的依赖就会转变为对IO性能的依赖,毕竟IO性能是网络新能的成千倍(随口说的)

 

下面就是实现方法啦

而我在此是使用函数的方法进行实现的

function GetSteamCache($cacheid,$lang="zh-cn"){
	ini_set('user_agent','Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36 SE 2.X MetaSr 1.0'); //避免接口因为User-agent而返回Forbiden
        $_time = 86400000;//缓存时间(ms) || 86400000ms = 1day
	$dir="./cache/";
	$cachefile = $dir.'/' . $cacheid .'.dat';  //缓存位置,建议更改为绝对定位而避免缓存位置错误,并且需要保证该目录存在,不然会造成后面的读取文件错误而报错
	$cachetime = $_time;
	if($cacheid == ""){
		echo("null id");
		exit;
	}else{
		if(file_exists($cachefile) && (time()-filemtime($cachefile) < $cachetime) && filesize($cachefile) > 0){  //此处检测缓存文件是否有效
			$fp = fopen($cachefile, 'r');
			$json = fread($fp,filesize($cachefile));//直接读取本地缓存好的dat文件
			fclose($fp);
			return $json;
			exit;  
		}else{
			$link = "http://store.steampowered.com/api/appdetails/?appids=".$cacheid."&l=".$lang;
			$json = file_get_contents($link);
			$fp = fopen($cachefile, 'w');
			//echo($json);
			$gameinfoarr = json_decode($json, true);
			//echo $gameinfoarr[$cacheid]['success'];
			while(!isset($gameinfoarr[$cacheid]['success'])){ //当API返回空数据的时候,等待1s后再次请求
				sleep(1);
				$i = $i + 1;
				$link = "http://store.steampowered.com/api/appdetails/?appids=".$cacheid."&l=".$lang;
				$json = file_get_contents($link);
				$gameinfoarr = json_decode($json, true);
			}
			fwrite($fp, $json);  
			fclose($fp);
			return($json);
			exit;
		} 
	}
}

于是,在这种缓存API的方式下,我们的请求效率大大提升。

 

最后,我只是萌新,或者大佬们有更加不那么simple的解决方案,也请来讨论下,不要把我批判一番

后记:配合memcache效果更佳。

4 条评论

点击这里取消回复。

昵称

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

  1. Moon

    这个 API 能设置 filters 限制返回内容

    1. Y2Nk4

      也是一个不错的方法

  2. liwanglin12

    这种情况当然是选择原谅。。当然是选择Memcache/Redis。。。

    1. 很懒的樱花

      都怪steam的API太皮,返回太多信息了,暂时还没怎么研究Memcache/Redis所以先不用啦,迟点研究下可能会优化下