springcloud

SpringCloud微服务架构理论入门

boot和cloud版本选型

Spring Cloud

{
  "git": {
    "branch": "840b0c1351793b1b32adfa273eadc917caab250b",
    "commit": {
      "id": "840b0c1",
      "time": "2022-08-10T11:24:16Z"
    }
  },
  "build": {
    "version": "0.0.1-SNAPSHOT",
    "artifact": "start-site",
    "versions": {
      "spring-boot": "2.7.2",
      "initializr": "0.13.0-SNAPSHOT"
    },
    "name": "start.spring.io website",
    "time": "2022-08-10T11:40:35.590Z",
    "group": "io.spring.start"
  },
  "bom-ranges": {
    "codecentric-spring-boot-admin": {
      "2.4.3": "Spring Boot >=2.3.0.M1 and <2.5.0-M1",
      "2.5.6": "Spring Boot >=2.5.0.M1 and <2.6.0-M1",
      "2.6.8": "Spring Boot >=2.6.0.M1 and <2.7.0-M1",
      "2.7.4": "Spring Boot >=2.7.0.M1 and <3.0.0-M1",
      "3.0.0-M4": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
    },
    "solace-spring-boot": {
      "1.1.0": "Spring Boot >=2.3.0.M1 and <2.6.0-M1",
      "1.2.1": "Spring Boot >=2.6.0.M1 and <2.7.0-M1"
    },
    "solace-spring-cloud": {
      "1.1.1": "Spring Boot >=2.3.0.M1 and <2.4.0-M1",
      "2.1.0": "Spring Boot >=2.4.0.M1 and <2.6.0-M1",
      "2.3.0": "Spring Boot >=2.6.0.M1 and <2.7.0-M1"
    },
    "spring-cloud": {
      "Hoxton.SR12": "Spring Boot >=2.2.0.RELEASE and <2.4.0.M1",
      "2020.0.6": "Spring Boot >=2.4.0.M1 and <2.6.0-M1",
      "2021.0.0-M1": "Spring Boot >=2.6.0-M1 and <2.6.0-M3",
      "2021.0.0-M3": "Spring Boot >=2.6.0-M3 and <2.6.0-RC1",
      "2021.0.0-RC1": "Spring Boot >=2.6.0-RC1 and <2.6.1",
      "2021.0.3": "Spring Boot >=2.6.1 and <3.0.0-M1",
      "2022.0.0-M1": "Spring Boot >=3.0.0-M1 and <3.0.0-M2",
      "2022.0.0-M2": "Spring Boot >=3.0.0-M2 and <3.0.0-M3",
      "2022.0.0-M3": "Spring Boot >=3.0.0-M3 and <3.0.0-M4",
      "2022.0.0-M4": "Spring Boot >=3.0.0-M4 and <3.1.0-M1"
    },
    "spring-cloud-azure": {
      "4.3.0": "Spring Boot >=2.5.0.M1 and <3.0.0-M1"
    },
    "spring-cloud-gcp": {
      "2.0.11": "Spring Boot >=2.4.0-M1 and <2.6.0-M1",
      "3.3.0": "Spring Boot >=2.6.0-M1 and <2.7.0-M1"
    },
    "spring-cloud-services": {
      "2.3.0.RELEASE": "Spring Boot >=2.3.0.RELEASE and <2.4.0-M1",
      "2.4.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1",
      "3.3.0": "Spring Boot >=2.5.0-M1 and <2.6.0-M1",
      "3.4.0": "Spring Boot >=2.6.0-M1 and <2.7.0-M1",
      "3.5.0": "Spring Boot >=2.7.0-M1 and <3.0.0-M1"
    },
    "spring-geode": {
      "1.3.12.RELEASE": "Spring Boot >=2.3.0.M1 and <2.4.0-M1",
      "1.4.13": "Spring Boot >=2.4.0-M1 and <2.5.0-M1",
      "1.5.14": "Spring Boot >=2.5.0-M1 and <2.6.0-M1",
      "1.6.10": "Spring Boot >=2.6.0-M1 and <2.7.0-M1",
      "1.7.2": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
      "2.0.0-M4": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
    },
    "spring-shell": {
      "2.1.0": "Spring Boot >=2.7.0 and <3.0.0-M1"
    },
    "vaadin": {
      "14.8.13": "Spring Boot >=2.1.0.RELEASE and <2.6.0-M1",
      "23.1.4": "Spring Boot >=2.6.0-M1 and <2.8.0-M1"
    },
    "wavefront": {
      "2.0.2": "Spring Boot >=2.1.0.RELEASE and <2.4.0-M1",
      "2.1.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1",
      "2.2.2": "Spring Boot >=2.5.0-M1 and <2.7.0-M1",
      "2.3.0": "Spring Boot >=2.7.0-M1 and <3.0.0-M1"
    }
  },
  "dependency-ranges": {
    "native": {
      "0.9.0": "Spring Boot >=2.4.3 and <2.4.4",
      "0.9.1": "Spring Boot >=2.4.4 and <2.4.5",
      "0.9.2": "Spring Boot >=2.4.5 and <2.5.0-M1",
      "0.10.0": "Spring Boot >=2.5.0-M1 and <2.5.2",
      "0.10.1": "Spring Boot >=2.5.2 and <2.5.3",
      "0.10.2": "Spring Boot >=2.5.3 and <2.5.4",
      "0.10.3": "Spring Boot >=2.5.4 and <2.5.5",
      "0.10.4": "Spring Boot >=2.5.5 and <2.5.6",
      "0.10.5": "Spring Boot >=2.5.6 and <2.5.9",
      "0.10.6": "Spring Boot >=2.5.9 and <2.6.0-M1",
      "0.11.0-M1": "Spring Boot >=2.6.0-M1 and <2.6.0-RC1",
      "0.11.0-M2": "Spring Boot >=2.6.0-RC1 and <2.6.0",
      "0.11.0-RC1": "Spring Boot >=2.6.0 and <2.6.1",
      "0.11.0": "Spring Boot >=2.6.1 and <2.6.2",
      "0.11.1": "Spring Boot >=2.6.2 and <2.6.3",
      "0.11.2": "Spring Boot >=2.6.3 and <2.6.4",
      "0.11.3": "Spring Boot >=2.6.4 and <2.6.6",
      "0.11.5": "Spring Boot >=2.6.6 and <2.7.0-M1",
      "0.12.0": "Spring Boot >=2.7.0-M1 and <2.7.1",
      "0.12.1": "Spring Boot >=2.7.1 and <3.0.0-M1"
    },
    "okta": {
      "1.4.0": "Spring Boot >=2.2.0.RELEASE and <2.4.0-M1",
      "1.5.1": "Spring Boot >=2.4.0-M1 and <2.4.1",
      "2.0.1": "Spring Boot >=2.4.1 and <2.5.0-M1",
      "2.1.5": "Spring Boot >=2.5.0-M1 and <2.7.0-M1"
    },
    "mybatis": {
      "2.1.4": "Spring Boot >=2.1.0.RELEASE and <2.5.0-M1",
      "2.2.2": "Spring Boot >=2.5.0-M1"
    },
    "camel": {
      "3.5.0": "Spring Boot >=2.3.0.M1 and <2.4.0-M1",
      "3.10.0": "Spring Boot >=2.4.0.M1 and <2.5.0-M1",
      "3.13.0": "Spring Boot >=2.5.0.M1 and <2.6.0-M1",
      "3.17.0": "Spring Boot >=2.6.0.M1 and <2.7.0-M1",
      "3.18.0": "Spring Boot >=2.7.0.M1 and <3.0.0-M1"
    },
    "picocli": {
      "4.6.3": "Spring Boot >=2.4.0.RELEASE and <3.0.0-M1"
    },
    "open-service-broker": {
      "3.2.0": "Spring Boot >=2.3.0.M1 and <2.4.0-M1",
      "3.3.1": "Spring Boot >=2.4.0-M1 and <2.5.0-M1",
      "3.4.1": "Spring Boot >=2.5.0-M1 and <2.6.0-M1",
      "3.5.0": "Spring Boot >=2.6.0-M1 and <2.7.0-M1"
    }
  }
}

cloud组件停更说明,Nacos很重要

订单-支付模块微服务

DependencyManagement与Dependencies

支付模块构建

订单模块调用支付模块

测试post请求时,要用postman,浏览器不支持post请求

热部署Devtools(开发时开启,生产上线时关闭)

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

