华尔街的运营方上海阿牛信息科技有限公司是全球金融信息服务提供商,每天全平台为近200万用户提供资讯、数据、研究等服务。旗舰产品华尔街APP长期位居各应用市场财经资讯类客户端第1位。由于将重大事件、市场的基本面变化和100多种全球资产价格紧密关联,在金融领域具有极高渗透率。首创的7x24快讯模式已经成为在中文世界理解全球市场的最快来源。也因此,该产品有技术架构复杂,需要高并发承载能力等特性。
原先的系统是PHP monolithic架构,功能按模块划分,日积月累,最后模块达到60+个,新人接手项目会拿到一整个系统的代码,以及需要整个系统的配置,而他可能只需要专注开发不到1/10的业务。
我们的主要业务是即时资讯,资讯具有时效性,网站的访问量会呈现锯齿形分布。当遇到特大新闻如英国退欧、美国、法国等,我们要能弹性地通过增加服务资源,提高服务的容量。
我们希望一个低优先级服务出现问题之后,不影响主要服务;一个主要服务能更高的可用性,就算出现问题,也要优雅降级。
PHP单体应用在生产服务的时候,所有业务都跑在一个程序里,增加了系统的脆弱性,一个隐藏的性能问题,能在服务量激增的时候成为压垮骆驼的一根稻草。
由于没有方便的和运维工具,导致排题的效率低,使得在系统遇到问题的时候排查困难,耗时过长。
每台服务器部署相同的服务端PHP代码,由PHP-fpm解释执行,通过Nginx进行反向代理。
因此,在2016年11月至2017年3月,我们采用微服务架构启动重构,尝试解决一部分上述问题,在伸缩性上能以服务为单位进行拓容,同时,这一设计会在某些方面增大我们的开发成本和运维成本。
很显然,以前在单体应用中能直接登录服务器,查看出错日志,现在错误散落在不同的服务中,为我们的错误排查带来了困难。
每个服务有互相的MySQL、Redis,公共服务方面需要高可用的服务发现,调用链分析,日志收集储存设施等。
每台服务器上均衡地部署服务,LB接受用户的请求,将请求转发到API gateway,API gateway向服务发现查询具体服务的IP和端口,服务执行完业务逻辑后向上返回数据。
自带一些pprof包可以profile当前程序的CPU消耗、内存占用、锁状态、channel阻塞等,非常便利我们定位问题。
我们选用go-micro作为开发框架,里面包含几乎所有微服务组件,并且支持非常好的拓展性,通过接口的设计方式,让我们可以拓展一些自己的组件,如服务发现、传输协议等。
拆分的原则是通过服务功能划分,尽量避免双向依赖。我们拆分出了13个服务,包括用户、内容、实时新闻、评论、搜索、商城、支付、三方代理等服务。
这是我们最先选择的方案,因为Docker 1.12之后已经将Swarm功能集成到Docker Engine,能以最少的配置启动Docker集群。经过简化和设计的控制台API,方便地管理集群、调整服务如控务的数量、CPU、内存等。往集群内加入机器非常简单,只需要运行一条命令即可。使用manager-worker架构,manager作为调度节点,支持高可用。
但遇到了非常致命的问题,比如频繁更新服务的时候会出现服务访问不到,某服务的负载均衡后挂载的服务IP是其它服务的,服务之间的通信有几率出现超时问题,归根结底,还是社区正在不断完善swarm,有很多不稳定的地方,网络方面没有进行优化。
这是谷歌主导的服务编排工具,它支持Docker,相比Docker Swarm来说,它的概念更多,分层更细。功能方面多于Docker Swarm,支持一些高级功能如秘钥管理、配置管理、自动拓容等。在生产的应用比较广泛,稳定性更高。
裸机部署是我们的一个备案,考虑到以上两个方案在当时没有具体线上实施的经验,所以如果Docker Swarm和Kubernetes都没有成功,我们直接裸机部署。
裸机部署的需要解决单机端口冲突,如果一个服务在一个服务器上最多只部署一个,那么可以通过写脚本,并划分服务器角色的方式进行部署,利用ansible可以定义user服务集群、content服务集群、comment服务集群等,通过分发二进制文件的方式让服务启动,这样的方案要考虑到服务更新、服务重启、服务删除等逻辑,同一时间只有部分节点更新,在服务未更新成功的时候流量暂时不能打到正在更新的节点。
由于之前使用github开发人员的代码提交在有翻墙工具的帮助下速度依然不是很理想,我们自建了Gitlab仓库,自此开发过上了幸福的生活。
swarm和kubernetes是基于docker快速创建删除服务,通过增加容器为服务拓容,缩减容器为服务缩小规模,所以所有项目必须要构建docker镜像。按项目类型划分,我们遇到3种镜像打包情况。
后端服务90%是golang项目,针对golang的镜像,我们采取将golang项目编译成可执行文件,基于最小的alpine镜像打包入docker,这里遇到过一个问题,就是alpine里缺少openssl的证书,无法支持https,我们自定义了新的基础镜像,不仅将证书文件打入镜像,同时为了线上调试方便,增加了tcpdump、strace、bash等工具,在初期调试容器间通信问题时发挥重要的作用。
的后台以及m站基于Vue,编译后生成的静态文件打入镜像,通过nginx访问。 为了支持HTTP2,我们打入nginx镜像缺少的证书。
主站PC站基于nodejs、Vue实现服务端渲染,所以不仅需要依赖nodejs,而且需要利用pm2进行nodejs生命周期的管理。为了加速线上镜像构建的速度,我们利用taobao源进行加速, 并且将一些常见的npm依赖打入了基础镜像,避免每次都需要重新下载,镜像打包从开始的3分钟缩减到1.5分钟。
我们利用Gitlab CI配置了测试、镜像构建、镜像发布、自动部署等流程,后端服务从提交代码到测试分支到测试自动部署完成花费1.5分钟,前端服务平均为2.5分钟。
腾讯云的容器服务是在腾讯云的Iaas上为每个用户构建容器集群,腾讯云提供的微服务架构和持续集成与交付的应用场景基本满足了我们的述求。
腾讯云在Kubernetes上实现了他们的存储、负载均衡等产品的插件、复用了腾讯云本身平台的、日志等能力。减少了我们接入和开发的成本。
我们将我们的应用重构成微服务的架构,每个微服务部署成腾讯云容器服务上的一个服务,前端接入通过一个负载均衡。后端服务间可互相访问。
服务器安全方面,内部服务器通过VPC进行网络隔离,将网络划分为生产、测试,在生产中又划分backend子网和data子网,设定子网之间的访问规则。
利用locust模拟线G的机器上的服务进行压测,达到1w/s QPS以上,并且服务的负载并没达到极限,这已经是之前PHP生产20+台16C32G服务器能达到的QPS.
通过追踪API调用链的流向与耗时,我们可以找出性能的瓶颈。我们通过zipkin实际优化了几种情况:
当拉取文章列表的时候,我们需要拉取文章对应的作者信息,开始的时候我们使用拉取单个作者信息的方式,后来性能调优阶段,我们将其改为批量拉取作者列表,减少RPC的冗余。
对于有些本身就比较耗时并且对即时性不是那么苛刻的计算服务,我们为了服务的响应时间,会适量地加上缓存。
排题的流程一般有两种情况,一种是用户发现问题,申报问题,开发人员跟进问题;一种是我们的优先发现问题,开发人员在用户反馈前跟进并修复。在报警方面,我们通过为系统谨慎设置报警阈值,当触警时,开发人员会收到邮件。
这里我们在报警的定义上有过思考,即什么样的报警算是有意义的?我们遇到过每天10几条重复的报警,通常开发人员开始时会对报警非常重视,当重复的报警一再出现,渐渐失去了对报警的关注。所以我们有解除一些不必要的报警,并且对剩余一些报警进行调查,甚至有些警报是因为工具本身的不准确引起的。
后端程序内接入Sentry日志报警系统,golang程序捕获panic日志以及error日志,并发送报警邮件。
通过在服务器上运行telegraf daemon进程,收集服务器metrics并发送给influxdb,使用Grana作为前端面板,对服务器负载以及容器的平均CPU、内存占用率进行。
本文介绍了华尔街通过重构和服务容器的重新部署,实践微服务架构的情况。经过几个月的开发测试,我们不仅完成了线上服务从PHP到Golang的转型,更在服务的稳定性上经历了,支撑了几次重大新闻的高流量。
在运维方面,由于系统对系统完整的,与Kubernetes健全的上线、下线、回滚、拓容功能配合,能以极快的速度处理线上问题。
推荐: