京东京麦:微服务架构下的高可用网关与容错实践(有彩蛋)

标签: | 发表时间:2017-12-27 12:33 | 作者:
出处:http://mp.weixin.qq.com


本文根据DBAplus社群第130期线上分享整理而成, 文末还有好书送哦~


讲师介绍

王新栋

京东资深架构师


  • 从事京麦平台的架构设计与开发工作。熟悉各种开源软件架构,在Web开发,架构优化上有较丰富的实战经历。

  • 有多年在NIO领域的设计、开发经验,对HTTP、TCP长连接技术有深入研究与领悟,目前主要致力于移动与PC平台网关技术的优化与实现。

  • 个人公众号:程序架道(xindongbook17)


自微服务概念诞生以来,众多的软件架构都在践行着这一优秀的设计理念。各自的系统在这一指导思想下收获了优雅的可维护性,但一方面也给接口调用提出了新的要求。比如众多的API调用急需一个统一的入口来支持客户端的调用。在这种情况下API Gateway诞生,我们将接入、路由、限流等功能统一由网关负责,各自的服务提供方专注于业务逻辑的实现,从而给客户端调用提供了一个稳健的服务调用环境。之后,我们在网关大调用量的情况下,还要保证网关的可降级、可限流、可隔离等等一系列容错能力。


今天借社群这个平台,跟大家分享微服务下京麦开放平台的网关实现,以及我们如何抗量、如何在大访问量的情况下做容错处理。重点介绍容错的方法及每种容错方法的使用场景与经验。



一、网关


这里说的网关是指API网关,直面意思是将所有API调用统一接入到API网关层,由网关层统一接入和输出。一个网关的基本功能有:统一接入、安全防护、协议适配、流量管控、长短链接支持、容错能力。有了网关之后,各个API服务提供团队可以专注于自己的的业务逻辑处理,而API网关更专注于安全、流量、路由等问题。


单体应用



业务简单、团队组织很小时,我们常常把功能都集中于一个应用中,统一部署、统一测试,玩得不易乐乎。但随着业务迅速发展,组织成员日益增多,我们再将所有的功能集中到一个Tomcat中去时,每当更新一个功能模块,势必要更新所有的程序,搞不好,还要牵一发动全身,实在难以维护。


微服务



单体应用满足不了我们逐渐增长的扩展需求之后,微服务就出现了,它将原来集中于一体的比如商品功能、订单功能、用户功能拆分出去,各自有各自的自成体系的发布、运维等,这样就解决了在单体应用下的弊端。


API网关



进行微服务后,原先客户端调用服务端的地方就要有N多个URL地址,包括商品的、订单的、用户的。这时就必须要有个统一的入口和出口,这种情况下,我们的API Gateway就出现了,很好地帮助我们解决了微服务下客户端调用的问题。


泛化调用



对于普通的RPC调用,我要拿到服务端提供的class或者jar包,这样过于繁重,更不好维护。不过成熟的RPC框架都支持泛化调用,我们的网关就是基于这种泛化调用来实现的。服务端开放出来他们的API文档,我们拿到接口、参数、参数类型通过泛化调用到服务端程序。 


public Object $invoke(String method, String[] parameterTypes, Object[] args);



二、容错


容错,这个词的理解,书面意思就是可以容下错误,不让错误再次扩张,让这个错误产生的影响在一个固定的边界之内,“千里之堤,毁于蚁穴”,我们采用容错的方式就是不让这种蚁穴继续变大。在工作中,降级、限流、熔断器、超时重试等都是常见的容错方法。


抗量



所谓的抗量,其实就是增大我们系统的吞吐量。所以容错的第一步就是系统要能抗量,没有量的情况下几乎用不到容错。我们的容器使用的是Tomcat,在传统的BIO模型下,一请求一线程,在机器线程资源有限的情况下是没有办法来实现我们的目标。NIO给我们提供了这个机会,基于NIO的机制,利用较少的线程来处理更多的连接。连接多不可怕,通过调整机器的参数一台8c8g的机器,超过10w是不成问题的。Tomcat的Conector修改成NIO后我们再从代码层面引入了Servlet3,它是从Tomcat7以后支持的,NIO是Tomcat6以后就支持的。


