SpringCloud

2022/3/24 java框架

SpringCloud组件预览

# 1. 快速入门

# 1.1 需求分析

现在有两个模块一个支付模块一个订单模块,需求为订单模块要去调用支付模块的接口完成支付

cloud-provider-payment8001提供的接口

@Slf4j
@RestController
@RequestMapping("/payment")
public class PaymentController {
    @Autowired
    private PaymentServcie paymentServcie;

    @PostMapping("/create")
    public Result<Payment> create(@RequestBody Payment payment) {
        int i = paymentServcie.create(payment);
        log.info("插入结果: " + i);
        return i > 0 ? new Result<>(200, "插入成功") : new Result<>(400, "插入失败");
    }

    @GetMapping("/get/{id}")
    public Result<Payment> getPaymentById(@PathVariable String id) {
        Payment payment = paymentServcie.getPaymentById(id);
        return payment != null ? new Result<>(200, "查询成功", payment) : new Result<>(400, "查询失败");
    }
}

# 1.2 远程调用

  1. 订单模块中在容器中放入RestTemplate实例

    @Configuration
    public class ApplicationContextConfig {
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
  2. 订单模块controller使用restTemplate进行调用

    @RestController
    @RequestMapping("/consumer")
    public class OrderController {
        private final static String PAYMENT_URL = "http://localhost:8001";
    
        @Autowired
        private RestTemplate restTemplate;
    
        @PostMapping("/payment/create")
        public Result<Payment> create(@RequestBody Payment payment) {
            return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, Result.class);
        }
    
        @GetMapping("/payment/get/{id}")
        public Result<Payment> getPayment(@PathVariable String id) {
            return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, Result.class);
        }
    }
    

# 2. Eureka

Eureka包含两个组件:Eureka ServerEureka Client

Eureak Server提供服务注册服务,各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中存储所有可用的服务节点的信息,服务节点的信息可以在界面中直观看到。

Eureka Client通过注册中心进行访问,它是一个java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询round-robin负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureak Server在多个心跳周期内没有接受到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。

# 2.1 Eureka Server配置

  1. 新建一个maven工程并引入如下依赖(目前工程名为cloud-eureka-server7001)

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
  2. application.yaml配置

    server:
        port: 7001
    eureka:
        instance:
            # eureka服务端实例名称
            hostname: localhost
        client:
            # 不向注册中心注册自己
            register-with-eureka: false
            # 表示自己是注册中心,不需要去检索服务
            fetch-registry: false
            service-url:
                # 注册中心地址
                defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    
  3. 启动类配置

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

# 2.2 Eureka Client配置

  1. 在需要注册进Eureka Server的项目中添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  2. 并在启动类上添加注解

    @EnableEurekaClient
    
  3. 配置文件中填写Eureka Server的地址

    eureka:
        client:
            # 注册EurekaServer
            register-with-eureka: true
            # 抓取已在EurekaServer的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
            fetch-registry: true
            service-url:
                # 注册中心地址
                defaultZone: http://localhost:7001/eureka/
    

# 2.2 Eureka 集群

  1. 修改Eureka Server配置文件

    server:
        port: 7001
    eureka:
        instance:
            # eureka服务端实例名称
            hostname: eureka7001.com
        client:
            # 不向注册中心注册自己
            register-with-eureka: false
            # 表示自己是注册中心,不需要去检索服务
            fetch-registry: false
            service-url:
                # 注册中心地址
                defaultZone: http://eureka7002.com:7002/eureka/
    
    server:
        port: 7002
    eureka:
        instance:
            # eureka服务端实例名称
            hostname: eureka7002.com
        client:
            # 不向注册中心注册自己
            register-with-eureka: false
            # 表示自己是注册中心,不需要去检索服务
            fetch-registry: false
            service-url:
                # 注册中心地址
                defaultZone: http://eureka7001.com:7001/eureka/
    
  2. 修改Eureka Client配置文件

    eureka:
        client:
            # 注册EurekaServer
            register-with-eureka: true
            # 抓取已在EurekaServer的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
            fetch-registry: true
            service-url:
                # 注册中心地址,多个注册中心以逗号隔开或者注册任一地址即可
                defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/
    
  3. cloud-consumer-order80模块的restTemplate开启负载均衡功能

    @Configuration
    public class ApplicationContextConfig {
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
  4. 修改controller的默认远程调用地址为服务名称

    @RestController
    @RequestMapping("/consumer")
    public class OrderController {
        private final static String PAYMENT_URL = "http://COUD-PAYMENT-SERVICE";
    
        @Autowired
        private RestTemplate restTemplate;
    
        @PostMapping("/payment/create")
        public Result<Payment> create(@RequestBody Payment payment) {
            return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, Result.class);
        }
    
        @GetMapping("/payment/get/{id}")
        public Result<Payment> getPayment(@PathVariable String id) {
            return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, Result.class);
        }
    }
    
  5. (可选)修改Eureka显示的主机名称

    eureka:
        instance:
            # 实例名称
            instance-id: payment8001
            # 是否显示ip
            prefer-ip-address: true
    
  6. (可选)获取Eureka上的服务信息

    所有的信息都封装在一个DiscoveryClient实例中

    @Autowired
    private DiscoveryClient discoveryClient;
    

    启动类上需要添加注解

    @EnableDiscoveryClient
    

# 2.3 Eureka保护模式

