微服务相关

SpringCloud

SpringCloud 核心组件

1.服务注册发现 - Netflix Eureka

1.基础架构

Eureka架构中的三个核心角色:

  • 服务注册中心

    Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-demo

  • 服务提供者

    提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的user-service

  • 服务消费者

    消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的consumer-demo

2.高可用的Eureka Server

Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。

服务同步

多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

我们假设要搭建两条EurekaServer的集群,端口分别为:10086和10087

1)我们修改原来的EurekaServer配置:

server:
  port: 10086 # 端口
spring:
  application:
    name: eureka-server # 应用名称,会在Eureka中显示
eureka:
  client:
    service-url: # 配置其他Eureka服务的地址,而不是自己,比如10087
      defaultZone: http://127.0.0.1:10087/eureka

所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务进行注册,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:

  • 删除了register-with-eureka=false和fetch-registry=false两个配置。因为默认值是true,这样就会吧自己注册到注册中心了。
  • 把service-url的值改成了另外一台EurekaServer的地址,而不是自己

2)另外一台配置恰好相反:

server:
  port: 10087 # 端口
spring:
  application:
    name: eureka-server # 应用名称,会在Eureka中显示
eureka:
  client:
    service-url: # 配置其他Eureka服务的地址,而不是自己,比如10087
      defaultZone: http://127.0.0.1:10086/eureka

注意:idea中一个应用不能启动两次,我们需要重新配置一个启动器:

3)启动测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5O20XxOn-1577005458023)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day02/assets/1525615165157.png)]

4)客户端注册服务到集群

因为EurekaServer不止一个,因此注册服务的时候,service-url参数需要变化:

eureka:
  client:
    service-url: # EurekaServer地址,多个地址以','隔开
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
3.服务提供者

服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。

服务注册

服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-erueka=true参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。第一层Map的Key就是服务名称,第二层Map的key是服务的实例id。

服务续约

在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);

有两个重要参数可以修改服务续约的行为:

eureka:
  instance:
    lease-expiration-duration-in-seconds: 90
    lease-renewal-interval-in-seconds: 30
  • lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
  • lease-expiration-duration-in-seconds:服务失效时间,默认值90秒

也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。

但是在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。

eureka:
  instance:
    lease-expiration-duration-in-seconds: 10 # 10秒即过期
    lease-renewal-interval-in-seconds: 5 # 5秒一次心跳

实例id

先来看一下服务状态信息:

在Eureka监控页面,查看服务注册信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2UpznpTj-1577005458024)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day02/assets/1525617060656.png)]

在status一列中,显示以下信息:

  • UP(1):代表现在是启动了1个示例,没有集群
  • DESKTOP-2MVEC12:user-service:8081:是示例的名称(instance-id),
    • 默认格式是:${hostname} + ${spring.application.name} + ${server.port}
    • instance-id是区分同一服务的不同实例的唯一标准,因此不能重复。

我们可以通过instance-id属性来修改它的构成:

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}

重启服务再试试看:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A7fex3M0-1577005458025)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day02/assets/1525617542081.png)]

4.服务消费者

获取服务列表

当服务消费者启动是,会检测eureka.client.fetch-registry=true参数的值,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒会重新获取并更新数据。我们可以通过下面的参数来修改:

eureka:
  client:
    registry-fetch-interval-seconds: 5

生产环境中,我们不需要修改这个值。

但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。

5.失效剔除和自我保护

失效剔除

有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。

可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒,生成环境不要修改。

这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如10S

自我保护

我们关停一个服务,就会在Eureka面板看到一条警告:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ZWy7XVi-1577005458025)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day02/assets/1525618396076.png)]

这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:

eureka:
  server:
    enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
    eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)

2.配置中心 - spring cloud config

Spring Cloud Config 是一种用来动态获取Git、SVN、本地的配置文件的一种工具

1.添加依赖
<dependencies>
   	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-config</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
	</dependency>
</dependencies>

3.负载均衡-Netflix Ribbon

实际环境中,我们往往会开启很多个user-service的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?

一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。

不过Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。

什么是Ribbon:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-quRfLBga-1577005458025)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day02/assets/1525619257397.png)]

接下来,我们就来使用Ribbon实现负载均衡。

1.启动两个服务实例

首先我们启动两个user-service实例,一个8081,一个8082。

2.开启负载均衡

因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。直接修改代码:

在RestTemplate的配置方法上添加@LoadBalanced注解:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用:

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        // 地址直接写服务名称即可
        String baseUrl = "http://user-service/user/";
        ids.forEach(id -> {
            // 我们测试多次查询,
            users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
            // 每次间隔500毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return users;
    }
}
3.源码跟踪

为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。