利用Servlet3的特性,所有的request和response都由Tomcat的工作线程来处理,我们将业务逻辑异步到别的业务线程中去。异步环境下,可以提高单位时间内的吞吐量。所有的Servlet请求都是由Tomcat的Executor线程池的线程处理的,也就是Tomcat的工作线程。这些线程处理的时间越短越好,越能迅速地将线程归还给Executor线程池。现在Servlet支持异步后就能将耗时的操作,比如有RPC请求的交给业务线程池来处理,使得Tomcat工作线程可立即归还给Tomcat工作线程池。另外,将业务异步处理之后,我们可以对业务线程池进行线程池隔离,这样就避免了因一个业务性能问题而影响了其它的业务。



总结一下异步的优势:

1、可以用来做消息推送,通过Nginx做代理,设置连接超时时间,客户端通过心跳探测。

2、提高吞吐量,就像上面说的。

3、请求线程和业务线程分开,从而可以通过业务线程池对业务线程做隔离。


关于Servlet3的异步原理与实践可以参看笔者以前写的文章 《Servlet3异步原理与实践》



脱离DB



这里不是说DB的性能不行,分库分表、DB集群化之后,在一定量的情况下是没有问题的。但是,如果从抗量的角度说的话,为何不使用Redis呢?如果软件架构里面有一种银弹的话,那么Redis就是这种银弹。


另外一个脱离DB的原因是,每当大促备战前夕我们一项重点的工作就是优化慢SQL,但它就像小强一样生命力是那样的顽强,杀不绝。如果有那么一个慢SQL,平时是没有问题的,比如一个查询大字段的SQL,平时量小不会暴露问题,但量一上来了,就是个灾难。


再就是我们的网关,包括接入、分发、限流等这些功能都应该是很轻的,所以我们就通过数据异构的方式把数据重新转载到Redis中,而且是将数据持久化到Redis里面去。当然,使用Redis的过程中也需要注意大key,大访问量下也能让集群趴下。


还有一个很重要的原因,我觉得必须说一下,我们使用的DB是MySQL,鉴于MySQL的failover机制生效时间总是要长于Redis集群,最后就是因为DB切换的时候,常常伴随Web应用服务器要重启,将原来的连接释放掉,才能方便使用新的数据库连接。


多级缓存



最简单的缓存就是查一次数据库然后将数据写入缓存,比如在Redis中设置过期时间。因为有过期失效,因此我们要关注下缓存的穿透率,这个穿透率的计算公式,比如查询方法queryOrder(调用次数1000/1s)里面嵌套查询DB方法query Product From Db(调用次数300/s),那么Redis的穿透率就是300/1000,在这种使用缓存的方式下,是要重视穿透率的,穿透率大了说明缓存的效果不好。


还有一种使用缓存的方式就是将缓存持久化,也就是不设置过期时间,这个会面临一个数据更新的问题。


  • 一般有两种办法,一个是利用时间戳,查询默认以Redis为主,每次设置数据的时候放入一个时间戳,每次读取数据的时候用系统当前时间和上次设置的这个时间戳做对比,比如超过5分钟,那么就再查一次数据库,这样可以保证Redis里面永远有数据,一般是对DB的一种容错方法。

  • 还有一个就是让Redis真正作为DB来使用。就是图里画的通过订阅数据库的binlog,通过数据异构系统将数据推送给缓存,同时将将缓存设置为多级。可以通过使用jvmcache作为应用内的一级缓存,一般是体积小,访问频率大的更适合这种jvmcache方式,将一套Redis作为二级remoto缓存,另外的最外层三级Redis作为持久化缓存。


超时与重试


超时与重试机制也是容错的一种方法,凡是发生RPC调用的地方,比如读取Redis、DB、MQ等,因为网络故障或者是所依赖的服务故障了,长时间不能返回结果,就会导致线程增加,加大CPU负载,甚至导致雪崩。所以对每一个RPC调用都要设置超时时间。


对于强依赖RPC调用资源的情况,还要有重试机制,但重试的次数建议1-2次,另外如果有重试,超时时间还要相应都调小,比如重试1次,那么一共是发生2次调用。如果超时时间配置的是2s,那么客户端就要等待4s才能返回。因此重试+超时的方式,超时时间要调小。