保护模式主(Eureka Server独有)要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的信息,也就是不会注销任何服务

server配置

eureka:
    server:
        # 关闭自我保护默认开启
        enable-self-preservation: false
        # 心跳包时间默认30s
        eviction-interval-timer-in-ms: 2000

client的配置

eureka:
    instance:
        # 客户端向服务端发送心跳的时间间隔:单位秒(默认30s)
        lease-renewal-interval-in-seconds: 1
        # Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认90s),超时将删除服务
        lease-expiration-duration-in-seconds: 2

# 3. Zookeeper

  1. 安装,Zookeeper Server需要单独安装

    docker run --name zookeeper -d -e TZ="Asia/Shanghai" -p 2181:2181 -v ~/zookeeper:/data zookeeper:3.5.9
    
  2. client,在上述的例子中将Eureka的依赖换成zookeeper的依赖即可

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    </dependency>
    
  3. 启动类上需要添加注解

    @EnableDiscoveryClient
    
  4. 配置文件

    spring:
        application:
            name: cloud-provider-payment
        cloud:
            zookeeper:
                connect-string: 127.0.0.1:2181
    
  5. 远程调用的方式与[上述相同](#2.2 Eureka 集群)

# 4. Consul

# 4.1 安装

  1. 安装,官网 (opens new window)

  2. 开发者方式启动

    consul agent -dev
    
  3. 默认有一个http://localhost:8500/的访问地址

# 4.2 客户端

  1. 导入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    
  2. application.yaml配置文件

    spring:
        application:
            name: consul-provider-payment
        cloud:
            consul:
                host: localhost
                port: 8500
                discovery:
                    service-name: ${spring.application.name}
    
  3. 启动类需要添加注解

    @EnableDiscoveryClient
    
  4. 远程调用的方式与[上述相同](#2.2 Eureka 集群)

# 5. Ribbon

Spring Cloud RibbonNetflix Ribbon提供的一套客户端负载均衡的工具

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

LB负载均衡Load Balance:简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡有软件NginxLVS、硬件F5

Ribbon本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程调用技术

eureka已经自带了ribbon的依赖,无需引入过多的依赖

# 5.1 负载均衡算法

ribbon默认负载均衡算法为轮询,它自己提供了几种常用的负载均衡策略,也提供了相应的方法自定义负载均衡策略(注意所有针对负载均衡算法的改动都是针对调用端而言的)

常用策略

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

# 5.2 修改负载均衡算法

修改负载均衡是相当于调用端来说的

  1. 在调用端注入想要使用的Bean,注意这个类不能在@ComponentScan所扫描的包及其子包下

    @Configuration
    public class MySelfRule {
        @Bean
        public IRule mySelfRule() {
            return new RandomRule();
        }
    }
    
  2. 在启动类上添加注解

    // name 要调用的服务名称
    // configuration 负载均衡算法
    @RibbonClient(name = "COUD-PAYMENT-SERVICE", configuration = RandomRule.class)
    

# 5.3 自定义负载均衡算法

  1. 修改restTemplate,去掉@LoadBalanced注解

    @Configuration
    public class ApplicationContextConfig {
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
  2. 自定义算法实现

    public interface LoadBalancer {
        ServiceInstance instances(List<ServiceInstance> serviceInstances);
    }
    
    
    @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 instances(List<ServiceInstance> serviceInstances) {
            int index =  getAndIncrement() % serviceInstances.size();
            return serviceInstances.get(index);
        }
    }
    
  3. 使用

    @RestController
    @RequestMapping("/consumer")
    public class OrderController {
        @Autowired
        private RestTemplate restTemplate;
        @Autowired
        private DiscoveryClient discoveryClient;
        @Autowired
        private LoadBalancer loadBalancer;
    
        @GetMapping("/payment/lb")
        public String getPaymentLb() {
            List<ServiceInstance> instances = discoveryClient.getInstances("COUD-PAYMENT-SERVICE");
            if (instances == null || instances.size() <= 0) {
                return null;
            }
            ServiceInstance instance = loadBalancer.instances(instances);
            URI uri = instance.getUri();
            return restTemplate.getForObject(uri + "/payment/lb", String.class);
        }
    }
    

# 6. OpenFeign

OpenFeignSpring CloudFeign的基础上支持了SpringMVC的注解,如@RequestMapping等,OpenFeign@FeignClient注解可以解析SpringMVC@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务(⭐️Feign针对调用端而言)

# 6.1 服务调用

  1. 导入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. 编写服务调用接口

    @FeignClient(name = "COUD-PAYMENT-SERVICE")	// name为要调用的服务名称
    public interface PaymentClient {
        @GetMapping("/payment/get/{id}")
        public Result<Payment> getPaymentById(@PathVariable("id") String id);
    }
    
  3. 主启动类上添加注解

    @EnableFeignClients
    
  4. 使用

    @RestController
    @RequestMapping("/consumer")
    public class OrderFeignController {
        @Autowired
        private PaymentClient paymentClient;
    
        @GetMapping("/payment/get/{id}")
        public Result<Payment> getPayment(@PathVariable("id") String id) {
            return paymentClient.getPaymentById(id);
        }
    }
    

# 6.2 超时控制

默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

ribbon:
    # 建立连接后从服务器读取到可用资源所能够使用的最大时间
    ReadTimeout: 5000
    # 两端建立连接所能够使用的最大时间
    ConnectTimeout: 5000

# 6.3 日志增强

Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解FeignHttp请求的细节

总共提供了四个日志级别

  1. NONE:默认的,不显示任何日志
  2. BASIC:仅记录请求方法、URL、响应状态码及执行时间
  3. HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
  4. FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据

修改配置文件

logging:
    level:
        # 指定具体的类的日志等级
        com.meinil.springcloud.client.PaymentClient: debug

编写配置类

@Configuration
public class FeignConfig {
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

# 7. Hystrix

服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的"扇出"。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,即"雪崩效应"

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源在几秒钟之内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

Hystrix是一个用于分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务徐失败,避免级联故障,以提高分布式系统的弹性。

"断路器"本身是一种开关,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩

# 7.1 重要概念

  1. 服务降级fallback:当服务端出错时,不会让客户端无限等待下去,而是会立刻返回一个友好提示

    触发服务降级的情况

    • 程序运行异常
    • 超时
    • 服务熔断触发服务降级
    • 线程池/信号量满载
  2. 服务熔断break:服务端达到最大访问量,调用服务降级的方法处理

  3. 服务限流flowlimit:维持高并发操作的有序进行

# 7.2 服务降级

降级分为客户端降级可服务端降级,两者所用依赖相同

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

# 7.2.1 服务端降级

  1. Controller

    @RestController
    @RequestMapping("/payment/hystrix")
    public class PaymentHystrixController {
        @Autowired
        private PaymentHystrixService paymentService;
    
        @GetMapping("/timeout/{id}")
        public String paymentError(@PathVariable String id) {
            return paymentService.paymentError(id);
        }
    }
    
  2. service

    @Service
    public class PaymentHystrixService {
        @Value("${server.port}")
        private String serverPort;
    
        @HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = {
                @HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds", value = "3000")
        })
        public String paymentError(String id) {
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "paymentError: \tThread: " + Thread.currentThread().getName() + "\tserverPort: " + serverPort + "\tid: " + id;
        }
    
        public String timeoutHandler(String id) {
            return "paymentTimeoutHandler: \tThread: " + Thread.currentThread().getName() + "\tserverPort: " + serverPort + "\tid: " + id;
        }
    }
    

    HystrixCommand指定了超时或者异常处理的方法timeoutHandler

  3. 启动类上需要添加注解

    @EnableCircuitBreaker
    

# 7.2.2 客户端降级

降级一般是客户端做⭐️

  1. 依赖与上述相同

  2. 修改配置文件,开启熔断器

    feign:
        hystrix:
            enabled: true
    
  3. 启动类上需要添加注解

    @EnableHystrix
    
  4. 远程调用

    @FeignClient(name = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
    public interface PaymentHystrixClient {
        @GetMapping("/payment/hystrix/timeout/{id}")
        public String paymentError(@PathVariable("id") String id);
    }
    
  5. Controller

    @RestController
    @RequestMapping("/consumer")
    public class PaymentHystrixController {
        @Autowired
        private PaymentHystrixClient paymentHystrixClient;
       
        @GetMapping("/payment/hystrix/timeout/{id}")
        @HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = {
                @HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds", value = "1500")
        })
        public String paymentError(@PathVariable("id") String id){
            return paymentHystrixClient.paymentError(id);
        }
    
        public String timeoutHandler(String id) {
            return "客户端降级: \tThread: " + Thread.currentThread().getName() + "\tid: " + id;
        }
    }
    

# 7.2.3 全局统一降级

以客户端为例

  1. 默认降级方法

    @RestController
    @RequestMapping("/consumer")
    @DefaultProperties(defaultFallback = "globalHandler")
    public class PaymentHystrixController {
        @Autowired
        private PaymentHystrixClient paymentHystrixClient;
    
        @HystrixCommand
        @GetMapping("/payment/hystrix/timeout/{id}")
        public String paymentError(@PathVariable("id") String id){
            return paymentHystrixClient.paymentError(id);
        }
    
        public String globalHandler() {
            return "全局统一降级处理";
        }
    }
    
  2. 远程调用实现类降级

    @FeignClient(name = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentHystrixClientFallback.class)
    public interface PaymentHystrixClient {
        @GetMapping("/payment/hystrix/timeout/{id}")
        public String paymentError(@PathVariable("id") String id);
    }
    
    @Component
    public class PaymentHystrixClientFallback implements PaymentHystrixClient {
        @Override
        public String paymentError(String id) {
            return "实现类降级";
        }
    }
    

# 7.3 服务熔断

熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。

当检测到该节点微服务调用响应正常后,恢复调用链路。也就是熔断会直接断开服务连接,使得请求无法到达服务端,进而保护服务端,防止过载。而降级不会

SpringCloud框架里,熔断机制通过Hystrix实现。Hystrix会监视微服务调用的状况,当失败的调用到达一定阈值时,缺省是5s内20次失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand

熔断器一般包含三个部分

  • Closed:熔断关闭不会对服务进行熔断

  • Open:请求不再调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟进入半熔断状态

  • Half-Open:部分请求根据规则调用当前服务,如果请求成功且符合规则,则认为当前服务状态恢复正常,关闭熔断

# 7.3.1 实例演示

  1. 服务层

    @Service
    public class PaymentHystrixService {
        @HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback", 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"),       // 失败率达到多少熔断
        })
        public String paymentCircuitBreaker(Integer id) {
            if (id < 0) {
                throw new RuntimeException("*******id不能为负数");
            }
    
            return "id: " + id + "\tThread: " + Thread.currentThread().getName() + "\tUUID: " + UUID.randomUUID().toString();
        }
        public String paymentCircuitBreakerFallback(Integer id) {
            return "id不能为负数,请稍后再试\tid: " + id;
        }
    }
    
  2. controller

    @RestController
    @RequestMapping("/payment/hystrix")
    public class PaymentHystrixController {
        @Autowired
        private PaymentHystrixService paymentService;
    
        @GetMapping("/breaker/{id}")
        public String breaker(@PathVariable Integer id) {
            return paymentService.paymentCircuitBreaker(id);
        }
    }
    

