文章目录
- 一、负载均衡 Robbin
- 1.1 启动多个服务的提供方
- 1.2 在服务的消费方配置负载均衡算法
- 二、熔断器 Hystrix
- 2.1 雪崩问题
- 2.2 Hystrix 服务降级
- 2.2.1. 在服务消费者中配置Hystrix
- 2.2.2 如果没个方法都写一个熔断很麻烦,如何写全局的熔断?
- 2.2.3 设置熔断超时
- 2.3 Hystrix 服务熔断
- 三、声明式调用 Feign
- 3.1. 改造服务消费者
- 3.2 声明式调用如何集成熔断器?
- 四、网关路由 Zuul(Zuul的四种路由)
- 4.1 快速入门(通过路径进行访问)
- 4.2 面向服务的路由(通过服务Id进行访问)
- 4.3 简化路由配置
- 4.4 默认的路由规则
- 4.5 Zuul过滤器
- 4.5.1 ZuulFilter
- 4.5.2 自定义过滤器
一、负载均衡 Robbin
1.1 启动多个服务的提供方
将原先的服务复制一份,并启动。
1.2 在服务的消费方配置负载均衡算法
01.在引导类上添加负载均衡算法注解:
02.在Controller中直接使用服务提供方名称
03.在配置文件中修改负载均衡策略
二、熔断器 Hystrix
作用:用于隔离访问远程服务、第三方库,防止出现级联失败。
2.1 雪崩问题
Hystix解决雪崩问题的手段有两个:
- 线程隔离
- 服务熔断
线程隔离
== Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。
服务降级:优先保证核心服务,而非核心服务不可用或弱可用。
== 用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
== 服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。
2.2 Hystrix 服务降级
2.2.1. 在服务消费者中配置Hystrix
- 在配置文件pom.xml中添加熔断器的依赖
<!-- 添加熔断器依赖 Hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 在引导类中添加依赖
@SpringBootApplication
@EnableDiscoveryClient //开启服务注册中心 Eureka
@EnableCircuitBreaker //开启熔断器服务 Hystrix
public class ConsumerApplication {
/* 使用RestTemplate做远程调用方法 */
@Bean
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
System.out.println("调用方启动成功!!");
}
}
- 在对应的控制层添加熔断方法并进行绑定
4. 测试
先将服务提供方,服务消费者停止;
再重启服务消费者。
2.2.2 如果没个方法都写一个熔断很麻烦,如何写全局的熔断?
在类上添加注解@DefaultProperties
指定一个全局的熔断方法
Hystric服务降级小结:
- 引入Hystrix依赖
- 修改配置文件(这里没做修改)
- 在引导类上添加注解
@EnableCircuitBreaker
- 定义熔断方法
4.1 局部熔断方法:要和被熔断的方法返回值列表一致
4.2 全局熔断方法:返回值类型要和被熔断的方法一致- 声明被熔断的方法
@HystrixCommand(fallbackMethod="局部熔断方法名")
- 在类上添加注解
@DefaultProperties(defaultFallback = "全局熔断方法名")
2.2.3 设置熔断超时
在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystix的默认超时时长为1,我们可以通过在服务调用方的配置文件中配置修改这个值:
我们可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
来设置Hystrix超时时间。该配置没有提示。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms
2.3 Hystrix 服务熔断
熔断状态机3个状态:
- Closed:关闭状态,所有请求都正常访问。
- Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
- Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时
三、声明式调用 Feign
为什么叫伪装?
Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。
3.1. 改造服务消费者
- 添加 Feign 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 在引导类上添加注解
//@SpringBootApplication
//@EnableDiscoveryClient //开启服务注册中心 Eureka
//@EnableCircuitBreaker //开启熔断器服务 Hystrix
@SpringCloudApplication //组合注解,相当于 @SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker
@EnableFeignClients // 开启feign客户端
public class ConsumerApplication {
/* 使用RestTemplate做远程调用方法 */
/*
@Bean
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
*/
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
System.out.println("调用方启动成功!!");
}
}
- 新建 Feign 接口
Feign 接口习惯以 client 结尾,并声明是一个Feign 接口,指定对应的微服务名称
。声明被调用的方法(全局路径需要在每个类上添加)。
@FeignClient("service-provider") //声明这是一个Feign接口,并指明微服务的名称
public interface BookClient {
@GetMapping("/book/{id}")
public String queryBookById(@RequestParam("id") int id);
}
- 修改 Controller 类
@Controller
@RequestMapping("consumer/book")
//@DefaultProperties(defaultFallback = "hystrixMethod") //定义全局的熔断方法
public class BookController {
@Autowired
private BookClient bookClient;
public String queryBookById(@RequestParam("id") int id){
return this.bookClient.queryBookById(id);
}
}
- 访问
3.2 声明式调用如何集成熔断器?
- feign 中集成了熔断 Robbin ,但是默认是关闭的,需要自己进行手动开启。
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
- 需要在 Client 中定义方法,需要熔断哪个方法就实现哪个方法。
@Component
public class BookClientHystrix implements BookClient{
@Override
public String queryBookById(int id) {
return "服务器正忙,请稍后再试!!!";
}
}
- 对 Feign 接口添加熔断类
@FeignClient(value = "service-provider", fallback = BookClientHystrix.class) //声明这是一个Feign接口,并指明微服务的名称
public interface BookClient {
@GetMapping("/book/{id}")
public String queryBookById(@RequestParam("id") int id);
}
四、网关路由 Zuul(Zuul的四种路由)
Zuul为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
路由:分发给不同的微服务(服务名)
负载均衡:同一个微服务,不同的实体类
4.1 快速入门(通过路径进行访问)
- 构建SpringCloud项目
- 修改 zuul 网关的配置文件 application.yml
server:
port: 10000
spring:
application:
name: xxacker-zuul
zuul:
routes:
service-provider: #路由名称,可以随便写,习惯上写服务名
path: /service-provider/** #此处为路由到服务的提供方,也可以路由到服务的消费方
url: http://localhost:8190 #服务提供方的地址
- 为引导类添加注解
@SpringBootApplication
@EnableZuulProxy //启用zuul网关组件
public class XxackerZuulApplication {
public static void main(String[] args) {
SpringApplication.run(XxackerZuulApplication.class, args);
System.out.println("Zuul启动成功!!!");
}
}
- 启动zuul网关项目,并进行访问
4.2 面向服务的路由(通过服务Id进行访问)
在原先路由规则中,路由的路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然就不合理了。应该根据服务的名称,去Eureka注册中心查找服务对应的所有实例列表,然后进行动态路由
!
对 xxackerZuul 服务进行优化:
- 添加 Eureka 的客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 添加 Eureka配置,修改路由的方式,获取服务信息
zuul:
routes:
service-provider: #路由名称,可以随便写,习惯上写服务名
path: /service-provider/** #此处为路由到服务的提供方,也可以路由到服务的消费方
#url: http://localhost:8190 #服务提供方的地址
serviceId: service-provider #服务名
eureka:
client:
registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
service-url:
defaultZone: http://127.0.0.1:10086/eureka
- 开启Eureka客户端发现功能
@SpringBootApplication
@EnableZuulProxy //启用zuul网关组件
@EnableDiscoveryClient //启用 Eureka 的客户端
public class XxackerZuulApplication {
public static void main(String[] args) {
SpringApplication.run(XxackerZuulApplication.class, args);
System.out.println("Zuul启动成功!!!");
}
}
- 访问
4.3 简化路由配置
zuul:
routes:
service-provider: /service-provider/** #路由名称,可以随便写,习惯上写服务名
#path: /service-provider/** #此处为路由到服务的提供方,也可以路由到服务的消费方
#url: http://localhost:8190 #服务提供方的地址
#serviceId: service-provider #服务名
4.4 默认的路由规则
在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:
- 默认情况下,一切服务的映射路径就是服务名本身。例如服务名为:
service-provider
,则默认的映射路径就 是:/service-provider/**
也就是说,刚才的映射规则我们完全不配置也是OK的,不信就试试看。
添加路由前缀:
zuul:
routes:
service-provider: /service-provider/**
service-consumer: /service-consumer/**
prefix: /api # 添加路由前缀
4.5 Zuul过滤器
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。
4.5.1 ZuulFilter
ZuulFilter是过滤器的顶级父类。其中定义的4个最重要的方法:
public abstract ZuulFilter implements IZuulFilter{
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();// 来自IZuulFilter
Object run() throws ZuulException;// IZuulFilter
}
-
shouldFilter
:返回一个Boolean
值,判断该过滤器是否需要执行。返回true执行,返回false不执行。 -
run
:过滤器的具体业务逻辑。 filterType
:返回字符串,代表过滤器的类型。包含以下4种:
-
pre
:请求在被路由之前执行 -
route
:在路由请求时调用 -
post
:在route和errror过滤器之后调用 -
error
:处理请求时发生错误调用
-
filterOrder
:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
4.5.2 自定义过滤器
模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。
- 定义过滤器类
- 内容
@Component
public class LoginFilter extends ZuulFilter {
/*过滤器的类型: pre route post error*/
@Override
public String filterType() {
return "pre";
}
/*执行顺序:返回值越小,优先级越高*/
@Override
public int filterOrder() {
return 5;
}
/*是否执行该过滤器:true:执行run;false:不执行run*/
@Override
public boolean shouldFilter() {
return true;
}
/*编写过滤器的业务逻辑*/
@Override
public Object run() throws ZuulException {
//初始化context上下文对象,servlet spring
RequestContext context = RequestContext.getCurrentContext();
//获取request对象
HttpServletRequest request = context.getRequest();
//获取参数
String token = request.getParameter("token");
if(StringUtils.isBlank(token)){
//拦截
context.setSendZuulResponse(false); //是否转发请求,false:表示不转发请求
context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED); //401-身份未认证 设置相应状态码
context.setResponseBody("Request Error ! ! !"); //设置相应的提示
}
// 校验通过,把登陆信息放入上下文信息,继续向后执行
context.set("token", token);
/*返回null,表示过滤器什么都不做*/
return null;
}
}
- 访问