[学习笔记]Spring Cloud详细教程(二)

服务调用Ribbon

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具.

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用.Ribbon客户端组件提供一系列完善的配置项如连接超时\重试等.简单的说,就是在配置文件中列出Load Balancer(LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)区连接这些机器.我们很容易使用Ribbon实现自定义的负载均衡算法.

getForEntity

restTemplate.getForEntity()功能与restTemplate.getForObject()相似,但它返回的是一个ResponseEntity对象,可以存放多种详细的信息,如请求结果\请求头等

@GetMapping("/consumer/payment/getEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
    ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);

    if (entity.getStatusCode().is2xxSuccessful()) {
        return entity.getBody();
    } else {
        return new CommonResult<>(444, "操作失败");
    }
}

CommonResult是自己定义的那个存储信息的类

Ribbon的负载均衡

Eureka依赖中存在Ribbon的包,因此Eureka的负载均衡算法就使用了Ribbon的
在这里插入图片描述
Ribbon中有七种负载均衡的实现

  • com.netflix,loadlancer.RoundRobinRule: 轮询
  • com.netflix,loadlancer.RandomRule: 随机
  • com.netflix,loadlancer.RetryRule: 轮询策略获取服务,如果获取服务失败则在指定时间内进行重试
  • WeightedResponseTimeRule: 对RoundRobinRule轮询的扩展,响应速度越快的实例选择权重越大,越容易被选择
  • BestAvailableRule: 过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • AvailabilitFilteringRule: 先过滤掉故障实例,再选择并发量较小的实例.
  • ZoneAvoidanceRule: 默认哦恩过则,复合判断server所在区域的性能和server的可用性选择服务器

更换负载均衡算法

  1. new一个需要的负载均衡算法

    @Configuration
    public class MyRuleConfig {
        @Bean
        public IRule myRule() {
            return new RandomRule();//负载均衡策略: 随机
        }
    }
    

    这个配置类不能存放在@ComponentScan注解的所在包及其子包下,而主启动类上的@SpringBootApplication就存在@ComponentScan注解,因此这个类不能放在主启动类所在包和子包下

    以下演示负载均衡包的位置
    在这里插入图片描述

  2. 启用自己选择的负载均衡算法

    在主启动类中使用@RibbonClient

    @SpringBootApplication
    @EnableEurekaClient
    //更换负载均衡策略
    @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRuleConfig.class)
    public class OrderMain8080 {
        public static void main(String[] args) {
            SpringApplication.run(OrderMain8080.class, args);
        }
    }
    

手写一个轮询算法

轮询算法的原理: rest接口请求次数%服务器据群总数 = 实际调用服务器位置下标,每次服务重启后rest接口计数从1开始

  1. 写一个接口

    public interface LoadBalancer {
        ServiceInstance instance(List<ServiceInstance> serviceInstances);
    }
    
  2. 写一个实现类

    @Component
    public class MyLB implements LoadBalancer {
        private AtomicInteger atomicInteger = new AtomicInteger(0);
    
        public final int getAndIncrement() {
            int current;
            int next;
    
            do {
                current = this.atomicInteger.get();
                next = current >= Integer.MAX_VALUE ? 0 : current + 1;
            } while (!this.atomicInteger.compareAndSet(current, next));
    
            return next;
        }
    
        @Override
        public ServiceInstance instance(List<ServiceInstance> serviceInstances) {
            int index = getAndIncrement() % serviceInstances.size();
    
            return serviceInstances.get(index);
        }
    }
    

    思路就是取得注册中心中所有服务后,按照轮询的规则返回一个服务

  3. 调用自己写的算法

    @GetMapping("/consumer/payment/lb")
    public String getPaymentLB() {
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    
        if (instances == null || instances.size() <= 0) {
            return null;
        }
    
        ServiceInstance instance = loadBalancer.instance(instances);
        URI uri = instance.getUri();
    
        return restTemplate.getForObject(uri + "/payment/lb", String.class);
    }
    

服务调用OpenFeign