这里也再谈一下一次PRC调用的时间都消耗在哪些环节。一次正常的调用统计的耗时主要包括:

①调用端RPC框架执行时间 + ②网络发送时间 + ③服务端RPC框架执行时间 + ④服务端业务代码时间。


调用方和服务方都有各自的性能监控,比如调用方tp99是500ms,服务方tp99是100ms,找了网络组的同事确认网络没有问题。那么时间都花在什么地方了呢?两种原因:客户端调用方,还有一个原因是网络发生TCP重传,所以要注意这两点。RPC耗时详细内容可以参看笔者写的另外一篇文章 《一次RPC调用时间都去哪儿了》


熔断


熔断技术可以说是一种“智能化的容错”,当调用满足失败次数,失败比例就会触发熔断器打开,有程序自动切断当前的RPC调用,来防止错误进一步扩大。实现一个熔断器主要是考虑三种模式:关闭、打开、半开。各个状态的转换如下图。



在了解了熔断器的状态机制后,我们可以自己来实现一个熔断器。当然也可以使用开源的解决方案,比如Hystrix中的breaker。下图是一个熔断器打开关闭的示意图。



这里要谈的是熔断器的使用注意项。我们在处理异常时,要根据具体的业务情况来决定处理方式,比如我们调用商品接口,对方只是临时做了降级处理,那么作为网关调用就要切到可替换的服务上来执行或者获取托底数据,给用户友好提示。还有要区分异常的类型,比如依赖的服务崩溃了,这个可能需要花费比较久的时间来解决,也可能是由于服务器负载临时过高导致超时。作为熔断器应该能够甄别这种异常类型,从而根据具体的错误类型调整熔断策略。增加手动设置,在失败的服务恢复时间不确定的情况下,管理员可以手动强制切换熔断状态。最后,熔断器的使用场景是调用可能失败的远程服务程序或者共享资源。如果是本地缓存本地私有资源,使用熔断器则会增加系统的额外开销。还要注意,熔断器不能作为应用程序中业务逻辑的异常处理替代品。


关于熔断的原理与实践可以参照这篇文章 《Hystrix熔断器技术解析》


线程池隔离




在抗量这个环节,Servlet3异步时,有提到过线程隔离。线程隔离的之间优势就是防止级联故障,甚至是雪崩。当网关调用N多个接口服务的时候,我们要对每个接口进行线程隔离,比如我们有调用订单、商品、用户。那么订单的业务不能够影响到商品和用户的请求处理。如果不做线程隔离,当访问订单服务出现网络故障导致延时,线程积压最终导致整个服务CPU负载满,就是我们说的服务全部不可用了,有多少机器都会被此刻的请求塞满。那么,有了线程隔离就会使得我们的网关能保证局部问题不会影响全局。


降级、限流


关于降级限流的方法业界都已经有很成熟的方法了,比如Failback机制,限流方法令牌桶、漏桶、信号量等。这里谈一下我们的一些经验。


降级一般都是由统一配置中心的降级开关来实现的,那么当有很多个接口来自同一个提供方,这个提供方的系统或这机器所在机房网络出现了问题,我们就要有一个统一的降级开关,不然就要一个接口一个接口地来降级,也就是要对业务类型有一个大闸刀。


还有就是降级切记暴力降级,什么是暴力降级?比如把论坛功能降调,结果用户显示一个大白板,我们要实现缓存住一些数据,也就是有托底数据。限流一般分为分布式限流和单机限流,如果实现分布式限流的话就要一个公共的后端存储服务比如Redis,在大Nginx节点上利用Lua读取Redis配置信息。我们现在的限流都是单机限流,并没有实施分布式限流。


网关监控与统计



API网关是一个串行的调用,每一步发生的异常都要记录下来,统一存储到一个地方比如Elasticserach中,便于后续对调用异常的分析。鉴于公司Docker申请都是统一分配,而且分配之前Docker上已经存在3个agnet了,不再允许增加。我们自己实现了一个agnet程序,来负责采集服务器上面的日志输出,然后发送到kafka集群,再消费到Elasticserach中,通过Web查询。现在做的追踪功能还比较简单,这块还需要继续丰富。



