springCloud

1.Hystrix

Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应

1.1.线程隔离,服务降级

Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。

用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理。

降级处理:优先保证核心服务,而非核心服务不可用或弱可用

用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。

服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。

触发Hystix服务降级的情况:

  • 线程池已满
  • 请求超时

1.2.使用Hystrix

1.2.1.引入Hystrix依赖 (下面所有的代码基于上一篇:spirngCloud-Eureka写的)
链接地址:https://www.cnblogs.com/cqyp/p/13284109.html
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
1.2.2.开启熔断
@SpringBootApplication
@EnableDiscoveryClient //启动客户端
@EnableCircuitBreaker   //开启熔断
public class ServiceConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceConsumerApplication.class,args);
    }
}

注: 这三个注解可以由一个组合注解代替:@springCloudApplication。

1.2.3.使用熔断

  • 在对应的方法上加@HystrixCommand 注解,为熔断方法
  • 定义熔断的方法
@Controller
@RequestMapping("consumer/user")
public class UserController {
    @Autowired
    private UserClient userClient;
    @GetMapping
    @ResponseBody
    @HystrixCommand(fallbackMethod = "queryUserByIdFallBack") //声明熔断方法
    public String queryUserById(@RequestParam("id") Integer id) {
        return this.userClient.queryUserById(id).toString();
    }

     //定义熔断方法,和被熔断方法的返回值一样
    public String queryUserByIdFallBack() {
        return "服务正忙,请稍后再试";

    }
  }

定义全局熔断方法:每个方法熔断都会执行FallbackMethod此方法

@Controller
@RequestMapping("consumer/user")
@DefaultProperties(defaultFallback = "FallbackMethod")//定义全局熔断方法
public class UserController {
    @Autowired
    private UserClient userClient;
    @GetMapping
    @ResponseBody
    @HystrixCommand
    public String queryUserById(@RequestParam("id") Integer id) {
        return this.userClient.queryUserById(id).toString();
    }

     //定义熔断方法,和被熔断方法的返回值一样
    public String FallbackMethod() {
        return "服务正忙,请稍后再试";

    }
  }
1.2.4.设置超时时间
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms
1.2.5.熔断原理

熔断机制原理很简单,和家里的电路一样,发生短路保险会烧断,来保护电路;

熔断状态机3个状态:

  • Closed:关闭状态,所有请求都正常访问。
  • Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
  • Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时。

2.Feign

跨服务调用,使用方便

2.1.导入依赖:

service-consumer:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.2.开启Feign功能

@SpringBootApplication
@EnableDiscoveryClient //启动客户端
@EnableFeignClients  //启用Feign组件代替RestTemplate远程调用 :其中包含熔断和负载均衡
public class ServiceConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceConsumerApplication.class,args);
    }
}

2.3.Feign调用的接口

  • 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像
  • @FeignClient,声明这是一个Feign客户端,类似@Mapper注解。同时通过value属性指定服务名称
  • 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果
@FeignClient(value = "service-provider") // 标注该类是一个feign接口
public interface UserClient {

    @GetMapping("user/{id}")     //和你需要调用的另一个服务的方法的路径一致
    User queryById(@PathVariable("id") Long id);
}

2.4.负载均衡

Feign中本身已经集成了Ribbon依赖和自动配置:

2.5.Hystrix支持

Feign默认也有对Hystrix的集成:

  • 开启Feign的熔断功能
feign:
  hystrix:
    enabled: true # 开启Feign的熔断功能
  • 编写一个类,定义熔断方法,实现Feigin调用的接口
@Component
public class UserClientFallback implements UserClient {

    @Override
    public User queryById(Long id) {
        User user = new User();
        user.setUserName("服务器繁忙,请稍后再试!");
        return user;
    }
}
  • 然后在接口的注解中指定该类为熔断类
@FeignClient(value = "service-provider", fallback = UserClientFallback.class) // 标注该类是一个feign接口
public interface UserClient {

    @GetMapping("user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}

3.Zuul网关

作用:不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。

1.快速入门

1.1.编写配置
server:
  port: 10010 #服务端口
spring:
  application:
    name: api-gateway #指定服务名
1.2.编写引导类

通过@EnableZuulProxy 注解开启Zuul的功能:

@SpringBootApplication
@EnableZuulProxy // 开启网关功能
public class ItcastZuulApplication {
public static void main(String[] args) {
    SpringApplication.run(ItcastZuulApplication.class, args);
}
}
1.3.编写路由规则

Zuul来代理service-provider服务和service-customer服务,

映射规则:

server:
  port: 10010 #服务端口
spring:
  application:
    name: api-gateway #指定服务名
zuul:
  routes:
  prefix: /api         #前缀
     service-provide: /user/**      #路由的名称,一般为服务名
     service-consumer: /consumer/**

path: http://127.0.0.1:10010/api/user/**

2.过滤器

Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。

2.1.ZuulFilter
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值来定义过滤器的执行顺序,数字越小优先级越高。

    2.2.自定义过滤器
@Component
public class LoginFilter extends ZuulFilter {
    /**
     * 过滤器类型,前置过滤器
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 过滤器的执行顺序
     * @return
     */
    @Override
    public int filterOrder() {
        return 1;
    }

    /**
     * 该过滤器是否生效
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 登陆校验逻辑
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        // 获取zuul提供的上下文对象
        RequestContext context = RequestContext.getCurrentContext();
        // 从上下文对象中获取请求对象
        HttpServletRequest request = context.getRequest();
        // 获取token信息
        String token = request.getParameter("access-token");
        // 判断
        if (StringUtils.isBlank(token)) {
            // 过滤该请求,不对其进行路由
            context.setSendZuulResponse(false);
            // 设置响应状态码,401
            context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
            // 设置响应信息
            context.setResponseBody("{\"status\":\"401\", \"text\":\"request error!\"}");
        }
        // 校验通过,把登陆信息放入上下文信息,继续向后执行
        context.set("token", token);
        return null;
    }
}
posted @ 2020-07-13 09:07  撑起一片阳光  阅读(174)  评论(0编辑  收藏  举报