Feign是一个声明式WebService客户端.使用Feign能让编写WebService客户端更加简单.它的使用方法是定义一个服务接口然后在上面添加注解.Feign支持可插拔式的编码器和解码器.SpringCloud对Feign进行了封装,使其支持了springMVC标准注解和HTTPMessageConverters.Feign可以与Eureka和Ribbon组合使用,以支持负载均衡.

Feign与OpenFeign的区别
在这里插入图片描述

OpenFeign的使用

  1. pom

    <!--openfeign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. yaml: 默认使用OpenFeign是不需要使用yaml的配置的,这里配置了Eureka的服务地址

    server:
      port: 8080
    
    eureka:
      client:
        register-with-eureka: false
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
    
  3. 主启动类:使用@EnableFeignClients打开Feign

    @SpringBootApplication
    // 打开Feign
    @EnableFeignClients
    public class OrderFeignMain8080 {
        public static void main(String[] args) {
            SpringApplication.run(OrderFeignMain8080.class, args);
        }
    }
    
  4. service层: 这是使用Feign的核心

    @Component
    @FeignClient(value = "CLOUD-PAYMENT-SERVICE")
    public interface PaymentFeignService {
        @GetMapping("/payment/get/{id}")
        CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
    }
    

    这是一个接口,这个接口中所有方法都要与生产者服务的方法保持一致,使用@FeignClient()找到服务,Feign会自动按照生产者中的方法实现这个接口,以后使用生产者的方法就直接调用该接口中的方法即可.

    注意需要使用@Component将这个接口注册进Spring中

  5. controller层: 调用service层

    @RestController
    @Slf4j
    public class PaymentFeignController {
        @Resource
        private PaymentFeignService paymentFeignService;
    
        @GetMapping("/consumer/payment/get/{id}")
        public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
            log.info("=================================================================================");
            return paymentFeignService.getPaymentById(id);
        }
    
    }
    

OpenFeign的超时控制

默认情况下Openfein的超时时间是1秒,也就是说1秒内服务端没有响应就会超时报错

超时时间可以通过如下配置设置

#Feign默认内置Ribbon,因此使用Ribbon进行超时控制
ribbon:
  # 建立连接允许的最长时间
  ReadTimeout: 5000
  # 从服务器读取资源允许的最长时间
  ConnectTimeout: 5000

Feign的日志增强

日志增强就是控制日志输出,让Feign打印更详细的日志

  1. config层

    @Configuration
    public class FeignConfig {
        @Bean
        Logger.Level feignLoggerLevel() {
            // 打印全日志
            return Logger.Level.FULL;
        }
    }
    
  2. yaml配置

    #为Feign接口进行日志增强
    logging:
      level:
        # 日志的打印级别
        pero.fisher.springcloud.service.PaymentFeignService: debug
    

服务降级Hystrix

在这里插入图片描述
Hystrix的功能

  • 服务降级
  • 服务熔断
  • 接近实时的监控

Hystrix官方文档

Hystrix的服务降级

当服务无法访问时,返回一个友好的响应(FallBack),不强行让用户等待

产生服务降级的情况

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池\信号量大满
生产者的服务降级
  1. pom

    <!--hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  2. yaml: 不需要关于hystrix配置

    server:
      port: 8001
    spring:
      application:
        name: cloud-provider-hystrix-payment
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka
    
  3. 主启动类: 由于一会要使用hystrix的服务降级功能,因此把@EnableCircuitBreaker注解打开

    @SpringBootApplication
    @EnableEurekaClient
    //打开熔断
    @EnableCircuitBreaker
    public class PaymentHystrixMain8001 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentHystrixMain8001.class, args);
        }
    }
    
  4. service层: 进行服务降级

    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
    })
    public String paymentInfo_TimeOut(Integer id) {
        int time = 5;
    
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        return "线程池: " + Thread.currentThread().getName() + "timeout==========>id: " + id;
    }
    
    public String paymentInfo_TimeOutHandler(Integer id) {
        return "线程池: " + Thread.currentThread().getName() + "timeoutHandler==========>宕了";
    }
    

    使用@HystrixCommand()注解进行服务降级

    其中fallbackMethod参数是服务降级后调用的方法

    commandProperties则规定超时多久算服务超时而触发服务降级,当没有超时降级,就不需要这个参数