三、总结


网关基本功能有统一接入、安全防护、协议适配等。这篇文章里我们并没有讲如何来实现这些基本的功能,因为现在有很多成熟的解决方案可以直接拿过来使用,比如Spring Cloud这种全家桶里面的很多组件,Mashape的API层Kong等。我们更关注的是实现了这些网关的基本功能之后,如何保证一个网关的运行,在大访问量的情况下如何能更好的支持客户端的调用,在突发情况下又是如何及时地响应这种突然的异常,如何将错误最小化,防止级联故障。我们的重点关注的是网关容错方面的经验与实践。


参考资料

https://martinfowler.com/articles/microservices.htm

lhttps://tomcat.apache.org/tomcat-7.0-doc/config/http.html

http://www.cnblogs.com/davenkin/p/async-servlet.html

《Tomcat内核设计剖析》



Q&A


Q1:异构数据库推送数据到Redis,是表数据还是联合查询后的数据?

A1:热点数据,不会是全量查询表,根据业务条件查询出来的结果集合。你可以理解为视图那样的方式,只是落地了另外一个库。


Q2:8c8g的机器,连接超过10w,机器调整哪些参数?

A2:

# 查看当前用户允许TCP打开的文件句柄最大数
ulimit -n

# 修改文件句柄
vim /etc curity mits.conf

* soft nofile 655350
* hard nofile 655350


注意:

  • soft nofile (软限制)是指Linux在当前系统能够承受的范围内进一步限制用户同时打开的文件数;

  • hard nofile (硬限制)是根据系统硬件资源状况(主要是系统内存)计算出来的系统最多可同时打开的文件数量;

  • 通常软限制小于或等于硬限制。


Q3:业务线程池使用JDK自带的还是贵公司研发的?如何优雅地关停,尤其是还有事务在处理?

A3:JDK自带的。事务,我们主要是考虑最终一致性就好了。互联网应用CAP里面 ,保证AP。


Q4:Tomcat上还要调整那些参数?

A4:Tomcat 改成 NIO。1000并发,目前线上是这样配置的。


直播链接

https://m.qlchat.com/topic/details?topicId= 2000000447502593


彩蛋来了

在本文微信订阅号(dbaplus)评论区留下足以引起共鸣的真知灼见,并在本文发布后的隔天 中午12点成为 点赞数最多的1名,可获得以下好书一本~

新规说明:同一个月份里,已获赠者将不可重复拿书。

特别鸣谢图灵教育为本次活动供图书赞助。

 

近期热文:

凡墙皆门:移动云自动化运维平台的破墙与进化之路

一篇含金量hin高的Nginx反向代理与负载均衡指南

方案虽好,成本先行:数据库Sharding+Proxy实践解析

一切皆API的大环境下,如何打造API Everything?

让MySQL数据库跑得更快的7条优化建议!


相关 [京东 微服务 架构] 推荐:

京东京麦:微服务架构下的高可用网关与容错实践(有彩蛋)

- -
本文根据DBAplus社群第130期线上分享整理而成,. 从事京麦平台的架构设计与开发工作. 熟悉各种开源软件架构,在Web开发,架构优化上有较丰富的实战经历. 有多年在NIO领域的设计、开发经验,对HTTP、TCP长连接技术有深入研究与领悟,目前主要致力于移动与PC平台网关技术的优化与实现. 个人公众号:程序架道(xindongbook17).

谈微服务架构

- - 人月神话的BLOG
其实在前面很多文章谈到SOA,特别是系统内的SOA和组件化的时候已经很多内容和微服务架构思想是相同的,对于微服务架构,既然出现了这个新名称,那就再谈下微服务架构本身的一些特点和特性. 从这个图可以看到微服务架构的第一个重点,即业务系统本身的组件化和服务化,原来开发一个业务系统本身虽然分了组件和模块,但是本质还是紧耦合的,这关键的一个判断标准就是如果要将原有的业务系统按照模块分开部署到不同的进程里面并完成一个完整业务系统是不可能实现的.

微服务与架构师