2.

<build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>2.2.2.RELEASE</version>
        <configuration>
          <fork>true</fork>
          <addResources>true</addResources>
        </configuration>
      </plugin>
    </plugins>
  </build>

3.

4.按住shift+ctrl+alt+/

消费者订单模块

RestTemplate (Spring Framework 5.2.2.RELEASE API)

80端口到8001端口的远程调用,接口方式封装

配置类,两个注解一定要加。写完后就可以在controller层注入

package com.atguigu.springcloud.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {

    @Bean
    public RestTemplate getRestTemplate(){

        return new RestTemplate();
    }
}
//相当于在xml里
//ApplicationContext.xml <bean id="" class="">

注入后在controller层使用restTemplate

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderController {

    public static final String PAYMENT_URL  = "http://localhost:8001";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")   //客户端一般是get,consumer代表客户端,消费者
    public CommonResult<Payment> create(Payment payment){
        //三个参数,读操作用post,写操作用get
        return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){

        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }
}

80模块里没有dao这些,是调用8001的。

所以先跑8001,再跑80。出现端口被占用,改为了8081

插入时显示插入成功,而数据库只有主键

需要在8001,支付模块里的controller层方法里边加入注解

如果service没有出来,右键项目show in 后,打开》idea,打开workspace.xml

工程重构

发现两个模块都有相同的entities包,包里有相同的类

自定义包的pom
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>

把entities包复制进来,然后使用MAVEN clean,install,最后把坐标导入原来两个的模块,删除entities

<dependency><!--引入定义的api通用包,可以使用payment支付entity-->
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

Eureka

Eureka基础知识

两个组件

  • Server提供服务注册服务

  • Client通过注册中心进行访问

Server一段时间没收到client的消息后,会把它移除

单机Eureka构建步骤

EurekaServer服务器安装

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

支付服务8001入住EurekaServer

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

主启动类 @EnableEurekaClient

需要保持一致

订单微服务80入住EurekaServer

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

主启动类 @EnableEurekaClient

Eureka集群

原理

eureka集群环境构建

7001与7002相互绑定

server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com  #eureka服务端的实例名称
  client:
    register-with-eureka: false #false表示不向注册中心注册自己。
    fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/

再写主启动类

订单支付两微服务注册Eureka集群

修改两个yml文件

支付微服务集群配置

新建cloud-provider-payment8002;跟8001相同的配置

  • 先修改8001,8002也修改一样的。看看哪个端口被使用

//获得端口号,有8001,8002两个,看是哪个
@Value("${server.port}")
private String serverPort;
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {
    //controller调service,service调dao

    @Resource
    private PaymentService paymentService;

    //获得端口号,有8001,8002两个,看是哪个
    @Value("${server.port}")
    private String serverPort;

    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody Payment payment){
        int result = paymentService.create(payment);
        log.info("*****插入结果:"+result);
        if(result > 0) {
            return  new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
        }else{
            return  new CommonResult(444,"插入数据库失败, ",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);

        if(payment != null) {
            return  new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
        }else{
            return  new CommonResult(444,"没有对应记录,查询ID:"+id,null);
        }
    }
}

测试的bug:不管怎么刷新请求,请求的端口号一直是8001,原因是我们在80的controller中写死了

原因:写死了路径,改为eureka上的

///不要把地址写死
//public static final String PaymentSrv_URL = "http://localhost:8001";
// 通过在eureka上注册过的微服务名称调用
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

然后再测试,又出现bug

原因:现在注册中心不再是暴露出具体的端口号,而是微服务名称 CLOUD-PAYMENT-SERVICE,但是这个微服务名称代表的集群中有很多个,比如8001、8002…用哪个,它并不知道

@LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力

actuator微服务信息完善

1、主机名称:服务名称修改

(1)当前问题

含有主机名称

在8001跟8002里修改yml,加上这个

  instance:
    instance-id: payment8001
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      #defaultZone: http://localhost:7001/eureka
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  # 集群版
  instance:
    instance-id: payment8001

修改之后

2、访问信息有IP信息提示

(1)当前问题

没有IP提示

修改8001和8002yml,加上这个

    prefer-ip-address: true     #访问路径可以显示IP地址
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      #defaultZone: http://localhost:7001/eureka
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  # 集群版
  instance:
    instance-id: payment8002
    prefer-ip-address: true     #访问路径可以显示IP地址

服务发现discovery

修改8001和8002的controller和主启动类

加上

@Resource
private DiscoveryClient discoveryClient;

//相当于获得一层楼的公司列表,再获得具体公司的服务
@GetMapping(value = "/payment/discovery")
    public Object discovery(){
        //微服务名称有哪几个,获得的是一个服务清单列表
        List<String> services = discoveryClient.getServices();
        for (String element : services) {
            log.info("****element"+element);
        }

        //获得微服务名称后,进一步获得微服务的信息,看看一个微服务下面的具体实例
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance instance : instances) {
            log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }
        return this.discoveryClient;
    }
@EnableDiscoveryClient      //在主启动类加上

Eureka自我保护理论

故障现象

类似公司暂时没交钱,大楼不会马上清空

禁止自我保护

只测7001和8001

改7001的yml,改为单机模式后加上后面的

改8001的yml,改为单机模式后,再把默认配置的值改小

已关闭自我保护,8001也成功入住

8001会消失

支付服务注册进zookeeper(没做)

Consul

Consul简介

是什么

能干啥

安装并运行Consul

1、在软件界面直接cmd

2、然后consul --version:查看 consul 版本号

3、再使用开发模式启动

consul agent -dev

4、通过以下地址可以访问Consul的首页:http://localhost:8500

服务提供者注册进Consul

1、创建支付服务8006模块

2、改pom

<!--SpringCloud consul-server -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

3、写yml

###consul服务端口号
server:
  port: 8006

spring:
  application:
    name: consul-provider-payment
  ####consul注册中心地址
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        #hostname: 127.0.0.1
        service-name: ${spring.application.name}

4、写主启动类

5、写controller

服务消费者注册Consul

1、新建Module消费服务order80

2、改pom

3、写yml

4、写主启动类

5、配置bean

6、写controller

三个注册中心异同点

1、CAP

C:Consistency(强一致性)

A:Availability(可用性)

P:Partition tolerance(分区容错性)

CAP理论关注粒度是数据,而不是整体系统设计的策略

2、经典CAP图

最多只能同时较好的满足两个。

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三大类:

  • CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大

  • CP - 满足一致性,分区容忍必的系统,通常性能不是特别高

  • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些

AP(类似淘宝,京东,为了可用性,舍弃c)

AP(Eureka):

AP架构:

当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。

结论:违背了一致性C的要求,只满足可用性和分区容错,即AP

CP(类似上班,来就是来了,没来就是没来)

CP(Zookeeper/Consul) 临时性

CP架构:

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性

结论:违背了可用性A的要求,只满足一致性和分区容错,即CP

Ribbon

入门

官网

https://github.com/Netflix/ribbon/wiki/Getting-Started

Ribbon目前也进入维护模式 https://github.com/Netflix/ribbon

负载均衡:

LB负载均衡(Load Balance)是什么?

  • 简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用),常见的负载均衡有软件Nginx,LVS,硬件 F5等

Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别?

  • Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的

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

集中式LB(类似医院)

即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;

进程内LB(类似医院的科室)

将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器

Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址

负载均衡+RestTemplate调用

负载均衡和rest调用

Ribbon在工作时分成两步:

第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server

第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址;其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权

总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。

POM 中既可以加 ribbon 的依赖也可以不用加,因为 eureka 已经集成了 ribbon

二说

getForObject方法/getForEntity方法

getForObject方法:

返回对象为响应体中数据转化成的对象,基本上可以理解为Json

getForEntity方法:

返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等

object是短的字符串,足够用

Ribbon核心组件IRule

IRule:根据特定算法中从服务列表中选取一个要访问的服务

  • com.netflix.loadbalancer.RoundRobinRule:轮询

  • com.netflix.loadbalancer.RandomRule:随机

  • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务

  • WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择

  • BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

  • AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例

  • ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器

如何替换

这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

即不能放在主启动类所在的包下,因为@SpringBootApplication包含了@ComponentScan,它会扫描这个类所在的包及其子包

默认是轮询,把轮询改为随机

@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule(){
        return new RandomRule();//定义为随机
    }
}
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)

测试后不再是8001和8002交替出现,而是随机的

Ribbon负载均衡算法