消费者的服务降级
  1. pom导包与生产者相同

  2. yaml: 消费者使用类Feign,需要添加一个让Feign支持Hysrtix的配置

    server:
      port: 8080
    eureka:
      client:
        register-with-eureka: false
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/
    
    # 让feign支持hystrix
    feign:
      hystrix:
        enabled: true
    
  3. 主启动类

    @SpringCloudApplication
    //这里使用Feign
    @EnableFeignClients
    @EnableCircuitBreaker
    public class OrderHystrixMain8080 {
        public static void main(String[] args) {
            SpringApplication.run(OrderHystrixMain8080.class, args);
        }
    }
    
  4. controller层: 这里直接就在controller层进行服务降级,注解的使用方法与之前生产者的相同

    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
    })
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    String timeout(@PathVariable("id") Integer id) {
        int a = 1 / 0;
        String res = paymentHystrixService.timeout(id);
        return res;
    }
    
    public String paymentInfo_TimeOutHandler(Integer id) {
        return "消费端繁忙";
    }
    
全局服务降级

上述服务降级只是一个方法一个方法的降级,是一种局部的降级,还可以进行局部降级,就是说对整个类配置一个降级方法

@RestController
@Slf4j
//全局服务降级
@DefaultProperties(defaultFallback = "paymentGlobalFallback")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    String ok(@PathVariable("id") Integer id) {
        String res = paymentHystrixService.ok(id);
        return res;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand
    String timeout(@PathVariable("id") Integer id) {
        int a = 1 / 0;
        String res = paymentHystrixService.timeout(id);
        return res;
    }

    public String paymentGlobalFallback() {
        return "功能异常";
    }
}

使用@DefaultProperties(defaultFallback)注解进行全局服务降级

通配服务降级

还有一种利用Feign的服务降级方法

  1. 实现Feign接口, 这个实现类中的方法就是以后服务降级调用的方法

    @Component
    public class PaymentFallbackService implements PaymentHystrixService {
        @Override
        public String ok(Integer id) {
            return "ok宕了";
        }
    
        @Override
        public String timeout(Integer id) {
            return "timeout宕了";
        }
    }
    
  2. Feign接口注解@FeignClient加入fallback参数

    @Service
    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
    public interface PaymentHystrixService {
        @GetMapping("/payment/hystrix/ok/{id}")
        String ok(@PathVariable("id") Integer id);
    
        @GetMapping("/payment/hystrix/timeout/{id}")
        String timeout(@PathVariable("id") Integer id);
    }
    

当这个Feign接口中的任何一个方法触发了服务降级,都会调用它的实现类的对应方法

服务熔断

当服务的访问量达到最大后,服务器将拒绝访问,然后调用服务降级的方法,并返回一个友好的响应
在这里插入图片描述
Hystrix熔断器存在半打开状态.当一段时间内出现了大量的错误访问,Hystrix会为了保护主机而打开断路器,此时任何访问都不予回应,而是返回一个友好的响应,类似于服务降级.一段时间后,断路器会处于半打开状态,此时,hystrix会尝试响应几个请求,如果这些请求的正确率达到一定程度,就将断路器关闭,响应回复正常.

熔断的配置如下

@HystrixCommand(fallbackMethod = "paymentCircuitBreak_fallback", commandProperties = {
    @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//是否开启断路器
    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数
    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //时间窗口期
    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//失败率, 达到失败率会跳闸
})
//上面的配置的含义就是在10秒之内,访问10次的失败率是60%,则打开断路器.此后一段时间内,所有访问都将视为错误访问进入paymentCircuitBreak_fallback方法
public String paymentCircuitBreak(Integer id) {
    if (id < 0) {
        throw new RuntimeException("id不可用");
    }

    String serialNum = IdUtil.simpleUUID();

    return Thread.currentThread().getName() + "获得流水号" + serialNum;
}
public String paymentCircuitBreak_fallback(Integer id) {
    return "id不可用 请重试";
}

