参考:
引言
SpringCloud中将整个系统拆分成多个小的模块,也就是我们经常听到的微服务,这样做的好处在于灵活部署、可扩展、技术异构等。
但是同时也有一定的问题,对于像我一样刚刚接触SpringCloud的人来说,微服务之间如何进行通信(如何进行方法调用)是一个非常困惑的点,因为子系统和子系统是处于不同的环境,那么自然而然就想到了RPC。
既然是RPC,那么必须要知道的是被调用方的ip地址,试想如下场景:
这样做的缺点在于一旦被调用方的地址发生变化,调用方都需要手动去更新地址,对于一个庞大的系统而言,这无疑是非常麻烦的。
为了解决微服务架构中的服务实例维护问题(ip地址),产生了大量的服务治理框架和产品。这些框架和产品的实现都围绕着服务注册与服务发现机制来完成对微服务应用实例的自动化管理。
在SpringCloud中我们的服务治理框架一般使用的就是Eureka。
什么是Eureka
我们假设有A,B,C,D 4个模块,创建一个E服务,将A、B、C、D四个服务的信息都注册到E服务上,E服务维护这些已经注册进来的信息
A、B、C、D四个服务都可以拿到Eureka(服务E)那份注册清单。A、B、C、D四个服务互相调用不再通过具体的IP地址,而是通过服务名来调用!
Eureka中的细节部分
Eureka可以分成两个部分:
- Eureka-Server:服务注册中心
- Eureka-Client:在服务注册中心注册的模块
在Eureka Server一般我们会这样配置:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
Eureka Client 可以分为服务提供者和服务消费者,但是每个模块可以同时充当两种角色,在Eureka Client中我们一般这样配置:
eureka:
client:
service-url:
defaultZone: http://localhost:9100/eureka #配置服务注册中心的地址
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
status-page-url: http://${spring.cloud.client.ip-address}:${server.port}/docs.html
hostname: user-web
Eureka的服务治理机制
-
服务提供者
-
服务注册:启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息。
-
服务续约:在注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka Server: "我还活着 ”
-
服务下线:当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server, 告诉服务注册中心:“我要下线了 ”。
-
服务消费者
-
获取服务:当我们启动服务消费者的时候,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单
-
服务调用:服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方。
-
Eureka Server
-
失效剔除:默认每隔一段时间(默认为60秒) 将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
-
自我保护:EurekaServer 在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%(通常由于网络不稳定导致)。Eureka Server会将当前的实例注册信息保护起来, 让这些实例不会过期,尽可能保护这些注册信息
那么Eureka只是一个服务治理的框架,现在所有的模块都能够知道到底有哪些模块的服务是可以用的以及对应的服务名,那么如何调用这些服务呢?这就不得不提SpringCloud中的Feign了。
What is Feign ?
刚刚讲到了服务的注册和发现,在微服务架构中,业务都会被拆成一个个独立的服务,服务与服务的通讯是基于http restful 的,在Spring Cloud中有两种服务的调用方式,一种是ribbon+restTemplate,一种是feign。 因此在介绍Feign之前我们先介绍一下Ribbon和Hystrix
Ribbon
Ribbon是支持负载均衡,默认的负载均衡策略是轮询,我们也是可以根据自己实际的需求自定义负载均衡策略的。
实现起来也很简单:继承AbstractLoadBalancerRule类,重写public Server choose(ILoadBalancer lb, Object key)即可。
SpringCloud 在CAP理论是选择了AP的,在Ribbon中还可以配置重试机制的,关于Ribbon的原理可以学习一下这篇文章: www.cnblogs.com/kongxiangha…
Hystrix
考虑一下这种场景:如果我们在调用多个远程服务时,某个服务出现延迟,会怎么样?
在高并发的情况下,由于单个服务的延迟,可能导致所有的请求都处于延迟状态,甚至在几秒钟就使服务处于负载饱和的状态,资源耗尽,直到不可用,最终导致这个分布式系统都不可用,这就是“雪崩”。
针对上述问题, Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护功能。
- Fallback(失败快速返回):当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝), 向调用方返回一个错误响应, 而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延 *资源/依赖隔离(线程池隔离):它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响, 而不会拖慢其他的依赖服务。
上面已经介绍了Ribbon和Hystrix了,可以发现的是:他俩作为基础工具类框架广泛地应用在各个微服务的实现中。我们会发现对这两个框架的使用几乎是同时出现的。
为了简化我们的开发,Spring Cloud Feign出现了!它基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix, 除了整合这两者的强大功能之外,它还提 供了声明式的服务调用(不再通过RestTemplate)。
Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign, 我们可以做到使用HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。
下面看看Feign是怎么实现远程调用的!
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。
简而言之:
-
Feign 采用的是基于接口的注解
-
Feign 整合了ribbon,具有负载均衡的能力
-
整合了Hystrix,具有熔断的能力
怎么使用?
在程序的启动类ServiceFeignApplication ,加上@EnableFeignClients注解开启Feign的功能
Feign 是基于接口的注解
FileClient.java
FileDownloadApi.java
在Feign中使用Hystrix
Feign是自带断路器的,不过没有默认打开,需要在配置文件中打开
feign.hystrxi.enabled=true
只需要在FileClient中添加fallback类
FileClient.class
BreakDownHandleClass.class
如果FileDownload对应的服务不可用,则会返回service is unavailable now
Feign的实现原理
- 通过@EnableFeignClients注解开启FeignClient
- 根据Feign的规则实现接口,并加@FeignClient注解
- 程序启动后,会进行包扫描,扫描所有加了@FeignClient注解的类,并将这些信息注入到IOC容器中
- 当接口的方法被调用,通过jdk代理,生成具体的RequestTemplate
- RequestTemplate再生成Request
- Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient可以是Okhttp
- 最后Client被封装到LoadBalanceClient类,这个类结合了Ribbon做负载均衡