2013中国软件开发者大会编程语言与工具专题论坛中,新浪微博架构师宋琦介绍了PHP在新浪微博中的应用,并且分享了很多微博主站所做的性能优化的工作。
在进入新浪微博之前,宋琦一直在做商业产品,使用的是传统的LAMP架构。而通常商业产品不涉及到很大流量,只是业务逻辑比较复杂。宋琦说当时他关注的主要是框架、扩展性、安全性方面的话题,对性能关注的较少。“之后选择做微博,是因为我认为对于程序员来说,微博产品所带来的技术挑战是一般产品无法比的。在大数据和大访问量下,任何东西都有可能成为非常棘手的问题。”宋琦说。
因为微博本身的一些特性,使得传统的LAMP架构,传统的框架、模板系统等在微博中变得不再适用,需要更加深入了解系统底层,才能应对更多挑战。
微博海量数据不容小觑
宋琦列了一组数据,微博用户数大约5.5亿,假设每个人有100条关注关系,那么总共就有550亿条用户关系,用两个long也就是16个字节来表示一个关注关系的话,那么单独保存这些关注关系就需要800G的存储空间。“以往在db里用一个map表就解决的事,放在这个场景下就变得可笑了。”宋琦说。
那么微博的访问量有多大呢?宋琦说,在2013年除夕当晚,蛇年第一秒共发出近35000条微博,第一分钟发出近73万条微博。这个只是上行的数据量,而下行的数据量是这个的N倍还多。由此可见,这样的数据量非常惊人。
响应速度关乎用户体验
在如此大的数据量和访问量下,微博对响应速度的要求非常高。有研究数据显示,如果一个页面响应速度超过3秒,那么57%的用户就会放弃;而在登录某网站时,若超过5秒,74%的用户就不会再登录;亚马逊的主页响应时间每延长1秒,每年就会减少16亿美元的销售额;而Google搜索结果每慢0.4秒,一天搜索量将减少800万次。
“总之,这些数据都说明了响应速度的重要性,可以说响应速度是保障流畅用户体验的基础。我们Team的主要KPI之一就是提升微博的性能。”宋琦补充道。
数据加载的优化
宋琦指出,微博有很多的配置文件,有几十个,这些配置每一次请求都要被加载,都要重新申请内存存放这些配置。而配置文件的修改是非常少的,那么可不可以只加载一次就可以一直使用呢?很遗憾PHP是做不到的,PHP脚本中所申请的资源在请求结束后全都会被释放。“于是我们同样使用PHP扩展来解决了它,我们创建了一个名叫Weibo_Conf的扩展,他在php-fpm启动阶段扫描指定的目录,将其下的.ini文件加载到内存中,每5分钟更新一次。这样服务器每一次接到请求时,不需要重新加载这些配置,而是直接取用,效率提升了不少。”宋琦介绍说。
除了配置外,还有一些需要常驻内存,而且不常变化的东西,但是这些的量更大一些,比如白名单/黑名单,或者切词服务的字典等。这些东西以往都是放在数据库或者MC当中,然后通过http开放接口供远程调用。“因为他们的逻辑都比较简单或者固定,不太常变化,我们将他们独立出来做成单独的C服务,用的是yar的兄弟yar-c,yar-c所做的工作就是提供一个基于C的RPC框架,帮你完成网络和进程方面的管理,让你可以专注于实现业务逻辑,同时更方便的是,通过PHP的yar扩展可以直接调用,也就是说PHP端不论是对于基于yar-c的socket RPC服务还是基于yar的http RPC服务,无需做出改变既可以通用。”宋琦说道。
缓存优化
众所周知,对于访问量巨大的服务来说,缓存是必不可少的,而微博是一个重度依赖缓存的应用。宋琦指出,微博前端展现的数据全部依赖于开放平台,开放平台提供的是基于http的RESTFul的接口,响应速度一般,所以为了提速同时降低对开放平台造成的负载,微博大量的使用了MC来缓存数据。“但是用MC也不是轻轻松松就能满足我们需要的。我们做个计算,假设我们每台服务器上开启128个php-fpm,总共100台服务器,如果每个请求响应时间是1秒,从请求开始就连接MC并且等到请求结束后才自动释放,那么每一台MC上至少有上万的连接,实际微博的量比这个还要大的多,这样MC的效率就会慢下来,同时增加了PHP处理请求的耗时,又进一步加剧了连接占用的情况,很容易造成雪崩效应。”宋琦指出。
针对此问题,宋琦展示了两种解决方案:
一种是引入一个proxy来代理对MC的访问,twitter采用的是这种方案,通过代理,php-fpm与proxy、proxy与MC之间都可以维持长连接,并且请求可以在proxy上做合并。twitter开源了他们的这个代理twemproxy。可以看到在加入twemproxy之后,MC集群的连接数大大降低了。但是在加入了一层proxy之后,因为要通过一层twemproxy,会使得MC请求的响应变慢,于是采用了另外一个方案:增加一层缓存做成多级缓存。
首先通过会话保持,让一个用户的每一次访问都落到固定的一台机器上,然后我们在前端机的本地开启一个本地缓存,用它来挡在MC集群之前,降低对MC的访问。这层本地缓存的实际命中率大概在60%~70%左右,这些请求只需要从本地缓存中获取数据,就不需要走网络从MC集群上获取数据。