服务限流

杀死高并发等操作,避免过度拥挤,规定一秒运行多少操作并有序进行

Hystrix监控的图形化

  1. pom依赖

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

    每个被监控的服务都要导入actuator依赖,也就是

    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. yaml: 不需要多余的配置

    server:
      port: 9001
    
  3. 主启动类: 开启@EnableHystrixDashboard注解

    @SpringBootApplication
    @EnableHystrixDashboard
    public class HystrixDashboardMain9001 {
        public static void main(String[] args) {
            SpringApplication.run(HystrixDashboardMain9001.class, args);
        }
    }
    

访问http://host:port/hystrix就可以进入Hystrix的监控界面
在这里插入图片描述

服务监控
  1. 在现在使用的SpringCloud版本,也就是Hoxton.SR1版本,要想将服务让Hystrix监控到,必须在主启动类中进行如下配置:

    @SpringBootApplication
    @EnableEurekaClient
    //打开熔断
    @EnableCircuitBreaker
    public class PaymentHystrixMain8001 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentHystrixMain8001.class, args);
        }
    
        //ServletRegistrationBean的默认路径不是/hystrix.stream,Hystrix不能找到这个服务,因此要手动修改路径
        @Bean
        public ServletRegistrationBean getServlet(){
            HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
            ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
            registrationBean.setLoadOnStartup(1);
            registrationBean.addUrlMappings("/hystrix.stream");
            registrationBean.setName("HystrixMetricsStreamServlet");
            return registrationBean;
        }
    }
    

    另外别忘导入actuator依赖

  2. Web图形化监控端
    在这里插入图片描述

  3. 点击Monitor Stream会进入该监控界面
    在这里插入图片描述
    以后这个服务的每一个操作状态都会在这里显示出来

服务网关Gateway

在这里插入图片描述
Gateway是在Spring生态系统之上构建的ApI网关服务,基于Spring 5,SpringBoot 2和Project Reactor等技术,使用非阻塞API.

Gateway旨在提供一种简单而有效的方式来对API进行路由,一级提供一些强大的过滤器功能,例如:熔断\先流\重试等.

Spring Cloud Gateway是基于WEbFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty.

Spring Cloud GateWay的目标是提供统一的路由方式,且基于Filter链的方式提供了网关基本功能,如: 安全,监控/指标,限流.

Spring Cloud GateWay还支持WebSocket

Gateway核心理念

  • Route(路由): 路由是构建网关的基本模块,他由ID,目标URI,一系列的断言和过滤器组成,如果断言为true,则匹配该路由.
  • Predicate(断言): 匹配HTTP请求中所有的内容,如果请求与断言相匹配则进行路由
  • Filter(过滤): 就是Spring框架中的GatewayFilter实例,使用过滤器,可以在请求被路由前或后对请求进行修改

Gateway工作流程
在这里插入图片描述