LoadBalancerInterceptor帮我们根据service名称,获取到了服务实例的ip和端口。

我们进行源码跟踪:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1jJfq2wz-1577005458026)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day02/assets/1525620483637.png)]

继续跟入execute方法:发现获取了8082端口的服务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R42S5e55-1577005458026)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day02/assets/1525620787090.png)]

再跟下一次,发现获取的是8081:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p6wWF4Cs-1577005458026)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day02/assets/1525620835911.png)]

4.负载均衡策略

Ribbon默认的负载均衡策略是简单的轮询,我们可以测试一下:

编写测试类,在刚才的源码中我们看到拦截中是使用RibbonLoadBalanceClient来进行负载均衡的,其中有一个choose方法,是这样介绍的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IKVgC7Op-1577005458027)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day02/assets/1525622320277.png)]

现在这个就是负载均衡获取实例的方法。是轮询方式。

是否可以修改负载均衡的策略呢?

SpringBoot也帮我们提供了修改负载均衡规则的配置入口:

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式是:{服务名称}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的实现类。

5.重试机制

Eureka的服务治理强调了CAP原则中的AP,即可用性和可靠性。它与Zookeeper这一类强调CP(一致性,可靠性)的服务治理框架最大的区别在于:Eureka为了实现更高的服务可用性,牺牲了一定的一致性,极端情况下它宁愿接收故障实例也不愿丢掉健康实例,正如我们上面所说的自我保护机制。

但是,此时如果我们调用了这些不正常的服务,调用就会失败,从而导致其它服务不能正常工作!这显然不是我们愿意看到的。

我们现在关闭一个user-service实例,

因为服务剔除的延迟,consumer并不会立即得到最新的服务列表,此时再次访问你会得到错误提示:

但是此时,8081服务其实是正常的。

因此Spring Cloud 整合了Spring Retry 来增强RestTemplate的重试能力,当一次服务调用失败后,不会立即抛出一次,而是再次重试另一个服务。

只需要简单配置即可实现Ribbon的重试:

spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true # 开启Spring Cloud的重试功能
user-service:
  ribbon:
    ConnectTimeout: 250 # Ribbon的连接超时时间
    ReadTimeout: 1000 # Ribbon的数据读取超时时间
    OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
    MaxAutoRetries: 1 # 对当前实例的重试次数

根据如上配置,当访问到某个服务超时后,它会再次尝试访问下一个服务实例,如果不行就再换一个实例,如果不行,则返回失败。切换次数取决于MaxAutoRetriesNextServer参数的值

引入spring-retry依赖

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

4.断路器 - Netflix Hystrix

1.简介

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Of2m9mS1-1577005458027)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day03/assets/1525658562507.png)]

2.熔断器的工作机制:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ClHjbMhF-1577005458028)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day03/assets/1525658640314.png)]

正常工作的情况下,客户端请求调用服务API接口:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n2UHhsPo-1577005458028)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day03/assets/1525658906255.png)]

当有服务出现异常时,直接进行失败回滚,服务降级处理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-umax4QQ2-1577005458029)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day03/assets/1525658983518.png)]

当服务繁忙时,如果服务出现异常,不是粗暴的直接报错,而是返回一个友好的提示,虽然拒绝了用户的访问,但是会返回一个结果。

系统特别繁忙时,一些次要服务暂时中断,优先保证主要服务的畅通,一切资源优先让给主要服务来使用,在双十一、618时,京东天猫都会采用这样的策略。

3.实现
1.引入依赖

首先在user-consumer中引入Hystix依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.开启熔断
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class ConsumerDemoApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        // 这次我们使用了OkHttp客户端,只需要注入工厂即可
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerDemoApplication.class, args);
    }
}
3.改造消费者

我们改造user-consumer,添加一个用来访问的user服务的DAO,并且声明一个失败时的回滚处理函数:

@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.setName("用户信息查询出现异常!");
        return user;
    }
}
  • @HystrixCommand(fallbackMethod="queryUserByIdFallback"):声明一个失败回滚处理函数queryUserByIdFallback,当queryUserById执行超时(默认是1000毫秒),就会执行fallback函数,返回错误提示。
  • 为了方便查看熔断的触发时机,我们记录请求访问时间。
4.优化

虽然熔断实现了,但是我们的重试机制似乎没有生效,是这样吗?

其实这里是因为我们的Ribbon超时时间设置的是1000ms:

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RauIoGiI-1577005458029)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day03/assets/1525666632542.png)]

而Hystix的超时时间默认也是1000ms,因此重试机制没有被触发,而是先触发了熔断。

所以,Ribbon的超时时间一定要小于Hystix的超时时间。

