本文主要介绍的微服务是spring cloud,它一个服务治理框架和一系列框架的由序集合,其利用springboot的开发便利性巧妙的简化了分布式系统基础设施的开发,如服务发现注册、负载均衡、断路器、数据监控等,都可以用springboot的开发风格做到一键启动和部署。学习和了解微服务对快速熟悉和掌握不同项目的服务架构大有益处
分为以下几个部分进行介绍:
一、微服务应用的简单业务场景
二、Spring Cloud核心组件:Eureka
三、Spring Cloud核心组件:Feign
四、Spring Cloud核心组件:Ribbon
五、Spring Cloud核心组件:Hystrix
六、Spring Cloud核心组件:Zuul
七、高并发场景下配置优化
八、总结
一、业务场景介绍
先来给大家说一个都熟悉的支付订单的功能,大体的流程如下:
上述业务场景的流程涉及到订单服务、库存服务、物流服务、用户积分服务。其调用关系如下图:
二、Spring Cloud核心组件:Eureka
考虑一个问题:订单服务想要调用库存服务、物流服务,或者是积分服务,怎么调用?
那么大家算算,Eureka Server作为一个微服务注册中心,每秒钟要被请求多少次?一天要被请求多少次?
按标准的算法,每个服务实例每分钟请求2次拉取注册表,每分钟请求2次发送心跳
深入原理:
一句话概括:维护注册表、拉取注册表、更新心跳时间,全部发生在内存里!
在拉取注册表的时候:
在注册表发生变更的时候:
总结一下:
三、Spring Cloud核心组件:Feign
实现优雅的rpc调用:解决完服务在哪里之后,该如何去调用服务呢?难道订单服务要自己写一大堆代码,跟其他服务建立网络连接,然后构造一个复杂的请求,接着发送请求过去,最后对返回的响应结果再写一大堆代码来处理吗?想到这里是不是直想挠头,为了保住自己本来就不多的头发!来看看Feign是如何处理的吧!
看完上面的代码是不是感觉干净清爽!没有底层的建立连接、构造请求、解析响应的代码,直接就是用注解定义一个 FeignClient接口,然后调用那个接口就可以了,Feign都帮你做好了。
Feign实现这些的原理:一个关键机制就是使用了动态代理。
四、Spring Cloud核心组件:Ribbon
服务集群部署:如果库存服务部署了3台怎么办,如下所示:
问题来了,Feign怎么知道该请求哪台机器呢?这时就轮到Ribbon出马了,其提供了解决微服务负载均衡的问题。Ribbon 是一个基于Http和TCP的客服端负载均衡工具,它是基于Netflix Ribbon实现的。它不像spring cloud服务注册中心、配置中心、API网关那样独立部署,但是它几乎存在于每个spring cloud 微服务中。包括feign提供的声明式服务调用也是基于该Ribbon实现的。
此外,Ribbon是和Feign以及Eureka紧密协作,完成工作的,具体如下:
对上述整个过程,再来一张图:
五、Spring Cloud核心组件:Hystrix
断路器,旨在通过熔断机制控制服务和其依赖服务之间的延迟和故障。比如:在一个大型的微服务架构里,一个服务可能要依赖很多服务,像本文的业务场景,订单服务依赖库存服务、物流服务、积分服务三个服务。假设订单服务最多只有100个线程可以处理请求,如果库存服务挂了,一般会抛出一个异常。如果系统处于高并发的场景下,大量请求涌过来的时候,订单服务的100个线程都会卡在请求库存服务这块,这导致订单服务没有一个线程可以处理请求,服务器完全不响应任何请求。
如上图,这么多服务互相调用,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。比如服务B挂了,会导致服务A的线程全部卡在请求服务B这里,没有一个线程可以工作。上面这个,就是微服务架构中恐怖的服务雪崩问题。
使用Hystrix。Hystrix是隔离、熔断以及降级的一个框架。Hystrix处理方式是提供很多个小小的线程池(其中一种解决方式),比如订单服务请求库存服务是一个线程池,请求物流服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。
当然会导致服务A的那个用来调用服务B的线程都卡死不能工作了啊!但是由于服务A调用服务C、服务D的这两个线程池都是正常工作的,所以这两个服务不会受到任何影响。但是如果服务B都挂了,服务A每次调用都要去卡住几秒钟干啥呢?有意义吗?当然没有!所以我们直接对服务B熔断不就得了,比如在5分钟内请求积分服务直接就返回了,不要去走网络请求卡住几秒钟,这个过程,就是所谓的熔断!如果服务A知道服务B已经熔断了,可以做一些特殊的处理,比如返回一些兜底的数据或者友好的提示,这个过程,就是所谓的降级。
为帮助大家更直观的理解,接下来用一张图,梳理一下Hystrix隔离、熔断和降级的全流程:
六、Spring Cloud核心组件:Zuul
前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign和Ribbon支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散。我们还是少考虑了一个问题,外部的应用如何来访问内部各种各样的微服务呢?在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务
为什么需要微服务网关?
在微服务架构模式下后端服务的实例数一般是动态的,动态改变的服务实例的访问地址信息很难被客户端发现。因此在基于微服务的项目中为了简化前端的调用逻辑,引入API Gateway作为轻量级网关,而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。
七、高并发下配置优化
假设你的服务A,每秒钟会接收30个请求,同时会向服务B发起30个请求,然后每个请求的响应时长经验值大概在200ms,那么你的hystrix线程池需要多少个线程呢?计算公式是:30(每秒请求数量) * 0.2(每个请求的处理秒数) + 4(给点缓冲buffer) = 10(线程数量)。
为什么10个线程可以轻松抗住每秒30个请求?
一个线程200毫秒可以执行完一个请求,那么一个线程1秒可以执行5个请求,理论上,只要6个线程,每秒就可以执行30个请求。也就是说,线程里的10个线程中,就6个线程足以抗住每秒30个请求了。剩下4个线程都在玩儿,空闲着。那为啥要多搞4个线程呢?很简单,因为你要留一点buffer空间。万一在系统高峰期,系统性能略有下降,此时不少请求都耗费了300多毫秒才执行完,那么一个线程每秒只能处理3个请求了,10个线程刚刚好勉强可以hold住每秒30个请求。所以你必须多考虑留几个线程。
一个接口,理论的最佳响应速度应该在200ms以内,或者慢点的接口就几百毫秒。如果一个接口响应时间达到1秒+,建议考虑用缓存、索引、NoSQL等各种你能想到的技术手段,优化一下性能。否则你要是胡乱设置超时时间是几秒,甚至几十秒,万一下游服务偶然出了点问题响应时间长了点呢?那你这个线程池里的线程立马全部卡死!合理的超时时间设置为多少?答案应该不超过300毫秒(这个需要根据实际压测数据做些优化)。为啥呢?如果你的超时时间设置成了500毫秒,想想可能会有什么后果?考虑极端情况,如果服务B响应变慢,要500毫秒才响应,你一个线程每秒最多只能处理2个请求了,10个线程只能处理20个请求。大量的线程会全部卡死,来不及处理那么多请求,最后用户会刷不出来页面。如果你的线程池大小和超时时间没有配合着设置好,很可能会导致服务B短暂的性能波动,瞬间导致服务A的线程池卡死,里面的线程要卡顿一段时间才能继续执行下一个请求。哪怕一段时间后,服务B的接口性能恢复到200毫秒以内了,服务A的线程池里卡死的状况也要好一会儿才能恢复过来。你的超时时间设置的越不合理,比如设置的越长,设置到了1秒、2秒,那么这种卡死的情况就需要越长的时间来恢复。所以说,此时你的超时时间得设置成300毫秒,保证一个请求300毫秒内执行不完,立马超时返回。这样线程池里的线程不会长时间卡死,可以有条不紊的处理多出来的请求,大不了就是300毫秒内处理不完立即超时返回,但是线程始终保持可以运行的状态。这样当服务B的接口性能恢复到200毫秒以内后,服务A的线程池里的线程很快就可以恢复。
八、总结:
总结一下,上述几个Spring Cloud核心组件,在微服务架构中,分别扮演的角色:
当然spring cloud家族还有Spring Cloud Config、Spring Cloud Bus、Spring Cloud for Cloud Foundry、Spring Cloud Cluster、Spring Cloud Consul、Spring Cloud Security、Spring Cloud Sleuth、Spring Cloud Data Flow、Spring Cloud Stream、Spring Cloud Task、Spring Cloud Zookeeper、Spring Cloud Connectors、Spring Cloud Starters、Spring Cloud CLI等等,具体信息请查看:
中文文档:https://springcloud.cc/
英文文档:http://spring.io/projects/spring-cloud
来源:网易工程师-周延旭