• SPRING CLOUD微服务DEMO-下篇


    1 Hystix

    1.1 简介

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

    我感觉难以解释清楚,鉴于接下来的demo项目基本不会用这个模块,就过一遍代码算了。

    1.2 配置并测试

    模拟一下熔断:一旦请求的接口超过1s没有响应就不再继续请求,开始执行实现定义好的回滚函数,返回一个提示信息。
    注意:Ribbon的重试机制和Hystrix的熔断机制有一定关系。

    1.2.1 引入依赖

    往user-consume即服务调用者的pom文件里面添加hystrix依赖。

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

    1.2.2 开启熔断

    在UserConsumeApplication中添加@EnableCircuitBreaker

    可以看到注解有点多了,可以用@SpringCloudApplication代替上面三个注解

    //@EnableDiscoveryClient // 开启EurekaClient功能
    //@EnableCircuitBreaker  //开启熔断
    //@SpringBootApplication
    @SpringCloudApplication   //三合一注解
    

    1.2.3 改造consume

    改造consume即消费者的调用方法,记录调用接口耗费的时间,编写调用超时的回滚方法。

    @HystrixCommand(fallbackMethod="queryUserByIdFallback"):声明一个失败回滚处理函数queryUserByIdFallback,当queryUserById执行超时(默认是1000毫秒),就会执行fallback函数,返回错误提示。

    @Component
    public class UserDao {
        @Autowired
        private RestTemplate restTemplate;
    
        private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
    
        @HystrixCommand(fallbackMethod = "queryUserByIdFallback")
        public User queryUserById(Long id){
            long begin = System.currentTimeMillis();
            String url = "http://user-service/user/" + id;
            User user = this.restTemplate.getForObject(url, User.class);
            long end = System.currentTimeMillis();
            // 记录访问用时:
            logger.info("访问用时:{}", end - begin);
            return user;
        }
    
        public User queryUserByIdFallback(Long id){
            User user = new User();
            user.setId(id);
            user.setUsername("用户信息查询出现异常!");
            return user;
        }
    }
    
    @Service
    public class UserService {
        @Autowired
        private UserDao userDao;
    
        public List<User> queryUserByIds(List<Long> ids) {
            List<User> users = new ArrayList<>();
            ids.forEach(id -> {
                // 我们测试多次查询,
                users.add(this.userDao.queryUserById(id));
            });
            return users;
        }
    }
    

    1.2.4 改造service

    即改造服务提供者,将方法随机延迟一段时间,模拟超时。

    @Service
    public class UserService {
        @Autowired
        private UserMapper userMapper;
    
        public User queryById(Long id)  throws InterruptedException {
            // 为了演示超时现象,我们在这里然线程休眠,时间随机 0~2000毫秒
            Thread.sleep(new Random().nextInt(2000));
            return this.userMapper.selectByPrimaryKey(id);
        }
    }
    
    

    1.2.5 结果

    很明显,超过1000ms的请求被回滚方法处理掉了。

    1.2.6 设置Hystrix超时时间

    之前我们设置的Robbin的ReadTimeout是1000ms,即请求一个接口超过1000ms未响应就请求另一个有相同功能的接口而Hystrix的超时时间默认也是1000ms,观察现象可知,先触发了熔断。但这样是不合理的,明明可以请求另一个接口得到结果,结果触发了熔断,返回的是找不到结果。
    所以注意一点:Hystrix的超时时间一定要大于Robbin的重试时间。

    hystrix:
      command:
      	default:
            execution:
              isolation:
                thread:
                  timeoutInMillisecond: 6000 # 设置hystrix的超时时间为6000ms
    

    2. Feign

    2.1 简介

    Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

    2.2 使用Feign

    Feign的引入和配置全部都在consume中进行

    2.2.1 引入依赖

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

    2.2.2 UserConsumeApplication添加注解

    @EnableFeignClients
    

    2.2.3 编写UserClient接口

    在项目中添加client包,下面新建UserClient接口

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

    2.2.4 使用UserClient请求数据

    改造UserService,改为使用UserClient请求数据

    @Service
    public class UserService {
        //@Autowired
        //private UserDao userDao;
    
        @Autowired
        private UserClient userClient;    //注入UserClient
    
        public List<User> queryUserByIds(List<Long> ids) {
            List<User> users = new ArrayList<>();
            ids.forEach(id -> {
                //使用UserClient请求接口
                users.add(userClient.queryUserById(id));
                //users.add(this.userDao.queryUserById(id));
            });
            return users;
        }
    }
    

    2.3 负载均衡

    Feign是对请求的封装,本身集成了Ribbon负载均衡。可以对其进行配置,和单独使用Robbin的写法一样

    user-service:
      ribbon:
        ConnectTimeout: 250 # 连接超时时间(ms)
        ReadTimeout: 1000 # 通信超时时间(ms)
        OkToRetryOnAllOperations: true # 是否对所有操作重试
        MaxAutoRetriesNextServer: 1 # 同一服务不同实例的重试次数
        MaxAutoRetries: 1 # 同一实例的重试次数
    

    2.4 Hystrix支持

    Feign也集成了Hystrix。

    需要配置以开启Hystrix

    feign:
      hystrix:
        enabled: true # 开启Feign的熔断功能
    

    回滚方法不像之前那样写了,要专门定义一个类。

    @Component
    public class UserFeignClientFallback implements UserClient {
        @Override
        public User queryUserById(Long id) {
            User user = new User();
            user.setId(id);
            user.setUsername("用户查询出现异常");
            return user;
        }
    }
    

    在UserClient接口上配置回滚类

    @FeignClient(value = "user-service", fallback = UserFeignClientFallback.class)
    public interface UserClient {
        @GetMapping("/user/{id}")
        User queryUserById(@PathVariable("id") Long id);
    }
    

    2.5.请求压缩

    Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:

    feign:
      compression:
        request:
          enabled: true # 开启请求压缩
        response:
          enabled: true # 开启响应压缩
    

    同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:

    feign:
      compression:
        request:
          enabled: true # 开启请求压缩
          mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
          min-request-size: 2048 # 设置触发压缩的大小下限
    

    注:上面的数据类型、压缩大小下限均为默认值。

    3. Zuul网关

    3.1 简介

    Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon和Hystrix等组件配合使用。核心是一系列的过滤器,完成身份认证与权限管理动态路由、压力测试等功能。

    工作流程如下所示

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

    3.2 搭建

    3.2.1 建一个新的module

    其他步骤前面已经说过,选择模块时注意选择Zuul即可。

    3.2.2 引入依赖

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    

    3.2.3 添加注解

    @SpringBootApplication
    @EnableZuulProxy        //开启zuul网关
    @EnableDiscoveryClient  //开启Eureka客户端发现功能
    public class ZuulApplication {
        public static void main(String[] args) {
            SpringApplication.run(ZuulApplication.class, args);
        }
    }
    

    3.3 路由功能

    测试之前先把之前添加的user-service的随机延迟给注释掉

    Zuul的路由功能:

    • 配置
    server:
      port: 10010 #服务端口
    spring:
      application:
        name: api-gateway #指定服务名
    
    eureka:
      client:
        registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
        service-url:
          defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
      instance:
        prefer-ip-address: true
        ip-address: 127.0.0.1
    
    zuul:
      prefix: /api # 添加路由前缀
      routes:
        user-service: # 路由id,一般与服务名相同
          path: /user-service/** # 映射路径
          serviceId: user-service
    
    
    • 解释:Zuul从Eureka中拉取服务列表,如果有人请求/api/user-service/**,将请求转发给服务列表中的

      user-service服务。

    坑爹的一点:之前测试的时候Zuul一直报找不到user-service服务的错误,找了一段时间后来自己好了。

    没有再复现出来,不知道为啥。如果报错了,可以尝试着用maven的reimport,再等等试试。

    • 由于路由id一般与服务名相同,因此zuul提供了一种简化配置,与上面实现相同功能
    zuul:
      prefix: /api # 添加路由前缀
      routes:
        user-service: /user-service/** # 这里是映射路径
    

    访问http://localhost:10010/api/user-service/user/20即可得到结果。

    3.4 过滤功能

    3.4.1 ZuulFilter

    ZuulFilter是过滤器的顶级父类,我们自己实现的过滤器都要继承自它。我们看看其中重要的四个方法:

    public abstract ZuulFilter implements IZuulFilter{
        abstract public String filterType();
        abstract public int filterOrder();
        boolean shouldFilter();
        Object run() throws ZuulException;
    }
    
    • shouldFilter:返回布尔值,判断过滤器是否需要执行。true表示执行,false反之。
    • run:表示具体的过滤逻辑。
    • filterType:返回字符串,表示本过滤器的类型
      • pre:请求在被路由之前执行。
      • routing:在路由请求时调用
      • post:在routing和errror过滤器之后调用
      • error:处理请求时发生错误调用
    • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

    3.4.2 生命周期

    • 正常流程:
      • 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
    • 异常流程:
      • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给post过滤器,最后返回给用户。
      • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
      • 如果是post过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达post过滤器了。

    3.4.3 使用场景

    • 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
    • 异常处理:一般会在error类型和post类型过滤器中结合来处理。
    • 服务调用时长统计:pre和post结合使用。

    3.4.4 模拟登录校验

    添加登录过滤器

    @Component
    public class LoginFilter extends ZuulFilter {
        @Override
        public String filterType() {
            // 登录校验,肯定是在前置拦截
            return "pre";
        }
        @Override
        public int filterOrder() {
            // 顺序设置为1
            return 1;
        }
        @Override
        public boolean shouldFilter() {
            // 返回true,代表过滤器生效。
            return true;
        }
        @Override
        public Object run() {
            // 登录校验逻辑。
            // 1)获取Zuul提供的请求上下文对象
            RequestContext ctx = RequestContext.getCurrentContext();
            // 2) 从上下文中获取request对象
            HttpServletRequest req = ctx.getRequest();
            // 3) 从请求中获取token
            String token = req.getParameter("access-token");
            // 4) 判断
            if(token == null || "".equals(token.trim())){
                // 没有token,登录校验失败,拦截
                ctx.setSendZuulResponse(false);
                // 返回401状态码。也可以考虑重定向到登录页。
                ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            }
            // 校验通过,可以考虑把用户信息放入上下文,继续向后执行
            return null;
        }
    }
    

    3.5 负载均衡和熔断功能

    Zuul内部集成了Ribbon负载均衡和Hystrix熔断器,需要配置

    zuul:
      retryable: true
    ribbon:
      ConnectTimeout: 250 # 连接超时时间(ms)
      ReadTimeout: 2000 # 通信超时时间(ms)
      OkToRetryOnAllOperations: true # 是否对所有操作重试
      MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
      MaxAutoRetries: 1 # 同一实例的重试次数
    hystrix:
      command:
      	default:
            execution:
              isolation:
                thread:
                  timeoutInMillisecond: 6000 # 熔断超时时长:6000ms
    

    总结

    一直在想着如何配置,如何实现,容易犯一叶障目不见泰山的毛病,下面就概括一下。

    Eureka 注册中心

    服务提供者在里面注册,服务消费者拉取服务列表,根据服务列表去发请求并获取数据。

    这其中涉及到最重要的一个问题:如何保证服务列表里面的服务是有效的

    • 对于提供者,这个问题是:你怎么才能知道我什么时候可用,什么时候不可用?

    Eureka和提供者就形成了约定:提供者准备好后在Eureka里面注册,Eureka知道提供者可用了。注册后提供者每隔一段时间向Eureka发个消息说我还活着,Eureka就知道提供者依旧可用。Eureka每隔一段时间就会扫描整个服务列表,把很长时间没报告过的服务剔除出去。这样注册中心的服务列表就能始终更新,始终可用。

    • 对于消费者,这个问题是:我怎么知道你不行了?

    消费者每隔一段时间就从注册中心拉取服务列表,注册中心和服务提供者的约定保证了这个列表可以信任,我就根据这个列表发出请求,得到数据。

    注意上面的三个时间段,这都是可以配置的,怎么配置就根据实际情况来了。

    Robbin 负载均衡

    服务的消费者在拉取服务列表后,针对同一业务可能有多个可选的服务提供者。

    如果一直使用其中一个,那就是传说中的"一核有难,八核围观",表现差劲,浪费资源,这就需要负载均衡了。

    Ribbon提供了诸如随机、轮询等多种策略可选。

    它还提供了重试机制:选择了一个服务后,如果这个服务坏了(毕竟每隔一段时间才拉取服务列表,服务列表也是每隔一段时间才更新),或者说反应太慢,到了一定时间就考虑再换一个服务。

    Hystrix 熔断机制

    为了避免一个模块的错误拖垮整个系统,因此将其隔离起来。

    不要让一颗老鼠屎坏了一锅汤?这个机制我不太能讲清楚。

    但是注意它和Ribbon的联系,一个服务没有反应,应该先用Ribbon进行重试,而不是先熔断。然后这又引出来一个问题,一个错误如果每次都被重试机制掩盖了,那系统还会被拖垮吗?还是说这个业务的服务都坏了,试来试去找不到一个好用的,才触发熔断?想不清楚,真遇到了再说吧。

    Feign 请求封装

    封装了RestTemplate,让请求的编码更简单。

    请求必然涉及负载均衡和熔断问题,所以Feign里面也可以配置Robbin和Hystrix。

    但不光如此,Feign还提供了请求压缩等小功能。

    Zuul 网关

    微服务提供者的接口是暴露在外的,要是谁都能用那就乱套了。

    就像学校的器材室有各种器材,但是谁都能自由取用就乱了。因此我们需要一个管理器材的老师,拿器材的人来了,先判断他有没有老师的批准,再根据老师的批准决定他能拿什么器材,最后把他带到对应的器材室拿相应的器材。

    Zuul就像是这个老师,负责鉴权(判断一个人能不能拿器材,能拿什么器材)、路由(领到地方拿器材)。

    Zuul虽然不发请求,但是对请求有很强的干预能力,所以它也可以配置负载均衡和熔断。

  • 相关阅读:
    回调函数和表驱动法编程
    学会看datasheet W25Q128为例
    STM32 Makefile的一次bug解决过程
    STM32 一种参数检查用法介绍
    STM32 中断和事件
    STM32 OV2640将数据缓存至SRAM
    STM32 .ld链接文件分析及一次bug解决过程
    浅谈嵌入式软件设计
    STM32 Makefile的设置和工程管理
    [转]Linux下的lds链接脚本详解
  • 原文地址:https://www.cnblogs.com/qianbixin/p/10936458.html
Copyright © 2020-2023  润新知