孙立:你是如何在架构层面,提高开发人员开发效率的?比如通过合理的分层,不同层安排不同能力的开发人员。
孙朝晖:首先孙立老师已经谈到了这个问题的两个核心,第一是合理的分层,第二是让不同能力层次的队伍有机组合。
- 对于分层,具体到我们的技术体系,可以清晰地分成四个层次,对应四个技术层次,分别是:前端(JavaScript开发)、Web应用(PHP开发)、中间件(Java开发)和通信与管理基础(C开发)。各层有独立的团队,开发人员专注于本层次的技术发展,各层次的开发团队Leader每日进行晨会交流开发进度,每周例会进行技术整合研讨。对于较完整的功能模块,设置有“技术方案评审会”,各团队专家参加,通过对各层技术特点的分析,综合考虑方案的可行性。
- 对于组织结构保证,每个团队都会有1~2名技术专家,1名团队Leader,若干开发人员。每个团队的技术专家负责开发基础框架,比如我们PHP应用的MVC框架、Java的Service Framework、前端JavaScript的基础类库,对于不同层次的开发人员根据能力安排角色,决定担任部件级别开发还是组合功能级别开发,保证每个团队都有不同层次的人员合理组合,每个开发人员都保持技术上升通道。这样会有效地提高开发效率,同时每个技
术人员都有清晰的当前定位和未来定位,有利于技术团队的成长。
孙立:要变更数据库结构,有什么经验可以分享?
孙朝晖:这个问题是互联网行业的通用难题,数据量大,数据结构变更的代价太大,对于这个问题,我想从四个方面进行介绍。
- 在技术方案评审阶段,充分考虑数据存储方式的合理性。不是每种数据都适合采用数据库存储,有些数据适合采用JSON,或者采用缓存中的List对象存储,而不适合分解到数据单元,这样的数据就没有必要设计成为关系型结构。
- 在数据结构设计过程中,保留适当的冗余字段,尤其是预估有动态变更的数据结构,而且状态标志尽量组合成为数据位结构,即典型的Mask应用。需要扩充状态位时,采用位扩充的方式替代字段扩充。
- 对于数据量很大的数据结构,比如我们业务当中的Feed数据,保持数据按照代龄进行分层,并且有合理的归档结构,保持活跃数据所在库的规模不过度,对于活跃数据库需要紧急调整的时候,变更代价也是可控制的。
- 对于数据量非常大的数据需要调整数据结构,那么只能靠数据库架构解决。我们采用的数据库架构都是M-M-S-S架构,需要进行大规模数据迁移,所以会利用备份库进行轮换下线(或半下线)维护变更,通过轮次的数据结构调整,最终达到数据结构完全变更。
孙立:如何搭建更加有效的测试环境?测试环境和线上环境毕竟不可能完全一样。
孙朝晖:我想分成两个方面来回应。
- 功能测试环境的建设:功能测试环境主要面对的挑战不是线上、线下系统结构不一致,而是测试数据的不一致,由于数据不一致导致某些边界条件没有测试出来。对于这个问题,需要保持线上、线下关键数据的增量同步,采用小时间粒度的定期日志备份方法解决,同时要保证活跃数据的规模,以便能够控制线下数据库同步的规模。
功能测试环境组建的另外一个要点是关注网络结构的等价性,通过虚拟机系统增加与现网等价的网络拓扑结构。尤其是在交换设备和域名系统上尽可能保持一致,因为有很多问题是由于物理部署结构引起的,从开发的角度很难发现,只有通过一个相对完整的功能测试环境方可发现这一类问题。
- 性能测试环境的建设:性能测试环境的建设主要用于发现性能问题和容量规划。对于发现性能问题的目标,只要保持数据库服务器尽可能使用物理主机,在物理主机上采用多实例隔离的模式部署,其他服务器可采用虚机化技术,这样可以保持测试数据的可用性。
需要解决的主要问题是容量规划这项工作,由于线上与线下服务器的硬件类型和规格不一致,网络拓扑结构也不同,也无法完全模拟线上环境。所以我们一般采用的方法是,对服务器进行角色类型划分,同一种角色的服务器在性能测试环境下必须有统一类型的服务器对应,有对应的确定性物理配置,在性能环境测试后,首先安排预上线测试环境,通过七层交换或者应用程序本身的流量复制,把线上的服务压力导流到预上线环境,进行小规模再测试,得出单机的性能数据与性能测试环境的对比,并根据单机的性能测算对比,做出线上的容量规划。按照容量规划进行系统实施后,根据实际的测试数据再调整,有可能因容量估计过高,服务器能力有冗余,需将服务器标记为空闲,留待后续部署使用。
孙立:你如何看待NoSQL的?
孙朝晖:我们在建设Feed中心时经过了对NoSQL的充分测试,比较了多种NoSQL技术方案,包括Cassendra、MongoDB、Redis和MySQL HandlerSocket,最终我们采用了MySQL HanlderSocket作为我们的NoSQL解决方案。
NoSQL对SNS类的应用场景来说还是很实用且必要的。因为我们要求的Write throughput很大,数据结构简单,仅需要弱一致性,所以在这种场景下使用NoSQL还是比较合适的。
但对于目前主流NoSQL产品推崇的自动分区等高端功能特性,我们经过测试和权衡还是觉得很难用到实际场景下。一方面是这种自动化分区管理功能使得系统自身的复杂性太高,部署和监控复杂;另一方面是NoSQL产品缺乏专用的备份工具,系统出现单点故障后,恢复的代价太高,给运维造成了很大的压力。
我们最终选择实用MySQL HanlderSocket作为NoSQL解决方案的原因是此方案能够利用MySQL数据库本身的全部分布式特性和管理特性,系统可控性更强,对于运维、数据老化处理、性能调整等方面都有成熟的方向。
对于NoSQL本身的发展和应用方向目前我个人还不是看得很清楚,例如几乎全能的Redis出现,大家会如何使用?在我们的技术体系内使用Redis作为内存缓存,替代了Memcached,没有启用持久化功能。
我个人认为,采用NoSQL技术还是需要经过充分评估的,尤其是与物理架构设计师和运维部门充分沟通,平衡开发和运维的整体工作压力。另外使用之前尽量充分阅读源代码,具备自我技术支持能力后再投入使用。
孙立:你如何看待横向扩展(Scale Out)和纵向扩展(Scale Up)?
孙朝晖:我个人认为,Scale Out是互联网服务的必由之路,分布式技术是互联网服务不可或缺的技术能力。但随着服务器规模的不断扩大,运维的压力会越来越大,到了一定系统规模,必须回归到提升单机QPS能力的方向上来。但是我觉得这里的提升单机QPS不是简单的ScaleUp,而是一个综合优化的过程,比如下面这些实例。
- 我们采用配置了SSD卡的服务器,以本地存储方式替代SAN存储设备作为MySQL数据库的文件存储,提升了MySQL单机访问能力。
- 我们正在测试使用虚拟机的方式,将Java中间件的物理服务器分拆成为多个虚拟机服务器。由于受到JVM的内存限制,我们的单机中间件服务器始终没有得到充分的利用,可以尝试一分多,提高系统并行访问能力。
- 对单机的性能提升,我们更倾向于在单机内部采取应用结构内的Scale Out方式,比如刚才说的虚拟机拆分,还有使用异步I/O替代多线程、对象池技术等,最后可能采取选用内存升级或CPU升级的方案。而Scale Up仅是为了满足某个具体需求采用的综合解决方案的一个组成部分。
孙立:大型网站往往会将多种语言进行融合使用,关于这方面,你有什么经验分享?
孙朝晖:这是一个互联网行业遇到的通用问题,由于互联网行业采用开源软件比较多,往往由于开源软件的引入,引入了一些非本服务体系内的主流开发语言,比如Scala、Erlang等。这个问题我有两点想说。
第一,在我们的技术体系内,我是比较倾向于减少开发语言使用的。我们每个层次采用的技术都比较确定,Web采用PHP,中间采用Java,基础通信部分采用C语言开发,个人认为引入太多的开发语言和技术,会对技术团队成长和线上业务的可维护性带来很大的问题。
第二,虽然我们的开发技术较少,但仍然有异构平台之间的通信问题。在我们的业务中,还有一部分是用Windows平台C#技术开发的。为了解决异构通信的问题,我们自主研发了基于ProtocolBuffer序列化协议和TCP的RPC通信协议,分别使用C#、Java、C(包括PHP Extension)开发了RPC的通信协议栈,解决了跨平台通信问题。当然选用Thift也是一个不错的选择,我们没有选用它主要是为了兼容我们IM软件的采用SIP协议。我认为解决异构系统通信问题的需要考虑以下几点。
- 在技术层次之间的通信协议尽量不要选用某种语言的私有标准,尤其是数据序列化,这样有利于扩展到通用的技术平台。
- 在通信模型上尽量选用通用设计结构。比如我们的进程间通信模型选择了Unix Domain Socket,保持与远程通信方式接口一致,保持可一致性,方便于服务的物理架构迁移。
- 在开源软件选择上尽量选择我们技术标准范围内的技术。如果没有找到对应项,一般选择改造现有相似开源软件或者独立开发的方向。由于我们选择Java和C开发技术,开源软件很多,目前来看还没有出现某个方面找不到对应功能的开源软件的情况。
孙立:对于大型系统的突发性故障,如何在架构层面帮助快速定位故障的原因?
孙朝晖:互联网应用系统由于涉及多服务集成,以致多数的异步调用造成突发性故障很难排查,针对这个问题我们主要采取以下几点策略。
- 每次出现故障时进行分析,准确定位故障的源头逻辑节点与物理节点,统计后作为知识库,标识出每个系统中的脆弱节点,出现故障后,优先挑选脆弱节点排查。
- 在架构进行Review时,尽量保持每个业务流程保证不超过3个物理节点的请求跳转,尽量减少物理层次深度。还有保证在一次调用流程中,级联的异步调用至少2次以下。在所有发出连续同步调用的初始节点上负责统一trace,方便故障排查。在物理架构图中标出这些调用流程中的关键节点,出现问题优先从关键节点开始排查。
- 在物理架构上,尽量明确服务器的角色,在某个角色服务器上部署同类型的服务。对于Java中间件服务器由于服务数目较多,尽量采用虚拟机技术拆分。单独服务器承载独立服务,比较容易定义监控策略并发出预警。
- 每次故障后Review告警与故障的关系,对没有及时告警的部分增加告警监控,告警不准确的部分及时修正,增强监控和告警的准确性。
- 在我们的技术体系内开发了针对分布式应用系统的部署和监控系统,应用服务器会集中的监控服务器汇总系统日志,统一与预警和告警系统集成。
主持人介绍:冯大辉,现任丁香园 (http://www.dxy.cn)网站CTO。曾历任支付宝架构师、数据库团队负责人等职。
提问嘉宾介绍:孙立,去哪儿网高级系统架构师,曾就职于凤凰网、酷6和搜狐。对分布式搜索引擎开发、大数据量网站系统架构优化、运维监控等有丰富的经验。开源项目phplock和phpbuffer的作者,近期开发了NoSQL数据库存储INetDB。
回答嘉宾介绍:孙朝晖,飞信互联网产品首席架构师。负责飞信SNS业务相关产品,空间、开放平台的总体架构设计与飞信
开发团队技术社区建设。2010年加入北京新媒传信科技有限公司。曾历任微软(中国)高级顾问、高级项目经理。
(本文选自《程序员》杂志11年05期,更多精彩内容敬请关注05期杂志)