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監控頁面
其他服務也想鏈路追蹤,按照上面的步驟整合即可