随着运维的复杂和繁琐化,运维工作也在进行职责的细分,比如:基础运维、系统运维、应用运维,见名识意大概能猜出其职责,基础运维偏重基础资源,系统运维偏重系统也就是自动化运维,其中应用运维是近年新出来的名词,其既要对系统负责又要对产品的用户体验和成本负责,这就要求运维人员在做好日常系统运维的基础上,深入研究业务,研究产品架构和数据流。随着微服务设计理念的普及,一个大型应用被拆分成多个微服务,虽然带来了诸多益处,但各微服务间的调用错综复杂,增加了运维、排障、调优的难度,而应用运维就是为了解决这个问题而产生的,工作中要与开发、基础运维、DBA、CDN等各部门打交道,主导问题解决。
一个应用运维脑袋里至少要有产品业务逻辑架构图、数据请求流程图、服务器部署架构图这3张图,结合各种运维工具,当出现故障能第一时间定位是哪个微服务、哪个机房、哪个负载均衡、哪台服务器、哪几行代码导致,并找到相应的负责人,第一时间进行问题解决,如果短时间解决不了进行相应的故障升级、业务降级、流量调度。而且应用运维在心中应该有一本账,知道系统架构的短板在哪,为了提升整个系统的性能和吞吐量,如何进行优化;在有新的功能和应用场景时,跟随开发共同研究解决方案,评估技术可行性和业务逻辑的合理性,为公司节约基础成本,因为微服务的一个改动可能就会恶性影响到上下游资源,特别是在业务量大的场景,多调用一次每天可能就会多1000万次请求,少调用一次可能就会优化500万次请求,上下游依赖的微服务容量以及跨南北调用的问题等都要考虑。
从繁琐的工作中寻找规律,应用运维拆分收敛后基本上就是三块:围绕数据、围绕运维管理、围绕产品本身三大部分,其中围绕数据的包含告警、监控、数据分析、异常检测、性能分析,围绕运维管理的包含资产管理、配置管理、代码上线、服务调度,围绕产品本身的包含调优、消峰、降级、容灾,其余的事情基本都是基础运维处理,下面拿我负责的内容服务平台的前端业务做一个抛砖引玉应用运维的介绍。
内容服务平台的总体概述:
公司的内容服务平台是一个微服务架构设计的大型内容管理系统,上行写入面向公司编辑和抓站,下行是在线的页面、接口、文件(图片)和裁图服务,为了灵活应对消峰和系统迭代开发等场景,系统做了彻底的服务化和组件化拆分,模块间通过HTTP型API进行调用,一些模块通过消息中间件进行异步解耦。内容服务平台在公司的一个生态位置图如下:
从上图看,内容服务平台是一个核心的数据源,同时提供了4大块在线服务,在此之上生长了各种应用。各产品可以直接在页面服务模块开发应用,如果是APP类也可以通过接口服务获取平台的内容和功能再自行处理,下面以此产品为例展开介绍运维工作围绕数据、围绕运维管理、围绕产品本身的三块内容。
一、围绕数据
围绕数据都有哪些事儿呢?顺着数据去考虑,比如监控告警、异常检测、日志分析、性能分析、机器学习等等,其中围绕数据的应用核心三点是数据采集、分析和使用。
一个系统在运行中会产生各种数据,而产品的健康状况就藏在这些数据里,应用运维需要通过各种手段和工具将这些数据收集起来,并对其收敛做成监控,进而对监控收敛做成告警。我们通过告警发现故障、监控定位故障点、产品运维处理分发故障,短时间解决不了的,进行故障升级、业务降级、切换流量。为了能够将还没有形成故障的薄弱接口提前暴露出来优化解决,预防故障,我们依托历史数据做了异常检测,每天将前一天的薄弱接口计算出来,发给此接口的最近修改人,推动其优化。
随着用户量的增加和系统的更加复杂化,慢慢就有了监控每个环节性能的需求,如果从用户的访问流考虑,把每个环节都做埋点日志打出来,就可以做到产品的APM性能分析,从而掌握每个环节的健康情况,总之围绕数据的应用有足够多的想象空间,最终可以通过机器学习来寻找更多的数据规律,为各层面的人员提供服务。
目前内容服务平台每天产生的数据在2T以上,画了一个围绕数据的整体架构图如下:
我们将监控告警从整体上分为了系统层和应用层,系统层的基础监控(cpu、内存、网络、硬盘io)通过zabbix来实现,应用层面的监控告警走数据分析的渠道。
我们每天的异常检测的几张图表如下(在此多谢雄飞同学的一起研究贡献):
二、围绕运维管理
运维管理是比较老生常谈的东西,从运维这个工种出现开始大家就一直在做,做到现在收敛总结一下基本就是资产管理、配置管理、代码上线、服务调度降级管理四块,当然根据产品属性的不同有一些个性化的管理工具,那个就另说了。
我们现在资产管理自己开发的,配管用的puppet和salt,底层套了版本管理,用户层加了ui界面,代码上线和服务调度降级也是个性化开发的,作为一个有历史的互联网公司,其实运维对运维管理这块更多是理解和应用而不是搭建开发的层面,因为很多这些工具都有前辈做好了,运维管理这一块就不展开说了。
三、围绕产品本身
能做到每天上班喝着茶看着报纸的产品运维其实不存在,随着业务系统的迭代和应用场景的变化等,产品和集群架构的各种工作其实没有停歇,好在多机房多服务器部署对于容灾做的很成熟,即使有单台服务器或单个机房有问题也可以自动负载调度,繁琐的工作中总结了这些方法论。
拿内容服务平台里的动态前端模块做例子,微服务化拆分后,它作为下行用户体验的一个核心体现,系统是nginx+php-fpm的结构,作为接口层(HTTP API)和用户接入层对外服务,目前服务的业务域名有200个,功能接口上万个,日访问量在7到10个亿,平均响应时间从我接手时139ms优化到现在已经到了30ms,只有网民的读没有写场景(评论调第三方接口,平台本身不提供功能),但后端php会根据不同ua、args、reffer、cookie判断吐不同的html页面,会通过HTTP API调用为其他业务系统提供数据,典型的前端型业务,由于为N多个业务部门提供服务,并且开放权限申请和域名接入,其后在平台上各自编写代码,所以我称作其为平台型前端业务。在操作上,开通域名站点和权限后,业务开发登录后台,按照规则在微服务主应用平台上编写代码,然后审核上线到动态前端的服务器上对外服务,动态前端完成了页面渲染、php路由、功能调用和缓存功能(使用MC和reids),同时也依赖了大量外部接口,这些接口通过curl和js(ajax)进行调用。在划分上,接口层是作为功能应用(HTTP API)吐的是json串,无状态的为其它业务模块服务,用户接入层就是直接面向网民的访问服务。
1、调优
1)系统软件的调优
系统软件的调优指的是centos、nginx、php-fpm、ats等这种软件的软调优,这种调优虽然比不上加机器,但是也是非常非常重要的。比如在我接手这个业务时,动态前端使用的是nginx1.4.7版本,由于也没出什么问题就一直没动,后来在升级了tengine2.1.2之后,调整了一些参数,平均响应时间提升了将近一半,所以系统软件的调优也是不容小看的。系统软件的调优包含系统软件的版本升级和系统软件的参数的调整优化。
2)业务分池故障隔离调优
任何做法都有其原因,比如说N多业务使用一套CMS,前端访问使用一个大池子,而不是每个业务单独来一套,其实是共享的概念,这种共享做法的好处自不必多说,大大减少了资源浪费和节约了人力成本。但随着加入业务的越来越多,关联调用越来越复杂,访问量越来越大,系统的高可用性越来越重要,因为出故障的影响越来越严重,随之带来了各种大系统病。
比如说动态前端,用户访问虽然会通过dns智能解析把不同请求分发到不同机房,但从业务逻辑看终究是一个大池子,如果某个业务接口或页面模板有问题,会瞬间把php进程池堵死,到时不分谁的业务,全部报错,结果就是个别业务代码影响到了全部业务。分析后,分池隔离是势在必行的一个事儿,一来减少业务间的相互影响,二来让优质的业务跑的更健壮。然后分析服务属性,发现有两类业务,一类是直接面向网民的用户接入层,这类访问受最后一公里上网环境的影响,更容易出各种问题,另一个是作为微服务提供HTTP API功能调用吐的是json串,这个一般是外部系统过来的功能调用,环境相对简单。将这两个业务拆开成页面池和接口池后,理论上故障减少一半,整个业务更加健壮。
分池是一种隔离方法,可以完全通过调度分发实现,不需要开发重构代码,随着业务的变化,可灵活实施拆分,比如结合业务方需求,将需要重保的业务分出来、将访问量大的业务分出来、将危险的接口分出来。。。。所谓道法自然,术变万千。
3)程序执行时间限制调优
作为一个平台,为了保障整体业务的健壮,必须通过时间限制割肉放弃慢或错误的请求,俗话说不能因为“几颗老鼠屎坏了一锅汤”,程序执行时间限制分为两个层次,一个是外部php woker进程允许的php脚本的最大执行时间,另一个是脚本代码里通过curl依赖的外部接口的时间限制。
对于php worker进程的时间限制,分池后,同一个池子还是混跑着各种接口,如果某个业务的代码有问题,将php资源池堵死,同池内的其它业务同样会受到牵连,为防止这个问题,必须要做php的执行时间限制,将执行慢的业务割肉抛弃,不至于影响总体业务,慢请求达到指定时间后马上杀掉,返回502代码。配置后,反向倒逼业务方对自个儿的代码和逻辑进行调优,定期将502/504的top排名发给业务方,给他们以压力,对应的配置如下:
1
2
3
|
request_terminate_timeout = 10 #php-fpm.conf 中的执行时间配置 request_slowlog_timeout = 5 #php-fpm.conf 中slowlog的时间,这个如果等于执行时间会报错 max_execution_time = 10 #php.ini 中的执行时间配置,最好和php-fpm.conf同时配置 |
对于程序中curl的时间限制,升级lib库后已支持到ms级控制,可以根据接口的情况进行限制,系统有一个整体阀值。比如说我们当前的限制,所有接口默认超时时间是1s,最长容忍设置为2秒,ms级的控制业务方可以自行配置,因为当访问量上来后,各业务方必须对自个儿的用户体验负责。最长容忍设置为2s,同时也倒逼业务方对其使用的接口进行不断调优。
4)依赖资源local化调优
这个是最常识,但是又最容易被忽略的地方,比如说动态前端依赖了mc、redis这些缓存资源,能做到本机房、同网段、同交换机效率是最高的,最次也是同内网跨机房,少一个节点,就少一个故障点,少一个资源消耗。对于通过curl或者js依赖的接口,要做到不跨南北、不跨运营商调用,否则单看网络时延就够大的,而且可能丢包,实际操作中其实很难做到,所以对于实时性不是非常强的接口,要对接口的缓存结果做下curl的cache。
5)HTTP API提供内网接口调优
一个HTTP API功能接口,通过域名解析到公网IP进行调用,如果大部分调用是由公司内部系统发起的,那么就没必要走公网透传一遍,这个时候就需要做一个解析到内网IP供内网调用的域名,也就是做一个内网接口,减少网络时延消耗和故障率。
动态前端有一块功能是为其他业务系统提供HTTP API的接口调用,吐的是json串,有很大一部分请求是公司内部系统发起,在结构上,我们接口域名解析到公司的外网vip(lvs),vip下属挂着7层的Ha,下面才是我们的服务器,走外网调用相当于这些全部环节透传一遍,还要走外网网关、外网链路、外网带宽,而做好内网接口后,故障少了的同时也提高了系统性能。
6)本地内存和网络缓存的二级缓存模式
这个很重要,像cms当前的业务,我们为开发在本地服务器上安装了php的yac扩展,配置了key64M和value1024M的缓存供其使用。开发在使用一些热资源时,进行本地和远程mc或redis的双写,读取时先在本地内存找,本地找不到再到mc或redis上回源,远程也没有再进行穿透,这大大提高了系统的吞吐量和性能,同时呢也起到了一定的削峰作用,相当于每台服务器都有一个高速缓存,mc或者redis成为了他们的回源缓存,速度可想而知。
1
2
3
4
5
6
7
|
[yac] extension=yac.so yac. enable = 1 yac.keys_memory_size = 64M yac.values_memory_size = 1024M yac.compress_threshold = -1 yac.enable_cli = 0 |
2、削峰
随着业务和技术发展,push类的业务成为一种需要常态应对的场景,热点事件、热点活动。。。。可以看下我们的QPS图,这种场景的处理已经很迫切,需要找出峰值的瓶颈点,找出解决方案,不影响服务,除了模块提前扩容,简单总结一下几个做法。
1)动静分离后的cdn方案(访问削峰);
对于下行访问,分析出有峰值的域名业务,针对性处理,cdn结合轻量级缓存nginx cache是一个非常强悍的削峰方案(cdn本质其实就是类nginx cache加上调度)。适合的场景是静态业务,要对一个域名做cdn得尽量动静分离的彻底,而且域名下属服务业务逻辑简单。
2)服务器轻量级nginx cache方案(访问削峰)
并不是所有的业务都适合cdn或做nginx cache。比如说目前的动态前端,很多服务会根据ua、args、reffer、cookie综合判断后吐一个内容,如果按照这四个合集再加上url做一个key,基本上没有命中率的,反而是增加了业务复杂性。这就需要改造,在业务和cdn间取一个都能接受的方案,太复杂的判断在cdn上是不好做的,cdn本身也是一个无状态的服务。比如动态前端有个push属性的域名业务,必须做削峰处理,分析后会根据ua、参数判断吐两份不同html,在业务方做了js的ua判断改造,cdn做了个性化参数判断后才上了cdn,削峰问题能够解决,对各方来说都能松一口气。
3)消息队列消费削峰(写削峰、数据库相关重逻辑削峰);
采用消息队列是上行写入、数据库相关重逻辑、质量重点保障业务的削峰利器,以时间成本换取业务的正常服务,因为总有一些逻辑比较重的业务又是绝对性不能出错的服务,像cms里写入、发布环节的一些模块,都是通过消息中间件进行解耦削峰的。
3、降级
先说下什么是降级,所谓降级就是业务出问题时,还能够降低要求为网民服务,不致于直接服务挂掉,最常用的做法有静态化、返回默认值和摘掉接口。举个简单例子,动态前端的评论是依赖其他部门的系统,通过curl去获取的评论、评论数等,一旦评论系统挂掉,假设我们缓存了最近一次拿的评论数据,就可以通过历史缓存为网民展示出问题前的最近一次评论,虽然做不到实时展示,但至少系统没有报错,网民还是可以看到内容。或者使用将评论系统摘掉的策略,虽然用户无法看到和使用评论,但至少能看到其它信息,不至于评论影响到全部业务,当然这个方式就比较重了,影响用户体验。
与程序执行时间限制调优类似,降级也分两个层次,一个是程序直接对外结果的降级,比如说html文件或者json串,另一个是程序内部写的curl依赖接口的降级。第一个可以结合类nginx cahce来做,使用stale文件进行服务降级,也可通过程序静态化实现,第二个就要程序层面实现了,下面会说一下我们实现的方式。
1)php内curl获取外部接口缓存降级,配合惩罚机制(程序自动)
简单说下实现,每次curl获取数据后,同时将内容存入MC,形成一个最近1小时的历史数据,当检测到连续几次curl超时或数据错误就默认接口出了问题,自动启用降级,取MC最近一次可用的历史数据为用户服务,设置惩罚策略,比如说降级5分钟后自动恢复业务,如果继续出问题,就会循环往复这种操作。
2)html文件在判断请求队列达到阀值后自动降级(程序自动)
类nginx cache方式不多说,简单讲讲程序的做法。tcp连接数侧面反应了任务队列的处理情况,我们恰是通过判断tcp连接队列(80、9000端口的连接数)来控制降级的,程序会隔一段时间存一次正确的静态化结果至服务器硬盘,当tcp连接队列一旦超过閥值,表示业务可能有问题,立马不再穿透到后端取数据,转而取本机静态化数据,当连接队列恢复正常后,自动取消静态化。
3)手动降级(按外部接口、按服务url)
这是作为产品运维要求开发部门做的一个功能,程序毕竟是死的,人是活的,有自动降级必定要有手动降级作为补充操作,一旦遇到必须手动降级的操作,如果有操作页面,就不需要每次都去找开发改代码。例如我想降级平台某个url或一个curl依赖的接口,只需要按照要求输入相关配置——url、降级时间、回源周期等,就可以立即降级了。
4、容灾
对于容灾,也是一定要考虑的,包含机房间容灾和机器间容灾。我们现在的思路,绝大部分业务通过热备加健康心跳来实现,在评估过的服务器基础上,多加两台服务器,如果有服务器死掉,流量会自动调度到其余服务器上。
比如说当前的cms,lvs下挂了ha集群,ha集群下面挂了我们的webserver,每一层都有健康心跳,当某台服务器死掉后,流量会自动负载到其余活着的服务器上,当服务器起来后,流量再自动调度回来,为了提高用户服务质量,同时做好容灾,我们会把服务器分到不同的机房中去。