1.集群、分布式、微服務
首先先理解三個感念
- 什么是集群?:
同一個業務,部署在多個服務器上,目的是實現高可用,保證節點可用!
- 什么是分布式?:
一個業務分拆成多個子業務,部署在不同的服務器上,每個子業務都可以做成集 群,目的是為了分攤服務器(軟件服務器(tomcat 等)和硬件服務器:主機節點)的壓力。
- 什么又是微服務?:
相比分布式服務來說,它的粒度更小,小到一個服務只對應一個單一的功能,只 做一件事,使得服務之間耦合度更低,由於每個微服務都由獨立的小團隊負責它的開發, 測試,部署,上線,負責它的整個生命周期,因此它敏捷性更高,分布式服務最后都會向 微服務架構演化,這是一種趨勢。
分布式和微服務有什么區別
2.RPC協議 與 HTTP協議
RPC 協議
即 Remote Procedure Call(遠程過程調用),是一個計算機通信協議。該協 議允許運行於一台計算機的程序調用另一台計算機的子程序,而程序員無需額外地為這個交互作用編程。
HTTP 協議
超文本傳輸協議,是一種應用層協議。規定了網絡傳輸的請求格式、響應格式、 資源定位和操作的方式等。但是底層采用什么網絡傳輸協議,並沒有規定,不過現在都是采 用 TCP 協議作為底層傳輸協議
兩者區別、聯系、選擇
3.微服務概念
微服務是一種架構風格,一個大型復雜軟件應用由一個或多個微服務組成。系統中的各個微 服務可被獨立部署,各個微服務之間是松耦合的。每個微服務僅關注於完成一件任務並很好 地完成該任務。在所有情況下,每個任務代表着一個小的業務能力。
既然微服務是一種架構風格,那么天上飛的理念必定有落地的實現,從而有個下列相關技術
- 微服務開發技術:SpringBoot...
- 微服務治理技術:SpringCloud(微服務一站式解決方案)...
- 微服務部署技術:Docker,K8S,Jekins...
4.接下來我們就一起好好學學SpringCloud:微服務治理技術
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
5.Spring、SpringBoot、SpringCloud 之間的關系
6.SpringCloud 版本選擇(一定要按照官方規定的對應版本使用,否則會出現意想不到Bug)
7.我使用的版本
- SpringCloud:Hoxton.SR1
- SpringBoot: 2.2.2.RELEASE
- SpringCloud:Alibaba:2.1.0.RELEASE
- java:JAVA8
- maven:3.5.2
- mysql:5.5
8.SpringCloud之Eureka注冊中心
創建一個maven模塊:cloud-common
修改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);
}
}
啟動主程序
創建一個cloud-eureka-provider8001模塊
修改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);
}
測試一下
創建一個maven模塊:cloud-eureka-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-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();
}
10.SpringCloud 之聲明式調用 Feign
- 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服務停了
12.SpringCloud 之可視化監控 Dashboard
導入相關依賴
<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
界面參數相關介紹
13.SpringCloud 之網關 GateWay
網關包含了對請求的路由和過濾兩個最主要的功能:
- 路由: 負責將外部請求轉發到具體的微服務實例上,是實現外部訪問統一入口的基礎
- 過濾: 負責對請求的處理過程進行干預,是實現請求校驗、服務聚合等功能的基礎,以后的 訪問微服務都是通過網關跳轉后獲得。
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監控頁面
其他服務也想鏈路追蹤,按照上面的步驟整合即可