首先先理解三个感念
- 什么是集群?:
同一个业务,部署在多个服务器上,目的是实现高可用,保证节点可用!
- 什么是分布式?:
一个业务分拆成多个子业务,部署在不同的服务器上,每个子业务都可以做成集 群,目的是为了分摊服务器(软件服务器(tomcat 等)和硬件服务器:主机节点)的压力。
- 什么又是微服务?:
相比分布式服务来说,它的粒度更小,小到一个服务只对应一个单一的功能,只 做一件事,使得服务之间耦合度更低,由于每个微服务都由独立的小团队负责它的开发, 测试,部署,上线,负责它的整个生命周期,因此它敏捷性更高,分布式服务最后都会向 微服务架构演化,这是一种趋势。
分布式和微服务有什么区别
RPC 协议
即 Remote Procedure Call(远程过程调用),是一个计算机通信协议。该协 议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。
HTTP 协议
超文本传输协议,是一种应用层协议。规定了网络传输的请求格式、响应格式、 资源定位和操作的方式等。但是底层采用什么网络传输协议,并没有规定,不过现在都是采 用 TCP 协议作为底层传输协议
两者区别、联系、选择
微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微 服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好 地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。
既然微服务是一种架构风格,那么天上飞的理念必定有落地的实现,从而有个下列相关技术
- 微服务开发技术:SpringBoot...
- 微服务治理技术:SpringCloud(微服务一站式解决方案)...
- 微服务部署技术:Docker,K8S,Jekins...
SpringCloud 实际是一组组件(管理微服务应用的组件:管理组件的组件)的集合
- 注册中心:Eureka、Zookeeper、Consul
- 负载均衡:Ribbon
- 声明式调用远程方法:OpenFeign
- 熔断、降级、监控:Hystrix
- 网关:Gateway、Zuul
- 分布式配置中心: SpringCloud Config
- 消息总线: SpringCloud Bus
- 消息驱动: SpringCloud Stream
- 链路跟踪:Sleuth
- 服务注册和配置中心:Spring Cloud Alibaba Nacos
- 熔断、降级、限流:Spring Cloud Alibaba Sentinel
- 分布式事务: SpringCloud Alibaba Seata
Spring官方版本参考
7.我使用的版本- SpringCloud:Hoxton.SR1
- SpringBoot: 2.2.2.RELEASE
- SpringCloud:Alibaba:2.1.0.RELEASE
- java:JAVA8
- maven:3.5.2
- mysql:5.5
修改pom.xml
<?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.qbb</groupId>
<artifactId>cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-common</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.qbb.cloud2022.CloudCommonApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
写yml配置文件
不需要配置
主启动类
可以不写
package com.qbb.cloud2022;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CloudCommonApplication {
public static void main(String[] args) {
SpringApplication.run(CloudCommonApplication.class, args);
}
}
业务
无
测试一下
创建一个maven模块:cloud-eureka-server7001无
修改pom.xml导入依赖
<?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.qbb</groupId>
<artifactId>cloud-eureka-server7001</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-eureka-server7001</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.qbb.cloud2022.CloudEurekaServer7001Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
写application.yml文件
server:
port: 7001
# 应用名称
spring:
application:
name: cloud-eureka-server7001
eureka:
instance:
hostname: localhost
client:
fetch-registry: false #单机版Eureka,不需要去其它EurekaServer获取注册信息
register-with-eureka: false #当前EurekaServer不需要注册到其它EurekaServer #我作为Eureka服务器,其它服务如果需要注册到Eureka服务端,注册地址在这里指定。
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-03-31 13:20
* @Description:
*/
@EnableEurekaServer // 开启eureka-server服务
@SpringBootApplication
public class CloudEurekaServer7001Application {
public static void main(String[] args) {
SpringApplication.run(CloudEurekaServer7001Application.class, args);
}
}
启动主程序
修改pom.xml
<?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.qbb</groupId>
<artifactId>cloud-eureka-provider8001</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-eureka-provider8001</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.qbb</groupId>
<artifactId>cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.qbb.cloud2022.CloudEurekaProvider8001Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
写yml配置文件
注释接没写啦,在server端都描述过了
server:
port: 8001
spring:
application:
name: cloud-eureka-provider
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/qbbit3
username: root
password: root
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka/
mybatis:
type-aliases-package: com.qbb.cloud2022.mybatis.entity
主启动类
package com.qbb.cloud2022;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@MapperScan("com.qbb.cloud2022.mapper")
@EnableEurekaClient
@SpringBootApplication
public class CloudEurekaProvider8001Application {
public static void main(String[] args) {
SpringApplication.run(CloudEurekaProvider8001Application.class, args);
}
}
业务
controller
package com.qbb.cloud2022.controller;
import com.qbb.cloud2022.com.qbb.springcloud.entity.Movie;
import com.qbb.cloud2022.service.MovieService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-03-31 20:22
* @Description:
*/
@RestController
@RequestMapping("/movie")
public class MovieController {
@Autowired
MovieService movieService;
@GetMapping("/findById")
public Movie findById(Integer id) {
return movieService.findById(id);
}
}
service
package com.qbb.cloud2022.service.impl;
import com.qbb.cloud2022.com.qbb.springcloud.entity.Movie;
import com.qbb.cloud2022.mapper.MovieMapper;
import com.qbb.cloud2022.service.MovieService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-03-31 20:53
* @Description:
*/
@Service
public class MovieServiceImpl implements MovieService {
@Resource
private MovieMapper movieMapper;
@Override
public Movie findById(Integer id) {
return movieMapper.findById(id);
}
}
mapper
package com.qbb.cloud2022.mapper;
import com.qbb.cloud2022.com.qbb.springcloud.entity.Movie;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-03-31 20:54
* @Description:
*/
public interface MovieMapper {
@Select("select * from movie where id=#{id}")
Movie findById(@Param("id") Integer id);
}
测试一下
修改pom.xml
<?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.qbb</groupId>
<artifactId>cloud-eureka-consumer80</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-eureka-consumer80</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.qbb</groupId>
<artifactId>cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.qbb.cloud2022.CloudEurekaConsumer80Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
写yml配置文件
server:
port: 80
spring:
application:
name: cloud-eureka-consumer
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/qbbit3
username: root
password: root
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka/
mybatis:
type-aliases-package: com.qbb.cloud2022.mybatis.entity
主启动类
package com.qbb.cloud2022;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@MapperScan("com.qbb.cloud2022.mapper")
@EnableEurekaClient
@SpringBootApplication
public class CloudEurekaConsumer80Application {
public static void main(String[] args) {
SpringApplication.run(CloudEurekaConsumer80Application.class, args);
}
}
业务
controller
package com.qbb.cloud2022.controller;
import com.qbb.cloud2022.com.qbb.springcloud.entity.User;
import com.qbb.cloud2022.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 11:07
* @Description:
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/findById")
public User findById(Integer id) {
User user = userService.findById(id);
return user;
}
}
service
package com.qbb.cloud2022.service.impl;
import com.qbb.cloud2022.com.qbb.springcloud.entity.User;
import com.qbb.cloud2022.mapper.UserMapper;
import com.qbb.cloud2022.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 11:09
* @Description:
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User findById(Integer id) {
return userMapper.findById(id);
}
}
mapper
package com.qbb.cloud2022.mapper;
import com.qbb.cloud2022.com.qbb.springcloud.entity.User;
import org.apache.ibatis.annotations.Select;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 11:11
* @Description:
*/
public interface UserMapper {
@Select("select * from user where id=#{id}")
User findById(Integer id);
}
测试一下
可以发现User服务和Movie服务都是正常的,现在是没有做远程调用的
eureka-server集群和常用的一些配置如何配置eureka集群呢?非常简单,如下:
再创建一个eureka-server服务,修改yml文件,其他的不需要动
server:
port: 7001
# 应用名称
spring:
application:
name: cloud-eureka-server
#集群版配置
eureka:
instance:
hostname: localhost
client:
fetch-registry: true #需要去其它EurekaServer获取注册信息
register-with-eureka: true #其它服务如果需要注册到Eureka服务端,注册地址在这里指定。
service-url:
defaultZone: http://localhost:7002/eureka/
注意:主启动类上加@EnableEurekaServer
修改consumer和provider的yml配置文件
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/
我们把eureka-server7001关闭了,并不会立马剔除服务,eureka-server7002依然可以提供服务
eureka自我保护机制eureka-server7001关闭了,并不会立马剔除服务,而是默认90s后剔除:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
如果我们修改默认的配置,就可以设置为我们想要的剔除服务方式
在eureka-server端的yml文件添加配置
server:
enable-self-preservation: false # 关闭自我保护模式(默认是打开的)
eviction-interval-timer-in-ms: 2000
在eureka-client端的yml文件添加配置
eureka:
instance:
# 续约间隔,默认30秒
lease-renewal-interval-in-seconds: 1
# 服务失效时间,默认90秒
lease-expiration-duration-in-seconds: 2
9.SpringCloud 之负载均衡 Ribbon
Ribbon=客户端负载均衡+RestTemplate 远程调用(用户服务调用电影服务)
如何使用 Ribbon
1)、引入 Ribbon 的 Starter(其实 eureka-client 依赖中已经包含了该 ribbon 的依赖信息)
<!-- 引入ribbon实现远程调用和负载均衡功能 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
我就不引入了
2)、使用 RestTemplate 的工具来给远程发送请求(后面我会整合OpenFeign来实现远程调用)
创建一个配置类
package com.qbb.cloud2022.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 13:10
* @Description:
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 开启客户端负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
修改consumer的controller
@GetMapping("/movie")
public Map<String,Object> findUserAndMovieById(Integer id){
Map<String, Object> map = userService.findUserAndMovieById(id);
return map;
}
修改consumer的service
@Autowired
private RestTemplate restTemplate;
@Override
public Map<String, Object> findUserAndMovieById(Integer id) {
// 查询寻用户信息
User user = userMapper.findById(id);
// 查询电影信息
Movie movie = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER/movie/findById?id=" + id, Movie.class);
// 创建map存储数据
Map<String, Object> map = new HashMap<>();
map.put("user", user);
map.put("movie", movie);
return map;
}
注意看看注册中心是否有consumer和provider服务
说起负载均衡令我想起Nginx,那么Ribbon 和 Nginx 对比有什么区别呢?
1,nginx 是服务器端的负载均衡器(nginx本省就是一个服务,他是可以直接部署我们的静态资源的),所有请求发送到 nginx 之后,nginx 通过反向代理 的功能分发到不同的服务器(比如 tomcat),做负载均衡
2,ribbon 是客户端的负载均衡器("客户端",这里说的客户端要打一个引号,他是相对于被调用放的服务来说的,调用方就可以理解为是客户端),他是通过将 eureka 注册中心上的服务,读取下来, 缓存在本地,本地通过轮询算法,实现客户端的负载均衡(比如 dubbo、springcloud)
注意:
nginx 对外,ribbon 对内,两个互补使用。Nginx 存在于服务(无论是服务消费方还是服 务提供方)的前端。Ribbon 存在于微服务调用的消费方(调用的前端)
上面我们的远程调用解决了,负载均衡概念也说了一下,接下来我们来实际解决一下负载均衡,Ribbon就可以帮我们实现负载均衡,具体操作方式如下:
Ribbon 其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结 合使用,和 eureka 结合只是其中的一个实例。
由于我们上面已经在UserServer也就是用户服务(客户端)引入了 Ribbon 依赖,所以实现负载均衡非常简单
在RestTemplate配置类的restTemplate()方法上加入@LoadBalance
package com.qbb.cloud2022.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 13:10
* @Description:
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 开启客户端负载均衡,默认采用的是本地轮训的方式
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
接下来测试一下,这次我就不创建两个provider微服务了,直接使用IDEA的cope configuration
达到如下效果,这里我就复制一个服务测试一下就好啦
修改一下provider:controller代码
@Value("${server.port}")
private Integer port;
@GetMapping("/findById")
public Movie findById(Integer id) {
System.out.println(port);
return movieService.findById(id);
}
浏览器发送6次请求测试一下:http://localhost/user/movie?id=1
上面可以看出,的确是轮训的负载均衡方式,现实中我们可能不是所有的服务器性能都是一样的,有些服务器性能好,有些差,这个时候我们其实更希望性能好的服务器可以处理多一点的请求,那怎么做呢?Ribbon替我们已经做了,当然你觉得不好也可以自定义负载均衡规则,后面说
Ribbon给我们提供了如下的负载均衡方式
修改负载均衡规则
package com.qbb.cloud2022.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 18:27
* @Description:
*/
@Configuration
public class MyRule {
@Bean
public IRule iRule(){
return new RandomRule(); // 修改负载均衡方式为,随机
}
}
测试一下:
注释掉@LoadBalance
创建一个LoadBalancer接口和MyRule实现类
LoadBalancer
package com.qbb.cloud2022.ribbon;
import org.springframework.cloud.client.ServiceInstance;
import java.util.List;
public interface LoadBalancer {
//收集服务器总共有多少台能够提供服务的机器,并放到list里面
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
MyRule
package com.qbb.cloud2022.ribbon;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 18:43
* @Description:
*/
@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)); // 第一个参数是期望值,第二个参数是修改值是 CAS算法
System.out.println("*******第几次访问,次数next: " + next);
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) { // 得到机器的列表
int index = getAndIncrement() % serviceInstances.size(); // 得到服务器的下标位置
return serviceInstances.get(index);
}
}
consumer添加一个访问接口
// controller
@GetMapping("/lb")
public String getMyLB(){
return userService.geMyLB();
}
// service
@Override
public String geMyLB() {
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-EUREKA-PROVIDER");
if (instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
// 远程调用
return restTemplate.getForObject(uri+"/movie/myLB",String.class);
}
provider添加一个访问接口
@GetMapping("/myLB")
public String myLB() {
log.info("调用的服务端口为:{}",port);
return movieService.geMyLB();
}
测试一下:http://localhost/user/lb
- Feign 是一个声明式的 web 服务客户端,让编写 web 服务客户端变得非常容易,只需创建 一个接口并在接口上添加注解即可。说白了,Feign 就是 Ribbon+RestTemplate,采用 RESTful 接口的方式实现远程调用,取代了 Ribbon 中通过 RestTemplate 的方式进行 远程通信。
- OpenFeign 是在Feign 的基础上支持了 SpringMVC 注解(例如@RequestMapping 等), 通过动态代理的方式实现负载均衡调用其他服务,说白了,OpenFign 是 SpringCloud 对 netflix 公司的 Feign 组件的进一步封装,封装之后 OpenFeign 就成了 SpringCloud 家族的一个组件了。
为什么要使用 Feign?
- Feign 可以把 Rest 的请求进行隐藏,伪装成类似 SpringMVC 的 Controller 一样。你不用再自己拼接 url,拼接参数等等操作,一切都交给 Feign 去做。
- Feign 项目主页:https://github.com/OpenFeign/feign
前面说了一些有点和介绍,那么具体怎么使用OpenFeign呢?
创建一个:cloud-consumer-feign-consumer80模块
修改pom.xml
<?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.qbb</groupId>
<artifactId>cloud-consumer-feign-consumer80</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-consumer-feign-consumer80</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.qbb</groupId>
<artifactId>cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.qbb.cloud2022.CloudConsumerFeignConsumer80Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
写yml配置文件
server:
port: 80
spring:
application:
name: cloud-eureka-consumer
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/qbbit3
username: root
password: root
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/
instance:
# 更倾向使用ip地址,而不是host名
prefer-ip-address: true #设置成当前客户端ip
instance-id: ${spring.cloud.client.ip-address}:${server.port}
# 续约间隔,默认30秒
lease-renewal-interval-in-seconds: 1
# 服务失效时间,默认90秒
lease-expiration-duration-in-seconds: 2
mybatis:
type-aliases-package: com.qbb.cloud2022.mybatis.entity
主启动类
package com.qbb.cloud2022;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients // 开启 Feign 支持,实现基于接口的远程 调用
@MapperScan("com.qbb.cloud2022.mapper")
@EnableEurekaClient
@SpringBootApplication
public class CloudConsumerFeignConsumer80Application {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerFeignConsumer80Application.class, args);
}
}
业务
controller,service,mapper直接copy
创建一个FeignMovieService接口
package com.qbb.cloud2022.feign;
import com.qbb.cloud2022.com.qbb.springcloud.entity.Movie;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 19:20
* @Description:
*/
@Component
@FeignClient(value = "CLOUD-EUREKA-PROVIDER")
public interface FeignMovieService {
@GetMapping("/movie/findById")
public Movie findById(@RequestParam("id") Integer id);
}
注意:一定要在调用方consumer,和被调用方provider加入@RequestParam("id"),一定一定一定!!! 否则会出现一下错误
测试一下远程调用:
OpenFeign自带了Ribbon负载均衡,默认也是轮训,启动8002测试负载均衡效果
注意OpenFeign默认调用等待时间是1s,如果超过一秒还没有拿到结果,会报错的
实际情况有可能我们网络不好,或者业务逻辑复杂,处理时间超过是要大于1s的.还有负载均衡规则不想使用轮训.那怎么办呢?那我们就要修改配置文件覆盖默认规则了
#设置Feign客户端超时时间(openfeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ConnectTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ReadTimeout: 5000
负载均衡配置和上面一样
package com.qbb.cloud2022.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 18:27
* @Description:
*/
@Configuration
public class MyRule {
@Bean
public IRule iRule(){
return new RandomRule(); // 修改负载均衡方式为,随机
}
}
测试结果,是没有问题的
如果我们项查看 OpenFeign 的远程调用日志
创建一个FeignConfig配置类
package com.qbb.cloud2022.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level level() {
/**
* NONE:默认的,不显示任何日志
* BASIC:仅记录请求方法、RUL、响应状态码及执行时间
* HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
* FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
*/
return Logger.Level.FULL;
}
}
修改yml文件设置远程调用接口的日志级别
logging:
level:
# 这里必须为debug级别,并且要为远程调用的Feign接口设置哦
com.qbb.cloud2022.feign.FeignMovieService: debug
11.SpringCloud 之熔断器 Hystrix
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖 不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况 下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。“断路器”本身是一 种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝), 向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或 者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占 用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
说几个基本概念吧:
服务雪崩
- 服务之间复杂调用,一个服务不可用,导致整个系统受影响不可用(原因:服务器的大量连 接被出现异常的请求占用着,导致其他正常的请求得不到连接,所以导致整个系统不可用)
服务降级 Fallback
- 服务器忙(挂了),请稍候再试,不让客户端等待并立刻返回一个友好提示
服务熔断 Breaker
- 类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并 返回友好提示,就好比保险丝。
服务熔断诱因:服务的降级->进而熔断->恢复调用链路
服务限流 Flowlimit
- 限制某个服务每秒的调用本服务的频率,例如秒杀高并发等操作,严禁一窝蜂的过来拥挤,
大家排队,一秒钟 N 个,有序进行
先使用 Ribbon 和 Hystrix 组合实现服务降级
在cloud-eureka-consumer80导入依赖
<!-- 引入hystrix进行服务熔断 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在主启动类上加@EnableCircuitBreaker开启断路保护功能
package com.qbb.cloud2022;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableCircuitBreaker
@MapperScan("com.qbb.cloud2022.mapper")
@EnableEurekaClient
@SpringBootApplication
public class CloudEurekaConsumer80Application {
public static void main(String[] args) {
SpringApplication.run(CloudEurekaConsumer80Application.class, args);
}
}
在远程调用的方法上添加服务降级注解,通过@HystrixCommand(fallbackMethod="xxx")来指定出错时调用的局部降级 xxx 方法
// 服务降级执行的方法
public Map<String, Object> movieFallbackMethod(Integer id) {
// 查询寻用户信息
// User user = userMapper.findById(id);
// 查询电影信息
// Movie movie = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER/movie/findById?id=" + id, Movie.class);
// 创建map存储数据
Map<String, Object> map = new HashMap<>();
map.put("user", "服务器忙,请稍后再试");
map.put("movie", null);
return map;
}
@HystrixCommand(fallbackMethod = "movieFallbackMethod")
@Override
public Map<String, Object> findUserAndMovieById(Integer id) {
// 查询寻用户信息
User user = userMapper.findById(id);
// 查询电影信息
Movie movie = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER/movie/findById?id=" + id, Movie.class);
// 创建map存储数据
Map<String, Object> map = new HashMap<>();
map.put("user", user);
map.put("movie", movie);
return map;
}
正常访问是没有问题的,当我们把服务提供方provider关闭在再调用
配置全局服务降级方案:
在类上添加:
@DefaultProperties(defaultFallback = "defaultFallback") // 指定服务降级执行的方法
修改原方法上的注解和编写服务降级方法,如下:
public Map<String, Object> defaultFallback() {
// 创建map存储数据
Map<String, Object> map = new HashMap<>();
map.put("user", "服务器忙,请稍后再试");
map.put("movie", null);
return map;
}
@HystrixCommand
@Override
public Map<String, Object> findUserAndMovieById(Integer id) {
// 查询寻用户信息
User user = userMapper.findById(id);
// 查询电影信息
Movie movie = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER/movie/findById?id=" + id, Movie.class);
// 创建map存储数据
Map<String, Object> map = new HashMap<>();
map.put("user", user);
map.put("movie", movie);
return map;
}
需要注意:
1.@DefaultProperties(defaultFallback = "defaultFallBack"):在类上指 明统一的失败降级方法;该类中所有方法返回类型要与处理失败的方法的返回类型一致。
2.对于全局降级方法必须是无参的方法
关闭8001服务测试一下:
哪些情况会触发服务降级:
- 程序运行异常
- 超时自动降级(默认 1 秒)
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
这里我就不一一测试了,测试一个超时降级:
**注意:Hystrix 的默认超时时长为 1s,我们可以在配置文件中修改默认超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000 #设置hystrix的超时等待时间
修改后可以正常访问了,不走降级了
熔断器原理:
- 在服务熔断中,使用的熔断器,也叫断路器,其英文单词为:Circuit Breaker 熔断机制与家里使用的电路熔断原理类似;当如果电路发生短路的时候能立刻熔断电路,避 免发生灾难。在分布式系统中应用服务熔断后;服务调用方可以自己进行判断哪些服务反应 慢或存在大量超时,可以针对这些服务进行主动熔断,防止整个系统被拖垮。
- Hystrix 的服务熔断机制,可以实现弹性容错;当服务请求情况好转之后,可以自动重连。 通过断路的方式,将后续请求直接拒绝,一段时间(默认 5 秒)之后允许部分请求通过, 如果调用成功则回到断路器关闭状态,否则继续打开,拒绝请求的服务。
3 个状态:
- Closed:关闭状态(断路器关闭),所有请求都正常访问。
- Open:打开状态(断路器打开),所有请求都会被降级。Hystrix会对请求情况计数,当一 定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值 是50%,请求次数最少不低于20次。
- Half Open:半开状态,不是永久的,断路器打开后会进入休眠时间(默认是5S)。随后断 路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会关闭断 路器,否则继续保持打开,再次进行休眠计时。
yml添加如下配置
hystrix:
command:
default:
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20
# 上面三个值合起来解释就是(默认值):Hystrix会统计10秒钟达到20请求,且错误请求的占比>50%的话后面的10秒请求会走服务熔断
execution:
isolation:
thread:
timeoutInMilliseconds: 4000 #设置hystrix的超时等待时间
修改远程调用的方法
// @HystrixCommand(fallbackMethod = "movieFallbackMethod")
@HystrixCommand
@Override
public Map<String, Object> findUserAndMovieById(Integer id) {
if (id == 0) {
throw new RuntimeException();
}
// 查询寻用户信息
User user = userMapper.findById(id);
// 查询电影信息
Movie movie = restTemplate.getForObject("http://CLOUD-EUREKA-PROVIDER/movie/findById?id=" + id, Movie.class);
// 创建map存储数据
Map<String, Object> map = new HashMap<>();
map.put("user", user);
map.put("movie", movie);
return map;
}
我们配置相关的策略以后,考验手速的时候到了,先拼命的刷新:http://localhost/user/movie?id=0, 再测试一下正常访问:http://localhost/user/movie?id=1, 你会发现服务熔断了,在过一会又好了
使用 Feign+Hystrix 组合
导入依赖:cloud-consumer-feign-consumer80 模块
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
主启动类上加入@
@EnableCircuitBreaker // 开启服务熔断保护
修改配置文件
# 开启feign对hystrix的支持
feign:
hystrix:
enabled: true
编写Feign客户端异常处理类
package com.qbb.cloud2022.handler;
import com.qbb.cloud2022.com.qbb.springcloud.entity.Movie;
import com.qbb.cloud2022.feign.FeignMovieService;
import org.springframework.stereotype.Component;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 21:22
* @Description:
*/
@Component
public class FeignServiceExceptionHandler implements FeignMovieService {
@Override
public Movie findById(Integer id) {
Movie movie= new Movie(-1,"网络异常,请稍后再试~~~");
return movie;
}
}
修改远程调用接口,给@FeignClients注解添加FallBack属性
package com.qbb.cloud2022.feign;
import com.qbb.cloud2022.com.qbb.springcloud.entity.Movie;
import com.qbb.cloud2022.handler.FeignServiceExceptionHandler;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-01 19:20
* @Description:
*/
/**使用Hystrix进行服务的熔断
1)、引入Hystrix的starter
2)、开启xxx功能 :@EnableCircuitBreaker
3)、@FeignClient(value="CLOUD-PROVIDER-MOVIE",fallback=指定这个接口的异常处 理类(异常处理类必须实现这个接口))
*/
@Component
@FeignClient(value = "CLOUD-EUREKA-PROVIDER",fallback = FeignServiceExceptionHandler.class)
public interface FeignMovieService {
@GetMapping("/movie/findById")
public Movie findById(@RequestParam("id") Integer id);
}
测试:将cloud-eureka-provider8001服务停了
导入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
注意一定要配置成,IP地址和端口号形式
actuator 可监控的行为:
修改配置文件
# 暴露项目的hystrix数据流
management:
endpoints:
web:
exposure:
# 访问/actuator/hystrix.stream能看到不断更新的监控流
include: hystrix.stream
远程调用一下,再访问: http://localhost/actuator/hystrix.stream, 就可以看到如下数据了
上图看的太丑了,还不直观,有没有更好的可视化界面呢?有的
引入 HystrixDashboard 开启可视化监控添依赖信息
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
修改配置文件
hystrix:
dashboard:
#要把监控地址加入proxyStreamAllowList
proxy-stream-allow-list: "localhost"
主启动类上加@EnableHystrixDashboard、@EnableHystrix 注解
访问:localhost/hystrix
点击监控Monitor
界面参数相关介绍
网关包含了对请求的路由和过滤两个最主要的功能:
- 路由: 负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础
- 过滤: 负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础,以后的 访问微服务都是通过网关跳转后获得。
GateWay 工作流程
- 客户端向Spring Cloud Gateway发出请求。
- 在Gateway Handler Mapping中找到与请求匹配的路由,将其发送到Gateway Web Handler.
- Handler再通过指定的过滤器链来将请求发送给我们实际的服务执行业务逻辑,然后返回。
三大核心组件及作用
- 断言(Predicate):只有断言成功后才会匹配到微服务进行路由,路由到代理的微服务。
- 路由(Route):分发请求
- 过滤(Filter):对请求或者响应报文进行处理
具体使用:创建一个cloud-gateway-gateway9527模块
修改pom.xml
<?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.qbb</groupId>
<artifactId>cloud-gateway-gateway9527</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-gateway-gateway9527</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.qbb.cloud2022.CloudGatewayGateway9527Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
写yml配置文件
server:
port: 9527
spring:
application:
name: cloud-gateway-gateway9527
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka/
主启动类
package com.qbb.cloud2022;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class CloudGatewayGateway9527Application {
public static void main(String[] args) {
SpringApplication.run(CloudGatewayGateway9527Application.class, args);
}
}
修改配置文件
spring:
application:
name: cloud-gateway-gateway9527
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: cloud-consumer-feign-consumer80 # 路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost # 匹配后提供服务的路由地址
predicates:
- Path=/** # 断言,路径相匹配的进行路由
注意: - Path:/** 冒号后面是没有空格的!!!
测试一下
修改配置文件,路径匹配规则
routes:
- id: cloud-consumer-feign-consumer80 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: http://localhost #匹配后提供服务的路由地址
predicates:
#- Path=/** #断言,路径相匹配的进行路由
- Path=/user/** # 含有user的请求转发给cloud-consumer-feign-consumer80服务
# - After=2021-12-17T19:18:25.913+08:00[Asia/Shanghai]
# - Cookie=username,qbb
- id: cloud-eureka-provider8001
uri: http://localhost:8001
predicates:
# - Path=/movie/** # 含有movie的请求转发给cloud-eureka-provider8001服务
# http://localhost:8001/movie/findById?id=1
- Path=/movie/** #断言,路径相匹配的进行路由
上面我们是通过IP+Port方式配置的,还可以通过服务名匹配路由
routes:
- id: cloud-consumer-feign-consumer80 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
#uri: http://localhost #匹配后提供服务的路由地址
uri: lb://CLOUD-EUREKA-CONSUMER
predicates:
#- Path=/** #断言,路径相匹配的进行路由
- Path=/user/** # 含有user的请求转发给cloud-consumer-feign-consumer80服务
# - After=2021-12-17T19:18:25.913+08:00[Asia/Shanghai]
# - Cookie=username,qbb
- id: cloud-eureka-provider8001
#uri: http://localhost:8001
# 集群负载均衡的配置
uri: lb://CLOUD-EUREKA-PROVIDER
predicates:
# - Path=/movie/** # 含有movie的请求转发给cloud-eureka-provider8001服务
# http://localhost:8001/movie/findById?id=1
- Path=/movie/** #断言,路径相匹配的进行路由
GateWay不仅可以通过yml配置文件的方式配置,还可以通过配置类的方式配置
package com.qbb.cloud2022.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-02 0:07
* @Description:
*/
@Configuration
class GatewayConfig {
@Bean
public RouteLocator getRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("qbb", predicateSpec -> predicateSpec.path("/**").uri("https://news.baidu.com"));
routes.route("ll", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
测试:http://localhost:9527/sports
注释掉GatewayConfig类的@Configuration注解,我们继续看下面的配置
routes:
- id: cloud-consumer-feign-consumer80 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
#uri: http://localhost #匹配后提供服务的路由地址
uri: lb://CLOUD-EUREKA-CONSUMER
predicates:
#- Path=/** #断言,路径相匹配的进行路由
- Path=/user/** # 含有user的请求转发给cloud-consumer-feign-consumer80服务
# - After=2021-12-17T19:18:25.913+08:00[Asia/Shanghai]
# - Cookie=username,qbb
- After=2022-04-01T08:00:00.0+08:00 # 断言,在此时间后请求才会被匹配
# - Before=2022-05-01T09:08+08:00 # 断言,在此时间前请求才会被匹配
# - Between=2021-05-01T08:00:00.0+08:00,2022-05-02T09:10+08:00 # 断言, 在此时间区间内访问的请求才会被匹配
# - Cookie=username,qbb # 断言,请求头中携带Cookie: username=atguigu才可以匹配
# - Cookie=id,9527 - Header=X-Request-Id,\d+ # 断言,请求头中要有X-Request-Id属性并且值 为整数的正则表达式
# - Method=POST # 断言,请求方式为post方式才会被匹配
# - Query=pwd,[a-z0-9_-]{6} # 断言,请求参数中包含pwd并且值长度为6才会 被匹配
我就不一个个截图了,我都测试过,推荐各位小伙伴使用PostMan或者Apifox等工具
再来看看,过滤功能:Filter
根据 filter 的作用时机:
- 局部作用的 filter:GatewayFilter(一般使用系统自带的) pre 类型的在请求交给微服务之前起作用 post 类型的在响应回来时起作用
- 全局作用的 filter:GlobalFilter(一般需要自定义)
修改配置文件,做个案例
配置文件的方式:
filters:
- AddRequestParameter=love,0720 # 在匹配请求的请求参数中添加一对请求参数
- AddResponseHeader=you,qiu # 在匹配的请求的响应头中添加一对响应头
创建一个MyParamGatewayFactory类的方式:
package com.qbb.cloud2022.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit)
* @version 1.0
* @date 2022-04-02 0:07
* @Description:
*/
@Component
public class MyParamGatewayFactory extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
// queryParams.get("love").get(0);
String love = queryParams.getFirst("love");
if (StringUtils.isEmpty(love)) {
System.out.println("没有携带love参数");
} else {
System.out.println("love参数值:" + love);
}
return chain.filter(exchange);
};
}
@Override
public String name() {
return "MyParamFilter";
}
}
全局过滤器:GlobalFilter
判断请求参数是否携带token
package com.qbb.cloud2022.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求参数Map
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
//获取请求参数token值
String token = queryParams.getFirst("token");
if (StringUtils.isEmpty(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//结束本次请求,返回响应报文
return exchange.getResponse().setComplete();
}
System.out.println("获取到请求参数为:" + token);
//放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
14.SpringCloud 之分布式链路请求跟踪 Sleuth
简介:
在分布式系统中,微服务有多个,服务之间调用关系也比较复杂,如果有的微服务网络或者 服务器出现问题会导致服务提供失败,如何快速便捷的去定位出现问题的微服务, SpringCloud Sleuth 给我们提供了解决方案,它集成了 Zipkin、HTrace 链路追踪 工具,用服务链路追踪来快速定位问题。Zipkin 使用较多。Zipkin 主要由四部分构成: 收集器、数据存储、查询以及 Web 界面。Zipkin 的收集器负责将各系统报告过来的追踪 数据进行接收;而数据存储默认使用 Cassandra,也可以替换为 MySQL;查询服务用来 向其他服务提供数据查询的能力,而 Web 服务是官方默认提供的一个图形用户界面。
下载 Zipkin-server
https://repo1.maven.org/maven2/io/zipkin/java/zipkin-server/2.12.9/zipkin-server-2.12.9-exec.jar
运行 zipkin-server-2.12.9-exec
java -jar zipkin-server-2.12.9-exec.jar
访问 Zipkin 控制台
http://localhost:9411/zipkin/
cloud-eureka-consumer80 模块整合 Zipkin
<!--包含了sleuth+zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
yml 文件配置 zipkin
spring:
zipkin: #指定数据提交到的zipkin服务端接收
base-url: http://localhost:9411
sleuth:
sampler: #采样率值介于0~1之间,1表示全部采样
probability: 1
访问接口,刷新zipkin监控页面
其他服务也想链路追踪,按照上面的步骤整合即可
至此SpringCloud第一版整合完毕,其中还有些SpringCloud Config,SpringCloud Bus,SpringCloud Stream 没整合,后面SpringCloud Alibaba 使用Nacos和Sentienl可以完美替代并且会有更好的体验,所以就不整合了,让我们一起期待SpringCloud Alibaba