- - 乱象,印迹
因为工作的关系,最近面试了很多软件架构师,遗憾的是真正能录用的很少. 很多候选人有多年的工作经验,常见的框架也玩得很溜. 然而最擅长的是“用既定的技术方案去解决特定的问题”,如果遇到的问题没有严格对应的现成框架,就比较吃力. 这样的技能水平或许适合某些行业,但很遗憾不符合我们的要求. 软件架构师到底应该做什么,又为什么这么难做好,这都是近来的热门问题,我也一直在和朋友们讨论.

面向服务与微服务架构

- - CSDN博客推荐文章
最近阅读了 Martin Fowler 和 James Lewis 合著的一篇文章  Microservices, 文中主要描述和探讨了最近流行起来的一种服务架构模式——微服务,和我最近几年工作的实践比较相关感觉深受启发. 本文吸收了部分原文观点,结合自身实践经验来探讨下服务架构模式的演化. 面向服务架构 SOA 思想概念的提出已不是什么新鲜事,大概在10年前就有不少相关书籍介绍过.

微服务架构实践感悟

- - mindwind
从去年初开始接触微服务架构的一些理念,然后到今年开始实施系统第四个大版本的架构升级决定采用这套架构理念. 最近关于微服务架构的讨论还是多起来,因为国外一些著名互联网公司(如:Amazon、Netflix 等)从实践中摸索出了一套新的大型系统架构方法论,并取得了成功,树立了很好的示范,然后这套方法论渐渐就被一些技术理论派 人士命名为微服务架构(Microservices).

微服务架构成功之路

- - CSDN博客推荐文章
本文来源于我在InfoQ中文站翻译的文章,原文地址是:http://www.infoq.com/cn/news/2015/07/success-of-microservices. 近年来,在软件开发领域关于微服务的讨论呈现出火爆的局面,有人倾向于在系统设计与开发中采用微服务方式实现软件系统的松耦合、跨部门开发;同时,反对之声也很强烈,持反对观点的人表示微服务增加了系统维护、部署的难度,导致一些功能模块或代码无法复用,同时微服务允许使用不同的语言和框架来开发各个系统模块,这又会增加系统集成与测试的难度,而且随着系统规模的日渐增长,微服务在一定程度上也会导致系统变得越来越复杂.

微服务架构-模块迁移

- - 人月神话的BLOG
对于遗留的单体应用,要进行微服务架构的改造往往比一个全新应用基于微服务架构实现更加困难. 对于单体应用的微服务架构改造,最常见的方式仍然是将低耦合的模块逐步迁出. 下面以一个采购系统中招投标模块迁出为例进一步思考单体应用的微服务架构改造步骤. 在整个模型中我们将模型进行简化,当迁出一个功能模块进行微服务化的时候,首先要考虑的就是对该模块进行集成架构分析,考虑该模块和外围的集成情况,其次才是考虑该模块内部的私有数据.

SOA和微服务架构沟通(2.8)

- - 人月神话的BLOG
今天在广州交流SOA和微服务架构,特对关键内容做简单记录. 对于SOA和微服务架构的区别,在知乎一个回答里面我已经进行了详细的说明,即微服务架构强调的第一个重点就是 业务系统需要彻底的组件化和服务化,原有的单个业务系统会拆分为多个可以独立开发,设计,运行和运维的小应用. 这些小应用之间通过服务完成交互和集成.

微服务下的数据架构

- - IT瘾-dev
微服务是一个软件架构模式,对微服务的讨论大多集中在容器或其他技术是否能很好的实施微服务,而本文将从以下几个角度来和大家分享在微服务架构下进行数据设计需要关注的地方,旨在帮助大家在构建微服务架构时,提供一个从数据方面的视角:. 按照 Martin Fowler 的定义,微服务是一个软件架构模式,通过开发一系列的小型服务的方式来实现一个应用.

微服务架构之事件驱动架构 - 简书

- -
为了解决传统的单体应用(Monolithic Application)在可扩展性、可靠性、适应性、高部署成本等方面的问题,许多公司(比如Amazon、eBay和NetFlix等)开始使用微服务架构(Microservice Architecture)构建自己的应用. 微服务(Microservices) 是一种软件架构风格 (Software Architecture Style),它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础,利用模组化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic) 的 API 集相互通讯.