# 7.4 服务监控

除了隔离依赖服务的调用 意外,Hystrix还提供了准实时的调用监控Hystrix DashboardHystrix会持续地记录素哟偶通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求、多少成功、多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面

  1. 新建模块cloud-consumer-hystrix-dashboard9001

  2. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    
  3. 启动类

    @SpringBootApplication
    @EnableHystrixDashboard
    public class HystrixDashboardApplication9001 {
        public static void main(String[] args) {
            SpringApplication.run(HystrixDashboardApplication9001.class, args);
        }
    }
    
  4. 被监控的项目配置

    /**
     * 此配置是为了服务监控为配置,与服务容错本身无关,SpringCloud升级后的bug
     * ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream"
     * z只要在自己的项目中配置如下的servlet即可
     */
    @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;
    }
    

    被监控的项目需要有如下的依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  5. 访问测试

    http://localhost:9001/hystrix
    

    输入监控的项目地址

    http://localhost:8001/hystrix.stream
    

# 8. Gateway

SprngCloud GatewaySpringCloud的一个全新项目,基于Spring5.0SprngBoot2.0Project Reator等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。

SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.xReactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty

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

# 8.1 基本概念

路由Route:路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由

断言Predicate:开发人员可以匹配请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

过滤Filter:指的是Spring框架中的GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后进行修改