原理

源码

看视频

手写轮询算法

不会

OpenFeign

概述

官网

Spring Cloud

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

Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可

GitHub:https://github.com/spring-cloud/spring-cloud-openfeign

Feign可以简化操作,使用注解就可以完成ribbon的操作

服务接口绑定器

Feign和OpenFeign的区别:

使用步骤

现在变成OpenFeign

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

主启动类需要加上 表示说明是feign,激活并开启

@EnableFeignClients

新建service包,再建PaymentFeignService接口

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    //直接调8001的controller
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}

再写controller

@RestController
@Slf4j
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    //先找CLOUD-PAYMENT-SERVICE的/payment/get/{id},实际上是调8001的controller
    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        return paymentFeignService.getPaymentById(id);
    }

}

启动集群7001,7002,8001,8002,80

总结

超时控制

模拟超时

在8001controller

@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
    //暂停几秒钟线程
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return serverPort;

}

在80service接口加入

@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();

在80controller加入

@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
    return paymentFeignService.paymentFeignTimeout();
}

启动7001,7002,8001,80

测试:8001自己能等3秒,但是openfeign默认是等待一秒,所以报错

OpenFeign超时控制是什么?

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

OpenFeign默认支持Ribbon

YML文件里需要开启OpenFeign客户端超时控制

80的yaml文件中增加

#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ReadTimeout: 5000
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ConnectTimeout: 5000

日志打印功能

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。 说白了就是对Feign接口的调用情况进行监控和输出

日志级别

  • NONE:默认的,不显示任何日志

  • BASIC:仅记录请求方法、URL、响应状态码及执行时间

  • HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息

  • FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据

在80中创建配置类

配置日志bean

@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

YML文件里需要开启日志的Feign客户端

logging:
  level:
    # feign日志以什么级别监控哪个接口
    com.atguigu.springcloud.service.PaymentFeignService: debug

Hystrix断路器

hystrix概述

分布式面临问题

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败

服务雪崩:

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

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和,比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加、备份队列、线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统,所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩

是什么

能干什么

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

官网资料:https://github.com/Netflix/Hystrix/wiki/How-To-Use

Hystrix官宣,停更进维:https://github.com/Netflix/Hystrix

被动修复bugs、不再接受合并请求、不再发布新版本

hystrix重要概念

服务降级(返回友好信息)

服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback

哪些情况会出发降级?

  • 程序运行异常

  • 超时

  • 服务熔断触发服务降级

  • 线程池/信号量打满也会导致服务降级

服务熔断(直接拒绝访问)

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

就是保险丝 : 服务的降级->进而熔断->恢复调用链路

服务限流

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

hystrix案列

构建模块hystrix8001

<!--hystrix-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
server:
  port: 8001

spring:
  application:
    name: cloud-provider-hystrix-payment
  main:
    allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
      defaultZone: http://eureka7001.com:7001/eureka
@Service
public class PaymentService {

    /**
     * 正常访问,肯定ok
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id){
        return "线程池:" + Thread.currentThread().getName() + "paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O";
    }

    public String paymentInfo_TimeOut(Integer id){
        int timeNumber = 3;
        try {
            TimeUnit.SECONDS.sleep(timeNumber);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOut,id: " + id + "\t" + "O(∩_∩)O" + "耗时:"+timeNumber+"秒钟";
    }

}
@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_OK(id);
        log.info("****result"+result);
        return result;
    }

    @GetMapping(value = "/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("****result"+result);
        return result;
    }
}

高并发测试

上述在非高并发情形下,还能勉强满足

Jmeter压测测试:

开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务

再来一个访问:http://localhost:8001/payment/hystrix/ok/1

看演示结果:两个都在自己转圈圈,为什么会被卡死?

  • tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理

构建 hystrix-order80 再压测

cloud-consumer-feign-hystrix-order80

<!--openfeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
server:
  port: 8081

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class, args);
    }
}
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {

    @GetMapping(value = "/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping(value = "/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping(value = "/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

}

测试正常

高并发测试:

故障现象和导致原因

  • 8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕

  • 80此时调用8001,客户端访问响应缓慢,转圈圈

上诉结论:正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生

如何解决?解决的要求

超时导致服务器变慢(转圈):超时不再等待

出错(宕机或程序运行出错):出错要有兜底

解决:

  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级

  • 对方服务(8001)宕机了,调用者(80)不能一直卡死等待,必须有服务降级

  • 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者)自己处理降级

服务降级(即可以放在消费端也可以放在服务端)一般放在客户端

8001fallback

@Service
public class PaymentService {
    /**
     * 设置时间是3秒,而OK方法里是5秒,3秒<5秒,所以必定出错,就会走下面handler的方法
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "paymentInfoTime_OutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
    })
    public String paymentInfo_TimeOut(Integer id){
        int timeNumber = 5;
        try {
            TimeUnit.SECONDS.sleep(timeNumber);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOut,id: " + id + "\t" + "O(∩_∩)O" + "耗时:"+timeNumber+"秒钟";
    }

    public String paymentInfo_TimeOutHandler(Integer id){
        return "线程池:" + Thread.currentThread().getName() + "系统忙或者运行报错,请稍后再试,id: " + id + "\t" + "(ㄒoㄒ)" ;
    }

}
//主启动类加注解   @EnableCircuitBreaker   表示激活并开启

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

测试:走的是下面的方法

80fallback

yml加上

在主启动类加上注解 @EnableHystrix

在controller修改

@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
    String result = paymentHystrixService.paymentInfo_TimeOut(id);
    return result;
}

public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
    return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}

是否调用兜底fallback方法是取决于 @HystrixProperty 中的 value = "1500",只要这个value的值大于服务端8001的睡眠时间或进来就直接异常,比如说 10/0 就会走80的fallback方法,

目前问题和解决方案

在controller配置 @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")

并加上全局fallback处理方法,注掉原来的fallback

@HystrixCommand //加了@DefaultProperties属性注解,并且没有写具体方法名字,就用统一全局的
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
//    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
//            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
//    })
    @HystrixCommand //加了@DefaultProperties属性注解,并且没有写具体方法名字,就用统一全局的
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        int age = 10/0;
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
    }

    //下面是全局fallback方法
    public String payment_Global_FallbackMethod() {
        return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
    }


}

测试

controller层里是业务,fallcak不是业务,fallback和业务逻辑混一起???混乱

新建类实现接口,如果正常就正常调用;如果异常就找到实现类,实现类进行处理。这样就可以controller层分开

@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "----PaymentFallbackService fall back paymentInfo_TimeOut,o(╥﹏╥)o";
    }
}

在接口中把实现类配上

测试

故意关闭微服务8001,客户端自己调用提示,此时服务端provider已经down了,但是我们做了服务降级处理, 让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

服务熔断

大神论文: CircuitBreaker

熔断:断开后可以慢慢恢复

修改:cloud-provider-hystrix-payment8001

PaymentService加上这个方法

//====服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_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"),// 失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
    if (id < 0) {
        throw new RuntimeException("******id 不能负数");
    }
    String serialNumber = IdUtil.simpleUUID(); //这是api调用的产生随机id

    return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
}

public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
    return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " + id;
}

为什么要配参数 请求次数(10)达到60%失败就跳闸

controller

// ====服务熔断=====
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
    String result = paymentService.paymentCircuitBreaker(id);
    log.info("****result: " + result);
    return result;
}

测试

open->half open ->close

狂点负数后,正数也会失败,(这时失败率超过60%),过一会,再刷新,又可以了

原理

步骤

断路器什么时候起作用

需要达到一定的值,且这个值达到一定的失败率

断路器开启或者关闭的条件:

当满足一定的阀值的时候(默认10秒内超过20个请求次数)
当失败率达到一定的时候(默认10秒内超过50%的请求失败)
到达以上阀值,断路器将会开启
当开启的时候,所有请求都不会进行转发
一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发;如果成功,断路器会关闭;若失败,继续开启。重复4和5

降级跟熔断是分开的,但是他们有关联

断路器打开之后

all配置

//========================All
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
        groupKey = "strGroupCommand",
        commandKey = "strCommand",
        threadPoolKey = "strThreadPool",

        commandProperties = {
                // 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
                @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
                // 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
                @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
                // 配置命令执行的超时时间
                @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
                // 是否启用超时时间
                @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
                // 执行超时的时候是否中断
                @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
                // 执行被取消的时候是否中断
                @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
                // 允许回调方法执行的最大并发数
                @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
                // 服务降级是否启用,是否执行回调函数
                @HystrixProperty(name = "fallback.enabled", value = "true"),
                // 是否启用断路器
                @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
                // 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
                // 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
                @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
                // 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
                // circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
                // 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
                @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
                // 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
                // 如果成功就设置为 "关闭" 状态。
                @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
                // 断路器强制打开
                @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
                // 断路器强制关闭
                @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
                // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
                @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
                // 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
                // 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
                // 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
                @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
                // 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
                @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
                // 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
                @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
                // 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
                @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
                // 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
                // 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
                // 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
                @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
                // 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
                @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
                // 是否开启请求缓存
                @HystrixProperty(name = "requestCache.enabled", value = "true"),
                // HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
                @HystrixProperty(name = "requestLog.enabled", value = "true"),
        },
        threadPoolProperties = {
                // 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
                @HystrixProperty(name = "coreSize", value = "10"),
                // 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
                // 否则将使用 LinkedBlockingQueue 实现的队列。
                @HystrixProperty(name = "maxQueueSize", value = "-1"),
                // 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
                // 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
                // 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
                @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
        }
)
public String strConsumer() {
    return "hello 2020";
}
public String str_fallbackMethod()
{
    return "*****fall back str_fallbackMethod";
}

服务限流

hystrix工作流程

https://github.com/Netflix/Hystrix/wiki/How-it-Works

官网图例

步骤说明:

  • 创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象

  • 命令执行。其中 HystrixComand 实现了下面前两种执行方式;而 HystrixObservableCommand 实现了后两种执行方式:execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常。queue():异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象。observe():返回 Observable 对象,它代表了操作的多个结果,它是一个 Hot Obserable(不论 “事件源” 是否有 “订阅者”,都会在创建后对事件进行发布,所以对于 Hot Observable 的每一个 “订阅者” 都有可能是从 “事件源” 的中途开始的,并可能只是看到了整个操作的局部过程)。toObservable(): 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有 “订阅者” 的时候并不会发布事件,而是进行等待,直到有 “订阅者” 之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)

  • 若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以 Observable 对象的形式返回

  • 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步)

  • 线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第8步)

  • Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() :返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知

  • Hystrix会将 “成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 “熔断/短路”

  • 当命令执行失败的时候, Hystrix 会进入 fallback 尝试回退处理, 我们通常也称该操作为 “服务降级”。而能够引起服务降级处理的情况有下面几种:第4步: 当前命令处于"熔断/短路"状态,断路器是打开的时候。第5步: 当前命令的线程池、 请求队列或 者信号量被占满的时候。第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候

  • 当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回

tips:如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常, Hystrix 依然会返回一个 Observable 对象, 但是它不会发射任何结果数据, 而是通过 onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者

服务监控hystrixDashboard

概述

仪表盘9001

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

主启动类 加上注解@EnableHystrixDashboard

启动

监控

在8001主启动类加入

/**
 * 此配置是为了服务监控而配置,与服务器容错本身无关,springcloud升级后的坑
 * ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
 * 只要在自己的项目里配置上下文的servlet就可以了
 */
@Bean
public ServletRegistrationBean getservlet(){
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/hystrix.stream");
    registrationBean.setName("HystrixMetricsStreamServlet");
    return registrationBean;
}

启动7001,9001

9001监控8001,填写监控地址:http://localhost:8001/hystrix.stream

测试地址:

http://localhost:8001/payment/circuit/31

http://localhost:8001/payment/circuit/-31

上述测试通过

一直访问31,然后点monitor

监控结果,成功。 流量少了后,直线会下降,圆圈会变小

一直访问-31

怎么看

7色

1圈

1线

曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。

整图看

Gateway 网关

概述简介

官网

上一代zuul 1.X:https://github.com/Netflix/zuul/wiki

当前gateway:Spring Cloud Gateway

是什么

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和 Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等

源码架构

能干啥

  • 反向代理

  • 鉴权

  • 流量控制

  • 熔断

  • 日志监控

微服务架构中网关在哪里?

Gateway非阻塞异步模型

1、neflix不太靠谱,zuul2.0一直跳票,迟迟不发布,一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有用起来也非常的简单便捷。Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的 Zuul 2.x,但 Spring Cloud 貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?多方面综合考虑Gateway是很理想的网关选择。

2、Gateway特征

  • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建

  • 动态路由:能够匹配任何请求属性

  • 可以对路由指定 Predicate(断言)和 Filter(过滤器)

  • 集成Hystrix的断路器功能

  • 集成 Spring Cloud 服务发现功能

  • 易于编写的 Predicate(断言)和 Filter(过滤器)

  • 请求限流功能

  • 支持路径重写

3、Gateway与Zuul区别

在SpringCloud Finchley 正式版之前,Spring Cloud 推荐的网关是 Netflix 提供的Zuul:

  • Zuul 1.x,是一个基于阻塞 I/ O 的 API Gateway

  • Zuul 1.x 基于Servlet 2. 5使用阻塞架构它不支持任何长连接(如 WebSocket) Zuul 的设计模式和Nginx较像,每次 I/ O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx 用C++ 实现,Zuul 用 Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得Zuul 的性能相对较差。

  • Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。 Zuul 2.x的性能较 Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway 的 RPS(每秒请求数)是Zuul 的 1. 6 倍。

  • Spring Cloud Gateway 建立 在 Spring Framework 5、 Project Reactor 和 Spring Boot 2 之上, 使用非阻塞 API。

  • Spring Cloud Gateway 还 支持 WebSocket, 并且与Spring紧密集成拥有更好的开发体验

4、Zuul1.x模型

Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型

学过尚硅谷web中期课程都知道一个题目,Servlet的生命周期?

servlet由servlet container进行生命周期管理,container启动时构造servlet对象并调用servlet init()进行初始化;container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service() container关闭时调用servlet destory()销毁servlet

上述模式的缺点:

servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势

所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet)并由该servlet阻塞式处理处理。所以Springcloud Zuul无法摆脱servlet模型的弊端

5、Gateway模型

WebFlux是什么

Web on Reactive Stack

传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的。但是 在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)

Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。

大三核心概念

路由

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

断言

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

过滤

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

总体

Gateway工作流程

官网总结

核心逻辑:路由转发+执行过滤器链

入门配置

新建9527模块

<dependencies>
    <!--gateway-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--eureka-client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--引入定义的api通用包,可以使用payment支付entity-->
    <dependency>
        <groupId>com.atguigu.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--boot web actuator-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--一般基础配置类-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
server:
  port: 9527

spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

无业务类

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

9527网关如何做路由映射呢?

cloud-provider-payment8001看看controller的访问地址

新增网关设置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

测试

  1. 启动7001

  2. 启动8001:cloud-provider-payment8001

  3. 启动9527网关

bug : 需要移除web依赖

经过测试,发现两个都可以访问

yml配置说明

Gateway网关路由有两种配置方式:

  1. 在配置文件yml中配置:见前面的步骤

  2. 代码中注入RouteLocator的Bean

官网案例:

自己写一个案例:

业务要求:通过9527网关访问到外网的百度新闻网址:百度新闻——海量中文资讯平台

编码:cloud-gateway-gateway9527

业务实现

com.atguigu.springcloud.config.GateWayConfig

@Configuration
public class GateWayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){

        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        routes.route("path_route_atguigu",r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();

        return routes.build();
    }

    @Bean
    public RouteLocator customRouteLocator2(RouteLocatorBuilder routeLocatorBuilder){

        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        routes.route("path_route_atguigu2",r -> r.path("/guoji").uri("http://news.baidu.com/guoji")).build();

        return routes.build();
    }

}

通过微服务名实现动态路由

以前的配置写死了

默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

启动:一个eureka7001 + 两个服务提供者8001/8002

需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能

lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

测试:http://localhost:9527/payment/lb

8001/8002两个端口切换

Predicate的使用

是什么

启动9527

Route Predicate Factories这个是什么东东?

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

Spring Cloud Gateway 创建 Route 对象时,使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

常用的Route Predicate

(1)After Route Predicate

我们的问题是:上述这个After好懂,这个时间串串???

新建测试类

public class T2 {
    public static void main(String[] args) {
        ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
        System.out.println( zbj);
    }
}

跑出来就是这个时间,然后在yml里加上

测试 http://localhost:9527/payment/lb 8001、8002会交替出现

改晚一个小时后,测试马上报错

可以用做系统上线,规定时间内才可以访问

###

(2)Before Route Predicate

yml

predicates:
  - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
  - Before=2023-03-08T24:21:51.718+08:00[Asia/Shanghai]         # 断言,路径相匹配的进行路由

(3)Between Route Predicate

yml

predicates:
  - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
  - Between=2022-02-02T17:45:06.206+08:00[Asia/Shanghai],2023-03-25T18:59:06.206+08:00[Asia/Shanghai]

(4)Cookie Route Predicate

Cookie Route Predicate需要两个参数,一个是 Cookie name,一个是正则表达式

路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由;如果没有匹配上则不执行

在yml加上

打开cmd

不带cookie访问

带cookie访问

如果加入curl返回中文乱码:Windows下安装使用Curl及解决中文乱码问题_leedee的博客-CSDN博客_curl.exe

(5)Header Route Predicate

两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行

yml

curl http://localhost:9527/payment/lb -H “X-Request-Id:123”

只有正数才能访问

(6)Host Route Predicate

Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符

它通过参数中的主机地址作为匹配规则

yml

测试:只有com结尾才正确

(7)Method Route Predicate

yml

测试: 只能用get请求

(8)Path Route Predicate

yml

(9)Query Route Predicate

支持传入两个参数,一个是属性名,一个为属性值,属性值可以是正则表达式

yml

测试: http://localhost:9527/payment/lb?username=1

小总结:说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理

Filter的使用

是什么

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用

Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

Spring Cloud Gateway的Filter

生命周期,Only Two

  • pre

  • post

种类,Only Two

  • GatewayFilter

  • GlobalFilter

GatewayFilter:Spring Cloud Gateway

常用的GatewayFilter

AddRequestParameter

filters:
  - AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024

自定义过滤器

MyLogGateWayFilter:com.atguigu.springcloud.filter.MyLogGateWayFilter

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        log.info("****************come in MyLogGateWayFilter:"+ new Date());

        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if(uname == null){
            log.info("******用户名为null,非法用户");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

启动7001、8001、8002、9527

正确:http://localhost:9527/payment/lb?uname=zhang3

没有uname参数就会报错

SpringCloud Config 分布式配置中心

概述

是什么

能干嘛

  • 集中管理配置文件

  • 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release

  • 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息

  • 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置

  • 将配置信息以REST接口的形式暴露:post、curl访问刷新均可…

与GitHub整合配置

由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式

官网:Spring Cloud Config

Config服务端配置与测试

服务端配置

用你自己的账号在Gitee上新建一个名为springcloud-config的新Repository

配置模块 cloud-config-center-3344

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <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.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
server:
  port: 3344

spring:
  application:
    name: cloud-config-center #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: https://github.com/Zhaojinye/springcloud-config.git #Gitee上面的git仓库名字
          ####搜索目录
          search-paths:
            - springcloud-config
      ####读取分支
      label: master

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

主启动类

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

windows下修改hosts文件,增加映射:127.0.0.1 config-3344.com

测试:http://config-3344.com:3344/master/config-dev.yml

配置读取规则

  • {application} 就是应用名称,对应到配置文件上来,就是配置文件的名称部分,例如我上面创建的配置文件

  • {profile} 就是配置文件的版本,我们的项目有开发版本、测试环境版本、生产环境版本,对应到配置文件上来就是以 application-{profile}.yml 加以区分,例如application-dev.yml、application-sit.yml、application-prod.yml

  • {label} 表示 git 分支,默认是 master 分支,如果项目是以分支做区分也是可以的,那就可以通过不同的 label 来控制访问不同的配置文件了

Config客户端配置与测试

先从服务端读取yml,再配上自己得yml

新建cloud-config-client-3355

 <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <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.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

bootstrap.yml

applicaiton.yml是用户级的资源配置项;bootstrap.yml是系统级的,优先级更加高

server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    #Config客户端配置
    config:
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
      uri: http://localhost:3344 #配置中心地址

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

修改config-dev.yml配置并提交到GitHub中,比如加个变量age或者版本号version

主启动类

com.atguigu.springcloud.ConfigClientMain3355

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

业务类

@RestController
public class ConfigClientController {
    @Value("${spring.cloud.config.info}")
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo() {
        return configInfo;
    }
}

测试:3355可以跟3344访问到一样的内容

修改github上的内容,3344会变化,而3355不会,只能重启

Config客户端之动态刷新

修改yml

# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

@RefreshScope业务类Controller修改

再测:http://localhost:3355/configInfo,发现3355还是没有变化

需要运维人员发送Post请求刷新3355

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

再次请求:http://localhost:3355/configInfo,成功实现了客户端3355刷新到最新配置内容,避免了服务重启

SpringCloud Bus 消息总线

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

概述

是什么

这里是推给一个,然后全部去传染

能干啥

这里是推给3344

Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。

总线

尚硅谷ActiveMQ教程(MQ消息中间件快速入门)_哔哩哔哩_bilibili

RabbitMq环境配置

安装Erlang,下载地址:http://erlang.org/download/otp_win64_21.3.exe

安装RabbitMQ,下载地址:https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe 进入RabbitMQ安装目录下的sbin目录,输入以下命令启动管理功能

rabbitmq-plugins enable rabbitmq_management

访问地址查看是否安装成功:http://localhost:15672/

tips:如果访问不到重启一下电脑就好了

输入账号密码并登录:guest guest

SpringCloud Bus动态刷新全局广播

搭建客户端微服务3366

pom

yml

主启动类

业务类

设计思想

(1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置

2)利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置

图二的架构显然更加适合,图一不适合的原因如下:

  1. 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责

  2. 破坏了微服务各节点的对等性

  3. 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改

操作

都导入

<!--添加消息总线RabbitMQ支持-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
  host: localhost
  port: 5672
  username: guest
  password: guest

客户端3355、66只加上面两个添加消息总线支持

配置中心3344添加消息总线支持

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

测试:

修改github

然后cmd发送请求

curl -X POST "http://localhost:3344/actuator/bus-refresh"

3344、3355、3366都改变了

一次修改,广播通知,处处生效

SpringCloud Bus动态刷新顶点通知

不想全部通知,只想定点通知:只通知3355,不通知3366

简单一句话:

  • 指定具体某一个实例生效而不是全部

  • 公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}

  • /bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例

案例:

我们这里以刷新运行在3355端口上的config-client为例:只通知3355,不通知3366

curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"

SpringCloud Stream 消息驱动

消息驱动概述

是什么

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

官网

Spring Cloud Stream

Spring Cloud Stream是用于构建与共享消息传递系统连接的高度可伸缩的事件驱动微服务框架,该框架提供了一个灵活的编程模型,它建立在已经建立和熟悉的Spring熟语和最佳实践上,包括支持持久化的发布/订阅、消费组以及消息分区这三个核心概念

Spring Cloud Stream Reference Documentation

Spring Cloud Stream中文指导手册:Spring Cloud Stream中文指导手册

设计思想

标准MQ

  • 生产者/消费者之间靠消息媒介传递信息内容——Message

  • 消息必须走特定的通道——消息通道MessageChannel

  • 消息通道里的消息如何被消费呢,谁负责收发处理——消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅

为什么用Cloud Stream

Binder

Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。

Stream中的消息通信方式遵循了发布-订阅模式

Topic主题进行广播:在RabbitMQ就是Exchange,在Kakfa中就是Topic

Stream标准流程套路

Binder:很方便的连接中间件,屏蔽差异

Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置

Source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入

编码API和常用注解

案例说明

消息驱动之生产者 provider8801

pml

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

yml

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: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: send-8801.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

主启动类

业务类

public interface IMessageProvider {
    String send();
}
@Service
@EnableBinding(Source.class) //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {


    @Resource
    private MessageChannel output;//消息发送管道


    @Override
    public String send() {
        String serial = UUID.randomUUID().toString();
        // 创建并发送消息
        output.send(MessageBuilder.withPayload(serial).build());
        System.out.println("***serial: " + serial);
        return null;
   }
}

controller

@RestController
@Slf4j
public class SendMessageController {

    @Resource
    private IMessageProvider messageProvider;

    @GetMapping(value = "/sendMessage")
    public String sendMessage(){
        return messageProvider.send();
    }
}

测试

消息驱动之消费者 consumer8802

pom

yml

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: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: receive-8802.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

主启动类

业务类

@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("消费者1号,------->接收到的消息:" + message.getPayload() + "\t port: " + serverPort);
    }
}

测试 8801发送信息,8802接收信息

分组消费与持久化 consumer8803

跟8802一样

  • RabbitMQ

  • 7001:服务注册

  • 8801:消息生产

  • 8802:消息消费

  • 8803:消息消费

运行后有两个问题:

  • 有重复消费问题

  • 消息持久化问题

http://localhost:8801/sendMessage

目前是8802/8803同时都收到了,存在重复消费问题

消费

分组

原理:微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。

不同组

8802/8803都变成不同组,group两个不同

8802修改YML:

8803 :atguiguB

分布式微服务应用为了实现高可用和负载均衡,实际上都会部署多个实例,本例阳哥启动了两个消费微服务(8802/8803),多数情况,生产者发送消息给某个具体微服务时只希望被消费一次,按照上面我们启动两个应用的例子,虽然它们同属一个应用,但是这个消息出现了被重复消费两次的情况。为了解决这个问题,在Spring Cloud Stream中提供了消费组的概念

结论:还是重复消费

8802/8803实现了轮询分组,每次只有一个消费者,8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费

相同组

8802/8803都变成相同组,group两个相同,都变成 atguiguA

group: atguiguA

结论:同一个组的多个微服务实例,每次只会有一个拿到

持久化

通过上述,解决了重复消费问题,再看看持久化

  • 停止8802/8803并去除掉8802的分组group: atguiguA,8803的分组group: atguiguA没有去掉

  • 8801先发送4条消息到rabbitmq

  • 先启动8802,无分组属性配置,后台没有打出来消息

  • 再启动8803,有分组属性配置,后台打出来了MQ上的消息

SpringCloud Sleuth 分布式请求链路跟踪

概述

为什么会出现这个技术?需要解决哪些问题?

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

是什么

官网:https://github.com/spring-cloud/spring-cloud-sleuth

Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin

搭建链路监控步骤

zipkin

SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包即可

https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/

cmd

java -jar zipkin-server-2.12.9-exec.jar

运行控制台:http://localhost:9411/zipkin/

术语—完整的调用链路: 表示一请求链路,一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来

名词解释:

  • Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识

  • span:表示调用链路来源,通俗的理解span就是一次请求信息

服务提供者 cloud-provider-payment8001

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

yml

controller

@GetMapping("/payment/zipkin")
public String paymentZipkin() {
    return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
}

服务消费者(调用方)cloud-consumer-order80

与8001前两部相同

controller

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

测试

SpringCloud Alibaba 入门简介

why会出现SpringCloud alibaba

Spring Cloud Netflix项目进入维护模式:Spring Cloud Greenwich.RC1 available now

Spring Cloud Netflix Projects Entering Maintenance Mode

什么是维护模式?

将模块置于维护模式,意味着 Spring Cloud 团队将不会再向模块添加新功能。我们将修复 block 级别的 bug 以及安全问题,我们也会考虑并审查社区的小型 pull request

进入维护模式意味着什么呢?

进入维护模式意味着 Spring Cloud Netflix 将不再开发新的组件,我们都知道Spring Cloud 版本迭代算是比较快的,因而出现了很多重大ISSUE都还来不及Fix就又推另一个Release了。进入维护模式意思就是目前一直以后一段时间Spring Cloud Netflix提供的服务和功能就这么多了,不在开发新的组件和功能了。以后将以维护和Merge分支Full Request为主

新组件功能将以其他替代平代替的方式实现

SpringCloud alibaba带来了什么

官网:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

诞生:2018.10.31,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 Maven 中央库发布了第一个版本

SpringCloud alibaba学习资料获取

官网:Spring Cloud Alibaba

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

SpringCloud Alibaba进入了SpringCloud官方孵化器,而且毕业了

英文:

https://github.com/alibaba/spring-cloud-alibaba Spring Cloud Alibaba Reference Documentation 中文:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

SpringCloud Alibaba Nacos服务注册和配置中心

Nacos简介

为什么叫Nacos

前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service

是什么

Nacos: Dynamic Naming and Configuration Service 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台

Nacos 就是注册中心 + 配置中心的组合 等价于 Nacos = Eureka+Config +Bus

能干嘛

  1. 替代Eureka做服务注册中心

  2. 替代Config做服务配置中心

去哪下

https://github.com/alibaba/Nacos

官网文档:

各种注册中心比较

安装并运行Nacos

本地Java8+Maven环境已经OK

先从官网下载Nacos:Releases · alibaba/nacos · GitHub

解压安装包,直接运行bin目录下的startup.cmd

startup.cmd -m standalone

命令运行成功后直接访问:http://localhost:8848/nacos 默认账号密码都是nacos

Nacos作为服务注册中心演示

官网文档:Spring Cloud Alibaba Reference Documentation

基于Nacos的服务提供者9001

父pom

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.1.0.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

本模块

 <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

yml

server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*'

主启动类

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

业务类

@RestController
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id) {
        return "nacos registry, serverPort: " + serverPort + "\t id" + id;
    }
}

测试 http://localhost:9001/payment/nacos/1

nacos控制台:

nacos服务注册中心+服务提供者9001都OK了

为了下一章节演示nacos的负载均衡,参照9001新建9002

新建cloudalibaba-provider-payment9002

或者取巧不想新建重复体力劳动,直接拷贝虚拟端口映射

-DServer.port=9002

构建一个和9001的9002模块

基于Nacos的服务消费者83

pom

为什么nacos支持负载均衡?

yml

server:
  port: 83

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider

主启动类

配置类(有ribbon必须要加restTemplate

//有ribbon就有这个RestTemplate
@Configuration
public class ApplicationContextBean {
    @Bean
    @LoadBalanced   //负载均衡必须开启,不然不知道找9001还是9002
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

controller

@Slf4j
@RestController
public class OrderNacosController {

    @Resource
    private RestTemplate restTemplate;

    //这里是去读yml文件,yml文件里写好了路径
    @Value("${service-url.nacos-user-service}")
    private String serverURL;

	//restTemplate风格
    @GetMapping(value = "/consumer/payment/nacos/{id}")
    private String paymentInfo(@PathVariable("id") Long id){

        return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
    }
}

测试

服务注册中心对比

Nacos全景图所示:

Nacos和CAP:

Nacos 支持AP和CP模式的切换:

curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

Nacos作为服务配置中心演示

Nacos作为配置中心3377-基础配置

pom

<!--nacos-config-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

两个yml

why配置两个?

Nacos 同 springcloud-config 一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。

springboot 中配置文件的加载是存在优先级顺序的,bootstrap 优先级高于application

bootstrap .yml

# nacos配置
server:
  port: 3377

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


# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
#  nacos-config-client.dev.yaml

application.yml

spring:
  profiles:
    active: dev # 表示开发环境

主启动类

controller

@RestController
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class ConfigClientController {
    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
        return configInfo;
    }
}

在Nacos中添加配置信息

① 理论 Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则

官网:Nacos Spring Cloud 快速开始

说明:之所以要配置 spring.application.name,是因为它是构成 Nacos 配置管理 dataId字段的一部分

在 Nacos Spring Cloud 中,dataId 的完整格式如下:(就是说在nacos端我们怎么命名文件的)

${prefix}-${spring.profiles.active}.${file-extension}

prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置 spring.profiles.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档。 注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId的拼接格式变成 ${prefix}.${file-extension}

file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml类型(注意nacos里必须使用yaml) 通过 Spring Cloud 原生注解 @RefreshScope 实现配置自动刷新 最后公式:

${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

综合以上说明,Nacos 的 dataid(类似文件名)应为: nacos-config-client-dev.yaml (必须是yaml)

② 实操

配置新增:nacos-config-client-dev.yaml

总结:

历史配置:Nacos会记录配置文件的历史版本默认保留30天,此外还有一键回滚功能,回滚操作将会触发配置更新

测试

  • 启动前需要在 nacos 客户端-配置管理-配置管理栏目下有对应的 yaml 配置文件

  • 运行cloud-config-nacos-client3377的主启动类

  • 调用接口查看配置信息:http://localhost:3377/config/info

修改下Nacos中的yaml配置文件,再次调用查看配置的接口

刷新

Nacos作为配置中心-分类配置

问题——多环境多项目管理

问题1:

实际开发中,通常一个系统会准备:dev开发环境、test测试环境、prod生产环境,如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?

问题2:

一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…那怎么对这些微服务配置进行管理呢?

Nacos的图形化管理界面

Namespace+Group+Data ID三者关系?

思考:为什么这么设计?

① 是什么?

类似Java里面的package名和类名,最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象

② 三者情况

③ 默认情况

Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT

Nacos默认的命名空间是public;Namespace主要用来实现隔离,比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的

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

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

最后是Instance,就是微服务的实例

三种方案加载配置 Case

① DataID方案

指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置

默认空间+默认分组+新建dev和test两个DataID

新建test配置DataID:nacos-config-client-test.yaml

通过spring.profile.active属性就能进行多环境下配置文件的读取:

测试:http://localhost:3377/config/info

② Group方案

通过Group实现环境区分,新建Group,实现同id,不同group

Data ID:nacos-config-client-info.yaml

Group:TEST_GROUP / DEV_GROUP

bootstrap+application

测试

③ Namespace方案

新建dev/test的Namespace

注意下面的命名空间ID:

回到服务管理-服务列表查看

点test有对应的id

按照域名配置填写:三个不同的group名

Nacos集群和持久化配置

官网说明

集群部署说明

说明:

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

按照上述,我们需要mysql数据库

官网说明:Nacos支持三种部署模式

重点说明:

Nacos持久化配置解释

Nacos默认自带的是嵌入式数据库derby:https://github.com/alibaba/nacos/blob/develop/config/pom.xml

derby到mysql切换配置步骤:

  • nacos-server-1.1.4\nacos\conf目录下找到sql脚本,执行 nacos-mysql.sql

# 先创建数据库
CREATE DATABASE nacos_config;
USE nacos_config;

  • 修改nacos/conf/application.properties文件(切换数据库),增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码

# 切换数据库
spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=root

重启再访问:http://localhost:8848/nacos,可以看到是个全新的空记录界面,

以前是记录进derby现在是mysql,证明数据库切换成功

Linux版Nacos+MySQL生产环境配置(没环境没弄)

SpringCloud Alibaba Sentinel 实现熔断与限流

概述

官网:https://github.com/alibaba/Sentinel

中文:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

是什么

一句话解释,之前我们讲解过的Hystrix

去哪下

能干嘛

怎么玩

Spring Cloud Alibaba Reference Documentation

服务使用中的各种问题:

  • 服务雪崩

  • 服务降级

  • 服务熔断

  • 服务限流

安装Sentinel控制台

sentinel组件由两部分构成:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持

  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器

后台 + 前台8080

安装步骤:

下载:https://github.com/alibaba/Sentinel/releases,下载到本地 sentinel-dashboard-1.7.1.jar

前提:java8环境OK + 8080端口不能被占用

运行命令:

java -jar sentinel-dashboard-1.7.1.jar

访问sentinel管理界面:http://localhost:8080,登录账号密码均为sentinel

初始化演示工程

启动Nacos8848成功:http://localhost:8848/nacos/#/login

新建 cloudalibaba-sentinel-service8401

<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!--openfeign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件+actuator -->
    <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>
    <!--日常通用jar包配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>4.6.3</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class, args);
    }
}
@RestController
public class FlowLimitController {

    @GetMapping("/testA")
    public String testA() {
        return "------testA";
    }

    @GetMapping("/testB")
    public String testB() {
        return "------testB";
    }
}

测试

启动Sentinel8080:java -jar sentinel-dashboard-1.7.0.jar 启动微服务8401 启动8401微服务后查看sentienl控制台 空空如也,啥都没有

Sentinel采用的懒加载说明:

执行一次访问即可:http://localhost:8401/testAhttp://localhost:8401/testB

结论:sentinel8080正在监控微服务8401

流控规则

基本介绍

解释说明:

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

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

  • 阈值类型/单机值:

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

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

  • 是否集群:不需要集群

  • 流控模式:

    • 直接:api达到限流条件时,直接限流。分为QPS和线程数

    • 关联:当关联的资到阈值时,就限流自己。别人惹事,自己买单

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

  • 流控效果:

    • 快速失败:直接抛异常

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

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

QPS和线程数的区别:

  • QPS 类似于银行的保安:所有的请求到Sentinel 后,他会根据阈值放行,超过报错

  • 线程数类似于银行的窗口:所有的请求会被放进来,但如果阈值设置为1,那么其他的请求就会报错也就是,银行里只有一个窗口,一个人在办理业务中,其他人跑过来则会告诉你“现在不行,没到你”

重要属性:

流控模式

(1)直接(默认)

QPS

  • 直接->快速失败:系统默认

  • 配置及说明:表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误

!

测试:快速点击访问:http://localhost:8401/testA

结果:Blocked by Sentinel (flow limiting)

线程数

开启两个线程

(2)关联

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

  • 当与A关联的资源B达到阀值后,就限流A自己

  • B惹事,A挂了

配置A:设置效果,当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名

Postman测试:

访问testB成功:

运行后发现testA挂了,点击访问:http://localhost:8401/testA,结果:Blocked by Sentinel (flow limiting)

流控效果

(1)快速失败(默认的流控处理)

直接失败,抛出异常:Blocked by Sentinel (flow limiting)

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

(2)预热

官网:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值

限流冷启动:https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81—%E5%86%B7%E5%90%AF%E5%8A%A8

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

多次点击:http://localhost:8401/testB

刚开始不行,后续慢慢OK

应用场景:

秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值

(3)排队等待

如下此设置的含义为:代表 1 秒匀速的通过 2 个请求,也就是每个请求平均间隔恒定为 1 / 2 = 0.5 s 也即 500 ms,每一个请求的最长等待时间为20s

同理,如果单机阈值为 1 时,每个请求的平均间隔恒定为 1000/1 = 10000 ms

说明:匀速排队,阈值必须设置为QPS

官网:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

降级规则

官网:https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7

1、介绍

进一步说明:

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)

Sentinel 的断路器是没有半开状态的:

半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用;有异常则继续打开断路器不可用。具体可以参考Hystrix

2、RT

(1)介绍

(2)实操

在controller中加入textD:

@GetMapping("/testD")
public String testD() {
    //暂停几秒钟线程
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("testC 测试RT");
    return "------testD";
}

打了 10个过来,要求0.2秒处理完,但是方法里设置了睡一秒,两个超过了

结论:

按照上述配置,永远一秒钟打进来10个线程(大于5个了)调用testC,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了,后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK

3、异常比例

(1)介绍

(2)实操

在controller中加入textD:

@GetMapping("/testD")
public String testD() {
    log.info("testD 测试RT");
    int age = 10 / 0;
    return "------testD";
}

(3)结论

按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。 断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了

异常数

(1)介绍

时间窗口一定要大于等于60秒、异常数是按照分钟统计的

(2)实战

在controller中加入textE:

 @GetMapping("/testE")
    public String testE() {
        log.info("testE 测试异常数");
        int age = 10 / 0;
        return "------testE 测试异常数";
    }

访问:http://localhost:8401/testE,第一次访问绝对报错,因为除数不能为零,我们看到error窗口,但是达到5次报错后,进入熔断后降级

热点key限流

1、基本介绍

何为热点:热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作

官网:https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81

承上启下复习start:

兜底方法:分为系统默认和客户自定义两种

  • 之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

  • 我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?

结论:从 HystrixCommand 到 @SentinelResource

2、实操

在controller中加入:

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

public String deal_testHotKey(String p1, String p2, BlockException exception){
        return "------------deal_testHotKey";
}

3、参数例外项

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流

特例情况:我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样

特例:假如当p1的值等于5时,它的阈值可以达到200

热点参数的注意点,参数必须是基本类型或者String

测试:

http://localhost:8401/testHotKey?p1=5 当p1等于5的时候,阈值变为200

http://localhost:8401/testHotKey?p1=3 当p1不等于5的时候,阈值就是平常的1

4、其它 手贱添加异常看看…/(ㄒoㄒ)/~~

系统规则

1、介绍

相当于在最外面的一层

https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81

2、各项配置参数说明

@SentinelResource

(1)按资源名称限流+后续处理

表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流

测试:

1秒钟点击1下,OK

超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生

(2)按照Url地址限流+后续处理

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息

测试:疯狂点击

返回sentinel自带的限流处理结果

(3)上面兜底方案面临的问题

  1. 系统默认的,没有体现我们自己的业务要求

  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观

  3. 每个业务方法都添加一个兜底的,那代码膨胀加剧

  4. 全局统一的处理方法没有体现

(4)客户自定义限流处理逻辑

测试

疯狂刷新

说明

(5)更多注解属性说明

@SentinelResource注解最主要的两个用法:限流控制和熔断降级的具体使用案例介绍完了。另外,该注解还有一些其他更精细化的配置,比如忽略某些异常的配置、默认降级函数等等,具体可见如下说明:

服务熔断功能

sentinel整合ribbon+openFeign+fallback

Ribbon系列

提供者

消费者

修改后请重启微服务:热部署对java代码级生效及时,对@SentinelResource注解内属性,有时效果不好

目的:fallback管运行异常、blockHandler管配置违规

测试地址:

http://localhost:84/consumer/fallback/1

没有任何配置

http://localhost:84/consumer/fallback/4 给客户error页面,不友好

只配置fallback

本例sentinel无配置

只配置blockhandler

//blockHandler只负责sentinel控制台配置违规

本例sentinel需配置:

服务降级:

异常超过2次后,断路器打开,断电跳闸,系统被保护

没有配置fallback,所以第一次和第二次异常。第三次是blockhandler直接降级

第三、四次···访问:

fallback和blockhandler都配置

本例sentinel需配置:

点得快,4本来是属于java异常,但是超过了ops,也归到sentinel的异常

忽略属性

OpenFeign系列

修改84模块

  • 84消费者调用提供者9003

  • Feign组件一般是消费侧

pom

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

yml

# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

主启动类,加上注解

业务类

带@Feignclient注解的业务接口,fallback = PaymentFallbackService.class

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackService implements PaymentService {
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(4444,"服务降级返回,------PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}

controller

@Resource
private PaymentService paymentService;

@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){

    return paymentService.paymentSQL(id);
}

测试 - http://localhost:84/consumer/paymentSQL/1

测试84调用9003,此时故意关闭9003微服务提供者,84消费侧自动降级,不会被耗死。

熔断框架比较

规则持久化

是什么

一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化。

怎么玩

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。

步骤

修改cloudalibaba-sentinel-service8401 pom

<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

yml

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
      datasource: #<---------------------------关注点,添加Nacos数据源配置
          ds1:
            nacos:
              server-addr: localhost:8848
              dataId: cloudalibaba-sentinel-service
              groupId: DEFAULT_GROUP
              data-type: json
              rule-type: flow



management:
  endpoints:
    web:
      exposure:
        include: '*'


feign:
  sentinel:
    enabled: true # 激活Sentinel对Feign的支持

添加Nacos业务规则配置

配置内容解析

[{
    "resource": "/rateLimit/byUrl",
    "IimitApp": "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 Up,2表示排队等待;

  • clusterMode:是否集群。

启动8401后刷新sentinel发现业务规则有了

快速访问测试接口 - http://localhost:8401/rateLimit/byUrl - 页面返回Blocked by Sentinel (flow limiting)

停止8401再看sentinel - 停机后发现流控规则没有了

重新启动8401再看sentinel

Springcloud Alibaba Seata处理分布式事务

分布式事务问题由来

分布式前

  • 单机单库没这个问题

  • 从1:1 -> 1:N -> N:N

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

Seata简介

是什么

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

官网:Seata

能干嘛

一个典型的分布式事务过程

分布式事务处理过程的一ID+三组件模型:

  • Transaction ID XID 全局唯一的事务ID

  • 三组件概念

    • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。

    • TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。

    • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

处理过程:

  • TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;

  • XID在微服务调用链路的上下文中传播;

  • RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;

  • TM向TC发起针对XID的全局提交或回滚决议;

  • TC调度XID下管辖的全部分支事务完成提交或回滚请求。

TC类似老师,TM类似助班,RM类似学生

去哪下

发布说明: https://github.com/seata/seata/releases

怎么玩

本地@Transactional 这是spring

全局@GlobalTransactional 这是spring cloud Alibaba seata

Seata-Server安装

官网地址 - Seata

下载版本 - 0.9.0

seata-server-0.9.0.zip解压到指定目录并修改conf目录下的file.conf配置文件

先备份原始file.conf文件

主要修改:自定义事务组名称+事务日志存储模式为db +数据库连接信息

file.conf

service模块

service {
    ##fsp_tx_group是自定义的
    vgroup_mapping.my.test.tx_group="fsp_tx_group" 
    default.grouplist = "127.0.0.1:8091"
    enableDegrade = false
    disable = false
    max.commitretry.timeout= "-1"
    max.ollbackretry.timeout= "-1"
}

store模块

## transaction log store
store {
	## store mode: file, db
	## 改成db
	mode = "db"
	
	## file store
	file {
		dir = "sessionStore"
		
		# branch session size, if exceeded first try compress lockkey, still exceeded throws exceptions
		max-branch-session-size = 16384
		# globe session size, if exceeded throws exceptions
		max-global-session-size = 512
		# file buffer size, if exceeded allocate new buffer
		file-write-buffer-cache-size = 16384
		# when recover batch read size
		session.reload.read_size= 100
		# async, sync
		flush-disk-mode = async
	}

	# database store
	db {
		## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
		datasource = "dbcp"
		## mysql/oracle/h2/oceanbase etc.
		## 配置数据源
		db-type = "mysql"
		driver-class-name = "com.mysql.jdbc.Driver"
		url = "jdbc:mysql://127.0.0.1:3306/seata"
		user = "root"
		password = "你自己密码"
		min-conn= 1
		max-conn = 3
		global.table = "global_table"
		branch.table = "branch_table"
		lock-table = "lock_table"
		query-limit = 100
	}
}

mysql5.7数据库新建库seata,在seata库里建表

建表db_store.sql在\seata-server-0.9.0\seata\conf目录里面

修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文件

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  # 改用为nacos
  type = "nacos"

  nacos {
  	## 加端口号
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
  }
  ...
}

目的是:指明注册中心为nacos,及修改nacos连接信息

先启动Nacos端口号8848 nacos\bin\startup.cmd

再启动seata-server - seata-server-0.9.0\seata\bin\seata-server.b

订单/库存/账户业务数据库准备

以下演示都需要先启动Nacos后启动Seata,保证两个都OK。

分布式事务业务说明

一言蔽之,下订单—>扣库存—>减账户(余额)。

创建业务数据库

  • seata_ order:存储订单的数据库;

  • seata_ storage:存储库存的数据库;

  • seata_ account:存储账户信息的数据库。

CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account;

按照上述3库分别建对应业务表

按照上述3库分别建对应的回滚日志表

  • 订单-库存-账户3个库下都需要建各自的回滚日志表

  • \seata-server-0.9.0\seata\conf目录下的db_ undo_ log.sql

  • 建表SQL

-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

订单/库存/账户业务微服务准备

新建订单Order-Module

新建库存Storage-Module

新建账户Account-Module

Test

Spring Cloud是一个用于构建分布式系统的开发工具集合。它提供了一些常用的组件和框架,包括服务注册和发现、负载均衡、断路器、分布式配置等等。在使用Spring Cloud时,有一些常见的错误和注意事项需要注意。 首先,关于Spring Boot和Spring Cloud版本对应错误。在使用Spring Cloud时,需要确保Spring Boot和Spring Cloud的版本兼容。不同版本之间可能存在依赖冲突或不兼容的情况,因此需要根据官方文档或者相关文档来选择合适的版本。 另外,Spring Cloud Config是一个用于集中管理和动态获取配置的工具。它支持从Git、SVN或本地文件系统中获取配置文件,并提供了服务器和客户端支持。你可以通过官方使用说明文档了解更多关于Spring Cloud Config的详细信息。 此外,关于选择使用Nacos还是Eureka作为服务注册和发现组件的问题。Nacos是一个功能更强大的服务注册和发现组件,它整合了Spring Cloud Eureka、Spring Cloud Config和Spring Cloud Bus的功能。使用Nacos可以实现配置的中心动态刷新,而不需要为配置中心新增集群或使用消息队列。另一方面,Eureka是Spring Cloud原生全家桶的一部分,相对来说更加稳定一些。选择使用哪个组件需要根据具体的需求和项目特点来决定。 综上所述,Spring Cloud是一个用于构建分布式系统的开发工具集合,它提供了一些常用的组件和框架。在使用Spring Cloud时,需要注意Spring Boot和Spring Cloud版本的兼容性,并可以使用Spring Cloud Config来动态获取配置。同时,可以选择使用Nacos或Eureka作为服务注册和发现组件,具体选择需要根据项目需求来决定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值