1.Ribbon客户端负载均衡
1.1 为什么要使用ribbon
问题:上一篇文章我们提到user(用户服务)和order(订单服务),order去访问user,如果user做了集群(复制),那么user就有多个服务(服务名相同,ip,端口 和服务实例id不同),order该访问哪个user服务呢?
1.2 ribbon概念
ribbon是SpringCloud中的客户端负载均衡器,它有随机,轮询,权重等负载均衡算法。
也就是说ribbon可以帮order选择一个user进行访问
1.3 ribbon 负载均衡算法
ribbon的负载均衡算法有7种,分别是随机,轮询等
1.4 ribbon 配置负载均衡算法
ribbon可以对所有服务进行统一配置负载均衡算法;也可以对某一个服务单独配置负载均衡算法;
配置负载均衡算法有注解和yml两种方式
1.4.1
使用注解对所有服务配置负载均衡算法(这里以”随机”均衡算法为例)
在启动类中进行配置:
//负载均衡算法
@Bean
public RandomRule randomRule(){
return new RandomRule();
}
1.4.2 使用yml对某一个服务配置负载均衡算法
user-server: #这里是某个服务的服务名
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
1.4.3 使用yml对所有的服务配置负载均衡算法
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
2.Ribbon的调优
2.1 ribbon的超时配置
ribbon:
ReadTimeout: 3000 #读取超时时间
ConnectTimeout: 3000 #链接超时时间
MaxAutoRetries: 1 #重试机制:同一台实例最大重试次数
MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数
OkToRetryOnAllOperations: false #是否所有操作都重试,因为针对post请求如果没做幂等处理可能会造成数据多次添加/修改
对具体的服务进行超时配置:如"<服务名>.ribbon..."
2.2 ribbon的饥饿加载
Ribbon在进行客户端负载均衡的时候并不是启动时就创建好的,而是在实际请求的时候才会去创建,所以往往我们在发起第一次调用的时候会出现超时导致服务调用失败,我们可以通过设置Ribbon的饥饿加载来改善此情况
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: user-server #针对于哪些服务需要饥饿加载
3. Feign(客户端负载均衡)
3.1 引入
当我们使用restTemplate去调用时
restTemplate.getForObject(“url地址”);
所需要的参数须在请求的URL中进行拼接 ,而Feign可以简化url拼接
4. OpenFeign(客户端负载均衡)
OpenFeign是基于Feign的,它支持SpringMvc的注解,如@RequestMapping等等。
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务
4.1 OpenFeign的实操
4.1.1 导入依赖
<!--2.导入Feign的包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
4.1.2 编写启动类,并在启动类上加上 @EnableFeignClients
@SpringBootApplication
@EnableFeignClients
public class PayApp
{
public static void main( String[] args )
{
SpringApplication.run(PayApp.class,args);
}
}
4.1.3 写一个Feign接口
/**
* @FeignClient("springCloud-user") --访问服务的服务名
*/
@FeignClient("springCloud-user")
public interface UserClient {
//和访问的服务的controller中的方法一致
@GetMapping("/user/{id}")
User getUsers(@PathVariable("id") Long id);
}
4.1.4 编写controller,注入Feign接口,并调用Feign方法
@RestController
public class PayController {
@Autowired
private UserClient userClient;
@GetMapping("/pay/{id}")
public User getUsers(@PathVariable("id") Long id){
return userClient.getUsers(id);
}
}
5.OpenFeign整合Hystrix
Hystrix是一个熔断器,解决调用服务请求发生的故障问题
Hystrix是为了防止单个服务故障导致整个服务瘫痪(雪崩效应)
5.1 设计原则
5.1.1 快速失败:
概念:服务出现熔断状态,请求会失败,并且线程不会等待。
5.1.2 服务降级:
服务降级后,会返回设置好的数据
5.1.3 熔断机制:
当多次请求访问服务失败时,服务会编辑为熔断状态
5.1.4 服务监控:
过一段时间,当请求来的时候,会判断该请求是否可以正常访问该服务,如果可以则删除服务的熔断状态
总结:当多次请求访问服务失败时,服务会编辑为熔断状态,请求访问该服务会导致服务快速失败,并且进行服务降级,并且返回设置好的数据;过一段时间,当请求来的时候,会判断该请求是否可以正常访问该服务,如果可以则删除服务的熔断状态
5.2 Hustrix 的资源隔离
5.2.1 线程池:
异步
线程数量=线程池+队列
5.2.2 信息量
同步
线程数量=计数器定义的数量
计数器实现:请求来就加1,请求完成就-1;
总结:Hustrix 的线程池相对于信息量可以更好的处理线程的高并发问题,因为有缓冲区;
当请求达到最大的线程量的时候,会快速失败,服务降级
5.3 Hustrix 实战(OpenFeign整合hystrix)
5.3.1 配置yml
#开启hystrix
feign:
hystrix:
enabled: true
5.3.2 在@FeignClient注解的接口,添加fallbackFactory = UserClientFallbackFactory.class
UserClientFallbackFactory--是自定义的降级类
@FeignClient(value = "springCloud-user",fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User getUsers(@PathVariable("id") Long id);
}
5.3.3 创建UserClientFallbackFactory,实现FallbackFactory接口;重写creat方法,返回@FeignClient注解的接口对象
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
//匿名内部类
return new UserClient() {
@Override
public User getUsers(Long id) {
//打印报错信息
throwable.printStackTrace();
//返回该请求失败后,设置好的数据
return new User(2L,"系统繁忙",3);
}
};
}
}
备注:该降级类需要@Component注解--交给Spring的ioc容器
6. zuul(服务网关)
6.1 概念:
所有服务请求的入口(所有的请求都需要通过zuul将请求分发到其他微服务 )
注意:服务与服务之间的请求调用不会经过zuul
6.2 zuul实操
注意:zuul集成了ribbon,hystrix;zuul需要交给eureka进行注册
6.2.1 新建一个项目,并且配置好服务注册的基本配置
6.2.2 导入zuul的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
6.2.3 编写启动类,启动类加上@EnableZuulProxy
备注:@EnableZuulProxy是开启ruul
@SpringBootApplication
@EnableZuulProxy
public class ZuulApp
{
public static void main( String[] args )
{
SpringApplication.run(ZuulApp.class,args);
}
}
6.2.4 配置yml
注册到服务中心的标准配置(图6.1);ruul的额外配置(图6.2)
图6.1
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1001/eureka/
instance:
instance-id: springCloud-zuul
server:
port: 10011
spring:
application:
name: springCloud-zuul
图6.2
zuul:
ignored-services: "*" #禁止通过服务名直接访问
prefix: "/services" #访问zuul时加上前缀
routes:
"springCloud-user": "/user/**" #给访问的服务配置别名
"springCloud-pay": "/pay/**" #给访问的服务配置别名
6.2.5 :启动ruul项目:
通过ruul去访问支付服务: http://localhost:10011/services/pay/支付的controller接口
备注:services--访问ruul的前缀,提高安全
pay--通过ruul访问支付服务的服务名的别名--提高安全
6.3 ruul的Filter
ruul的底层是Filter实现的,如图6.3.1
图6.3.1
6.3.1 ruul的执行原理
请求来的时候,先经过前缀(pre)过滤器;然后再经过路由(rote)过滤器;调用服务请求执行成功后,在经过后缀(post)过滤器;
如果pre,rote执行的过程中出现错误;会执行erro Filter,再执行postFilter,返回响应;
如果erro执行的过程中出现错误;会执行postFilter,返回响应;
如果postFiltero执行的过程中出现错误,会会执行erro Filter,直接返回响应;
6.3.2 ruul自定义Filter(这里以登录检查为例)
自定义Filter步骤:
自定义一个filter类,继承ZuulFilter;重写4个方法
1.设置filter类型
2.设置filter级别
3.判断该请求是否是登录,是则放行;否则被拦截,执行run方法
4.执行run方法:判断token信息,不存在则返回一个json数据给前端;
并且设置不执行zuul后面的过滤: currentContext.setSendZuulResponse(false);
注意:自定义的filter类要在类上加上@Component,交给Spring的ioc容器去管理
备注:可以用postman测试
package com.lh.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class LoginZuulFilter extends ZuulFilter {
//设置过滤器类型为前缀过滤器
@Override
public String filterType() {
return "pre";
}
//过滤级别
@Override
public int filterOrder() {
return 0;
}
//是否应该执行run方法;return true表示要执行--相当于登录拦截的配置类
@Override
public boolean shouldFilter() {
//获取request请求对象
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
//通过request对象去获取请求地址
String url = request.getRequestURI();
//判断该请求是否是登录请求;如果是则执行该pre过滤器自定义的run方法
//如果不是登录请求会跳过该pre过滤器,执行之后的过滤器
if (!url.contains("/login")){
return true;
}
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
//获取请求头token
String token = request.getHeader("token");
HttpServletResponse response = currentContext.getResponse();
response.setCharacterEncoding("UTF-8");
// response.setContentType("application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
//如果token为null;则返回一个错误信息给前端
if (token==null){
try {
currentContext.setSendZuulResponse(false);
response.getWriter().print("请重新登录");
} catch (Exception e) {
e.printStackTrace();
}
}
//返回null,表示该过滤器执行完毕
return null;
}
}
每日面试:
-
OpenFeign的工作原理
-
OpenFiegn和Ribbon的区别
-
Hystrix的功能有哪些
-
信号量和线程池隔离的区别
-
Hystrix的熔断机制
-
zuul的执行流程
-
请求来的时候,先经过前缀(pre)过滤器;然后再经过路由(rote)过滤器;调用服务请求执行成功后,在经过后缀(post)过滤器; 如果pre,rote执行的过程中出现错误;会执行erro Filter,再执行postFilter,返回响应; 如果erro执行的过程中出现错误;会执行postFilter,返回响应; 如果postFiltero执行的过程中出现错误,会会执行erro Filter,直接返回响应;
-
zuul如何定义filter自定义一个filter类,继承ZuulFilter;重写4个方法
-
1.设置filter类型
2.设置filter级别
3.判断该请求是否是登录,是则放行;否则被拦截,执行run方法
4.执行run方法:判断token信息,不存在则返回一个json数据给前端;
并且设置不执行zuul后面的过滤: currentContext.setSendZuulResponse(false); -
zuul如何匹配请求
-
zuul默认集成了ribbon,zuul通过Ribbon将客户端的请求分发到下游的微服务; 客户端的请求当通过route Filter的时候, 其中有一个 RibbonZuulFilter 可以通过负载均衡算法来请求分发