# 8.2 案例

  1. 新建模块cloud-gateway-gateway9527

  2. 导入依赖,注意:网关不能引入web的依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
  3. 配置文件

    server:
        port: 9527
    spring:
        application:
            name: cloud-gateway
        cloud:
            gateway:
                routes:
                    - id: cloud-provider-payment1   # 路由的id,没有固定规则但要求唯一,建议配合服务名使用
                      uri: http://localhost:8001    # 匹配后提供服务的路由地址
                      predicates:
                          - Path=/payment/get/**    # 断言,路径相匹配的进行路由
    
                    - id: cloud-provider-payment2   # 路由的id,没有固定规则但要求唯一,建议配合服务名使用
                      uri: http://localhost:8001    # 匹配后提供服务的路由地址
                      predicates:
                          - Path=/payment/lb/**     # 断言,路径相匹配的进行路由
    eureka:
        client:
            register-with-eureka: true
            fetch-registry: true
            service-url:
                defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/
    
  4. 代码方式配置

    @Configuration
    public class GatewayConfig {
        @Bean
        public RouteLocator routes(RouteLocatorBuilder builder) {
            return builder
                    .routes()
                    .route(
                            "path",
                            r -> r.path("/guonei").uri("http://news.baidu.com/guonei")
                    ).build();
        }
    }
    

# 8.3 动态路由

修改配置文件即可

server:
    port: 9527
spring:
    application:
        name: cloud-gateway
    cloud:
        gateway:
        	discovery:
                locator:
                    enabled: true               # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
            routes:
                - id: cloud-provider-payment1   # 路由的id,没有固定规则但要求唯一,建议配合服务名使用
                  uri: lb://cloud-provider-payment
                  predicates:
                      - Path=/payment/get/**    # 断言,路径相匹配的进行路由

                - id: cloud-provider-payment2   # 路由的id,没有固定规则但要求唯一,建议配合服务名使用
                  uri: lb://cloud-provider-payment
                  predicates:
                      - Path=/payment/lb/**     # 断言,路径相匹配的进行路由

eureka:
    client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
            defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/

# 8.4 断言

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。Spring Cloud GateWay包括许多内置的Route Predicate工厂。素有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合

Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给RouteSpring Cloud Gateway包含许多内置的Route Predicate Factories

所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and

  1. 在什么时间之后有效

    predicates:
        - Path=/payment/lb/**     
        - After=2022-03-13T22:40:32.588+08:00[Asia/Shanghai]
    
  2. 在什么时间之前有效

    predicates:
        - Path=/payment/lb/**     
        - Before=2022-03-13T22:40:32.588+08:00[Asia/Shanghai]
    
  3. 在什么时间之间有效

    predicates:
        - Path=/payment/lb/** 
        - Between=2022-03-13T22:40:32.588+08:00[Asia/Shanghai], 2022-03-13T22:50:32.588+08:00[Asia/Shanghai]
    
  4. cookie

    predicates:
        - Path=/payment/lb/** 
        - Cookie=cookieName, RegExp
    

    第一个参数为cookie名称,第二个为正则表达式

  5. 请求头

    predicates:
        - Path=/payment/lb/** 
        - Header=Authorization, \d+
    
  6. 限制主机请求

    predicates:
        - Path=/payment/lb/** 
    	- Host=**.example.com
    
  7. 限定请求方法

    predicates:
        - Path=/payment/lb/** 
    	- Method=GET,POST
    
  8. 请求参数

    predicates:
        - Path=/payment/lb/** 
        - Query=username, \d+
    

# 8.5 Filter

路由过滤器可用与修改进入的HTTP请求和返回的HTTP响应,路由过滤器只指定路由进行使用。Spring Cloud Gateway内置了多种路由过滤器,它们都由GatewayFilter的工厂类产生

过滤器的配置与断言相似,过滤器配置参考 (opens new window)

filters:
	- AddRequestHeader=X-Request-red, blue

这个配置会在所有的请求上添加一个请求头

自定义过滤器

@Component
public class LogGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("===========拦截请求=============" + new Date());
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null || uname.length() == 0) {
            System.out.println("===========非法用户===========");
            // 不通过返回
            return exchange.getResponse().setComplete();
        }
        // 验证通过放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

# 9. Config

# 9.1 理论知识

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

SpringCloud Config分为服务端和客户端两部分。

服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接服务器并为客户端提供获取配置信息,加密/解密信息等访问接口

客户端则通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容

# 9.2 服务端搭建

  1. github (opens new window)上新建一个仓库,有如下目录结构

    config-dev.yaml
    config-test.yaml
    config-prod.yaml
    
  2. 新建模块cloud-config-center3344

  3. 导入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    
  4. 配置文件

    server:
        port: 3344
    spring:
        application:
            name: cloud-config-center
        cloud:
            config:
                server:
                    git:
                        uri: https://github.com/xxx/springcloud-config.git       # 远程仓库地址
                        search-paths: springcloud-config                         # 配置文件所在目录
                label: main                                                  	 # 分支名
    eureka:
        client:
            register-with-eureka: true
            fetch-registry: true
            service-url:
                defaultZone: http://eureka7001.com:7001/eureka/
    
  5. 启动类

    @EnableConfigServer
    @EnableEurekaClient
    @SpringBootApplication
    public class ConfigCenterApplication3344 {
        public static void main(String[] args) {
            SpringApplication.run(ConfigCenterApplication3344.class, args);
        }
    }
    
  6. 测试

    http://localhost:3344/main/config-dev.yaml
    http://localhost:3344/main/config-test.yaml
    http://localhost:3344/main/config-prod.yaml
    

# 9.3 客户端搭建

  1. 新建模块cloud-config-client3355

  2. 导入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-client</artifactId>
    </dependency>
    
  3. 新建一个bootstrap.yaml

    server:
        port: 3355
    spring:
        application:
            name: cloud-config-client
        cloud:
            config:
                label: main                 # 分支名称
                name: config                # 配置文件名称
                profile: dev                # 环境
                uri: http://localhost:3344  # 配置中心地址
                # 自动拼接为 http://localhost:3344/main/config-dev.yaml
    

这样会存在一个问题,虽然我们能够读取配置中心的文件,但是它不会动态刷新,每次修改配置文件都要重启客户端(服务端无影响)

需要如下配置

  1. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. 修改bootstrap.yaml

    management:
        endpoints:
            web:
                exposure:
                    include: "*"
    
  3. 在需要动态刷新的组件上添加注解

    @RefreshScope
    
  4. 向客户端发送一个POST请求

    curl -X POST "http://localhost:3355/actuator/refresh"
    

# 10. BUS

SpringCloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新

SpringCloud Bus是用来将分布式系统的节点与轻量级消息系统连接起来的框架,它整合了Java时间处理机制和消息中间件的功能。SpringCloud Bus目前支持RabbitMQKafka

# 10.1 基本概念

总线:在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中素有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和浪费,所以称为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息

基本原理:ConfigClient实例都监听MQ中同一个topic(默认是SpringCloudBus).当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其他监听同一个Topic的服务就能得到通知,然后更新自身的配置

两种不同的方式触发

  1. 客户端变动,导致整体变动
  1. 配置中心变动,导致整体变动

显然第二种架构更为合适

# 10.2 RabbitMQ

  1. 安装Erlang (opens new window)RabbitMQ依赖Erlang,记得配置ERLANG_HOME

  2. 安装RabbitMQ (opens new window)

  3. 进入安装目录的sbin,执行如下命令

    rabbitmq-plugins enable rabbitmq_management
    
  4. 启动RabbitMQ

    rabbitmq-server.bat
    
  5. 访问测试

    http://localhost:15672/
    

    默认用户名密码为guest

# 10.3 案例演示

目前需要完成的功能

  1. 配置中心以及被推送的模块都需要此依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. 添加相关配置

    spring:
        rabbitmq:
            host: localhost
            port: 5672
            username: guest
            password: guest
    # 这个仅需配置中心配置
    management:
        endpoints:
            web:
                exposure:
                    include: "bus-refresh"
    
  3. 给配置中心发送一次POST请求,便会通知所有的客户端

    curl -X POST http://localhost:3344/actuator/bus-refresh
    
  4. 定点通知,仅通知某一个客户端

    curl -X POST http://localhost:3344/actuator/bus-refresh/{destination}
    

    destination

    服务名:端口号
    

# 11. Stream

消息驱动:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型

应用程序通过inputs或者outputs来与Spring Cloud Streambinder对象交互。通过我们配置来绑定binding,而Spring Cloud Streambinder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动方式

通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。

目前仅支持RabbitMQKafka

# 11.1 常用概念


流程图
  1. Binder:用于连接各种中间件,屏蔽差异
  2. Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
  3. SourceSink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入

架构图
组成 说明
Middleware 中间件,目前仅支持Kafka
Binder Binder是应用与消息中间件之间的封装,目前实现了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现
@Input 注解标识输入通道,通过该输入通道接受到的消息进入应用程序
@Output 注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener 监听队列,用于消费者的队列的消息接受
@EnableBinding 指信道和exchange绑定在一起

# 11.2 生产者

此次案例采用rabbitmq

  1. 新建模块cloud-stream-rabbitmq-provider8801

  2. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    
  3. 配置文件

    server:
        port: 8801
    spring:
        application:
            name: cloud-stream-provider
        cloud:
            stream:
                binders:                # 此处配置要绑定的rabbitmq的服务信息
                    defaultRabbit:      # 表示定义的名称,用于binding整合
                        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:
            register-with-eureka: true
            fetch-registry: true
            service-url:
                defaultZone: http://eureka7001.com:7001/eureka/
    
  4. service

    public interface MessageProvider {
        public String send();
    }
    
    @EnableBinding(Source.class)        // 消息推送的管道
    public class MessageProviderImpl implements MessageProvider {
        @Autowired
        private MessageChannel output;  // 消息发送管道
    
        @Override
        public String send() {
            String content = UUID.randomUUID().toString();
            output.send(MessageBuilder.withPayload(content).build());
            return null;
        }
    }
    
  5. controller

    @RestController
    public class SendMessageController {
        @Autowired
        private MessageProvider provider;
    
        @GetMapping("/sendMessage")
        public String sendMessage() {
            return provider.send();
        }
    }
    

# 11.3 消费者

  1. 新建模块cloud-stream-rabbitmq-consumer8802

  2. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    
  3. 配置文件

     server:
        port: 8802
    spring:
        application:
            name: cloud-stream-consumer
        cloud:
            stream:
                binders:                # 此处配置要绑定的rabbitmq的服务信息
                    defaultRabbit:      # 表示定义的名称,用于binding整合
                        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:
            register-with-eureka: true
            fetch-registry: true
            service-url:
                defaultZone: http://eureka7001.com:7001/eureka/
    
  4. 接受消息

    @Component
    @EnableBinding(Sink.class)
    public class MessageReceiverController {
        @Value("${server.port}")
        private String serverPort;
    
        @StreamListener(Sink.INPUT)
        public void input(Message<String> message) {
            System.out.printf("消费者%s, 接受到的消息为: %s\n", serverPort, message.getPayload());
        }
    }
    

    如果在生产者侧推送消息,则该组件会接收到推送的消息

# 11.4 分组

上述的案例中,如果消费者是集群部署的话,则会出现生产者推送一个消息,被所有消费者接收到,实际情况中我们需要避免这个问题

Stream中处于同一个group中的多个消费者是竞争关系,就够保证消息只会被其中一个应用消费一次

分组只需在配置文件中修改

server:
    port: 8803
spring:
    application:
        name: cloud-stream-consumer
    cloud:
        stream:
            binders:                # 此处配置要绑定的rabbitmq的服务信息
                defaultRabbit:      # 表示定义的名称,用于binding整合
                    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           # 设置要绑定的消息服务的具体配置
                    group: MessageA                 # 分组
eureka:
    client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
            defaultZone: http://eureka7001.com:7001/eureka/

添加分组后,消息会自动持久化,即如果服务重启,在服务重启期间生产者的消息会在服务上线之后继续推送

# 12. Sleuth

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

  1. 下载zipkin-server (opens new window)

  2. zipkin-server是一个可执行jar包,直接执行即可

  3. 访问测试

    http://127.0.0.1:9411/
    
  4. 被监控的服务需要引入依赖

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

    配置文件修改

    spring:
        zipkin:
            base-url: http://localhost:9411
        sleuth:
          sampler:
              probability: 1    # 采样率值介于0到1之间,1表示全部采集
    

# 13. Nacos

官方网站 (opens new window)

Nacos支持APCP模式的切换

C代表所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应

一般来说如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如Spring CloudDubbo服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。

如果需要在服务级别编辑或者存储配置信息,那么CP是必须的,K8SDNS服务则适用于CP模式。

CP模式下支持注册持久化实例,此时则以Raft协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误

# 13.1 注册中心

  1. 父工程中导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.2.2.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
    
  2. 新建一个模块cloud-alibaba-provider-payment9001

  3. 导入依赖

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
  4. 修改配置文件

    server:
        port: 9001
    spring:
        application:
            name: nacos-provider-payment
        cloud:
            nacos:
                discovery:
                    server-addr: 127.0.0.1:8848	# nacos的地址
    

提示:远程调用可以使用OpenFeign

# 13.2 配置中心

  1. 新建一个模块cloud-alibaba-config-nacos-client3377

  2. 导入依赖

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    
  3. 编写配置文件

    bootstrap.yaml

    server:
        port: 3377
    spring:
        application:
            name: nacos-config-client
        cloud:
            nacos:
                discovery:
                    server-addr: 127.0.0.1:8848     # nacos服务注册中心
                config:
                    server-addr: 127.0.0.1:8848     # nacos作为配置中心地址
                    file-extension: yaml            # 指定yaml格式的配置
    

    application.yaml:需要指定配置文件环境

    spring:
        profiles:
            active: dev
    
  4. Nacos的配置文件的格式为

    ${prefix}-${spring.profiles.active}.${file-extension}
    # 一般配置为服务名
    

    以上面的配置为例,这个文件需要建在nacos的配置中心中

    # nacos-config-client-dev.yaml
    
    config:
        version: 1
    
  5. 新建一个controller

    @RefreshScope
    @RestController
    public class NacosConfigController {
        @Value("${config.version}")
        private String version;
    
        @GetMapping("/config")
        public String getConfig() {
            return version;
        }
    }
    
  6. 测试已经可以动态修改了

# 13.3 基本概念

默认情况:

Namespace=public
Group=DEFAULT_GROUP
Cluster=DEFAULT
  1. Namespace命名空间,主要用来实现隔离。

    比方说我们现在有三个环境:开发、测试、生产环境,我们可以创建三个Namespace,不同的Namespace之间是隔离的。

  2. Group默认是DEFAULT_GROUPGroup可以把不同的微服务划分到同一个分组里面去

  3. Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是对指定微服务的一个虚拟划分。比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,这时候就可以给杭州机房的Service微服务起一个集群名称HZ,给广州机房的Service微服务起一个集权名称HZ,还可以尽量让同一个机房的微服务相互调用,以提升性能

  4. 最后就是instance,就是微服务的实例

# 13.4 使用详解

  1. DataId

    dataId就是在配置中心的文件名,可以根据spring.profiles.active更换不同的文件

  2. group如果要更换组的话,直接在config下更换即可

    server:
        port: 3377
    spring:
        application:
            name: nacos-config-client
        cloud:
            nacos:
                discovery:
                    server-addr: 127.0.0.1:8848     # nacos服务注册中心
                config:
                    server-addr: 127.0.0.1:8848     # nacos作为配置中心地址
                    file-extension: yaml            # 指定yaml格式的配置
                    group: DEV_GROUP                # 指定分组
    
  3. namespace,更换命名空间

    server:
        port: 3377
    spring:
        application:
            name: nacos-config-client
        cloud:
            nacos:
                discovery:
                    server-addr: 127.0.0.1:8848     # nacos服务注册中心
                config:
                    server-addr: 127.0.0.1:8848     # nacos作为配置中心地址
                    file-extension: yaml            # 指定yaml格式的配置
                    group: DEV_GROUP                # 指定分组
                    namespace: c0553a5e-ce37-4b9c-ad18-b8a4ad97fcee # 指定命名空间
    

# 13.5 持久化

默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前仅支持MySQL存储。

  1. nacos的安装目录下,会有一个conf的文件夹,找到nacos-mysql.sql将其导入

  2. 修改conf/application.properties

    spring.datasource.platform=mysql
    db.num=1
    db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
    db.user.0=nacos
    db.password.0=nacos
    

# 13.6 集群部署

  1. 请确保持久化工作已经完成

  2. conf/cluster.conf.example拷贝一份

    sudo cp cluster.conf.example cluster.conf
    
  3. 修改cluster.conf为自己的集群节点地址

    192.168.0.1:3344
    192.168.0.1:4444
    192.168.0.1:5555
    

    注:不能填写127.0.0.1

  4. 修改nginx配置文件

    upstream cluster {
        server 127.0.0.1:3333;
        server 127.0.0.1:4444;
        server 127.0.0.1:5555;
    }
    
    server {
        listen      1111;
        server_name localhost;
        location / {
            proxy_pass http://cluster;
        }
    }
    
  5. 分别启动三个nacos,对应端口号为333344445555

  6. 访问1111测试

# 14 Sentinel

下载 (opens new window)下来是一个jar包,直接使用java -jar启动即可。注意:请使用java8启动

启动后访问http://localhost:8080/,用户名密码默认为sentinel

# 14.1 实例演示

  1. 新建模块cloud-alibaba-sentinel-service8401

  2. 导入依赖

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    
  3. 由于sentinel使用懒加载机制,即服务需要被请求一次后才能在控制台监控到

# 14.2 流控规则

资源名:唯一名称,默认请求路径

针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)

阈值类型/单机阈值:

  • QPS(每秒钟的请求数量):当调用该apiQPS达到阈值时,进行限流

  • 线程数:当调用该api的线程数达到阈值时,进行限流

是否集群:不需要集群

流控模式:

  • 直接:api达到限流条件时,直接限流

  • 关联:当关联的资源达到阈值时,就限流自己

  • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】

    当B达到1时,会限制A的访问,应用场景:A为订单接口,B为支付接口,B因为流量过载,可以限制A的请求

流控效果:

  • 快速失败:直接失败,抛异常

  • Warm up:根据codeFactory(冷加载因子, 默认是3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值

  • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置QPS,否则无效

    冷启动,在预热时长内仅能达到单机阈值/codeFactor(默认3),预热时长之后QPS应为单机阈值

排队等待,控制请求以相同的时间间隔处理

# 14.3 熔断规则

配置相应规则即可

# 14.4 热点key

编写controller

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "consumerHandler")
public String testHostKey(@RequestParam(name = "p1", required = false) String p1,
                          @RequestParam(name = "p2", required = false) String p2) {
    return "-------TestHotKey";
}

public String consumerHandler(String p1,
                              String p2,
                              BlockException e) {
    return "--------降级方法";
}

SentinelResource指定的value值就是需要在sentinel中配置的资源名,blockHandler为降级方法

也可以对于特定的值进行特定的限定

# 14.5 降级方法

  1. 自定义降级方法

    public class CustomerBlockHandler {
        public static String handlerException(BlockException e) {
            return "全局异常处理: " + e.getMessage();
        }
    }
    
  2. controller

    @RestController
    public class RateLimitController {
        @GetMapping("/byResource")
        @SentinelResource(
                value = "byResource",
                blockHandlerClass = CustomerBlockHandler.class,
                blockHandler = "handlerException")
        public String byResource() {
            return "按资源名称名称限流测试OK";
        }
    }
    

# 14.6 异常处理

直接在SentinelResource注解中指定fallback的参数即可

@RestController
public class SentinelConsumerController {
    @Autowired
    private SentinelPaymentClient client;
    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/consumer/payment/{id}")
    @SentinelResource(value = "fallback", fallback = "handlerPayment")
    public String getPayment(@PathVariable(required = false) Integer id) {
        if (id == 2) {
            throw new RuntimeException("抛出异常");
        }
        return client.getPayment();
    }
    public String handlerPayment(Integer id) {
        return String.format("serverPort: %s异常处理界面", serverPort);
    }
}

如果fallbackblockHandler都配置的情况下,且达到流控的情况下,优先blockHandler

# 14.5 规则持久化

默认的流控的规则是跟随服务的生命周期

  1. 在需要持久化的服务里导入依赖

    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    
  2. 配置文件

    spring:
        application:
            name: cloud-alibaba-sentinel-service
        cloud:
            nacos:
                discovery:
                    server-addr: 127.0.0.1:8848
            sentinel:
                transport:
                    # sentinel dashboard的地址
                    dashboard: 127.0.0.1:8080
                    # 如果8080被占用,会自动从8719开始查找未被占用的端口
                    port: 8719
                # 持久化
                datasource:
                    ds1:
                        nacos:
                            server-addr: ${spring.cloud.nacos.discovery.server-addr}
                            dataId: ${spring.application.name}
                            groupId: DEFAULT_GROUP
                            data-type: json
                            rule-type: flow
    
  3. nacos中新建一个cloud-alibaba-sentinel-service

    [
        {
            "resource": "/testA",
            "limitApp": "default",
            "grade": 1,
            "count": 1,
            "strategy": 0,
            "controlBehavior": 0,
            "clusterMode": false
        }
    ]
    

    resource:资源名称

    limitApp:来源应用

    grade:阈值类型,0表示线程数,1表示QPS

    count:单机阈值

    strategy:流控模式,0表示直接,1表示关联,2表示链路

    controlBehavior:流控效果,0表示快速失败,1表示Warm Up2表示排毒等待

    clusterMode:是否集群

# 15. Seata

分布式事务:一次业务需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。

Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务

# 15.1 基本概念

Transaction ID:全局唯一的事务ID

Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚

Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议

Transaction Manager(RM):控制分支事务,负责分支注册、状态汇报,并接受事务协调器的指令,驱动分支(本地)事务的提交和回滚

  1. TMTC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;
  2. XID在微服务调用链路的上下文中传播
  3. RMTC注册分支事务,将其纳入XID对应全局事务的管辖
  4. TMTC 发起针对XID的全局提交或回滚决议
  5. TC调度XID下管辖的全部分支事务完成提交或回滚请求

# 15.2 下载配置

  1. 官网下载 (opens new window)

  2. 修改conf/file.conf

    # 更改为数据库存储
    store.mode = "db"
    
    # 数据库配置
    store.db
    
  3. 新建一个seata数据库,导入sql (opens new window)

  4. 修改conf/registry.conf

    type="nacos"
    
    # 修改nacos相关的配置
    nacos {}
    
  5. 先启动Nacos,在启动Seata

# 15.3 数据库搭建

  1. 创建三个库

    CREATE DATABASE seata_order;
    USE seata_order;
    CREATE TABLE t_order(
        id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
        user_id BIGINT(11) DEFAULT NULL COMMENT '用户id',
        product_id BIGINT(11) DEFAULT NULL COMMENT '产品id',
        count INT(11) DEFAULT NULL COMMENT '数量',
        money DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
        status INT(1) DEFAULT NULL COMMENT '订单状态:0创建中,1已完结'
    )ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
    
    CREATE DATABASE seata_storage;
    USE seata_storage;
    CREATE TABLE t_storage(
        id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
        product_id BIGINT(11) DEFAULT NULL COMMENT '产品id',
        total INT(11) DEFAULT NULL COMMENT '总库存',
        used INT(11) DEFAULT NULL COMMENT '已用库存',
        residue INT(11) DEFAULT NULL COMMENT '剩余库存'
    )ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
    INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1,1,100,0,100);
    
    CREATE DATABASE seata_account;
    USE seata_account;
    CREATE TABLE t_account(
        id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
        user_id BIGINT(11) DEFAULT NULL COMMENT '用户id',
        total DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
        used DECIMAL(10,0) DEFAULT NULL COMMENT '已用额度',
        residue DECIMAL(10,0) DEFAULT 0 COMMENT '剩余可用额度'
    )ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
    INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1,1,1000,0,1000);
    
  2. 每个数据库下面都需要一张日志表 (opens new window)

最后修改时间: 5 minutes ago