编者按:本文是在谢乐冰 4 月 23 日在数人云深圳分享课上的演讲,授权「高可用架构」首发。转载请注明来自@高可用架构
谢乐冰,数人云 COO,在德国工作十年,回国后加入惠普电信运营商部门,拥有多年项目经验和创业公司工作经验。在数人云负责产品售前和运营,专注行业的技术应用领域,为金融、电信、电商等行业提供服务.
从星巴克流程看微服务最重要能力
前两天在网上看到 Particular 公司工程师 Weronika 的一篇「我从星巴克咖啡学到的架构经验」文章非常不错,星巴克是传统公司里互联网做得最好的,大家去星巴克买咖啡,会发现效率非常高,它怎么把咖啡的效率做得很高?
下图是买咖啡的流程,开单子,收银,然后有一个人做咖啡,交付。
作为程序员提的问题是怎么优化这个流程?非常简单就是集群化。慢无非是因为高并发,客人太多,所以要做集群化,收了单以后,有很多人来做咖啡。这是第一步,也是现在互联网公司的普遍实践。
另外,如果把一个咖啡的任务传给下面的人,下面这个人还要问一下,顾客买的什么咖啡、要不要加牛奶、要冷的还是热的等,这样效率又降低了。所以,星巴克一般会在杯子上写下一些记号。
这个就是状态跟着任务走,所有做咖啡的人都是无状态的,而他们拿到的每个任务是有状态的,这有利于横向扩张、集群化,这和做软件工程是一个道理。
仅仅实现了集群化,又会碰到一些问题,因为每个人都在做咖啡,假如只有一台咖啡机,一堆人抢一台咖啡机的时候,就会出现一个情况:虽然业务可以横向扩展,但是资源很难横向扩展,比如数据库资源、IO 资源、文件系统等,这就出现了瓶颈,计算可以扩展,但是其他的资源不能扩展。
面对这种情况,大家想出了另一个办法,就是服务化。每个做咖啡的人都不是全能的,把他们划分为几个服务,有的人做咖啡、有的人做拿铁、有的人做餐、有的做茶,分成几个服务,拿到一个项目就分给不同的服务的人,服务的人每个人的资源是分开的,就像微服务化,每个微服务的数据库是分开的,大家就可以避免共享某种资源而造成某种资源的拥堵。
同时,每个步骤之间还要实现异步化,把咖啡的杯子传给下一个人放在某一个地方,工作人员会自己拿,两个接口。我把咖啡放在这,收银的人可以收银,前一个人拿了咖啡杯做,最后给交付的人一起交付。接口异步化把业务由集群化拆成服务化,服务之间通过异步接口联络,进一步变成分布式的系统,一个可扩、可缩,不会依赖于某种特定资源、各个环节之间异步的服务。
容错设计
分布式的系统很容易出错,在星巴克最后拿咖啡的时候,理论上讲他应该看小票,但是绝大多数情况下,做咖啡的人不会管你,一般喊一句,有一个美式是谁的,拿走就拿走了。当时我也在想,会不会出现有人冒领别人的咖啡,或者说有人拿错的情况,他为什么不查一下。
后来我想到,因为对于星巴克来说,每次确认一下对方,看小票,很大程度上会影响他的工作效率,一天之内可能 1,000 杯咖啡才会出现一个拿错的情况。如果 1,000 杯咖啡都要确认一下,实际上会在很大程度影响他的工作效率。
所以,星巴克干脆在接受一定错误率的情况下,不去查小票,以此提高工作效率。星巴克本身就允许一定的错误率,允许错误是一个特点。而且在整个流程过程中有可能做错咖啡,有可能用户不满意等等,会遗留下一些做了一半的咖啡,定期地也要清除一下丢掉。
把这三个步骤结合在一起,就是一个咖啡店流程的优化过程。
从星巴克制作咖啡过程可以借鉴到一个软件公司的过程,从互联网的单服务器程序,一步步拆分实现集群化,实现服务化,然后接口异步化,做容错等。
这也是本文讨论的重点,如何将传统系统变成高并发的系统。
案例1. 一个爬虫系统容器云改造的案例
我们曾经做过一个爬虫系统。爬虫系统比较像星巴克的流程,一开始提交抓取 URL 任务,下一个模块就出去抓数据,抓完后把结果存下来。爬虫要爬取的量很大,云平台最多时候有将近 1,000 台机器。
最早的版本是一个小的爬虫程序,Java 实现一个多线程版本,各个线程之间的状态通过传统的 Java EE Context 来共享。当爬虫多的时候就横向做了集群化。像咖啡馆里面,一个咖啡师忙不过来,就多招几个咖啡师进行处理。
集群化、无状态、异步化
在单机的状态,服务器是比较容易扩张的,现在把它发到云平台上做成分布式系统,可以通过把它服务化和接口异步化,每个任务都变成一个模块,使每一个模块都能横向扩展。
首先,把每一个模块拆成一个单独的程序。有控制服务、抓取服务、还有抽取服务,这个类似于原来的接单服务、收银服务、做咖啡服务和交付服务。原来是在一个单独的系统中共享状态,最后抽取服务可以直接输出结果,现在有分布式,把结果归并的过程多加了一个交付服务。
第二步,把每一个环节都服务化,每一个环节都可以横向扩展。下一步就把每个环节之间的接口,如果 Java 调用是一个同步的过程,可以通过 MQ 每一个环节,过程都变成了异步,控制服务会把任务扔到 MQ 中,抓取服务会在 MQ 中接受任务,类似于做咖啡,前一个服务员在咖啡杯上写字,下一个人去取的过程。
第三步,要解决任务的状态问题。星巴克是把任务的状态写在杯子上,爬虫的任务则可以放在任务列表里。当发出一个任务的时候,建立一个 Session,包含要抓 URL,相关的 Cookie 等。抓到内容以后,把抓的内容和 Session ID 放在一起,然后给抽取服务;抽取服务抽出的结果带了 Session ID 放在中间数据库中。
一次抓取 Session ID 包含十个动作,就像星巴克上一个订单有有 10 杯咖啡,都用同样的 Session ID,最后的服务会等 10 杯咖啡做好以后统一交付给用户,这就是 Session 跟着任务走。
作为爬虫服务还有一个全局状态,因为不同的页面有很多的链接跳转,要避免抓重,可以通过一个集中的 Redis 来标记 URL 什么时候抓过.
整个分布式的爬虫流程在一定程度模拟了现实生活中星巴克做咖啡的过程,如果有 1,000 台机器,即使是是京东、淘宝这样大型的网站,也可以以很快的速度全抓下来。
前文也说了,通过服务化、异步化提供了上云的基础,但是在一个分布式系统中要处理出错问题,很多时候为了保证平台的效率,必须容忍一定的错误。怎么做?
分布式容错
在一个分布式系统中,会碰到非常多的问题。举个例子,任务信息有没有可能丢?在星巴克,10 杯咖啡单子扔下去了,有可能在不注意的情况下,杯子丢掉了,10 个剩下 9 个。但是收任务的人应该收 10 个杯子给客户,但是只有 9 个,就一直等下去,这个任务就被卡死了。
或者做咖啡的人突然有事,前面的咖啡越堆越满,会不会把台面堆满,信息堆积造成内存溢出?
还有就是僵尸进程和数据,比如有一个客户在星巴克订一杯咖啡,但是有事走了,这就变成了僵尸任务,占在柜台上,这都是在分布式系统中要处理的问题。
墨菲定律:每个组件都可能失效,每个步骤都可能中断
所以要有一些手段,一是所有的任务都要设置一个超时,每次设置一个任务都有一个超时时间,到了 1 分钟不管是收到 9 杯咖啡、8 杯咖啡,都要先让客户拿 8 杯给客户先喝去,未来齐了再补 2 杯。
另外是要一个定时的 Cleaner,每隔一小时就有人巡查,把没人拿的咖啡丢掉,不能占了系统资源。任务也有超时,一个咖啡两个小时也没做完就得自我毁灭,对系统报告,有一个任务失败了就重新做,这样的容错机制保证了系统的高效运行。
当容器大规模运行时用到的中间层管理问题
刚才介绍了分布式系统的流程,做一个分布式系统需要很多的支撑组件。只有一个组件的时候,发布有 5 台机器监控 5 台机器,这 5 台机器的配置也会写死。一旦在一个分布式系统中,现在不是 5 个程序,机器有多少也不知道,我内部拿的 IP 还不是外部宿主机的 IP,有一系列的问题,怎么把应用快速部署下去,自动化部署,部署以后怎么扩张?
系统在扩张过程中要自动进行配置,配置信息也是不能写死的。容器一多以后底下机器也多,容器和机器就是多对多的关系,最好用自动化工具,不管是自动配置、自动发布,做一个自动化运维的系统。容器显然是不可能在 SSH 登上去看日志,要把日志集中搜集,在某处用搜索引擎进行搜索。容器很好用,但是容器多了很快就会碰到管理的问题,需要引入一个新的中间层管理,才能够发挥大规模效益。因此提出以下问题。
分布式服务配置中心怎么做?
这里举一个分布式配置中心的例子。当我的应用是一个典型网站,有了前端、Cache、业务逻辑,数据库。当我把应用部署到云端的时候,首先他们要彼此发现对方,在启动的时候到配置中心拉取配置。
传统的配置都是写死的,但是如果有一个配置中心,在拉取的时返回相关信息,告诉哪个业务 IP 是什么,配置中心会根据输入信息,生成文本静态的配置,把它返回回来。
这样的好处不管是容器落在哪个主机上,有什么特殊的变化,都能动态生成配置文件,让它运行起来。容器嵌入配置中心的 SDK,就能实现配置的热中心。
再举个例子,爬虫可以设置一个速率,要求一个爬虫每秒钟爬 100 次,集群中可能有 1000 - 2000 个爬虫,现在有一个任务需要更快点,可以把 1000 - 2000 个爬虫的配制改成每秒爬 200 次。常规的做法是把爬虫停掉,修改所有本地配置文件,再重新运行,这显然比较低效。
用配置中心可以更好解决上述问题,把 2,000 个爬虫全部杀掉,在配置中心把配置改成 200,接着启动 2000 个爬虫。爬虫启动的时候会到配置中心拉配置文件,这样就全部生效了,这稍微好一点,不需要去修改本地配置文件了,但是服务还是要中断,还是不够完美。
一种新的方法,类似于 ZooKeeper,把所有的爬虫都 watch 数据中心的某个特别的配置项,一旦配置项改动的时候,就会通过 PCD 长连接发一个推送,特定的配置发生了改变。当然爬虫程序也得能够接受这个改变速度,实现配置系统热中心。
什么样的应用能上云,成为可伸缩微服务?
第一个是无状态应用,这是大多数互联网公司已经有了,这类应用负载均衡不需要做会话保持,大量的状态都是放在共享的 Redis 或数据库中,这种应用能快速上云,能快速部署、弹性伸缩、自动容错,因为容器比较小,而且同时在一个集群中混合部署很多业务,提高资源利用率。
如果是有状态的应用,可以实现 X86 化,跑到虚拟机中。但是负载均衡是需要做保持的,一定程度用了 ESB、Java Session、Java Context,把状态在内存中保存,这样是可以上云,可以对它进行无状态改造,把相应的状态抽出来,就很容易做无状态化。
既使是有状态应用也可以把它放在云上,一是实现快速部署、开发、测试三个环节打通,这是容器做的。其次是可以快速扩容,既使是有状态,新用户跑到新应用上前端做会话保持,可能不能快速收缩,收缩的时候只等新扩的机器上面的用户慢慢都走了以后再把它关掉。也许能提升利用率,这个不一定能提升。
像中间件的容器一般来说是比较大,而且占的资源比较多,很多时候是 1G 跑不了几个容器,提升的利用率不会太多,也许能提升一半,不像用 Tomcat 混合部署可能能提升到 2 - 3 倍的资源利用率,反正能有所提升。
完全依赖本地持久化的应用上云比较难。
比较典型的是数据库。通过分布式存储一定程度能解决这个问题,或者数据库本身就是一个分布式数据库,自然可以上云。
一般传统的 Oracle 数据库,上云的意义不是太大,上云也得定点安装,业务数据不是在云上飘来飘去,需要指定机器,但是数据持久化部分还是挂载出来放在本地硬盘上。
大家经常问哪些应用适合上云,这不是黑白分明的问题。把应用改造到哪一步,上云就得到多少优点。最近五年开发应用,基本上 90%,不管是 WebLogic、WAS,都可以把应用部分、业务逻辑部分搬上云。
这就是刚才说的无状态改造,把负载均衡从有状态改为无状态,把相应的状态放在 Redis 缓存中,基本上就实现了。
案例2. 一个会议系统业务的微服务架构案例
刚才介绍的是相对比较传统的应用,下面介绍一个真正用户业务架构重构过程。公司是 Kuick,这是他们的网址(http://www.kuick.cn/),让你在没有客户端的情况下开多方的电话会议,共享桌面和 PPT,非常适合售前给客户讲 PPT,客户只需要一个网站,他打开网站,一是多方语音通话,二是可以共享桌面。
这个公司只有十几个人,开始跟我们合作的时候只有五六个人,已经做了一个很大的系统。
(图:视频会议系统 Kuick 点击图片全屏缩放)
Kuick 架构概述
这个就是它整个的 IT 架构,有多种客户端:PC、微信、安卓、iOS 等,支持全终端,访问是通过 API 服务器,前后端通过 API 分离,通过 API 服务器到会议服务器,会议服务器是承载了所有会的状态。通过 Session 实现了多人会议,状态会存在 Redis 里面。开会过程中需要一些 push,通过长链接和各个终端保持。
当会议进入语音阶段,通过语音的媒体服务器实现语音,在服务器端进行语音混音,有时候还需要通过电话的网关连到电话,支持多方参与。同时,还可以共享桌面,共享 PPT,支持文件服务,整个架构还是比较复杂的。
他们找我们的时候我们也觉得挺吃惊,挺厉害一个团队,四五个人就搞了这么一个复杂的系统。
当时他们主要碰到的问题是模块太多,等他们部署测试,发现非常麻烦,全部都是点对点连接,用的也是不同的技术,大规模扩展肯定会有问题。
当时我们一起做了架构的重构,把它服务化。不管是数人云的服务发现还是平安的服务发现,一般都是架构由一个点对点的模式变成了一个“星型”的微服务架构。
系统包括网关服务、文件服务、推送服务以及会议服务等等,通过服务发现代理,以服务的方式向外报。客户端负载均衡到 API Server 上,API Server 判断目前是需要开展一个会议,还是分享一个PPT,就往相应的服务器转发。
以前是调用方直接找服务,而且是在配置文件中写死。现在调用服务发现代理,服务代理发现根据它的 URL 导向文件服务进行下一步处理。
(图:Kuick 的微服务化 点击图片全屏缩放)
好处首先是原来复杂的配置文件,点对点的配置变成比较简单,只需要记录下一个服务的 URL 就行了。其次,每一个服务都可以横向扩展,例如会议服务成为瓶颈的时候就可以扩展会议服务。对外通过负载均衡,不管扩展多少节点,对外都是只有一个 URL。服务的配置和扩展变得非常容易,叫微服务化。做完微服务化就解决了 Kuick 应用上云的最大问题。
以前没有正经的测试环境,外网一开就成了生产环境。现在建立了开发、测试、生产三环境。因为他们做完了微服务化,相对来说也比较好做。不管是用数人云还是类似平台,鼠标一点,API 服务一发,几分钟就能发一个系统就出来,还有测试环境,因为是 Docker 化的生产环境一定能跑起来。
如何部署集成测试环境
他们就实现了三个环境,一是他们的集成测试,基本的开发环境,用一台机器、一台阿里云,所有的应用都跑起来,开发环境的小个集成测试。到 QA 测试,用两台机器,因为 QA 测试会跑一定的数据,接着是尽量模仿生产环境,肯定要多台机器。最后是真正的生产环境,有很多台机器。
因为机器数量是不一定,而且需要横向扩展管理,这时候可能需要一个云平台,把这两个管起来。
这是他们的集成测试,就是一台机器。这一台机器跑了非常多,这也是容器的好处,跟云平台、Marathon、Mesos 没有什么关系。容器的好处是用一台机器跑完整的生产环境,起到很多台机器的作用。这些配置可能是手动配的,写死的。但是开发过程中问题不大,通过环境变量把这个东西传进去。
QA 测试环境
QA 环境是两台机器,语音服务器用一台机器用起来。传统的核心逻辑部分用第二台机器,他们当时用的 Jenkins 适配集成,所有部署的脚本都可以一键部署。
现在的集成还没有用 Jenkins,是一套更轻量的,Jenkins 是容器时代以前的对象,并不是为容器设计的,因为 Jenkins 的构建环境和容器无关,只是打包的时候。现在准备环境和构建都在容器里进行,这样的话也更加轻量。
最后是一个完整的环境,他们的目标,内部测试环境已经是一个用云平台进行管理,业务可以灵活地分布在三台机器上,这和最后的线上部署差不多,可以选择特定的机器进行扩展。
参考阅读
我从星巴克咖啡学到的架构经验 (英文) :http://particular.net/blog/what-starbucks-can-teach-us-about-software-scalability