(七)、Feign:负载均衡 (基于客户端)
1.Feign简介
Ribbon基于客户端。
Feign是声明式Web Service客户端
,它让微服务之间的调用变得更简单,类似controller调用service。
SpringCloud集成了Ribbon和Eureka,也可以使用Feigin提供负载均衡的http客户端。
(Feign就是在Ribbon的基础上加了一层,将**使用方式转变为面向接口变成的方式**)
只需要创建一个接口,然后添加注解即可~
Feign,主要是社区版,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问两种方法
- 微服务名字 【ribbon 是通过微服务名字去访问】
- 接口和注解 【feign】
(1).Feign能干什么?
- Feign旨在使编写Java Http客户端变得更容易
- 前面在使用
Ribbon + RestTemplate
时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一个客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步的封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它 (类似以前Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon 时,自动封装服务调用客户端的开发量
Feign默认集成了Ribbon
- 利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
(2).Feign的使用步骤
- 创建springcloud-consumer-fdept-feign模块
- 拷贝springcloud-consumer-dept-80模块下的pom.xml,resource,以及java代码到springcloud-consumer-feign模块,并添加feign依赖分别在SpringCloud-api模块和SpringCloud-consumer-dept-feign (80)模块。
<!--Feign的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.6.RELEASE</version> </dependency>
- 进行配置在SpringCloud-api模块下创建 service/DeptClientServer.java并添加注解
接口
package com.jsxs.service; import com.jsxs.pojo.Dept; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import java.util.List; @FeignClient(value = "SpringCloud-provider-dept") //feign接口,里面的值是微服务的服务提供者ID名 public interface DeptClientServer { //接口 @GetMapping("/dept/add") public boolean addDept(Dept dept); @GetMapping("/dept/get/{id}") public Dept queryById(@PathVariable("id") Long deptno); @GetMapping("/dept/all") public List<Dept> all(); }
- 通过Ribbon实现:原来的controller:DeptConsumerController.java (80)
package com.jsxs.controller; import com.jsxs.pojo.Dept; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; @RestController public class DeptConsumerController { //注意: 这里我们只是引入了实体类的数据,并没有引入service接口 //思考: 消费者界面不应该存在Dao层也不应该存在Service层,该如何使用服务呢 //RestTemplate ... 我们直接调用就行,但需要注入到Spring中 @Resource // (URI url, 实体 map,Class<T> responseType) 地址---- 实体 ----返回类型.class private RestTemplate restTemplate; // 提供多种便捷访问远程 访问http服务的方法,简单的Rest // 设置服务层的前缀为常量 //private static final String REST_URL_PREFIX="http://localhost:8081"; // 使用Ribbon负载均衡,我们这里不应该写死。而应该通过id名(服务提供者),进行获取 SpringCloud-provider-dept private static final String REST_URL_PREFIX="http://SpringCloud-provider-dept"; // 根据id进行数据的查找 @RequestMapping("/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long deptno){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/queryById/"+deptno,Dept.class); } // 添加数据 @RequestMapping("/consumer/dept/add") public boolean add(Dept dept){ return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class); } // 查找全部数据 @RequestMapping("/consumer/dept/all") public List<Dept> all(){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/all",List.class); } }
5 .
通过Feign实现: 改造后controller:DeptConsumerController.java (80)
这里的this 是 DeptConsumerController
package com.jsxs.controller; import com.jsxs.pojo.Dept; import com.jsxs.service.DeptClientServer; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; @RestController public class DeptConsumerController { @Resource private DeptClientServer deptClientServer=null; @RequestMapping("/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long deptno){ return this.deptClientServer.queryById(deptno); } // 添加数据 @RequestMapping("/consumer/dept/add") public boolean add(Dept dept){ System.out.println(this+"-------------"); return this.deptClientServer.addDept(dept); } // 查找全部数据 @RequestMapping("/consumer/dept/all") public List<Dept> all(){ System.out.println(this+"-------------"); return this.deptClientServer.all(); } }
Feign和Ribbon二者对比,前者显现出面向接口编程特点,代码看起来更清爽,而且Feign调用方式更符合我们之前在做SSM或者SprngBoot项目时,Controller层调用Service层的编程习惯!
6.启动类设置开启
package com.jsxs; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient @EnableFeignClients(basePackages = {"com.jsxs"}) // 在微服务启动的时候,就会加载我们自定义的均衡算法。 public class FeignDeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(FeignDeptConsumer_80.class,args); } }
成功
2. Ribbon与Feign如何选择?
根据个人习惯而定,如果喜欢REST风格使用Ribbon;如果喜欢社区版的面向接口风格使用Feign.
Feign 本质上也是实现了 Ribbon,只不过后者是在调用方式上,为了满足一些开发者习惯的接口调用习惯!Feign 的作用就是替代原先Ribbon在Controller层中RestTemplate,使代码的可读性变高,但是性能变低了。
(八)、Hystrix:服务熔断
分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免失败
1.服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出
”,如果扇出的链路上某个微服务的调用响应时间过长,或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应
”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几十秒内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以达到单个依赖关系的失败而不影响整个应用程序或系统运行。
我们需要,弃车保帅!
2.什么是Hystrix
Hystrix是一个应用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整个体系服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控 (类似熔断保险丝) ,向调用方返回一个服务预期的,可处理的备选响应 (FallBack) ,而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
3.Hystrix能干嘛
- 服务降级
- 服务熔断
- 服务限流
- 接近实时的监控
…
当一切正常时,请求流可以如下所示:
当许多后端系统中有一个潜在阻塞服务时,它可以阻止整个用户请求:
随着大容量通信量的增加,单个后端依赖项的潜在性会导致所有服务器上的所有资源在几秒钟内饱和。
应用程序中通过网络或客户端库可能导致网络请求的每个点都是潜在故障的来源。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,从而备份队列、线程和其他系统资源,从而导致更多跨系统的级联故障。
当使用Hystrix包装每个基础依赖项时,上面的图表中所示的体系结构会发生类似于以下关系图的变化。每个依赖项是相互隔离的,限制在延迟发生时它可以填充的资源中,并包含在回退逻辑中,该逻辑决定在依赖项中发生任何类型的故障时要做出什么样的响应: