百味皆苦 java后端开发攻城狮

周阳springcloud

2020-09-16
百味皆苦

1:基础理论

1.1微服务各组件概览

  • 服务注册中心:
    • Eureka
    • Zookeeper
    • Consul
    • Nacos
  • 服务调用:
    • Ribbon
    • LoadBalancer
    • Feign
    • OpenFeign
  • 服务降级:
    • Hyxtrix
    • resilience4j
    • sentienl
  • 服务网关:
    • Zuul
    • Zuul2
    • gateway
  • 服务配置:
    • Config
    • Nacos
  • 服务总线:
    • Bus
    • Nacos

1.2微服务架构编码构建

  • 约定>配置>编码
1.2.1父工程
  • 新建maven工程,选maven-archetype-site
  • 工程名称和项目名称为:cloud2020
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.atguigu.springcloud</groupId>
  <artifactId>cloud2020</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>


  <!-- 统一管理jar包版本 -->
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <junit.version>4.12</junit.version>
    <log4j.version>1.2.17</log4j.version>
    <lombok.version>1.16.18</lombok.version>
    <mysql.version>5.1.47</mysql.version>
    <druid.version>1.1.16</druid.version>
    <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
  </properties>

  <!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version  -->
  <dependencyManagement>
    <dependencies>
      <!--spring boot 2.2.2-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.2.2.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--spring cloud Hoxton.SR1-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Hoxton.SR1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--spring cloud alibaba 2.1.0.RELEASE-->
      <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>

      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid.version}</version>
      </dependency>
      <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis.spring.boot.version}</version>
      </dependency>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
      </dependency>
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
      </dependency>
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
      </dependency>
    </dependencies>
  </dependencyManagement>

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

</project>
 
  • 父工程创建完成执行mvn:install将父工程发布到仓库方便子工程继承
1.2.2common模块
  • 系统中有重复部分,重构
  • 在父工程下创建cloud-api-commons
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-api-commons</artifactId>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <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>

        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
    </dependencies>

</project>
 
  • 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
    private Long id;
    private String serial;
}
  • json封装类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult <T>{

    private Integer code;
    private String message;
    private T data;

    public CommonResult(Integer code,String message){
        this(code,message,null);
    }
}
  • maven命令clean install
1.2.3微服务提供者支付模块8001
  • 在父工程下建cloud-provider-payment8001项目
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment8001</artifactId>

    <dependencies>
        <!--引用common模块-->
      	<dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
           <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


    </dependencies>


</project>
 
  • yml
server:
  port: 8001


spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities
 
 

  • 建表
CREATE TABLE `zhifu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `serial` varchar(200) DEFAULT NULL COMMENT '流水号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • mybatis映射
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atguigu.springcloud.dao.PaymentDao">


    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
            insert into payment(serial) values(${serial});

    </insert>

    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"></id>
        <id column="serial" property="serial" jdbcType="VARCHAR"></id>
    </resultMap>
    <select id="getPaymentById"  parameterType="Long" resultMap="BaseResultMap">
            select * from payment where id=#{id}
    </select>


</mapper>
  • 业务类
@Service
public class PaymentServiceImpl implements PaymentService {

    @Resource
    private PaymentDao paymentDao;

    public int create(Payment payment){
        return paymentDao.create(payment);
    }

    public Payment getPaymentById( Long id){

        return paymentDao.getPaymentById(id);

    }
}

  • controller
@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    @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,"插入数据库成功",result);
       }else {
           return new CommonResult(444,"插入数据库失败",null);
       }
    }
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("*****查询结果:"+payment);
        if (payment!=null){  //说明有数据,能查询成功
            return new CommonResult(200,"查询成功",payment);
        }else {
            return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
        }
    }
}

1.2.4微服务消费者订单模块80
  • 在父工程下建cloud-consumer-order80
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-order80</artifactId>

    <dependencies>
      	<!--引用common模块-->
      	<dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
 
  • yml
server:
  port: 80
 
 

  • 配置类
@Configuration
public class ApplicationContextConfig {

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

}

  • RestTemplate:提供了多种便捷访问远程HTTP服务的方法,是一种简单的访问restful服务模板类,是spring提供的用于访问rest服务的客户端模板工具集
  • 三个参数
    • url:rest请求地址
    • requestMap:请求参数
    • ResponseBean.class:HTTP响应被转换成的对象类型
@RestController
@Slf4j
public class OrderController {

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

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment>   create(Payment payment){
        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);
    }
}

  • 测试:先启动cloud-provider-payment8001,再启动cloud-consumer-order80

2:Eureka服务注册与发现

2.1基础知识

  • 什么是服务治理:在传统的rpc远程调用框架中,管理每个服务与服务之间的依赖关系比较复杂,所以需要服务治理,可以实现服务调用,负载均衡,容错等,实现服务注册与发现。
  • 什么是服务注册与发现:eureka采用了cs的设计架构,eureka server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用eureka客户端连接到eureka server并维持心跳连接。这样系统的维护人员就可以通过eureka server来监控系统中各个微服务是否正常运行。
  • 两个组件
    • eureka server:提供服务注册,各个微服务节点通过配置启动后,会在eureka server中进行注册,这样eureka serverz中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直接看到。
    • eureka client:通过注册中心进行访问。是一个Java客户端,用于简化eureka server的交互,客户端同时也具备一个内置的,使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向eureka server发送心跳(默认周期为30秒)。如果eureka server在多个心跳周期内没有收到某个节点的心跳,eureka server会将节点从服务注册表中移除(默认90秒)。

2.2单机构建

2.2.1eurekaServer7001
  • 创建eurekaServer端服务注册中心模块:cloud-eureka-server7001
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-eureka-server7001</artifactId>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>

    </dependencies>
</project>
 
  • yml
server:
  port: 7001

eureka:
  client:
    #表示是否将自己注册进EurekaServer,默认true
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: false
    service-url:
      #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://localhost:7001/eureka
  • 启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class,args);
    }
}
  • 测试:http://localhost:7001/,会出现界面
2.2.2eurekaClient
注册服务提供者8001
  • EurekaClient端cloud-provider-payment8001(1.2.3)将注册进EurekaServer成为服务提供者provider
  • 改pom
 <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 写yml
eureka:
  client:
    # 表示是否将自己注册进EurekaServer,默认true
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
       fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka 
  • 启动类
@EnableEurekaClient
@SpringBootApplication
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}
  • 测试:先要启动EurekaServer,http://localhost:7001/
注册服务消费者80
  • EurekaClient端cloud-consumer-order80(1.2.4)将注册进EurekaServer成为服务消费者consumer
  • 改pom
 <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 改yml
spring:
  application:
    name: cloud-order-service

eureka:
  client:
    register-with-eureka: true
    fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka
  • 主启动类添加注解@EnableEurekaClient

  • 测试:

    先要启动EurekaServer,7001服务

    再要启动服务提供者provider,8001服务

    http://localhost/consumer/payment/get/31

2.3集群构建

Eureka集群
  • 搭建Eureka注册中心集群,实现负载均衡+故障容错
  • 根据cloud-eureka-server7001创建新建cloud-eureka-server7002
  • 修改hosts文件
127.0.0.1  eureka7001.com
127.0.0.1  eureka7002.com
  • 7001YML
server:
  port: 7001

eureka:
  instance:
    #eureka服务端的实例名字
    hostname: eureka7001.com
  client:
    #表识不向注册中心注册自己
    register-with-eureka: false
    #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://eureka7002.com:7002/eureka/
  • 7002YML
server:
  port: 7002

eureka:
  instance:
    #eureka服务端的实例名字
    hostname: eureka7002.com
  client:
    #表识不向注册中心注册自己
    register-with-eureka: false
    #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://eureka7001.com:7001/eureka/
  • 修改支付服务8001微服务和消费服务80微服务的YML
service-url:
  defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
  • 测试

    先要启动EurekaServer,7001/7002服务

    再要启动服务提供者provider,8001服务

    再要启动消费者,80

    http://localhost/consumer/payment/get/31

服务提供者集群
  • 参考cloud-provider-payment8001新建cloud-provider-payment8002(dao,service,mapper,controller)
  • yml
server:
  port: 8002


spring:
  application:
    name: zhifu
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

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


mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities

  • 8001,8002 Controller
@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

    @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,"插入数据库失败,serverPort:"+serverPort,null);
        }
    }
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("*****查询结果:"+payment);
        if (payment!=null){  //说明有数据,能查询成功
            return new CommonResult(200,"查询成功,serverPort:"+serverPort,payment);
        }else {
            return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
        }
    }

    @GetMapping(value = "/payment/lb")
    public String getPaymentLB(){
        return serverPort;
    }


}
负载均衡
  • 订单服务访问地址不能写死,应该写微服务名称,修改80controller
@RestController
@Slf4j
public class OrderController {


    //改为微服务地址,就是在yml中配置的spring:application:name: zhifu
    public static final String PAYMENT_URL = "http://ZHIFU";


    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment) {
        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);
    }

}

  • 修改配置类
/**
 * 配置类
 */
@Configuration
public class ApplicationContextConfig {

    /**注入rest远程调用服务
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

  • 测试

    先要启动EurekaServer,7001/7002服务

    再要启动服务提供者provider,8001/8002服务

    http://localhost/consumer/payment/get/31

    负载均衡效果达到,8001/8002端口交替出现

    Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能了

Discovery服务发现
  • 对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
  • 修改cloud-provider-payment8001的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

  • 测试

    先要启动EurekaServer,7001/7002服务

    再启动8001主启动类,需要稍等一会

    http://localhost:8001/payment/discovery

自我保护
  • 一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存

3:Zookeeper服务注册与发现

  • zookeeper是一个分布式协调工具,可以实现注册中心功能
  • 关闭Linux服务器防火墙后启动zookeeper服务器
  • zookeeper服务器取代Eureka服务器,zk作为服务注册中心

3.1服务提供者8004

  • 新建cloud-provider-payment8004
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment8004</artifactId>

    <dependencies>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- springboot整合zookeeper客户端h -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
          	<!--排除zk3.5.3-->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
      	<!--添加自己安装的zookeeper版本,我这里是zk 3.5.6版本-->
        <!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.6</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


    </dependencies>

</project>
 
  • yml
# 8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
  port: 8004
 
# 服务别名---注册zookeeper到注册中心名称
spring:
  application:
    name: cloud-provider-payment
  cloud:
    zookeeper:
      connect-string: 192.168.136.140:2181 #zookeeper所在服务器的IP和端口号
  • 启动类
@SpringBootApplication
@EnableDiscoveryClient  //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class PaymentMain8004 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8004.class,args);
    }
}
  • controller
@RestController
@Slf4j
public class PaymentController {

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

    @GetMapping(value = "/payment/zk")
    public String paymentzk(){
        return "springcloud with zookeeper:"+serverPort+"\t"+ UUID.randomUUID().toString();
    }

}
  • 测试:http://localhost:8004/payment/zk

  • 服务节点是临时节点还是持久节点?是临时节点

3.2服务消费者80

  • 新建cloud-consumerzk-order80
  • pom
<dependencies>

    <dependency>
        <groupId>org.example</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>


    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <!-- springboot整合zookeeper客户端h -->
    <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-zookeeper-discovery -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        <!--排除zk3.5.3-->
        <exclusions>
            <exclusion>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--添加自己安装的zookeeper版本,我这里是zk 3.5.6版本-->
    <!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.5.6</version>
    </dependency>


    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>


</dependencies>
 
  • yml
server:
  port: 80

spring:
  application:
    name: cloud-consumer-order
  cloud:
    zookeeper:
      connect-string: 192.168.136.140:2181
  • 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class OrderZKMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderZKMain80.class,args);
    }
}

  • 配置类
@Configuration
public class ApplicationContextConfig {

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

}
  • controller
@RestController
@Slf4j
public class OrderZKController {

    public static final String INVOME_URL = "http://cloud-provider-payment";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/zk")
    public String payment (){
      String result = restTemplate.getForObject(INVOME_URL+"/payment/zk",String.class);
      return result;
    }
}
  • 测试:http://localhost/consumer/payment/zk

4:Consul服务注册与发现

  • consul是一套开源的分布式服务发现和配置管理系统,用go语言开发。
  • 官网
  • 提供了微服务系统中的服务治理,配置中心,控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用构建全方位的微服务。
  • 基于raft协议,比较简洁;key , Value的存储方式;Consul支持多数据中心;支持健康检查,同时支持HTTP和DNS协议,支持跨数据中心的WAN集群,提供图形界面,跨平台

4.1安装与运行

  • 下载完成后只有一个consul.exe文件,硬盘路径下双击运行,查看版本信息
  • 使用开发模式启动consul agent -dev
  • 通过以下地址可以访问Consul的首页:http;//localhost:8500

4.2服务提供者8006

  • 新建cloud-providerconsul-payment8006
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-providerconsul-payment8006</artifactId>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-consul-discovery -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>



    </dependencies>

</project>
 
  • yml
### consul 服务端口号
server:
  port: 8006


spring:
  application:
    name: consul-provider-payment
# consul注册中心地址
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
  • 主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8006 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8006.class,args);
    }
}
  • controller
@RestController
@Slf4j
public class PaymentController {

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

    @GetMapping(value = "/payment/consul")
    public String paymentConsul(){
        return "springcloud with consul: "+serverPort+"\t"+ UUID.randomUUID().toString();
    }
}
  • 测试:http://localhost:8006/payment/consul

4.3服务消费者80

  • 新建cloud-consumerconsul-order80
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumerconsul-order80</artifactId>



    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-consul-discovery -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>



    </dependencies>


</project>
  • yml
server:
  port: 80


spring:
  application:
    name: consul-consumer-order
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
  • 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class OrderConsulMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderConsulMain80.class,args);
    }
}

  • 配置类
@Configuration
public class ApplicationContextConfig {

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

}
  • controller
@RestController
@Slf4j
public class OrderConsulController {

    public static final String INVOME_URL = "http://consul-provider-payment";

    @Resource 
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/consul")
    public String payment (){
      String result = restTemplate.getForObject(INVOME_URL+"/payment/consul",String.class);
      return result;
    }


}
  • 测试:http://localhost/consumer/payment/consul

5:注册中心比较

  • 比较
组件名 语言 CAP 健康检查 对外暴露接口 cloud集成
Eureka Java AP 可配支持 HTTP 已集成
Zookeeper GO CP 支持 http/DNS 已集成
Consul Java CP 支持 客户端 已集成
  • CAP详解
    • C:Consistency(强一致性)
    • A:Availability(可用性)
    • P:Partition tolerance(分区容错)
  • CAP理论关注粒度是数据,而不是整体系统设计的策略
  • 最多只能同时满足两个
  • CAP理论的核心是:一个分布式系统不可能同时满足一致性,可用性和分区容错性这三个需求。因此根据CAP原则将NoSql数据库分为满足CA原则,CP原则和AP原则三大类。
  • CA:单点集群,满足一致性,可用性的系统,通常在可扩展性上不强大
  • CP:满足一致性,分区容错的系统,通常性能不高(Consul,Zookeeper)
  • AP:满足可用性,分区容错的系统,对一致性要求比较低(Eureka)

6:Ribbon负载均衡服务调用

  • springCloud Ribbon是基于Netflix实现的一套客户端负载均衡的工具;
  • 主要功能是提供客户端的软件负载均衡算法和服务调用。提供了一系列完善的配置项,如连接超时,重试等。
  • 负载均衡:将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
  • 对比Ribbon和Nginx
    • Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡是由服务端实现的
    • Ribbon本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用。
  • 集中式LB:在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发到服务的提供方。
  • 进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出合适的服务器。Ribbon就属于这种,它只是一个类库,集成在消费方进程中,消费方通过它来获取到服务提供方的地址。
  • Ribbon在工作时分为两步
    • 先选择EurekaServer,它优先选择在同一区域内负载较少的server
    • 再根据用户指定的策略,从server取到的服务注册列表中选择一个地址。
  • Ribbon提供了多种策略:轮询,随机,根据响应时间加权

Ribbon模块

名 称 说 明
ribbon-core 一些比较核心且具有通用性的代码,客户端 API 的一些配置和其他 API 的定义。
ribbon-loadbalancer 负载均衡模块,可独立使用,也可以和别的模块一起使用。 内置的负载均衡算法都实现在其中。
ribbon-eureka 基于 Eureka 封装的模块,能够快速、方便地集成 Eureka。
ribbon-transport 基于 Netty 实现多协议的支持,比如 HTTP、Tcp、Udp 等。
ribbon-httpclient 基于 Apache HttpClient 封装的 REST 客户端,集成了负载均衡模块,可以直接在项目中使用来调用接口。
ribbon-example Ribbon 使用代码示例,通过这些示例能够让你的学习事半功倍。

HttpClient

RestTemplate设计是为了Spring更好的请求并解析Restful风格的接口返回值而设计的,对HttpClient进行了封装以提高其易用性.

使用HttpClient调用远程服务,有这样几个步骤:

  • 创建HttpClient对象
  • 请求参数、地址配置
  • 请求封装与发送
  • Http请求结果的获取HttpEntity
  • 获取Http请求结果中的响应体
  • 将响应体转成java对象

实际演示如下

public class HttpClientTest {

  @Test
  void httpPost() throws Exception {
    //发送远程的http请求的地址
    String url = "http://localhost:8402/sms/send";
    //创建HttpClient对象
    CloseableHttpClient client = HttpClients.createDefault();
    //创建HttpPost对象, 发送post请求
    HttpPost method = new HttpPost(url);
    //封装发送到服务提供者的参数
    NameValuePair phoneNo = new BasicNameValuePair("phoneNo", "13214409773");
    NameValuePair content = new BasicNameValuePair("content", "HttpClient测试远程服务调用");
    List<NameValuePair> params = new ArrayList<>();
    params.add(phoneNo);
    params.add(content);
    //封装请求体数据
    method.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
    //发送具体的http请求
    HttpResponse response = client.execute(method);

    //获得服务提供者响应的具体数据
    HttpEntity entity = response.getEntity();

    //获得http的响应体
    InputStream is = entity.getContent();
    int len = 0;
    char[] buf = new char[1024];
    //使用字符流读
    InputStreamReader reader = new InputStreamReader(is);
    StringBuffer sb = new StringBuffer();
    while((len = reader.read(buf)) != -1){
      sb.append(String.valueOf(buf, 0, len));
    }

    //转成对象
    ObjectMapper mapper = new ObjectMapper();
    AjaxResponse ajaxResponse = mapper.readValue(sb.toString(), AjaxResponse.class);
    System.out.println(ajaxResponse);

  }

}

RestTemplate

RestTemplate是Spring提供的一个访问Http服务的客户端类。从名称上来看,该类更多是针对RESTFUL风格API设计的。RestTemplate的底层实现仍然是HttpClient或HttpUrlConnection或OkHttp(三者可选),只是对它进行了封装,从而降低编码复杂度

一些常用方法如下

HTTP Method 常用方法 描述
GET getForObject 发起GET请求响应对象
GET getForEntity 发起GET请求响应结果、包含响应对象、请求头、状态码等HTTP协议详细内容
POST postForObject 发起POST请求响应对象
POST postForEntity 发起POST请求响应结果、包含响应对象、请求头、状态码等HTTP协议详细内容
DELETE delete 发起HTTP的DELETE方法请求
PUT put 发起HTTP的PUT方法请求

使用RestTemplate ,必须是Spring环境,首先将它初始化为一个Bean

@Configuration
public class ContextConfig {

    //默认实现,实际与urlConnection是一样的底层实现
    @Bean
    @LoadBalanced  //实现远程服务调用的负载均衡
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    }
   //默认实现
    @Bean("urlConnection")
    public RestTemplate urlConnectionRestTemplate(){
        RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
        return restTemplate;
    }

    @Bean("httpClient")
    public RestTemplate httpClientRestTemplate(){
        RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
        return restTemplate;
    }

    @Bean("OKHttp3")
    public RestTemplate OKHttp3RestTemplate(){
        RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
        return restTemplate;
    }
}

实际演示

@ExtendWith(SpringExtension.class)  //Junit5
@SpringBootTest
public class RestTemplateTest {

  @Resource
  private RestTemplate restTemplate;

  @Test
  void httpPostForObject() throws Exception {
    //发送远程http请求的url
    String url = "http://localhost:8402/sms/send";
    //实现负载均衡时,地址是微服务的名称(大写),不再是某一个微服务实例的ip和端口
    String balanceUrl = "http://SERVICE-PROVIDE-SMS/sms/send";
    //发送到远程服务的参数
    MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
    params.add("phoneNo", "13214409773");
    params.add("content", "HttpClient测试远程服务调用");

    //通过RestTemplate对象发送post请求
    AjaxResponse ajaxResponse = restTemplate.postForObject(url, params, AjaxResponse.class);

    System.out.println(ajaxResponse);
  }
    
    @Test
    void httpPostForEntity() throws Exception {
        //发送远程http请求的url
        String url = "http://localhost:8402/sms/send";
        //发送到远程服务的参数
        MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
        params.add("phoneNo", "13214409773");
        params.add("content", "HttpClient测试远程服务调用");

        //通过RestTemplate对象发送post请求
        ResponseEntity<AjaxResponse> entitys = restTemplate.postForEntity(url, params, AjaxResponse.class);

        System.out.println(entitys.getBody());

        //查看响应的状态码
        System.out.println(entitys.getStatusCodeValue());

        //查看响应头
        HttpHeaders headMap = entitys.getHeaders();
        for(Map.Entry<String, List<String>> m : headMap.entrySet()){
          System.out.println(m.getKey() + ": " + m.getValue());
        }
  	}
}

核心组件IRule

  • IRule:根据特定算法从服务列表中选取一个要访问的服务
    • com.netflix.loadbalancer.RoundRobinRule:轮询
    • com.netflix.loadbalancer.RandomRule:随机
    • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试
    • WeightedResponseTimeRule :对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
    • BestAvailableRule :会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
    • AvailabilityFilteringRule :先过滤掉故障实例,再选择并发较小的实例
    • ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
  • 修改cloud-consumer-order80
  • 自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下。否则自定义配置类就会被所有Ribbon客户端共享,达不到特殊定制的目的
  • 新建MySelfRule规则类
@Configuration
public class MySelfRule {

    /**
     * @return ribbon负载均衡规则
     */
    @Bean
    public IRule myRule(){
        return new RandomRule();//定义为随机规则
    }
}

  • 修改启动类
@EnableEurekaClient
@SpringBootApplication
//注意修改name为自己的微服务名称
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }

}
  • 测试:http://localhost/consumer/payment/get/31

负载均衡算法

  • 负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启后rest接口计数从1开始
  • 自实现负载均衡
//使用CAS和自旋锁实现轮询算法
@Component
public class MyLB implements LoadBalancer {

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    //坐标
    private final int getAndIncrement(){
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current + 1;
        }while (!this.atomicInteger.compareAndSet(current,next));  //第一个参数是期望值,第二个参数是修改值是
        System.out.println("*******第几次访问,次数next: "+next);
        return next;
    }
 
   //负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启后rest接口计数从1开始
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {  //得到机器的列表
       int index = getAndIncrement() % serviceInstances.size(); //得到服务器的下标位置
        return serviceInstances.get(index);
    }
}

7:OpenFeign服务接口调用

  • Feign是一个声明式WebService客户端。
  • 使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。springcloud对Feign进行了封装。使他支持springmvc标准注解和HttpMessageConverters。Feign可以和Eureka和Ribbon组合使用支持负载均衡。
  • 以前在使用Ribbon+RestTemplate时,利用RestTemplate对HTTP请求的封装处理,形成一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,一个接口可能被多处调用,通常会针对每个微服务自行封装一些客户端来包装这些服务依赖的调用。所以Feign做了进一步封装,来帮助我们定义和实现依赖服务接口的定义。在Feign下,我们只需要创建一个接口并使用注解的方式(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上标注一个Feign注解)即可完成对服务提供方的接口绑定。
  • Feign和OpenFeign区别
    • Feign:是springcloud中的一个轻量级RESTFul的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。使用方式是使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
    • OpenFeign:在Feign基础上支持了springmvc的注解,如@RequestMapping等。OpenFeign的@FeignClient可以解析@RequestMapping下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

OpenFeign 是一个声明式网络服务客户端,它让编写网络服务客户端变得更简单。使用 Feign 调用其他服务时,我们几乎不需要编写任何代码。我们只需创建一个接口并对其进行注解即可。它支持 OpenFeign 和 Jakarta JAX-RS 注释。

Feign、OpenFeign 和 Spring HttpExchange 都是用于在 Java 应用程序中创建声明式 HTTP 客户端。

功能 Feign OpenFeign Spring HttpExchange
起源与目的 由 Netflix 开发 扩展自Feign,功能大大增强 Spring的一部分
Spring集成 可用于 Spring 和非 Spring 应用程序 通过Spring Cloud OpenFeign 模块集成 Spring特有
定制 可定制的组件、请求和响应 可定制的组件、请求和响应 更加细粒度的控制
社区 不太活跃 非常活跃 亲儿子,没得说
案例 微服务、RESTful API 集成 微服务、RESTful API 集成 Spring应用程序中的自定义HTTP交互

在Spring Boot项目中,个人更建议使用HttpExchange,你无需引入任何其它的依赖。不过OpenFeign 与服务发现和负载平衡等其他 Spring Cloud组件集成得很好。而 HttpExchange 使用 WebClient 来调用 HTTP 请求,因此能更好地控制请求和响应处理。但是,OpenFeign 是基于每请求线程模型的阻塞型客户端,对于每个请求,指定的线程都会阻塞,直到收到响应为止。HttpExchange 使用的 WebClient 是非阻塞和反应式的,因此很容易克服上述性能瓶颈。

7.1使用步骤

  • 新建cloud-consumer-feign-order80
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-order80</artifactId>


    <!--openfeign-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>
</project>
  • yml
server:
  port: 80
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
  • 启动类

需要使用 @EnableFeignClients 注解启用 Feign Clients,该注解可对所有注释为 @FeignClient 的接口进行组件扫描。这里配置了basePackages属性来指定要扫描的包,如果你的注解使用在启动类上,那么你可以不用指定。

@SpringBootApplication
@EnableFeignClients(basePackages="com.pack.feign")
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}
  • 业务逻辑接口+@FeignClient配置调用provider服务
  • 新建PaymentFeignService接口并新增注解@FeignClient
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
//@FeignClient(value = "CLOUD-PAYMENT-SERVICE", url = "http://localhost:8001")
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);
}

为了避免模板代码,feign还可以使用继承

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE", url = "http://localhost:8001")
public interface ParentPaymentFeignService extends PaymentFeignService{

    @GetMapping(value = "/payment/get/{parentId}")
    public CommonResult getParentPaymentById(@PathVariable("parentId") Long parentId);
}
  • controller
@RestController
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

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

  • 测试

    先启动2个eureka集群7001/7002

    再启动2个微服务8001/8002

    启动OpenFeign启动

    http://localhost/consumer/payment/get/31

    Feign自带负载均衡配置项

7.2超时控制

  • 服务提供方8001故意写暂停程序
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
    try { TimeUnit.SECONDS.sleep(3); }catch (Exception e) {e.printStackTrace();}
    return serverPort;
}
  • 服务消费方80添加超时方法PaymentFeignService
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
  • 服务消费方80添加超时方法OrderFeignController
@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
  //openfeign-ribbon,客户端一般默认等待一秒钟
   return paymentFeignService.paymentFeignTimeout();
}
  • 测试后出现请求超时http://localhost/consumer/payment/feign/timeout

  • OpenFeign默认等待一秒钟,超过后报错
  • YML文件里需要开启OpenFeign客户端超时控制
# 设置feign客户端超时时间(openFeign默认支持ribbon)
ribbon:
# 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout:  5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

7.3 OpenFeign配置

每个 Feign 客户端都由一组组件组成,这些组件共同作用于远程服务调用。无论何时创建命名客户端,Spring Cloud 都会使用 FeignClientsConfiguration 为这些组件创建默认值。在该类中的大多数bean定义,我们都可以通过自定义的方式进行覆盖。

默认有:

  • Decoder:要对响应进行解码,需要使用封装了 SpringDecoder 的 ResponseEntityDecoder。
  • Encoder:SpringEncoder 用于对 RequestBody 进行编码。
  • Logger:Slf4jLogger 是 Feign 使用的默认日志记录器。
  • Contract:提供注解处理的 SpringMvcContract。
  • Feign.Builder:用于构建Feign组件。
  • Retryer:当容器中不存在断路器相关依赖或为开启时,可配置重试请求的策略。
  • Client:这是全局的,用来处理请求。

自定义的配置如下

Spring Cloud 允许我们通过指定附加配置,用来覆盖 FeignClientsConfiguration 中的默认bean配置。

如下,覆盖 AccountFeignClient 中的默认 HttpClient,使用 ApacheHttp5Client。我们需要创建一个配置类 AccountFeignConfiguration 并声明客户端 bean。

@Configuation
public class PackAccountFeignConfiguration {
  @Bean
  public CloseableHttpClient feignClient() {
    return HttpClients.createDefault() ;
  }
}

接下来,可以在@FeignClient注解中如下定义

@FeignClient(
  value = "accountFeignClient",
  url = "http://localhost:8088/",
  configuration = PackAccountFeignConfiguration.class
)
public interface AccountFeignClient extends UserFeignClient {
}

通过configuration属性指定我们自定义的配置。你还需要引入下面的依赖

<dependency>  
    <groupId>io.github.openfeign</groupId>  
    <artifactId>feign-hc5</artifactId>
</dependency>

如果你使用okhttp,那么也请引入对应的依赖即可。

还可以通过配置文件进行属性覆盖,比如配置feign客户端超时时间

spring:
  cloud:
    openfeign:
      config:
        default:
          connectTimeout: 5000

还可以指定某个客户端的配置,比如给上面定义的客户端AccountFeignClient配置超时时间

spring:
  cloud:
    openfeign:
      config:
        accountFeignClient:
          connectTimeout: 5000

修改配置文件和配置类的生效优先级

spring:
  cloud:
    openfeign:
      client:
        default-to-properties: false

配置读写超时

spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            read-timeout: 5000
            connect-timeout: 5000

7.4拦截器

Feign 提供了 RequestInterceptor 接口,可用于执行身份验证或日志记录等任务。

@Bean
RequestInterceptor requestInterceptor() {
    // 为请求添加自定义的请求header。
  return requestTemplate -> {
    requestTemplate.header("requestID", "UUID");
  } ;
}

7.5异常处理

Feign 的默认 ErrorDecoder 会在出现错误时抛出 FeignException。我们可以编写自己的自定义 ErrorDecoder。这样,我们就可以进行特定的错误处理。

@Bean
ErrorDecoder errorDecoder() {
  return new ErrorDecoder() {
    public Exception decode(String methodKey, Response response) {
      return switch (response.status()) {
        case 400 -> new BadRequestException(response) ;
        case 429 -> new TooManyRequestsException(response) ;
        default -> new Exception("feign client exception") ;
    } ;
  } ;
}

7.6日志打印

  • 对Feign接口的调用情况进行监控和输出
  • 日志级别
    • NONE:默认的,不显示任何日志
    • BASIC:仅记录请求方法,URL,响应状态码以及执行时间
    • HEADERS:除了BASIC包含信息以外,还包含请求和响应的头信息
    • FULL:除了HEADER包含信息以外,还包含请求和响应的正文和元数据。
  • 配置日志bean
@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}
  • YML文件里需要开启日志的Feign客户端
logging:
  level:
# feign日志以什么级别监控哪个接口
    com.atguigu.springcloud.service.PaymentFeignService: debug

指定包目录

logging:
  level:
    com.pack.feign: DEBUG

7.7缓存

如果使用 @EnableCaching 注解,则会创建并注册一个 CachingCapability Bean,以便 Feign 客户端识别其接口上的 @Cache 注解

@Cacheable(cacheNames = "infos", key = "#id")
@GetMapping("/info/{id}")
public Object info(@PathVariable("id") Integer id) ;

使用redis缓存

spring:
  cache:
    type: redis
    redis:
      key-prefix: 'feign:'
      time-to-live: 60s

7.8参数映射

Spring Cloud OpenFeign 提供了等价的 @SpringQueryMap 注解,用于将 POJO 或 Map 参数注释为查询参数映射。

feign接口

@GetMapping("/format")
Object format(@SpringQueryMap Params params) ;

controller接口

@GetMapping("/format")
public Object format(Params params) {
  return this.demoFeign.format(params) ;
}

参数对象


public class Params {
  private Long id ;
  private String name ;
}

Spring Cloud OpenFeign 支持 Spring @MatrixVariable 注解。如果方法参数传递的是映射,则 @MatrixVariable 路径段是通过用 = 连接映射中的键值对创建的。如果传递的是不同的对象,则使用 = 将 @MatrixVariable 注解(如果已定义)中提供的名称或注解的变量名称与提供的方法参数连接起来。这里需要路径占位符的名称与@MatrixVariable注解的变量名一致。

@GetMapping("/m3/{params}")
public Object matrix3(@MatrixVariable Map<String, List<String>> params)

提供@CollectionFormat注解来支持 feign.CollectionFormat。可以通过传递所需的 feign.CollectionFormat 作为注解值,为 Feign 客户端方法(或整个类的所有方法)添加注解。

@GetMapping("/cf")
@CollectionFormat(feign.CollectionFormat.CSV)
public Object cf(@RequestParam("ids") List<String> ids) ;

//请求案例:GET http://localhost:8088/demos/cf?ids=S1,S2,S3

7.9 feign-reactive

Feign-reactive是一个用于在Spring Cloud应用程序中实现响应式微服务的框架。它支持在Spring Cloud应用程序中实现异步和非阻塞的远程调用。Feign-reactive的一些主要特点:

  1. 基于Feign的简洁风格:Feign-reactive继承了Feign的简洁风格,使得在编写基于微服务架构的应用程序时,可以更加方便地实现异步编程。
  2. 支持Reactive编程模型:Feign-reactive提供对Reactive编程模型的支持,使得在编写异步和非阻塞的代码时更加容易。
  3. 异步和非阻塞远程调用:通过Feign-reactive,可以轻松地实现异步和非阻塞的远程调用,从而提高应用程序的响应速度和吞吐量。
  4. 与Spring Cloud集成:Feign-reactive与Spring Cloud集成,使得可以在Spring Cloud应用程序中方便地使用Feign-reactive实现响应式微服务。
  5. 可扩展性:Feign-reactive具有可扩展性,可以根据需要添加自定义的拦截器、编码器和解码器等。

Feign-reactive是一个非常有用的框架,可以帮助开发人员轻松地实现响应式微服务,提高应用程序的性能和吞吐量。

依赖

<dependency>
  <groupId>com.playtika.reactivefeign</groupId>
  <artifactId>feign-reactor-spring-configuration</artifactId>
  <version>3.3.0</version>
</dependency>
<dependency>
  <groupId>com.playtika.reactivefeign</groupId>
  <artifactId>feign-reactor-cloud</artifactId>
  <version>3.3.0</version>
</dependency>
<dependency>
  <groupId>com.playtika.reactivefeign</groupId>
  <artifactId>feign-reactor-webclient</artifactId>
  <version>3.3.0</version>
</dependency>

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>

远程接口

@GetMapping("/demos/info/{id}")
public Object info(@PathVariable("id") Integer id) throws Exception {
  TimeUnit.SECONDS.sleep(3) ;
  Map<String, Object> result = new HashMap<>() ;
  result.put("code", 0) ;
  result.put("data", id) ;
  result.put("message", "success") ;
  return result ;
}

注解开启反应式功能

@EnableReactiveFeignClients
public class AppFeignReactorApplication {}

feign接口定义

@ReactiveFeignClient(
    url = "http://localhost:8088/demos", 
    name = "demoReactorFeign"
)
public interface DemoReactorFeign {
  @GetMapping("/info/{id}")
  public Mono<Object> info(@PathVariable("id") Integer id) ;
}

测试调用

@Resource
private DemoReactorFeign demoReactorFeign ;

@GetMapping("/{id}")
public Object info(@PathVariable("id") Integer id) {
  return this.demoReactorFeign.info(id) ;
}

降级配置

@ReactiveFeignClient(
    url = "http://localhost:8088/demos", 
    name = "demoReactorFeign", 
    fallback = DemoReactorFeignFallback.class,
    configuration = {DemoReactorFeignConfig.class}
)
public interface DemoReactorFeign {}


//降级接口定义
public class DemoReactorFeignFallback implements DemoReactorFeign {

  @Override
  public Mono<Object> info(Integer id) {
    return Mono.just("请求失败") ;
  }

}

//自定义配置
public class DemoReactorFeignConfig {

  @Bean
  public DemoReactorFeignFallback demoReactorFeignFallback() {
    return new DemoReactorFeignFallback() ;
  }
  
}

//当远程接口调用失败或者超时将会执行对应的fallback

超时配置

reactive:
  feign:
    client:
      config:
        demoReactorFeign:
          options:
            connectTimeoutMillis: 2000
            readTimeoutMillis: 2000

负载均衡配置

reactive:
  feign:
    loadbalancer:
      enabled: true

断路器配置

reactive:
  feign:
    circuit:
      breaker:
       enabled: true

8:Hystrix断路器

  • 分布式系统面临的问题:复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败。
  • 什么是服务雪崩
    • 多个微服务调用的时候,假设A调用B和C,B和C又调用其他微服务,这就是“扇出”。如果扇出的链路上某个微服务的调用时间过长或者不可用,对A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的雪崩效应。
    • 对于高流量的应用来说,单一的后端依赖可能会导致所有的服务器上的所有资源在几秒内饱和,比这更糟的是,这些应用程序可能还导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致系统发生级联故障,需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用系统。
  • Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖会发生不可避免的调用失败,比如超时,异常等。Hystrix能保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,提高分布式系统的弹性。
  • “断路器”本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似熔断保险丝),向调用方发送一个符合预期的可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常。这样就保证了服务调用方的线程不会被长时间,不必要的占用,从而避免故障蔓延,发生雪崩。
  • Hystrix能做到服务降级,服务熔断,接近实时的监控。

  • 服务降级:服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback。哪些情况会触发降级(程序运行异常,超时,服务熔断触发服务降级,线程池/信号量打满也会导致服务降级)
  • 服务熔断:类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。服务的降级->进而熔断->恢复调用链路
  • 服务限流:秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

8.1服务提供者8001

  • 新建cloud-provider-hystrix-payment8001
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-hystrix-payment8001</artifactId>


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


        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>

</project>
  • yml
server:
  port: 8001


eureka:
  client:
    register-with-eureka: true    #表识不向注册中心注册自己
    fetch-registry: true   #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
    service-url:
      # defaultZone: http://eureka7002.com:7002/eureka/    #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://eureka7001.com:7001/eureka/
#  server:
#    enable-self-preservation: false
spring:
  application:
    name: cloud-provider-hystrix-payment
#    eviction-interval-timer-in-ms: 2000
  • 主启动
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}
  • service
@Service
public class PaymentService {

    //成功
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_OK,id:  "+id+"\t"+"哈哈哈"  ;
    }

    //失败
    public String paymentInfo_TimeOut(Integer id){
        int timeNumber = 3;
        try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
    }

}
  • controller
@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    @GetMapping("/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("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("*******result:"+result);
        return result;
    }
}

  • 测试

    启动eureka7001

    启动cloud-provider-hystrix-payment8001

    http://localhost:8001/payment/hystrix/ok/31

    http://localhost:8001/payment/hystrix/timeout/31

8.2服务消费者80

  • 新建cloud-consumer-feign-hystrix-order80
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-hystrix-order80</artifactId>

    <dependencies>
        <!--新增hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
       <dependency>
             <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
       </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>

</project>
  • yml
server:
  port: 80


eureka:
  client:
    register-with-eureka: true    #表识不向注册中心注册自己
    fetch-registry: true   #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
    service-url:
             defaultZone: http://eureka7001.com:7001/eureka/

spring:
  application:
    name: cloud-provider-hystrix-order
  • 主启动
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}

  • service
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
  • controller
@RestController
@Slf4j
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_OK(id);
        log.info("*******result:"+result);
        return result;
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        log.info("*******result:"+result);
        return result;
    }

}
  • 高并发测试

    2W个线程压8001

    消费端80微服务再去访问正常的OK微服务8001地址

    http://localhost/consumer/payment/hystrix/timeout/31

    消费者80,呜呜呜

  • 8001同一层次的其他接口服务被困死,因为tomcat线程里面的工作线程已经被挤占完毕
  • 80此时调用8001,客户端访问响应缓慢,转圈圈
  • 正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生
  • 超时导致服务器变慢(转圈):超时不再等待
  • 出错(宕机或程序运行出错):出错要有兜底
  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
  • 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
  • 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

8.3服务降级

  • 8001先从自身找问题:设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback。
  • @HystrixCommand报异常后如何处理
  • 一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
@Service
public class PaymentService {

    //成功
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_OK,id:  "+id+"\t"+"哈哈哈"  ;
    }

    //失败
//fallbackMethod:指定兜底方法
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")  //3秒钟以内就是正常的业务逻辑
    })
    public String paymentInfo_TimeOut(Integer id){
       // int timeNumber = 5;
        int age = 10/0;
       // try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
        //return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)";
    }

    //兜底方法
    public String paymentInfo_TimeOutHandler(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   系统繁忙, 请稍候再试  ,id:  "+id+"\t"+"哭了哇呜";
    }

}
  • 主启动类添加新注解@EnableCircuitBreaker

  • 80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护
  • 我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务
  • yml
feign:
  hystrix:
    enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
  • 主启动类:@EnableHystrix
  • 业务类
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")  //3秒钟以内就是正常的业务逻辑
})
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秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
  • 问题:每个业务方法对应一个兜底的方法,代码膨胀,统一和自定义的分开
  • 使用@DefaultProperties设置全局fullback
  • 修改80controller
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")  //全局的
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/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
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        int age = 10/0;
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

    //下面是全局fallback方法
    public String payment_Global_FallbackMethod(){
        return "Global异常处理信息,请稍后再试,(┬_┬)";
    }
}
  • 服务降级,客户端去调用服务端,碰上服务端宕机或关闭
  • 本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦
  • 修改cloud-consumer-feign-hystrix-order80
  • 根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理
@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
    }
}
  • yml
feign:
  hystrix:
    enabled: true #在feign中开启hystrix,如果处理自身的容错就开启。开启方式与生产端不一样。
  • PaymentFeignClientService接口,指定fullback处理类
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {

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

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);


}
  • 测试

    单个eureka先启动7001

    PaymentHystrixMain8001启动

    正常访问测试:http://localhost/consumer/payment/hystrix/ok/31

    故意关闭微服务8001

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

8.4服务熔断

  • 熔断机制是应对雪崩效应的一种微服务链路的保护机制。当扇出链路的某个微服务出错不可用或响应时间太长时,会进行服务的降级,进而熔断该节点的微服务调用,快速返回错误的响应信息。当检测到该节点微服务调用的响应正常后,恢复调用链路。
  • 熔断机制通过Hystrix实现,通过检测微服务间的调用情况,当失败的调用达到一定阈值,缺省是5秒内20次失败,就会启动熔断机制,注解是@HystrixCommand。
  • 修改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();

    return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
    return "id 不能负数,请稍候再试,(┬_┬)/~~     id: " +id;
}
  • PaymentController
//===服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
    String result = paymentService.paymentCircuitBreaker(id);
    log.info("*******result:"+result);
    return result;
}
  • 测试

    自测cloud-provider-hystrix-payment8001

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

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

    多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行访问,需要慢慢的恢复链路

  • 熔断类型
    • 熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入熔断状态
    • 熔断关闭:熔断关闭不会对服务进行熔断
    • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
  • 断路器在什么情况下起作用?
    • 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒
    • 请求总数阈值:在快照时间窗内,必须满足请求总数阈值才能熔断,默认为20,也就是10秒内该命令调用不足20次,即使所有的请求都超时或失败,断路器都不会打开
    • 错误百分比阈值:当请求总数在快照时间窗内超过阈值,比如调用了30次,15次为超时,也就是达到了50%的错误,当默认设定50%阈值情况下,断路器打开。
  • 当开启的时候,所有请求都不会进行转发。一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复
  • 断路器打开之后,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fullback,通过断路器,实现了自动的发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
  • 原来的主逻辑如何恢复?实现了自动恢复功能,断路器打开对主逻辑进行熔断后,hystrix会启动一个休眠时间窗,在时间窗内,降级逻辑是暂时的主逻辑,当休眠时间窗到期后,断路器进入半开状态,释放一次请求到原来主逻辑上,如果请求正常返回,断路器闭合,主逻辑恢复,若这次请求依然有问题,继续保持熔断状态,休眠时间窗重新计时。

8.5服务监控9001

  • 新建cloud-consumer-hystrix-dashboard9001
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>


    <dependencies>
        <!--新增hystrix dashboard-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</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>

</project>
  • yml
server:
  port: 9001
  • 主启动类HystrixDashboardMain9001+新注解@EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class,args);
    }
}
  • 所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001

    http://localhost:9001/hystrix

  • 修改cloud-provider-hystrix-payment8001

  • 注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径

/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是“/hystrix.stream”
* 只要在自己的项目里配置上下面的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;
}
 

  • 启动1个eureka或者3个eureka集群均可

  • 9001监控8001

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

  • 测试地址

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

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

    上述测试通过

    先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的

  • 如何看

    • 实心圆:通过颜色代表实例的健康程度,绿色<黄色<橙色<红色。大小反应请求流量,流量越大实心圆越大。
    • 曲线:用来记录两分钟内流量的相对变化

image.png

image.png

9:Gateway新一代网关

  • gateway是上一代网关zuul1.x的代替

  • gateway是springcloud的一个全新项目,基于springboot2.0+spring5.0和project reactor等技术,旨在为微服务架构提供一种简单有效的统一API路由管理方式。

  • gateway是基于WebFlux框架实现的,底层使用了高性能Reactor模式通信框架Netty

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

  • gateway特性

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

    可以对路由指定predict(断言)和filter(过滤器)

    集成Hystrix的断路器功能

    集成springcloud服务发现功能

    请求限流功能

    支持路径重写

9.1核心概念和工作流程

  • Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  • Predicate(断言):参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  • Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

  • 工作流程
    • 客户端向gateway发送请求,然后在gateway handler mapping中找到与请求相匹配的路由,将其发送到gateway web handler
    • handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器可能会在发送代理请求之前或之后执行业务逻辑
    • filter在“pre”类型的过滤器可以做参数校验,权限校验,流量监控,日志输出,协议转换等。在“post”类型的过滤器中可以做响应内容,响应头的修改,日志的输出,流量监控等。

9.2入门配置9527

  • 新建cloud-gateway-gateway9527
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-gateway-gateway9527</artifactId>


    <dependencies>
        <!--新增gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</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-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>

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

        - id: payment_routh2
          uri: http://localhost:8001
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由


eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    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的访问地址

    我们目前不想暴露8001端口,希望在8001外面套一层9527

  • YML新增网关配置

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

    - id: payment_routh2
      uri: http://localhost:8001
      predicates:
        - Path=/payment/lb/**   #断言,路径相匹配的进行路由


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

  • test

    启动7001,cloud-provider-payment8001

    启动9527网关

    添加网关前:http://localhost:8001/payment/get/31

    添加网关后:http://localhost:9527/payment/get/31

9.3动态路由

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

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

  • yml

    需要注意的是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 #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由

        - id: payment_routh2
          #uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由


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

    http://localhost:9527/payment/lb

    8001/8002两个端口切换

9.4Predicate的使用

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

  • 常用的Route Predicate

    After Route Predicate

    - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
    

    Before Route Predicate

    - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
    - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
    

    Between Route Predicate

    - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] ,  2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
    
    

    Cookie Route Predicate

    - Cookie=username,atguigu    #并且Cookie是username=zhangshuai才能访问
    

    Header Route Predicate

    - Header=X-Request-Id, \d+   #请求头中要有X-Request-Id属性并且值为整数的正则表达式
    

    Host Route Predicate

    - Host=**.atguigu.com
    

    Method Route Predicate

    - Method=GET
    

    Path Route Predicate

    Query Route Predicate

    - Query=username, \d+ #要有参数名称并且是正整数才能路由
    
  • 总结

server:
  port: 9527
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由
 
        - id: payment_routh2
          #uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由
            #- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
            #- Cookie=username,zhangshuai #并且Cookie是username=zhangshuai才能访问
            #- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
            #- Host=**.atguigu.com
            #- Method=GET
            #- Query=username, \d+ #要有参数名称并且是正整数才能路由
 
 
eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

9.5Filter的使用

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

  • 路由过滤器由GatewayFilter工厂类来产生

  • 生命周期

    pre:在业务逻辑之前

    post:在业务逻辑之后

  • 种类

    GatewayFilter:单一

    GlobalFilter:全局

  • 自定义全局GlobalFilter

@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("username");
        if(StringUtils.isEmpty(uname)){
            log.info("*****用户名为Null 非法用户,(┬_┬)");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

  • 测试

    启动eureka7001,payment8001,8002,gateway9527

    http://localhost:9527/payment/lb?uname=z3

10:Config分布式配置中心

  • 分布式系统面临的配置问题:每一个微服务项目都有一个application.yml配置文件,服务多了之后就不好管理了。

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

  • Config分为服务端和客户端两部分

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

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

  • Config运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息。当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置。将配置信息以REST接口的形式暴露

10.1服务端配置3344

  • 用你自己的账号在Github上新建一个名为sprincloud-config的新Repository

  • 本地硬盘上新建git仓库并clone,此时在本地D盘符下D:\44\SpringCloud2020\springcloud-config

    创建三个yml文件,分别为dev,prod,test表示多个环境的配置文件

    保存格式必须为UTF-8

  • 新建Module模块cloud-config-center-3344它既为Cloud的配置中心模块cloudConfig Center
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-config-center-3344</artifactId>

    <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>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>

</project>
  • yml
server:
  port: 3344
spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri:  填写你自己的github路径
          # 搜索目录
          search-paths:
            - springcloud-config
      # 读取分支
      label: master
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

  • 测试通过Config微服务是否可以从Github上获取配置内容

    启动微服务3344

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

  • 配置读取规则

  • /{label}/{application}-{profile}.yml(最推荐使用这种方式)

    master分支

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

    dev分支

    http://config-3344.com:3344/dev/config-dev.yml

  • /{application}-{profile}.yml

    http://config-3344.com:3344/config-dev.yml

  • /{application}-{profile}[/{label}]

    http://config-3344.com:3344/config/dev/master

  • label:分支

    name:服务名

    profiles:环境(dev,test,prod)

10.2客户端配置3355

  • 新建cloud-config-client-3355
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-config-client-3355</artifactId>

    <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>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>

</project>
  • bootstap.yml
    • application.yml是用户级别的资源配置项
    • bootstrap.yml是系统级别的资源配置项(优先级更高)
  • springcloud会创建一个“Bootstrap Context”,作为spring的“Application context”的父上下文。初始化的时候,“Bootstrap Context”负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的“Environment”。“Bootstrap Context”和“Application context”有不同的约定,所以新增bootstrap.yml文件,保证“Bootstrap Context”和“Application context”配置的分离
  • 要将Client模块的application.yml改为bootstrap.yml。因为bootstrap.yml是先加载的,优先级高
server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
   # config客户端设置
    config:
      label: master # 分支名称
      name: config  # 配置文件名称
      profile: dev  # 读取后缀名称
      uri: http://localhost:3344  # 配置中心地址
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
  • 修改config-dev.yml配置并提交到GitHub中,比如加个变量age或者版本号version
  • 主启动类
@SpringBootApplication
public class ConfigClientMain3355 {
    public static void main(String[] args) {
            SpringApplication.run( ConfigClientMain3355.class,args);
        }
}

  • 业务类
@RestController
public class ConfigClientController {

    @Value("${config.info}")
    private String configInfo;

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

  • test

    启动Config配置中心3344微服务并自测

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

    http://config-3344.com:3344/master/config-test.yml

    启动3355作为Client准备访问

    http://localhost:3355/configInfo

    成功实现了客户端3355访问SpringCloud Config3344通过GitHub获取配置信息

  • 问题随时而来,分布式配置的动态刷新

    Linux运维修改GitHub上的配置文件内容做调整

    刷新3344,发现ConfigServer配置中心立刻响应

    刷新3355,发现ConfigServer客户端没有任何响应

    3355没有变化除非自己重启或者重新加载

    难道每次运维修改配置文件,客户端都需要重启??噩梦

10.3客户端动态刷新

  • 避免每次更新配置都要重启客户端微服务3355
  • 修改3355模块
  • POM引入actuator监控
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 修改YML,暴露监控端口
server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    config:
      label: master
      name: config
      profile: dev
      uri: http://localhost:3344
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
 
# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"
  • 业务类添加注解
@RefreshScope
@RestController
public class ConfigClientController {

    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return configInfo;
    }
}
  • 此时修改github

  • 需要运维人员发送Post请求刷新3355,必须是Post请求

    curl -X POST “http://localhost:3355/actuator/refresh”

    访问http://localhost:3355/configInfo

    更改成功,成功实现了客户端3355刷新到最新配置内容,避免了服务的重启

  • 引出新的问题

    假如有多个微服务客户端3355/3366/3377。。。。

    每个微服务都要执行一次post请求,手动刷新?

    可否广播,一次通知,处处生效?

    我们想大范围的自动刷新,求方法

11:Bus消息总线

  • 分布式自动刷新配置功能
  • Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新
  • Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。目前支持RabbitMQ和Kafka
  • Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改,事件推送等,也可以当做微服务间的通信通道。
  • 什么是总线:在微服务架构系统中,通常会使用轻量级的消息代理来构建一个共同的消息主题,并让系统中所有微服务实例都连接上来,由于该主题中产生的消息会被所有实例监听和消费,所以称为消息总线。
  • 基本原理:ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus),当一个服务刷新数据的时候,它会把这个信息放入到topic中,这样其他监听同一个topic的服务就能得到通知,然后去更新自身的配置。

11.1RabbitMQ环境配置

  • 安装Erlang

  • 安装RabbitMQ

  • 进入RabbitMQ安装目录下的sbin目录

    输入以下命令启动管理功能rabbitmq-plugins enable rabbitmq_management

    访问地址查看是否安装成功

    http://localhost:15672/

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

11.2Bus动态刷新全局广播

  • 必须先具备良好的RabbitMQ环境先
  • 演示广播效果,增加复杂度,再以3355为模板再制作一个3366
  • 新建cloud-config-client-3366
  • pom

<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>com.atguigu.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </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>
  • yml
server:
  port: 3366

spring:
  application:
    name: config-client
  cloud:
    config:
      label: master
      name: config
      profile: dev
      uri: http://localhost:3344
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
management:
  endpoints:
    web:
      exposure:
        include: "*"
  • 主启动
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3366 {
    public static void main(String[] args) {
            SpringApplication.run( ConfigClientMain3366.class,args);
        }
}
  • controller
@RestController
@RefreshScope
public class ConfigClientController {

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

    @Value("${config.info}")
    private String configInfo;


    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return "serverPort:"+serverPort+"\t\n\n configInfo: "+configInfo;
    }

}
  • 设计思想

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

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

  • 方式1不适合的原因

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

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

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

  • 给cloud-config-center-3344配置中心服务端添加消息总线支持
  • pom
<!--添加支持消息总线rabbitmq-->
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
  • yml
server:
  port: 3344
spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri:  https://github.com/hhf19906/springcloud-config.git  #git@github.com:hhf19906/springcloud-config.git
          search-paths:
            - springcloud-config
      label: master
 
# rabbitmq相关配置
rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

eureka:
  client:
    service-url:
      defaultZone:  http://localhost:7001/eureka
 
# rabbitmq相关配置,暴露bus刷新配置的端点
management:
  endpoints: 
    web:
      exposure:
        include: 'bus-refresh'
  • 给cloud-config-center-3355客户端添加消息总线支持
  • pom
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
  • yml
server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    config:
      label: master
      name: config
      profile: dev
      uri: http://localhost:3344

rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
management:
  endpoints:
    web:
      exposure:
        include: "*"

  • 给cloud-config-center-3366客户端添加消息总线支持
  • pom同上
  • yml同上

  • 测试

  • 运维工程师

    修改Github上配置文件增加版本号

    发送Post请求curl -X POST "http://localhost:3344/actuator/bus-refresh"

    一次发送,处处生效

  • 配置中心

    http://config-3344.com/config-dev.yml

  • 客户端

    http://localhost:3355/configInfo

    http://localhost:3366/configInfo

    获取配置信息,发现都已经刷新了

11.3Bus动态刷新定点通知

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

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

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

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

12:Stream消息驱动

  • 什么是stream:stream是一个构建消息驱动微服务的框架,通过输入输出流来与stream中的binder交互。通过我们配置来binding(绑定),stream的binder负责与消息中间件交互。目前:一句话只支持RabbitMQ,Kafka。

  • 一句话:就像JDBC一样,屏蔽底层消息中间件的差异,降低切换版本,统一消息的编程模型(比如系统A用Rabbitmq,系统B用Kafka,两个系统进行交互,需要有一个统一规范)

  • 标准MQ:

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

    消息必须走特定的通道,消息通道MessageChannel

    消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器订阅

  • 为什么使用stream

    不同的消息中间件在架构上不同,像rabbitMQ有exchange,Kafka有topic和partitions分区,这些中间件的差异性导致我们在业务需求中想往另外一种消息队列进行迁移,这时一堆东西需要重做,因为它跟我们的系统耦合了,这时stream给我们提供了一种解耦方式。

  • stream怎样统一底层差异

    在没有绑定器这个概念时,我们的boot应用直接与消息中间件进行信息交互的时候,由于各中间件构建初衷不同,实现细节上也有差异。通过定义绑定器作为中间层,完美实现了应用程序与中间件细节之间的隔离。通过向应用程序暴露统一的channel通道,使得应用程序不用考虑各中间件的实现。

    INPUT对应消费者

    OUTPUT对应生产者

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

    Topic主题进行广播

    在RabbitMQ就是Exchange

    在kafka中就是Topic

  • Spring Cloud Stream标准流程套路

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

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

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

  • 编码API和常用注解

组成 说明
Middleware 中间件,目前支持RabbitMQ,Kafka
Binder Binder是应用于中间件之间的封装,可以动态改变消息类型,通过配置文件实现
@Input 注解标识输入通道,通过该输入通道接收到的消息进入应用程序
@Output 注解标识输出通道,发布的消息通过该通道离开应用程序
@StreamListener 监听队列,用于消费者的消息接收
@EnableBinding 指通道channel和exchange绑定在一起

12.1消息驱动生产者8801

  • 新建cloud-stream-rabbitmq-provider8801
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-stream-rabbitmq-provider8801</artifactId>

    <dependencies>


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

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

     
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>



    </dependencies>

</project>
  • 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地址
  • 主启动类
@SpringBootApplication
public class StreamMQMain8801 {
    public static void main(String[] args) {
        SpringApplication.run(StreamMQMain8801.class, args);
    }
}

  • 发送消息接口
public interface IMessageProvider
{
    public String send();
}
  • 发送消息接口实现类
@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
public class SendMessageController
{
    @Resource
    private IMessageProvider messageProvider;

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

}

  • test

    启动7001eureka

    启动rabbitmq

    rabbitmq-plugins enable rabbitmq_management

    http://localhost:15672/

    启动8801

    http://localhost:8801/sendMessage

12.2消息驱动之消费者8802

  • 新建cloud-stream-rabbitmq-consumer8802
  • pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-stream-rabbitmq-consumer8802</artifactId>

    <dependencies>


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

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>


</project>
  • 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地址
  • 主启动类
@SpringBootApplication
public class StreamMQMain8802 {

    public static void main(String[] args) {
        SpringApplication.run(StreamMQMain8802.class, args);
    }

}
  • 业务类
@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接收消息

    http://localhost:8801/sendMessage

12.3分组消费与持久化

  • 依照8802,clone出来一份运行8803

  • 运行后两个问题

    有重复消费问题,8802,8803都消费了消息

    消息持久化问题,8803没有分组重启后没有消费消息

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

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

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

  • 8802修改YML,8803修改YML

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

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

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

    8801先发送4条信息到rabbitmq

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

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

13:Sleuth分布式请求链路追踪

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

  • Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案,来监控整个链路

  • 在分布式系统中提供追踪解决方案并且兼容支持了zipkin

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

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

    zipkin-server-2.12.9.exec.jar

    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

  • pom

<!--包含了sleuth+zipkin-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
  • yml
server:
  port: 8001


spring:
  application:
    name: cloud-payment-service
    # 链路追踪begin
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
     # 采样率值介于0到1之间,1则代表全部采集
    probability: 1
    # 链路追踪end
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource   # 当前数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver     # MySQL驱动包
    url: 
    username: root
    password: 

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities


eureka:
  client:
    register-with-eureka: true
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
  instance:
    instance-id: payment8001
    prefer-ip-address: true
 
  • 业务类PaymentController
@GetMapping("/payment/zipkin")
public String paymentZipkin()
{
  return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
}
  • cloud-consumer-order80
  • pom同上
  • yml
server:
  port: 80
 
spring:
    application:
        name: cloud-order-service
    zipkin:
      base-url: http://localhost:9411
    sleuth:
      sampler:
        probability: 1
 
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: false
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      #单机
      #defaultZone: http://localhost:7001/eureka
      # 集群
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  # 集群版

  • 业务类OrderController
 // ====================> zipkin+sleuth
    @GetMapping("/consumer/payment/zipkin")
    public String paymentZipkin()
    {
        String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
        return result;
    }
  • test

    依次启动eureka7001/8001/80

    80调用8001几次测试下

    打开浏览器访问:http:localhost:9411


Similar Posts

Comments