我们可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置Hystrix超时时间。

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

5.路由(网关) - Netflix Zuul

1.Zuul加入后的架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xtvzn2dV-1577005458029)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day03/assets/1525675648881.png)]

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

添加Zuul依赖:

<dependencies>
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
     </dependency>
</dependencies>
3.编写启动类

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

@SpringBootApplication
@EnableZuulProxy // 开启Zuul的网关功能
public class ZuulDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(ZuulDemoApplication.class, args);
	}
}
4.编写配置
server:
  port: 10010 #服务端口
spring: 
  application:  
    name: api-gateway #指定服务名
5.编写路由规则

需要用Zuul来代理user-service服务,先看一下控制面板中的服务状态:

  • ip为:127.0.0.1
  • 端口为:8081

映射规则:

zuul:
  routes:
    user-service: # 这里是路由id,随意写
      path: /user-service/** # 这里是映射路径
      url: http://127.0.0.1:8081 # 映射路径对应的实际url地址

我们将符合path 规则的一切请求,都代理到 url参数指定的地址

本例中,我们将 /user-service/**开头的请求,代理到http://127.0.0.1:8081

6.启动测试:

访问的路径中需要加上配置规则的映射路径,我们访问:http://127.0.0.1:8081/user-service/user/10

3.面向服务的路由

在刚才的路由规则中,我们把路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然就不合理了。

我们应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对!

1.添加Eureka客户端依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.开启Eureka客户端发现功能
@SpringBootApplication
@EnableZuulProxy // 开启Zuul的网关功能
@EnableDiscoveryClient
public class ZuulDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(ZuulDemoApplication.class, args);
	}
}
3.添加Eureka配置,获取服务信息
eureka:
  client:
    registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1
4.修改映射配置,通过服务名称获取

因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。

zuul:
  routes:
    user-service: # 这里是路由id,随意写
      path: /user-service/** # 这里是映射路径
      serviceId: user-service # 指定服务名称
5.启动测试

再次启动,这次Zuul进行代理时,会利用Ribbon进行负载均衡访问:日志中可以看到使用了负载均衡器.

4.简化的路由配置

在刚才的配置中,我们的规则是这样的:

  • zuul.routes.<route>.path=/xxx/**: 来指定映射路径。<route>是自定义的路由名
  • zuul.routes.<route>.serviceId=/user-service:来指定服务名。

而大多数情况下,我们的<route>路由名称往往和 服务名会写成一样的。因此Zuul就提供了一种简化的配置语法:zuul.routes.<serviceId>=<path>

比方说上面我们关于user-service的配置可以简化为一条:

zuul:
  routes:
    user-service: /user-service/** # 这里是映射路径

省去了对服务名称的配置。

5.默认的路由规则

在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:

  • 默认情况下,一切服务的映射路径就是服务名本身。
    • 例如服务名为:user-service,则默认的映射路径就是:/user-service/**

也就是说,刚才的映射规则我们完全不配置也是OK的,不信就试试看。

6.路由前缀

配置示例:

zuul:
  prefix: /api # 添加路由前缀
  routes:
      user-service: /user-service/** # 这里是映射路径

我们通过zuul.prefix=/api来指定了路由的前缀,这样在发起请求时,路径就要以/api开头。

路径/api/user-service/user/1将会被代理到/user-service/user/1

7.过滤器

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

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:请求在被路由之前执行
    • routing:在路由请求时调用
    • post:在routing和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
2.过滤器执行生命周期:

这张是Zuul官网提供的请求生命周期图,清晰的表现了一个请求在各个过滤器的执行顺序。

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hbPY6L8h-1577005458030)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day03/assets/1525681866862.png)]

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

所有内置过滤器列表:

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-huPpqrZg-1577005458030)(D:/Video/6.%E4%B9%90%E4%BC%98%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE/%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/day03/assets/1525682427811.png)]

3.使用场景

场景非常多:

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

接下来我们来自定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。

1.定义过滤器类
@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() throws ZuulException {
        // 登录校验逻辑。
        // 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;
    }
}

9.负载均衡和熔断

Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:

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

ls(token.trim())){
// 没有token,登录校验失败,拦截
ctx.setSendZuulResponse(false);
// 返回401状态码。也可以考虑重定向到登录页。
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
// 校验通过,可以考虑把用户信息放入上下文,继续向后执行
return null;
}
}


#### 9.负载均衡和熔断

Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:

```yaml
zuul:
  retryable: true
ribbon:
  ConnectTimeout: 250 # 连接超时时间(ms)
  ReadTimeout: 2000 # 通信超时时间(ms)
  OkToRetryOnAllOperations: true # 是否对所有操作重试
  MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
  MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值