Gateway配置

  1. pom

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

    这是Gateway的配置,当然还需要eureka客户端的包和一些通用的包,注意不需要加web相关的包

  2. yaml

    server:
      port: 9527
    
    spring:
      application:
        name: cloud-gateway
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true               # 开启从注册中心利用为服务名进行路由功能
          routes:
            - id: payment_route_01        # 路由ID
              uri: http://localhost:8001  # 提供服务的地址
              predicates:
                - Path=/payment/get/**    # 断言,路径匹配则进行路由
    
            - id: payment_route_02
              uri: http://localhost:8001
              predicates:
                - Path=/payment/lb/**
    
    eureka:
      instance:
        hostname: cloud-gateway-service
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka
    
    
  3. 主启动类

    @SpringBootApplication
    @EnableEurekaClient
    public class GateWayMain9527 {
        public static void main(String[] args) {
            SpringApplication.run(GateWayMain9527.class, args);
        }
    }
    

配置完成,启动后注册进注册中心,访问Gateway网关端口9527,网关就会路由到配置的端口,也就是8001

动态路由

上述配置将路由配置死了,其实还可以通过服务名的方式配置动态路由,访问Gateway端口后会路由到相同服务的不同端口中

只需要更改yaml配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true               # 开启从注册中心利用为服务名进行路由功能
      routes:
        - id: payment_route_01        # 路由ID
          uri: lb://CLOUD-PAYMENT-SERVICE
#          uri: http://localhost:8001  # 提供服务的地址
          predicates:
            - Path=/payment/get/**    # 断言,路径匹配则进行路由

        - id: payment_route_02
          uri: lb://CLOUD-PAYMENT-SERVICE
#          uri: http://localhost:8001
          predicates:
            - Path=/payment/lb/**


eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka

注意uri的更改

断言

断言(predicates)的作用是通过Gateway访问时,需要满足所有断言,否则不予通过

yaml配置如下

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true               # 开启从注册中心利用为服务名进行路由功能
      routes:
        - id: payment_route_01        # 路由ID
          uri: lb://CLOUD-PAYMENT-SERVICE
#          uri: http://localhost:8001  # 提供服务的地址
          predicates:
            - Path=/payment/get/**    # 断言,路径匹配则进行路由
            - After=2020-05-12T02:40:17.806+08:00[Asia/Shanghai]  # 某时间点之后断言成功
            - Cookie=username,yb      # 存在session的断言  session名,正则
            - Header=X-request-Id,\d+ # 请求头的断言 头名称,正则(\d+表示整数)
            - Host=**.host.com        # 访问主机的断言
            - Method=GET              # 访问方法的断言
            - Query=id,\d+            # 参数的断言

        - id: payment_route_02
          uri: lb://CLOUD-PAYMENT-SERVICE
#          uri: http://localhost:8001
          predicates:
            - Path=/payment/lb/**


eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka

注意看predicates参数,这就是断言

过滤器

Gateway中存在这内置过滤器,功能与断言相似,一般不太需要

还可以自定义过滤器,与譬如SpringBoot、Servlet中的过滤器功能相似

@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("执行过滤器0==========>" + new Date());

        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null) {
            log.info("用户名非法");
            // 返回状态码
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }

    @Override
    // 过滤器的优先级 数字越小优先级越高
    public int getOrder() {
        return 0;
    }
}

过滤器需要实现GlobalFilterOrdered接口

过滤器可以设置多个,然后一级一级过滤,getOrder()方法就是用来设置过滤器的优先级的

注意一定要使用@Component注解,将这个过滤器注册进Spring中

SpringCloud Config分布式配置中心

微服务意味着要将单体应用中的业务拆分成一个个字符无,每个服务的粒度相对较小,因此系统中会出现大量的服务.由于每个服务都需要必要的配置信息才能运行,所以一套集中式的\动态的配置管理设施是必不可少的.SpringCloud提供了ConfigServer来解决这个问题.

springCloud Config为微服务架构中的为服务提供集中化的外部配置支持,配置服务器为各个不同为服务应用的所有环境提供了一个中心化的外部配置.

Config的服务端配置

  1. 首先需要一个远程仓库存放各种配置文件,我在GitHub中名为springcloud-config的仓库中存放了如下配置文件
    在这里插入图片描述
    里面的内容是这样的

    config:
      info: "master branch,springcloud-config/config-dev.yml version=3" 
    
  2. pom

    <!--springcloud-config-server-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    

    这是config服务端的依赖,还需要eureka等常规配置

  3. yaml

    server:
      port: 3344
    spring:
      application:
        name: cloud-config-center
      cloud:
        config:
          server:
            git:
              # GitHub仓库
              uri: https://github.com/borzj/springcloud-config.git
              # 搜索目录
              search-paths:
                - springcloud-config
              force-pull: true
              username: xxx
              password: xxx
          # 选择分支
          label: master
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    

    重点是配置远程仓库

  4. 主启动类

    @SpringBootApplication
    @EnableConfigServer
    public class ConfigCenterMain3344 {
        public static void main(String[] args) {
            SpringApplication.run(ConfigCenterMain3344.class, args);
        }
    }
    

然后,访问http://host:port/filename就可以访问到github中对应文件的内容

Config客户端

  1. pom: 这里只给出了config客户端的依赖

    <!--springcloud-config-client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    
  2. yaml

    在这里用到的yaml叫做bootstrap.yaml

    bootstrap.yaml是系统级配置文件,优先级比application.yaml高

    初始化时,bootstrap负责从外部源加载配置属性并解析.

    客户端配置bootstrap文件后,会首先从客户端读取配置

    server:
      port: 3355
    spring:
      application:
        name: config-client
      cloud:
        config:
          label: master # 分支
          name: config  # 文件名
          profile: dev  #后缀
          uri: http://localhost:3344  # 服务器uri
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    
  3. 主启动类

    @SpringBootApplication
    @EnableEurekaClient
    public class ConfigClientMain3355 {
        public static void main(String[] args) {
            SpringApplication.run(ConfigClientMain3355.class, args);
        }
    }
    
  4. controller层: 使用从Config服务端拿到的配置

    @RestController
    public class ConfigClientController {
        @Value("${config.info}")
        private String configInfo;
    
        @GetMapping("/configInfo")
        public String getConfigInfo() {
            return configInfo;
        }
    }
    

然后访问controller层即可看到拿到的配置信息

Config动态刷新

按照上述配置中,如果远程仓库中的配置文件发生了改变,需要将微服务重启一遍才能更新到配置,这是一种灾难.因此我们需要使用动态刷新,实现热刷新

  1. 在yaml中

    server:
      port: 3355
    spring:
      application:
        name: config-client
      cloud:
        config:
          label: master # 分支
          name: config  # 文件名
          profile: dev  #后缀
          uri: http://localhost:3344  # 服务器uri
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    
    # 动态刷新,暴露监控端口
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    

    注意加入了management,用于对外暴露端口

  2. controller中加入@RefreshScope注解,用于刷新配置

    @RestController
    // 刷新配置
    @RefreshScope
    public class ConfigClientController {
        @Value("${config.info}")
        private String configInfo;
    
        @GetMapping("/configInfo")
        public String getConfigInfo() {
            return configInfo;
        }
    
    }
    
  3. 当远程配置文件改变后,需要post请求客户端,让它重新拉取配置

    使用命令curl -X POST "http://localhost:3355/actuator/refresh"

Bus消息总线

Config的动态刷新需要分别对每一个客户端都进行一次刷新,操作太过复杂.

Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的自动动态刷新,也就是一行命令刷新所有客户端

Bus是用来将分布式系统的节点与轻量级消息系统连接起来的框架,整合了Java的事件处理机制和消息中间件功能.Spring Cloud Bus目前支持RabbitMQ和Kafka
在这里插入图片描述
Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道.

在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有的微服务实例都连接上来,由于该主题中产生消息会被所有实例监听和消费,所以称它为消息总线.

安装RabbitMQ

我们选用RabbitMQ作为Bus的中间件,安装与配置流程如下

  1. 安装, RabbitMQ运行需要一个Erlang,所以安装RabbitMQ之前先将它装上

    Manjaro系统下直接使用sudo pacman -S rabbitmq,它会将RabbitQM依赖的包都下载号,包括Erlang

  2. 启动管理功能

    sudo rabbitmq-plugins enable rabbitmq_management

  3. 启动RabbitQM服务器

    sudo rabbitmq-server start

  4. 通过http://localhost:15672/访问RabbitMQ的可视化界面

    帐号和密码都是guest
    在这里插入图片描述

Bus全局广播

服务端的配置
  1. pom: 要加入RabbitMQ的依赖

    <!--添加消息总线RabbitMQ支持-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
  2. yaml: 加入RabbitMQ的配置,并暴露端口

    server:
      port: 3344
    spring:
      application:
        name: cloud-config-center
      cloud:
        config:
          server:
            git:
              # GitHub仓库
              uri: https://github.com/borzj/springcloud-config.git
              # 搜索目录
              search-paths:
                - springcloud-config
              force-pull: true
              username: xxx
              password: xxx
          # 选择分支
          label: master
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    
    #RabbitMQ相关配置,15672是Web管理界面的端口,5672是MQ的访问端口
    rabbitmq:
      host: localhost
      port: 5672
      username: guest
      password: guest
    
    #暴露bus刷新配置端口
    management:
      endpoints:
        web:
          exposure:
            include: "bus-refresh"
    
客户端配置
  1. pom: 也需要加入RabbitMQ依赖

    <!--添加消息总线RabbitMQ支持-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
  2. yaml: 配置RabbitMQ

    server:
      port: 3355
    spring:
      application:
        name: config-client
      cloud:
        config:
          label: master # 分支
          name: config  # 文件名
          profile: dev  #后缀
          uri: http://localhost:3344  # 服务器uri
      #RabbitMQ相关配置,15672是Web管理界面的端口,5672是MQ的访问端口
      rabbitmq:
        host: localhost
        port: 5672
        username: guest
        password: guest
    
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    
    # 动态刷新,暴露监控端口
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    

当远程配置文件改变是,现在只需要一个命令: curl -X POST http://localhost:3344/actuator/bus-refresh所有配置过bus的客户端都可以动态刷新配置

Bus的定点通知

如果你不想刷新所有的客户端,只需要刷新某一个客户端,Bus提供了定点通知的功能,命令如下:

curl -X POST http://localhost:3344/actuator/bus-refresh/服务名:端口号

SpringCloud Stream消息驱动

pringCloud Stream是一个构建消息驱动微服务的框架.用于屏蔽消息中间件的差异,同一消息编程模型

应用程序通过inputs和output与Stream中的binder交互.通过定义绑定器(binder)作为中间件完美地实现了应用程序与消息中间件细节之间的隔离.通过向应用程序暴露同一的Channel通道,使得应用程序不需要再考录各种不同的消息中间件实现.
在这里插入图片描述
tream中的消息通信方式遵循了发布-订阅模式

消息驱动的生产者

  1. pom:这里绑定RabbitMQ

    <!--stream-rabbit-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    
  2. yaml

    server:
      port: 8801
    spring:
      application:
        name: cloud-stream-provider
      cloud:
        stream:
          binders:          # 配置bindermq的服务信息
            defaultRabbit:  # 自定义binder名称
              type: rabbit  # 消息组件类型
              environment:  # rabbitmq的环境
                spring:
                  rabbitmq:
                    host: localhost
                    port: 5672
                    username: guest
                    password: guest
          bindings: # 服务的整合
            output:
              destination: studyExchange      # exchange名称,可以在可视化界面中看到
              content-type: application/json  # 消息类型,这是json类型,"text/plain"是文本类型
              binder: defaultRabbit           # 要绑定的消息名称
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    
  3. 主启动类

    @SpringBootApplication
    public class StreamMQMain8801 {
        public static void main(String[] args) {
            SpringApplication.run(StreamMQMain8801.class, args);
        }
    }
    
  4. service层: 定义发送消息方法

    接口:

    public interface IMessageProvider {
        String send();
    }
    

    实现类:

    //消息的推送管道
    @EnableBinding(Source.class)//org.springframework.cloud.stream.messaging.Source包下的
    @Slf4j
    public class IMessageProviderImpl implements IMessageProvider {
        @Resource
        private MessageChannel output;
    
        @Override
        public String send() {
            String s = UUID.randomUUID().toString();
            output.send(MessageBuilder.withPayload(s).build());
            log.info("=======================send" + s);
            return null;
        }
    }
    

    这里的service不再用@Service注解而是用@EnableBinding将消息推送到管道

  5. controller层: 调用service层发送消息

    @RestController
    public class SendMessageController {
        @Resource
        private IMessageProvider messageProvider;
    
        @GetMapping("/sendMessage")
        public String sendMessage() {
            return messageProvider.send();
        }
    }
    

消息驱动的消费者

  1. pom: 也是stream-rabbit

    <!--stream-rabbit-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    
  2. yaml

    server:
      port: 8802
    spring:
      application:
        name: cloud-stream-provider
      cloud:
        stream:
          binders:          # 配置bindermq的服务信息
            defaultRabbit:  # 自定义binder名称
              type: rabbit  # 消息组件类型
              environment:  # rabbitmq的环境
                spring:
                  rabbitmq:
                    host: localhost
                    port: 5672
                    username: guest
                    password: guest
          bindings: # 服务的整合
            input:
              destination: studyExchange      # exchange名称
              content-type: application/json  # 消息类型,这是json类型,"text/plain"是文本类型
              binder: defaultRabbit           # 要绑定的消息名称
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    

    绑定的destination应与生产者的相同

  3. 主启动类

    @SpringBootApplication
    public class StreamMQMain8802 {
        public static void main(String[] args) {
            SpringApplication.run(StreamMQMain8802.class, args);
        }
    }
    
  4. controller层: 仍然需要@EnableBinding过去管道消息

    @Component
    @EnableBinding(Sink.class)
    public class ReceiveMessageListenerController {
        @Value("${server.port}")
        private String serverPort;
    
        @StreamListener(Sink.INPUT)
        public void input(Message<String> message) {
            System.out.println("消费者" + serverPort + message.getPayload());
        }
    }
    

重复消费问题

在有多个微服务消费同一个消息时,在同一个分组中,组内会发生竞争,只能消费一次.在不同的分组中可以被消费多次.默认每个服务的分组是不同的.因此要想解决重复消费问题,就将多个微服务放在同一个组中.

spring: 
  cloud:
    stream:
    	bindings: # 服务的整合
            input:
              destination: studyExchange      # exchange名称
              content-type: application/json  # 消息类型,这是json类型,"text/plain"是文本类型
              binder: defaultRabbit           # 要绑定的消息名称
              #分组 解决重复消费问题
              group: streamconsumer

group设置分组名

消费持久化

配置group后,会将停机时生产者发来的没有消费的信息重新消费,这就是消息的持久化

Spring Cloud Sleuth分布式请求链路跟踪

在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每个前段请求都会形成一个复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败.

Spring Cloud Sleuth就是用来进行服务跟踪和链路监控的.

zipkin的安装

zipkin可以可视化展示Sleuth的监控

zipkin下载地址

下载完成,是一个jar包,使用java -jar运行jar包
在这里插入图片描述
zipkin默认使用了9411端口,通过http://localhost:9411访问
在这里插入图片描述
了解几个名词

  • Trace Id: 唯一标识一个链路的Id
  • Span: 标识发起的请求

Sleuth的使用

生产者
  1. pom

    <!--sleuth+zipkin-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    

    这个依赖中包含了sleuth和zipkin

  2. yaml: 配置zipkin和sleuth

    spring:
      # 配置zipkin和sleuth
      zipkin:
        # zipkin的地址
        base-url: http://localhost:9411
      sleuth:
        # 设置采样率, 介于0-1之间
        sampler:
          probability: 1
    
  3. 主启动类: 不需要另外添加新的注解

  4. controller(象征性写了个controller用来测试)

    @GetMapping("/payment/zipkin")
    public String zipkin() {
        return "调用了payment_zipkin";
    }
    
消费者
  1. pom: 也是zipkin的包

    <!--sleuth+zipkin-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    
  2. yaml

    spring:
      # 配置zipkin和sleuth
      zipkin:
        # zipkin的地址
        base-url: http://localhost:9411
      sleuth:
        # 设置采样率, 介于0-1之间
        sampler:
          probability: 1
    
  3. 主启动类: 不需要另外加什么注解

  4. controller: 调用生产者的服务

    @GetMapping("/consumer/payment/zipkin")
    public String zipkin() {
        return restTemplate.getForObject("http://localhost:8001" + "/payment/zipkin/" , String.class);
    }
    

Sleuth的测试

访问几次生产者的服务,在Zipkin的Web界面中就可以查看这个访问经过的链路
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值