# SpringCloud学习上篇
## springCloud 组件概述
- 服务注册中心
- Eureka 已停止更新
- Zookeeper 保守路线
- Nacos 阿里出品 推荐使用
- 服务调用1
- Ribbon 半生不熟
- LoadBanlancer 萌芽产品
- 服务调用2
- feign 死翘翘了
- OpenFeign
<!-- more -->
- 服务降级
- Hystrix 企业大规模使用 但是停更
- resilience4j 国外常用
- sentinel 阿里出品 国内用
- 服务网关
- Zuul
- Zuul2
- gateway 主流和重点
- 服务配置
- config 不再使用
- Nacos 阿里出品 推荐
- 服务总线
- Bus 淘汰
- Nacos
## 零散知识的记录
### RestTemplate的使用
Object 和 entity的区别
entity的范围更大 他的信息更全 不过一般建议使用object
```java
@GetMapping("payment/getById/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") long id){
return restTemplate.getForObject(PAYMENT_URL + "/payment/getById/"+id,CommonResult.class);
}
@GetMapping("payment/getEntity/{id}")
public CommonResult<Payment> getEntity(@PathVariable("id") long id){
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/getById/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}else return new CommonResult<>(444,"操作失败");
}
```
## 基础部分
### 环境搭建
1. maven创建工程选择maven-archetype-site
2. pom.xml 修改
```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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tech.haonan</groupId>
<artifactId>cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<modules>
<module>cloud-provider-payment8001</module>
</modules>
<name>Maven</name>
<!-- FIXME change it to the project's website -->
<url>http://maven.apache.org/</url>
<inceptionYear>2001</inceptionYear>
<distributionManagement>
<site>
<id>website</id>
<url>scp://webhost.company.com/www/website</url>
</site>
</distributionManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<junit.version>5.7.0-M1</junit.version>
<log4j.version>2.13.2</log4j.version>
<lombok.version>1.18.12</lombok.version>
<mysql.version>8.0.20</mysql.version>
<druid.version>1.1.22</druid.version>
<mybatis.spring.boot.version>2.1.2</mybatis.spring.boot.version>
<spring.boot.version>2.2.5.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR4</spring.cloud.version>
<spring.cloud.alibaba.dependencies.version>2.2.1.RELEASE</spring.cloud.alibaba.dependencies.version>
</properties>
<!--子模块继承之后,提供作用:锁定版本+子Modlue不用写groupId和version-->
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.dependencies.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<version>${lombok.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>${spring.boot.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<configuration>
<locales>en,fr</locales>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--fork:设置True,否则可能devtools不会起作用-->
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>
```
### 微服务模块的建立
1.建module 2.改pom 3.写yml 4.主启动 5.业务类
#### 目录结构
```shell
│ cloud2020.iml
│ pom.xml //总pom文件
└─cloud-provider-payment8001
│ pom.xml //模块pom文件
│
└─src
├─main
│ ├─java
│ │ └─tech
│ │ └─haonan
│ │ │ PaymentMain8001.java //启动类
│ │ │
│ │ ├─Controller //controller 层
│ │ │ PaymentController.java
│ │ │
│ │ ├─dao //持久层
│ │ │ PaymentDao.java
│ │ │
│ │ ├─entities //实体类
│ │ │ CommonResult.java
│ │ │ Payment.java
│ │ │
│ │ └─service //服务层
│ │ │ PaymentService.java
│ │ └─Impl //接口实现类
│ │ PaymentServiceImpl.java
│ │
│ └─resources
│ │ application.yml //yml文件
│ │
│ └─mapper
│ PaymentMapper.xml //mapper文件
│
└─test
└─java
```
#### 建立模块
#### 模块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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8001</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
```
#### 写yml
模块resource目录下新建application.yml 文件
```yml
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.43.50:4000/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: tech.haonan.entities
```
#### 主启动
```java
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
```
#### 业务类
vue - controller - service - dao -mysql
业务类的开发
##### 建表sql
```sql
use springcloud;
CREATE TABLE `payment`(
`id` bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY Comment 'ID',
`serial` varchar(200)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
```
##### entities
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private long id;
private String serial;
}
```
```java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult<T> {
private Integer responseCode;
private String message;
private T data;
public CommonResult(Integer responseCode, String message) {
this(responseCode,message,null);
}
}
```
##### dao
```java
@Mapper
@Repository
public interface PaymentDao {
int create(Payment payment);
Payment getPaymentById(@Param("id") Long id);
}
```
mapper文件
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="tech.haonan.dao.PaymentDao" >
<insert id="create" parameterType="tech.haonan.entities.Payment" useGeneratedKeys="true" keyProperty="id">
insert into payment(serial) values(#{serial});
</insert>
<resultMap id="BaseResultMap" type="tech.haonan.entities.Payment">
<id column="id" property="id" jdbcType="BIGINT"/>
<id column="serial" property="serial" jdbcType="VARCHAR"/>
</resultMap>
<select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
select * from payment where id=#{id};
</select>
</mapper>
```
##### service
接口
```java
public interface PaymentService {
int create(Payment payment);
Payment getPaymentById(Long id);
}
```
实现类
```java
@Component
public class PaymentServiceImpl implements PaymentService {
@Autowired
private PaymentDao paymentDao;
@Override
public int create(Payment payment) {
return paymentDao.create(payment);
}
@Override
public Payment getPaymentById(Long id) {
return paymentDao.getPaymentById(id);
}
}
```
##### controller
```java
@RestController
@Slf4j
@RequestMapping(value = "payment")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping(value = "create")
public CommonResult create( Payment payment){
int result = paymentService.create(payment);
log.info("*****插入结果: " + result);
if(result >0)
{
return new CommonResult(200,"插入数据成功",result);
}else {
return new CommonResult(444,"插入数据失败",null);
}
}
@GetMapping(value = "getById/{id}")
public CommonResult getById(@PathVariable("id") long id){
Payment result = paymentService.getPaymentById(id);
log.info("*****查询结果: " + result);
if(result!=null){
return new CommonResult(200,"查询成功",result);
}else{
return new CommonResult(444,"没有对应记录,查询的id为"+id,null);
}
}
}
```
### 第二个模块的建立
这里建立了一个用户模块 调用8001端口的payment模块
1.建module 2.改pom 3.写yml 4.主启动 5.业务类
#### 改pom
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order80</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--mysql connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
```
#### 写yml
```yml
server:
port: 80
```
#### 主启动
```java
@SpringBootApplication
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
```
#### 业务类
```shell
│ OrderMain80.java
│
├─config
│ ApplicationContextConfig.java
│
├─controller
│ OrderController.java
│
└─entities
CommonResult.java
Payment.java
```
##### ApplicationContextConfig.java
没明白这一段是干啥的
```java
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
```
##### OrderController.java
这里访问 localhost:80/consumer/payment/create 和 getById/xx 就能访问了
```java
@RestController
@Slf4j
@RequestMapping("consumer")
public class OrderController {
public static final String PAYMENT_URL = "http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
@GetMapping("payment/create")
public CommonResult<Payment> create(Payment payment){
return restTemplate.postForObject(PAYMENT_URL + "/payment/create",payment,CommonResult.class);
}
@GetMapping("payment/getById/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") long id){
return restTemplate.getForObject(PAYMENT_URL + "/payment/getById/"+id,CommonResult.class);
}
}
```
### 工具模块的建立和工程重构
#### 目录结构
```
F:.
│ cloud2020.iml
│ pom.xml //主pom文件
│
├─cloud-api-commons //工具模块
│ │ cloud-api-commons.iml
│ │ pom.xml
│ │
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─tech
│ │ │ │ └─haonan
│ │ │ │ └─entities //工具模块将两个模块都会使用的实体类提取出来 可以节省代码
│ │ │ │ CommonResult.java
│ │ │ │ Payment.java
│ │ │ │
│ │ │ └─resources
│ │ └─test
│ │ └─java
│ └─target //这里必须经过maven的install 必须有target 这样才能被其他maven项目引用
│ │ cloud-api-commons-1.0-SNAPSHOT.jar
│ │
│ ├─classes
│ │ └─tech
│ │ └─haonan
│ │ └─entities
│ │ CommonResult.class
│ │ Payment.class
│ │
│ ├─generated-sources
│ │ └─annotations
│ ├─generated-test-sources
│ │ └─test-annotations
│ ├─maven-archiver
│ │ pom.properties
│ │
│ ├─maven-status
│ │ └─maven-compiler-plugin
│ │ ├─compile
│ │ │ └─default-compile
│ │ │ createdFiles.lst
│ │ │ inputFiles.lst
│ │ │
│ │ └─testCompile
│ │ └─default-testCompile
│ │ createdFiles.lst
│ │ inputFiles.lst
│ │
│ └─test-classes
├─cloud-consumer-order80 //顾客模块 80端口
│ │ cloud-consumer-order80.iml
│ │ pom.xml
│ │
│ └─src
│ ├─main
│ │ ├─java
│ │ │ └─tech
│ │ │ └─haonan
│ │ │ │ OrderMain80.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ ApplicationContextConfig.java
│ │ │ │
│ │ │ └─controller
│ │ │ OrderController.java
│ │ │
│ │ └─resources
│ │ │ application.yml
│ │ │
│ │ └─mapper
│ └─test
│ └─java
└─cloud-provider-payment8001 //真正的服务模块 8001端口
│ pom.xml
│
└─src
├─main
│ ├─java
│ │ └─tech
│ │ └─haonan
│ │ │ PaymentMain8001.java
│ │ │
│ │ ├─Controller
│ │ │ PaymentController.java
│ │ │
│ │ ├─dao
│ │ │ PaymentDao.java
│ │ │
│ │ └─service
│ │ │ PaymentService.java
│ │ │
│ │ └─Impl
│ │ PaymentServiceImpl.java
│ │
│ └─resources
│ │ application.yml
│ │
│ └─mapper
│ PaymentMapper.xml
│
└─test
└─java
```
四个pom文件
#### 主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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tech.haonan</groupId>
<artifactId>cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<modules>
<module>cloud-provider-payment8001</module>
<module>cloud-consumer-order80</module>
<module>cloud-api-commons</module>
</modules>
<name>Maven</name>
<!-- FIXME change it to the project's website -->
<url>http://maven.apache.org/</url>
<inceptionYear>2001</inceptionYear>
<distributionManagement>
<site>
<id>website</id>
<url>scp://webhost.company.com/www/website</url>
</site>
</distributionManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<junit.version>4.13</junit.version>
<log4j.version>2.13.2</log4j.version>
<lombok.version>1.18.12</lombok.version>
<mysql.version>8.0.20</mysql.version>
<druid.version>1.1.22</druid.version>
<mybatis.spring.boot.version>2.1.2</mybatis.spring.boot.version>
<spring.boot.version>2.2.5.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR4</spring.cloud.version>
<spring.cloud.alibaba.dependencies.version>2.2.1.RELEASE</spring.cloud.alibaba.dependencies.version>
</properties>
<!--子模块继承之后,提供作用:锁定版本+子Modlue不用写groupId和version-->
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.dependencies.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<version>${lombok.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>${spring.boot.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
```
#### cloud-api-commons模块的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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-api-commons</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.4</version>
</dependency>
</dependencies>
</project>
```
#### cloud-consumer-order80 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order80</artifactId>
<dependencies>
<!--这里引用了了工具模块 将模块中的entity导入-->
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--mysql connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<configuration>
<locales>en,fr</locales>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--fork:设置True,否则可能devtools不会起作用-->
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>
```
#### cloud-provider-payment8001 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8001</artifactId>
<dependencies>
<!--这里引用了了工具模块 将模块中的entity导入-->
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<configuration>
<locales>en,fr</locales>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--fork:设置True,否则可能devtools不会起作用-->
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>
```
## 中级部分 涉及到cloud了
### Eureka
#### 介绍
服务治理, 在传统的rpc远程调用框架中,管理每个服务和服务之间的依赖关系比较复杂,所以需要使用服务治理,所以需要使用服务治理,管理服务于服务之间的依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现和注册
两个组件 : Eureka Server 和 Eureka Client
Eureka Server提供服务注册服务
Eureka Client 通过注册中心进行访问,是一个java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器,在应用启动后悔想Eureka Server发送心跳(默认周期是30秒),如果Rureka Server在多个心跳周期中没有收到某个节点的心跳,那么就会移除此服务节点。
#### 单机Euraka的构建
##### 生成注册中心模块
###### 建module
命名为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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<dependencies>
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>
```
###### 写yml
```yml
server:
port: 7001
eureka:
instance:
hostname: localhost #实例名称
client:
register-with-eureka: false # 不向注册中心注册自己
fetch-registry: false #false 表示自己就是注册中心 ,不需要检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
```
###### 主启动
```java
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
```
#### 注册client端
##### 项目介绍
```xml
项目介绍
cloud2020
|--------cloud-api-commons //通用模块 常用的实体类和一些工具类
|--------cloud-consumer-order80 //用户模块 调用8001端口实现功能
|--------cloud-eureka-server7001 //注册中心
|--------cloud-provider-payment8001 //支付模块
```
##### pom文件引入
在总pom文件中导入
```xml
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<!--记得去<properties>中去设置你需要的版本号-->
<version>${spring.cloud.starter.netflix.eureka.client.version}</version>
</dependency>
```
在cloud-provider-payment8001的pom文件中引入
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
```
##### yml文件添加
```yml
eureka:
client:
register-with-eureka: true #表示是否将自己注册进入EurekaServer 默认为true
fetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone : http://localhost:7001/eureka
```
##### 主启动添加注解`@EnableEurekaClient`
可以不加 `@EnableEurekaClient`注解了
```java
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
```
访问localhost:7001就可以看见eureka的有关信息
#### 集群的环境构建(注册中心实现集群)
目前的情况是只有一个7001端口的注册中心
##### 创建module
名字为cloud-eureka-server7002
主启动之类的东西和前面一样 就省略了
注册中心互相之间做到互相注册 相互守望
##### 修改hosts文件
主要是有两个注册中心 做一下映射而已 不映射也行 但是不好区分了
```shell
# springcloud 集群配置
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
```
##### 修改两个注册中心的application.yml文件
7001文件
```yml
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #实例名称
client:
register-with-eureka: false # 不向注册中心注册自己
fetch-registry: false #false 表示自己就是注册中心 ,不需要检索服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
```
7002文件
````yml
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #实例名称
client:
register-with-eureka: false # 不向注册中心注册自己
fetch-registry: false #false 表示自己就是注册中心 ,不需要检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
````
#### 将微服务部署到集群环境中(将服务注册进多个注册中心)
##### 8001 服务的部署
yml 文件修改
修改defaulZone的信息
```yml
# defaultZone : http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
```
##### 80服务的部署
修改defaulZone的信息
```yml
# defaultZone : http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
```
#### 使用Eureka的负载均衡功能
##### 更改8001和8002controller
重点是这下边这句话
```java
@Value("${server.port}") //将yml文件的serverport读取进来 然后下面输出
private String serverPort;
```
```java
@RestController
@Slf4j
@RequestMapping(value = "payment")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}") //将yml文件的serverport读取进来 然后下面输出
private String serverPort;
@PostMapping(value = "create")
public CommonResult create(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("*****插入结果: " + result);
if(result >0)
{
return new CommonResult(200,"插入数据成功,端口号"+serverPort,result);
}else {
return new CommonResult(444,"插入数据失败,端口号"+serverPort,null);
}
}
@GetMapping(value = "getById/{id}")
public CommonResult<Payment> getById(@PathVariable("id") long id){
Payment result = paymentService.getPaymentById(id);
log.info("*****查询结果: " + result);
if(result!=null){
return new CommonResult(200,"查询成功,端口号"+serverPort,result);
}else{
return new CommonResult(444,"没有对应记录,查询的id为"+id +"端口号:"+serverPort,null);
}
}
}
```
##### 80端口的配置更改
###### controller修改
将固定的网址和端口更改为可变的`http://CLOUD-PAYMENT-SERVICE`
```java
//public static final String PAYMENT_URL = "http://localhost:8001";
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
```
###### config 修改
添加` @LoadBalanced`注解
```java
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
```
#### 完成后的效果
默认采用轮询 访问 http://localhost/consumer/payment/getById/29时 会依次访问8001、8002端口的服务
#### 一些小细节的修改 (application.yml)
###### 修改Instances currently registered with Eureka中status 的名称和访问路径显示ip
application.yml添加以下内容
```yml
eureka:
instance:
instance-id: payment8002
prefer-ip-address: true
```
#### Eureka的服务发现Discovery
只拿8001举例子
##### controller 配置
```java
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("discovery")
public Object discovery(){
List<String> services= discoveryClient.getServices();
for(String ele : services){
log.info("ele " + ele);
}
List<ServiceInstance> instances= discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance:instances){
log.info(instance.getServiceId() + "\t" + instance.getHost() + '\t' +instance.getPort() +'\t' +instance.getUri());
}
return discoveryClient;
}
```
##### 启动类添加注解`@EnableDiscoveryClient`
```java
@SpringBootApplication
//@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
```
此时访问http://localhost:8001/payment/discovery
得到结果
```json
{
"discoveryClients": [
{
"services": [
"cloud-payment-service",
"cloud-order-service"
],
"order": 0
},
{
"services": [],
"order": 0
}
],
"services": [
"cloud-payment-service",
"cloud-order-service"
],
"order": 0
}
```
日志打印 两个服务信息就打印出来了
```shell
192.168.43.1 8001 http://192.168.43.1:8001
2020-05-17 14:07:35.662 INFO 13816 --- [nio-8001-exec-2] t.haonan.Controller.PaymentController : CLOUD-PAYMENT-SERVICE 192.168.43.1 8002 http://192.168.43.1:8002
```
#### Eureka的自我保护
##### 概述
某一时刻某一个微服务不可用了,Eureka不会立即清理,依旧会对该微服务的信息进行保存,Eureka会给90秒的时间等待心跳相应
因为可能微服务因为网络拥堵,导致不能进行通信了
属于CAP里面的AP分支
##### 禁止自我保护
7001yml文件配置
```yml
eureka:
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
```
还可以修改8001yml配置
```yml
eureka:
instance:
# Eureka服务端在收到最后一次心跳后等待时间的上限,单位为秒 默认是90s
lease-expiration-duration-in-seconds: 2
# Eureka客户端向服务端发送心跳的时间间隔 默认是30s
lease-renewal-interval-in-seconds: 1
```
关闭自我保护后 访问http://eureka7001.com:7001/
会看到红色字体的
**THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.**
### Zookeeper
#### 概述
节点是临时的 过时直接删除节点
#### zookeeper 的linux配置
##### 虚拟机中下载zookeeper的安装包
使虚拟机中的linux系统和外界win10系统互相ping通
win10 192.168.43.200 linux 192.168.43.50
##### 修改zookeeper的配置文件 zoo.cfg
主要是修改以下两项 两个文件夹第一个需要新建 第二个本来就有
```shell
dataDir=/usr/local/yhnapp/zookeeper/data
dataLogDir=/usr/local/yhnapp/zookeeper/logs
```
```shell
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/usr/local/yhnapp/zookeeper/data
dataLogDir=/usr/local/yhnapp/zookeeper/logs
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
## Metrics Providers
#
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true
```
##### 启动zookeeper
zookeeper/bin 目录下输入
> ./zkServer.sh start
看到以下内容表示成功
```shell
yhn@yhn-PC:/usr/local/yhnapp/zookeeper/bin$ ./zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /usr/local/yhnapp/zookeeper/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
```
#### server入驻zookeeper
##### 新建module
名字命名为cloud-provider-payment8004
##### 修改pom
主pom中加入
```xml
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-zookeeper-discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<version>${spring.cloud.starter.zookeeper.discovery.version}</version>
</dependency>
```
cloud-provider-payment8004模块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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8004</artifactId>
<dependencies>
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-zookeeper-discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
</dependencies>
</project>
```
##### 修改yml
```yml
server:
port: 8004
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 192.168.43.50:2181
```
##### 业务
```java
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/zk")
public String paymentZk(){
return "springcloud with zookeeper:" +serverPort +"\t" + UUID.randomUUID().toString();
}
}
```
##### 启动类
这里排除了数据库的自动配置 否则会报错
```java
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class,args);
}
}
```
##### 验证注册情况
虚拟机 linux中执行
> ./zkCli.sh
>
> [zk: localhost:2181(CONNECTED) 0] ls /services
> [cloud-provider-payment]
如果出现了项目的名字说明注册成功 我的叫做 `cloud-provider-payment`
以下是在zkCli.sh 中执行的情况 如果出现下面的情况 就说明成功了
```shell
[zk: localhost:2181(CONNECTED) 3] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 2] ls /services
[cloud-provider-payment]
[zk: localhost:2181(CONNECTED) 1] get /services/cloud-provider-payment/d369682f-92ba-47fc-adfa-55b0a6938ab1
{
"name":"cloud-provider-payment",
"id":"d369682f-92ba-47fc-adfa-55b0a6938ab1",
"address":"DESKTOP-ELIQ8H9",
"port":8004,
"sslPort":null,
"payload":{
"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
"id":"application-1",
"name":"cloud-provider-payment",
"metadata":{
}
},
"registrationTimeUTC":1589706305159,
"serviceType":"DYNAMIC",
"uriSpec":{
"parts":[
{
"value":"scheme",
"variable":true
},
{
"value":"://",
"variable":false
},
{
"value":"address",
"variable":true
},
{
"value":":",
"variable":false
},
{
"value":"port",
"variable":true
}
]
}
}
```
#### client入驻zookeeper
##### 新建module
命名为cloud-consumerzk-order80
##### 修改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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumerzk-order80</artifactId>
<dependencies>
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid</artifactId>-->
<!-- </dependency>-->
<!-- <!–mysql connector–>-->
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- </dependency>-->
<!-- <!–jdbc–>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-jdbc</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-zookeeper-discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
</dependencies>
</project>
```
##### 修改yml
```yml
server:
port: 80
spring:
application:
name: cloud-consumerzk-order80
cloud:
zookeeper:
connect-string: 192.168.43.50:2181
```
##### 业务
###### config
```java
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getTemplate(){
return new RestTemplate();
}
}
```
###### controller
```java
@RestController
@Slf4j
public class OrderZkController {
public static final String INVOKE_URL = "http://cloud-provider-payment";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/zk")
public String paymentInfo(){
String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk",String.class);
return result;
}
}
```
##### 启动类
```java
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
public class OrderZkMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderZkMain80.class,args);
}
}
```
##### 注册情况验证
虚拟机 linux中执行
> ./zkCli.sh
>
> [zk: localhost:2181(CONNECTED) 0] ls /services
出现下面表示成功
```shell
[cloud-consumerzk-order80, cloud-provider-payment]
```
访问localhost:80/consumer/payment/zk能够访问到localhost:8004/payment/zk 的内容 就可证明成功
### Consul
#### 概述
consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp公司用Go语言进行开发,
提供了微服务系统中的服务治理,配置中心,控制总线等功能,这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格.
#### consul的安装
直接使用docker 安装
##### 先用docker拉取镜像
> docker pull consul
##### docker执行命令
```shell
docker run -d -p 8500:8500 -v /project/docker/consul/data/:/consul/data -e CONSUL_BIND_INTERFACE='eth0' --name=consul1 consul agent -server -bootstrap -ui -client='0.0.0.0'
```
选项内容
```shell
-p 实体机映射端口:指定了docker内部端口
-v 后边的内容是 实体机地址 :docker内地址
agent: 表示启动 agent 进程
server: 表示 consul 为 server 模式
client: 表示 consul 为 client 模式
bootstrap: 表示这个节点是 Server-Leader
ui: 启动 Web UI, 默认端口 8500
node: 指定节点名称, 集群中节点名称唯一
client: 绑定客户端接口地址, 0.0.0.0 表示所有地址都可以访问
```
##### 验证
虚拟机里访问localhost:8500 如果出现图形化界面,就成功了
#### 服务提供者模块的配置
##### 建立模块
建立一个名为cloud-providerconsul-payment8006的模块
##### 配置pom文件
主pom文件添加
```xml
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-consul-discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<version>${spring.cloud.starter.consul.discovery.version}</version>
</dependency>
```
module 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-providerconsul-payment8006</artifactId>
<dependencies>
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid</artifactId>-->
<!-- </dependency>-->
<!-- <!–mysql connector–>-->
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- </dependency>-->
<!-- <!–jdbc–>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-jdbc</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-consul-discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
</dependencies>
</project>
```
##### yml文件配置
如果你是在docker里配置的内容 需要像我这样配置
```yml
# 我win10ip地址为192.168.43.200 虚拟机linux ip为192.168.43.50
server:
port: 8006
spring:
application:
name: consul-provider-payment
cloud:
consul:
host: 192.168.43.50 #consul的ip地址 也就是虚拟机的ip
port: 8500
discovery:
service-name: ${spring.application.name}
prefer-ip-address: true
ip-address: 192.168.43.200 # 这里需要配置虚拟机docker的consul内部访问外部实体机的地址
port: 8006 #payment的端口号
```
##### 主启动
```java
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
public class PaymentMain8006 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8006.class,args);
}
}
```
##### controller
```java
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "payment/consul")
public String paymentConsul(){
return "springcloud with consul:" +serverPort +"\t" + UUID.randomUUID().toString();
}
}
```
#### 消费者模块的配置
##### 建立模块
命名为cloud-consumerconsul-order80
##### 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumerconsul-order80</artifactId>
<dependencies>
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid</artifactId>-->
<!-- </dependency>-->
<!-- <!–mysql connector–>-->
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- </dependency>-->
<!-- <!–jdbc–>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-jdbc</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-consul-discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
</dependencies>
</project>
```
##### yml文件配置
```yml
# 我win10ip地址为192.168.43.200 虚拟机linux ip为192.168.43.50
server:
port: 80
spring:
application:
name: consul-consumer-order
cloud:
consul:
host: 192.168.43.50 #consul的ip地址 也就是虚拟机的ip
port: 8500
discovery:
service-name: ${spring.application.name}
prefer-ip-address: true
ip-address: 192.168.43.200 # 这里需要配置虚拟机docker的consul内部访问外部实体机的地址
port: 80
```
##### 业务类
###### config
```java
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
```
###### controller
```java
@RestController
@Slf4j
public class OrderController {
//注意这里的url是 想要服务提供者的yml配置内容 消费者的模块是 consul-consumer-order 不能填这个
public static final String INVOKE_URL = "http://consul-provider-payment";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/consul")
public String paymentInfo(){
String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul",String.class);
return result;
}
}
```
##### 主启动
```java
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
public class OrderConsulMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderConsulMain80.class,args);
}
}
```
### 三个注册中心的异同点
一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)
三者只能取其二
| 组件名 | 语言 | CAP | 服务健康检查 | 对外暴露接口 | springCloud集成 |
| --------- | ---- | ---- | ------------ | ------------ | --------------- |
| Eureka | Java | AP | 可配支持 | HTTP | 已集成 |
| Consul | Go | CP | 支持 | HTTP/DNS | 已集成 |
| Zookeeper | Java | CP | 支持 | 客户端 | 已集成 |
### Ribbon
#### 概述
spring cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具,具体的功能是提供客户端软件的负载均衡算法和服务调用
一句话就是负载均衡+RestTemplate调用
eureka 的包中含有
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
上边包中包含了 ribbon 包 所以我们不需要引入ribbon包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
```
#### Ribbon的核心组件IRule
| 类名 | 负载均衡方式 |
| -------------------------------------------------- | ------------------------------------------------------------ |
| com.netflix.loadbalancer.RoundRobinRule | 轮询 |
| com.netflix.loadbalancer.RandomRule | 随机 |
| com.netflix.loadbalancer.RetryRule | 先按照轮询的方式获取服务,如果获取失败了会在指定时间内进行重试,获取可用的服务 |
| com.netflix.loadbalancer.WeightedResponseTimeRule | 对轮询的扩展,相应时间越快的实例选择的权重越大 |
| com.netflix.loadbalancer.BestAvailableRule | 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 |
| com.netflix.loadbalancer.AvailabilityFilteringRule | 先过来掉故障实例,再选择并发较小的实例 |
| com.netflix.loadbalancer.ZoneAvoidanceRule | 默认规则,复合判断server所在区域的性能和server的可用性选择服务器 |
#### 替换默认的轮询负载均衡策略
首先官方文档明确给出了警告,自定义的配置类不能够放在@ComponentScan所扫米脑袋额当前包下及其子包下
否则我们的配置就会被所有的ribbon客户端所共享,不能做到特殊化定制的目的了
##### 针对cloud-consumer-order80 模块
```shell
F:.
│ cloud-consumer-order80.iml
│ pom.xml
│
├─src
│ ├─main
│ │ ├─java
│ │ │ ├─myrule //放在了启动类能扫描的范围之外
│ │ │ │ MySelfRule.java
│ │ │ │
│ │ │ └─tech
│ │ │ └─haonan
│ │ │ │ OrderMain80.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ ApplicationContextConfig.java
│ │ │ │
│ │ │ └─controller
│ │ │ OrderController.java
│ │ │
│ │ └─resources
│ │ │ application.yml
│ │ │
│ │ └─mapper
│ └─test
│ └─java
```
##### myrule包及其MySelfRule的建立
```java
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();
}
}
```
##### 启动类的修改 (添加@RibbonClient注解 并注明规则生效的具体位置)
```java
@SpringBootApplication
//@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE" ,configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
```
#### RIbbon的负载均衡算法
##### 原理
轮询的原理
rest接口的第几次请求书 % 服务器集群的总数量 = 实际调用服务器的位置下标,每次服务重启动后rest接口计数从零开始
```
List<ServiceInstance> instances discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
在只有8001和8002两个服务器的情况下
List [0] instances = 127.0.0.1:8001
List [1] instances = 127.0.0.1:8002
```
##### 源码的仿写
###### 8001和8002端口服务contoller增加代码
```java
@GetMapping("lb")
public String getPaymentLB(){
return serverPort;
}
```
###### 自己写算法实现轮询
cloud-consumer-order80 module的目录结构
```
F:.
│ cloud-consumer-order80.iml
│ pom.xml
│
├─src
│ ├─main
│ │ ├─java
│ │ │ ├─myrule
│ │ │ │ MySelfRule.java
│ │ │ │
│ │ │ └─tech
│ │ │ └─haonan
│ │ │ │ OrderMain80.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ ApplicationContextConfig.java
│ │ │ │
│ │ │ ├─controller
│ │ │ │ OrderController.java
│ │ │ │
│ │ │ └─lb
│ │ │ MyLbImpl.java
│ │ │ MyLoadBanlancer.java
│ │ │
│ │ └─resources
│ │ │ application.yml
│ │ │
│ │ └─mapper
│ └─test
└─java
```
如上图所示 新建lb包 里边新建MyLoadBanlancer接口 并完成接口的实现类MyLbImpl
```java
//MyLoadBanlancer接口
public interface MyLoadBanlancer {
ServiceInstance instance(List<ServiceInstance> serviceInstances);
}
```
```java
//接口的实现类MyLbImpl
@Component
public class MyLbImpl implements MyLoadBanlancer{
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement(){
int current;
int next;
//cas自旋锁 暂时还不懂
do {
current =this.atomicInteger.get();
next = current >= 2147483647 ? 0: current + 1;
}while (!this.atomicInteger.compareAndSet(current,next));
System.out.println("*****访问次数next" +next);
return next;
}
@Override
public ServiceInstance instance(List<ServiceInstance> serviceInstances) {
//轮询的核心代码 当前访问次数 模上 当前的节点服务器 这里是8001 和 8002 共两个
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
```
controller 新加
```java
@GetMapping("payment/MYLB")
public String MYLB(){
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(instances == null || instances.size() <=0)
return null;
ServiceInstance serviceInstance = MyLoadBanlancer.instance(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"payment/lb",String.class);
}
```
访问localhost/consumer/payment/MYLB 就会发现port每次刷新都会更改一次
### OpenFeign
#### 概述
Feign是一个声明式的WebService客户端,使用Feign能让编写Web Service客户端更加简单。
它的使用方法时定义一个服务接口然后在上面添加注解。Feign也支持可插拔式的编码器和解码器,Spring cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
#### 能干什么
前面使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法,但是在实际开发中,对于服务依赖的调用可能不止一处,往往一个借口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用,都是以,Feign在此基础上做了进一步的封装,让他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下我们只需调用一个接口并使用注解的方式来配置他(以前是在Dao接口上标注Mapper注解们,现在是一个为服务接口上边标注一个Feign注解即可)即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装调用客户端的开发量。
###### 举个例子
之前每个controller层都需要写这个
```java
@Autowired
private RestTemplate restTemplate;
```
#### OpenFeign的使用步骤
##### 总pom文件添加
```xml
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring.cloud.starter.openfeign.version}</version>
</dependency>
```
接口+注解 -> 微服务调用接口 + @FeignClient
##### 新建module
名为cloud-consumer-openfeign-order80
```shell
│ pom.xml
│
├─src
│ ├─main
│ │ ├─java
│ │ │ └─tech
│ │ │ └─haonan
│ │ │ │ OrderFeignMain80.java
│ │ │ │
│ │ │ ├─controller
│ │ │ │ OrdrFeignController.java
│ │ │ │
│ │ │ └─service
│ │ │ PaymentFeignService.java
│ │ │
│ │ └─resources
│ │ application.yml
│ │
│ └─test
│ └─java
```
##### 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-openfeign-order80</artifactId>
<dependencies>
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--mysql connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
```
##### yml文件
这里没有在eureka注册 ``register-with-eureka: false
```yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
```
##### 启动类
记得添加`@EnableFeignClients`注解
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}
```
##### 业务类
###### service
添加`@FeignClient`注解 里边的value是8001 8002 服务提供者的yml配置的spring.application.name=cloud-payment-service
```java
//PaymentFeignService.java
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/getById/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
```
###### controller
```java
//OrdrFeignController.java
@RestController
@Slf4j
public class OrdrFeignController {
@Autowired
private PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/getByid/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
```
默认开启ribbon的轮询负载均衡
访问localhost/consumer/payment/getByid/2 就能看见负载均衡的效果
#### openfeign的超时控制
##### 自己制造的超时报错
###### 8001 和 8002 的controller添加
这里访问的时候会睡眠3秒钟 客户端80要是访问只会等待1秒
```java
@GetMapping("/feign/timeout")
public String pymentFeignTimeOut(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
```
###### 80端口的service添加
```java
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeOut();
```
###### 80端口的Controller添加
```java
@GetMapping("/consumer/payment/feign/timeout")
public String paymentFeignTimeOut(){
//openfeign-ribbon 客户端一般默认等待1秒钟
return paymentFeignService.paymentFeignTimeOut();
}
```
然后访问localhost:8001/payment/feign/timeout 虽然会等三秒 但是能够访问到 返回了8001 而下边地址会报错 `Read timed out`
localhost/consumer/payment/feign/timeout
```json
{
"timestamp": "2020-05-19T14:19:36.926+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Read timed out executing GET http://CLOUD-PAYMENT-SERVICE/payment/feign/timeout"
}
```
##### yml文件中开启OpenFeign客户端的超时控制
cloud-consumer-openfeign-order80 80端口 的yml文件
```yml
ribbon: # 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ReadTimeout: 5000 # 指的是建立连接所用的时间
ConnectTimeout: 5000 #指的是建立连接后从服务器读取到可用资源的时间
```
然后访问localhost/consumer/payment/feign/timeout 就不会报超时错误了
#### openfeign的日志打印功能
对Feign接口的调用情况进行监控和输出
| 代码 | 解释 |
| ------- | ------------------------------------------------------- |
| None | 默认的,不显示任何日志 |
| BASIC | 仅记录请求方法、URL、相应状态码及执行时间 |
| HEADERS | 除了BASIC中定义的信息外,还有请求和响应的头信息 |
| FULL | 除了HEADERS中定义的信息外,还有请丢和相应的正文及元数据 |
###### 具体配置
新建config包 下面新建 FeignConfig类
```java
@Configuration
public class FeignConfig {
@Bean
Logger.Level feinLoggerLever(){
return Logger.Level.FULL;
}
}
```
yml文件 新增
```java
logging:
level:
# feign日志以什么级别监控那个接口
tech.haonan.service.PaymentFeignService : debug
```
然后请求localhost/consumer/payment/getById/29
控制栏中会得到以下内容 非常详细
```shell
2020-05-19 22:46:45.189 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] ---> GET http://CLOUD-PAYMENT-SERVICE/payment/getById/29 HTTP/1.1
2020-05-19 22:46:45.189 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] ---> END HTTP (0-byte body)
2020-05-19 22:46:45.220 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] <--- HTTP/1.1 200 (31ms)
2020-05-19 22:46:45.220 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] connection: keep-alive
2020-05-19 22:46:45.220 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] content-type: application/json
2020-05-19 22:46:45.220 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] date: Tue, 19 May 2020 14:46:45 GMT
2020-05-19 22:46:45.220 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] keep-alive: timeout=60
2020-05-19 22:46:45.220 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] transfer-encoding: chunked
2020-05-19 22:46:45.220 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById]
2020-05-19 22:46:45.221 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] {"responseCode":200,"message":"查询成功,端口号8001","data":{"id":29,"serial":"aavvcc"}}
2020-05-19 22:46:45.221 DEBUG 19332 --- [p-nio-80-exec-4] tech.haonan.service.PaymentFeignService : [PaymentFeignService#getPaymentById] <--- END HTTP (96-byte body)
```
### Hystrix
#### 概述
因为服务之间的调用层级随着项目的增大会更复杂,容易发生服务雪崩(链路上的某个微服务的调用的相应时间过长或者不可用,那么处于服务链最顶端的微服务的调用就会占用越来越多的系统资源,进而出现系统崩溃)
Hystrix是一个用于处理分布式系统延迟和容错的开源库,在分布式系统中很多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性.
断路器本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用放返回一个符合预期的、可处理的备选响应(FallBack) ,而不是长时间的等待或者抛出调用放无法处理的异常,这样就保证了服务调用方的线程不会长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延乃至雪崩。
#### 基本概念
##### 服务降级
通俗理解就是对方系统不可用,需要提供一个兜底的可用服务。
专业术语讲就是 向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者是抛出调用方无法处理的异常。
不让客户端等待,并立即返回一个友好提示,fallBack
那么那些情况会触发服务降级呢
1. 程序运行异常
2. 超时
3. 服务熔断触发服务降级
4. 线程池/信号量打满也会导致服务降级
##### 服务熔断
类比保险丝到达最大电流之后,直接进行熔断,服务器到达最大访问之后直接进行拒绝访问,然后调用服务降级的方式返回友好提示。
过程是 服务降级 -> 熔断 -> 恢复调用链路
##### 服务限流
秒杀高并发操作,严禁一蜂窝的过来拥挤,大家排队,1秒钟n个,有序进行。
#### 基础环境配置
##### eureka 7001配置
###### application.yml 配置
为了接下来的方便演示 ,只使用一个注册中心 。
```yml
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #实例名称
client:
register-with-eureka: false # 不向注册中心注册自己
fetch-registry: false #false 表示自己就是注册中心 ,不需要检索服务
# service-url:
# defaultZone: http://eureka7002.com:7002/eureka/ #这里和7002注册中心相互注册 相互守望
service-url:
defaultZone: http://eureka7001.com:7001/eureka/ #只有 7001自己一个注册中心了 不再进行相互守望
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
```
##### 建立8001提供服务的module并配置
###### 文件目录
```shell
├─java
│ └─tech
│ └─haonan
│ │ PaymentHystrixMain8001.java
│ │
│ ├─controller
│ │ PaymentController.java
│ │
│ └─service
│ PaymentService.java
│
└─resources
application.yml
```
###### 新建maven工程
新建maven工程 ,命名为`cloud-provider-hystrix-payment8001`.
###### pom文件修改
```xml
<dependencies>
<!--自己的工具包-->
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<!--web启动jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--使用注解简化代码-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
```
###### application.yml 文件
```yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment8001 #记得改这里的名字 否则会重复啊
eureka:
client:
register-with-eureka: true #表示是否将自己注册进入EurekaServer 默认为true
fetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone : http://localhost:7001/eureka #只有一个注册中心
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #两个注册中心
```
###### Main类
```java
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
```
###### PaymentService
```java
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "线程池 " + Thread.currentThread().getName() + "PaymentInfo_OK,id =" + id + "访问成功了";
}
public String paymentInfo_TimeOut(Integer id){
int timeNum = 3;
try { //设置睡眠3秒钟 肯定超时
TimeUnit.SECONDS.sleep(timeNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池 " + Thread.currentThread().getName() + "paymentInfo_TimeOut,id =" + id + "访问超时" +timeNum;
}
}
```
###### PaymentController
```java
@Slf4j
@RestController
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("****** result : " +result);
return result;
}
@GetMapping("payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("****** result : " +result);
return result;
}
}
```
##### 下载jmeter 并对8001进行高并发测试
**注意 jmeter在Windows下的启动一定要是 以管理员身份启动 否则是没有效果的**
Apache *JMeter*是Apache组织开发的基于Java的压力测试工具。
依次
- 添加线程组
- sample 中的 http request
- 设置线程组的 number of Threads (users) 为 200
- loop count 为 100 (两个参数随便设置 只要够用就行 )
- 然后点击开始测试按钮
如果配置的正确 那么 下方代码就会起作用 会在控制台不断打印内容
```java
log.info("****** result : " +result);
```
因为高并发访问 ,所以服务器不得不把很大一部分资源让给 `payment/hystrix/timeout/{id}` 这个接口 ,如此就会导致其他接口的速度相应的减慢。
##### 模块80客户服务的创建
###### 新建模块
新建一个模块命名为 `cloud-consumer-feign-hystrix-order80`
###### 目录结构
```shell
F:.
│ pom.xml
│
├─src
│ ├─main
│ │ ├─java
│ │ │ └─tech
│ │ │ └─haonan
│ │ │ │ OrderHystrixMain80.java
│ │ │ │
│ │ │ ├─controller
│ │ │ │ OrderHystrixController.java //controller层直接调用服务层接口 因为有openFeign
│ │ │ │ // Feign是一个声明式的WebService客户端 上边有
│ │ │ └─service
│ │ │ PaymentHystrixService.java //只用写接口不需要实现 很方便
│ │ │ //只需要声明要调用服务别名就行@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT8001")
│ │ └─resources
│ │ application.yml
```
###### 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-feign-hystrix-order80</artifactId>
<dependencies>
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
```
###### application.yml 文件的配置
```yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
ribbon:
ReadTimeout: 4000 #需要设置超时时间 因为80端口调用 8080端口要 用只至少三秒时间 会出现报错 这里给4秒没有问题
```
###### 启动类的配置
注意配置启动类的时候一定不要忘记屏蔽掉 springboot的autoConfiguration 中对数据库的启动 否则会报错
```java
@SpringBootApplication(exclude ={DataSourceAutoConfiguration.class}) //启动的时候不启动数据库
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
```
###### service层的书写
因为使用了 openFeign 所以service层只需要有接口 不需要实现类
```java
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT8001")
public interface PaymentHystrixService {
@GetMapping("payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
```
###### controller层的书写
这一层直接调用service层的接口函数
```java
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
```
###### 然后使用jmeter 进行 8001 和 80端口的高并发测试
虽然因为电脑cpu性能比较强 ,本身卡顿现象不明显 但是还是需要学习一下hytrix熔断和降级
##### 问题提出
1. 当一个接口接受到高并发请求的时候,会连累其他接口导致整个服务器上的网站受到影响,如何避免这种情况。
2. 当服务器出现宕机,或者遇到错误,不应该将报错信息返回到前台,应该返回一个用户友好的界面。
总结 : **超时不再等待 出错要有兜底**
##### 问题解决(针对本次问题)
1. 要调用的接口服务(8001)超时了,调用者(80)不能有一直卡死等待,必须要有服务降级
2. 要调用的接口服务(8001)宕机了,调用者(80)不能一直卡死等待,必须要有服务降级
3. 要调用的接口服务(8001)Ok,调用者(80)自己出故障或者有自我要求(例如 自己的等待时间小于服务提供者,自己处理降级)
#### hystrix流程
1. [Construct a `HystrixCommand` or `HystrixObservableCommand` Object](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow1)
2. [Execute the Command](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow2)
3. [Is the Response Cached?](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow3)
4. [Is the Circuit Open?](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow4)
5. [Is the Thread Pool/Queue/Semaphore Full?](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow5)
6. [`HystrixObservableCommand.construct()` or `HystrixCommand.run()`](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow6)
7. [Calculate Circuit Health](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow7)
8. [Get the Fallback](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow8)
9. [Return the Successful Response](https://github.com/Netflix/Hystrix/wiki/How-it-Works#flow9)****
![hystrix流程](https://raw.githubusercontent.com/wiki/Netflix/Hystrix/images/hystrix-command-flow-chart.png)
#### 降级
##### 注解介绍
`@HystrixCommand`
##### 8001降级设置
###### 启动类更改
添加`@EnableCircuitBreaker` 或者是 `@EnableHystrix`注解
````java
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //注意 添加了这一关注解 表示启动 hytrix的服务降级功能
// 注意 也可以添加这个注解 @EnableHystrix
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
````
###### service层更改
将服务改成 5 秒,肯定会超时 我们下一步设置自身调用超时时间的峰值,峰值可以正常运行,超过了需要有兜底的方法处理,做服务降级fallback
下边代码是写8001端口的 paymentInfo_TimeOut 方法一定会超时 如何设置兜底函数 兜底函数不会向用户返回错误信息,
可以自定义。
**只要是当前服务不可用了,我们都会调用兜底函数,比如 int a = 10/0; 会直接报错 调用兜底函数**
```java
//////////////////////////// PaymentService.java
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "线程池 " + Thread.currentThread().getName() + "PaymentInfo_OK,id =" + id + "访问成功了";
}
//这里注解主要是设置等待时间是3秒 如果超出3秒 那么8001服务的 paymentInfo_TimeOut 直接会调用兜底函数 这里故意睡眠了5秒 所以一定会调用兜底函数
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds" , value = "3000")
})
public String paymentInfo_TimeOut(Integer id){
int timeNum = 5; ////////////// 注意这里已经修改成 5秒了
try { //设置睡眠3秒钟 肯定超时
TimeUnit.SECONDS.sleep(timeNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池 " + Thread.currentThread().getName() + "paymentInfo_TimeOut,id =" + id + "访问超时" +timeNum;
}
public String paymentInfo_TimeOutHandler(Integer id){
return "我是兜底函数";
}
}
```
##### 80降级设置
自己也可以进行客户端降级保护
一般做fallback服务降级是放在客户端 ,之所以放在客户端是因为上游发现问题,进行及早处理。
**注意一点 热部署对java代码的改动明显,但是对于HystrixCommand内属性的修改建议重启微服务**
###### yml文件的修改
yml文件中开启hystrix
```yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
#### 注意以下行是新加的 但是我实际上试了 不加也行
feign:
hystrix:
enabled: true
#### 注意以下行是我注释掉的
#ribbon:
# ReadTimeout: 4000 #需要设置超时时间 因为80端口调用 8080端口要 用只至少三秒时间 会出现报错 这里给4秒没有问题
```
###### 启动类修改
添加`@EnableCircuitBreaker` 或者是 `@EnableHystrix`注解 这里就不展示了 上边 8001 有实例
###### controller层修改
就修改了paymentInfo_TimeOut方法
```java
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
// @GetMapping("consumer/payment/hystrix/timeout/{id}")
// public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
// String result = paymentHystrixService.paymentInfo_TimeOut(id);
// return result;
// }
//这里注解主要是设置等待时间是3秒 如果超出3秒 那么8001服务的 paymentInfo_TimeOut 直接会调用兜底函数 这里故意睡眠了5秒 所以一定会调用兜底函数
@GetMapping("consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds" , value = "3000")
})
public String paymentInfo_TimeOut(Integer id){
int timeNum = 5; ////////////// 注意这里已经修改成 5秒了
try { //设置睡眠3秒钟 肯定超时
TimeUnit.SECONDS.sleep(timeNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
// int a = 10/0;
return "线程池 " + Thread.currentThread().getName() + "paymentInfo_TimeOut,id =" + id + "访问超时" ;
}
public String paymentInfo_TimeOutHandler(Integer id){
return "我是消费者端(80)的兜底函数";
}
}
```
##### 引起降级的情况
1. 程序运行异常
2. 超时
3. 服务熔断触发服务降级
4. 线程池/信号量打满也会导致服务降级
#### 熔断
##### 简介
类比保险丝达到最大访问之后,直接拒绝方法,拉闸限电,然后调用服务降级的方式并返回友好提升
就是保险丝 服务的降级 -> 进而熔断 -> 恢复调用链路
可以理解为熔断是一种特殊的降级,程度更深
##### 熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务出错不可用或者相应时间太长的时候,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的相应信息.
当检测到该节点微服务调用相应正常后,**`恢复调用链路`**
在Spring Cloud框架中,熔断机制是通过Hystrix实现,hystrix会监控微服务间调用的状况
当失败的调用到达一定阈值,缺省是5秒调用,就会启动熔断机制,熔断机制的注解还是@HystrixCommand
half open 的意思是如果并发量下来了,那么hystrix的会尝试进行连接,如果发现能够成功,那么把模式改为closed(通)的状态
![熔断机制](https://martinfowler.com/bliki/images/circuitBreaker/state.png)
##### 8001进行服务熔断
###### service添加代码
增加以下内容
表示 失败率达到60% 那么 就直接拒绝服务 ,在没有恢复之前 ,即使正确的请求也拒绝服务,等待x(10)秒后再次进行尝试如果成功,恢复链路。
```java
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
//========= 熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
//com.netflix.hystrix.HystrixCommandProperties 所有的properties定义在这里边
@HystrixProperty(name = "circuitBreaker.enabled",value = "true") ,// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), // 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000") ,// 时间窗口期 进过多少时间恢复一次尝试
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60") // 失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
if(id<0){
throw new RuntimeException("!!!!!!!!! id 不能为负数");
}
// 糊涂工具包用来生成随机数 流水号
String serialNumber = IdUtil.simpleUUID();
// 等价于 UUID.randomUUID().toString(); 糊涂工具包 很好用 用来加密解密 等一些操作
return Thread.currentThread().getName() + "调用成功 流水号为: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id 不能为负数,请重试 此次输入的 id 为" + id;
}
```
###### controller层的添加
```java
// 上边是服务降级
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
//========= 熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("*****result:" + result);
return result;
}
```
##### 最后总结
###### 熔断的类型
- 熔断打开 : 请求不再调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开市场达到所设时钟则进入半熔断状态
- 熔断半开 :部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
- 熔断关闭: 不会对服务进行熔断
![熔断示意图](https://github.com/Netflix/Hystrix/wiki/images/circuit-breaker-1280.png)
###### 断路器是什么时候起作用
三个基本的参数
1. 快照时间窗: 断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
2. 请求总数阈值:在快照时间窗中,必须满足请求总数的阈值才能有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,计时所有的请求都超时或者其他原因失败,断路器都不会打开。
3. 错误百分阈值:当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,发生了15次超时异常,也就是超过50%的错误百分比,默认设置50%的阈值情况下,这时候就将断路器打开。
all配置
```java
```
#### 限流
阿里巴巴再学
#### 服务监控仪表盘(图像化展示)
##### 概述
除了隔离依赖服务的调用之外,hystrix还提供了准实时的调用监控(hystrix dashboard) ,hystrix会持续地记录所有通过hystrix发起的请求的执行信息,并以统计报表和图形化的形式展现给用户。
Netflix通过hystrix-metric-event-stream 项目实现了对以上指标的监控。spring cloud也提供了hystrix DashBoard的整合。
##### 仪表板9001
###### 新建cloud-consumer-hystrix-dashboard9001 项目
###### 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix-dashboard -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
```
注意 要监控的服务8001 也需要进行 添加pom依赖
这个是用来监控的东西
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
###### 启动类
注意一定开启@EnableHystrixDashboard 注解
```java
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
```
###### yml文件配置
9001 的配置
```yml
server:
port: 9001
hystrix:
dashboard:
proxy-stream-allow-list: "*"
```
##### 其他配置
###### 8001 yml配置
```yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment8001 #记得改这里的名字 否则会重复啊
eureka:
client:
register-with-eureka: true #表示是否将自己注册进入EurekaServer 默认为true
fetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone : http://localhost:7001/eureka #只有一个注册中心
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #两个注册中心
######################################
######################################
#以下是新加的
management:
endpoints:
web:
exposure:
include: "hystrix.stream"
```
##### 验证配置
###### 访问8001 的地址
如果能访问成功
```shell
1. http://localhost:8001/actuator
返回内容
{"_links":{"self":{"href":"http://localhost:8001/actuator","templated":false},"hystrix.stream":{"href":"http://localhost:8001/actuator/hystrix.stream","templated":false}}}
2. http://localhost:8001/actuator/hystrix.stream
这个网址访问后如果一直在打印输出 那么就表示没啥问题
其实他就是一个接口 给9001 提供信息 9001 用图标表示出来而已
```
###### 9001 测试
将第一个框中填入 对应地址
因为我的这个环境是检测8001 端口的活动 所以我填入下方内容
```
http://localhost:8001/actuator/hystrix.stream
```
后两个框随意填 一个是延迟多少秒更新内容 ,一个是别名
![](https://img2018.cnblogs.com/blog/1107037/201909/1107037-20190908231125067-850469310.png)
### 服务网关
比如医院的挂号窗口 ,承担限流
zuul zuul2产生了严重的分歧,而且核心人物跳槽,所以重点放在gateway上了。
但是实际上现在(2020/10/29 )我去github搜索得知,zuul2的star数有1万,而gateway只有2千
下图是springcloud的架构图 ,spring cloud gateway 就代替了zuul成为架构图中,API Gateway的实际实现。
<img src="https://pic1.zhimg.com/80/v2-6dfe0a1b689cd49d96de2dcc58106705_720w.jpg?source=1940ef5c" alt="springcloud架构图" style="zoom:150%;" />
这里的zuul集群替换为 spring cloud gateway,负载均衡得工作是Nginx做
<img src="https://pic1.zhimg.com/80/v2-afec39922278c0e9f8bd65f4a951b100_720w.png" alt="网关的作用" style="zoom:150%;" />
### Spring Cloud Gateway
#### 基本介绍
springcloud Gateway 是基于WebFlux框架实现的,而WebFlux框架底层使用了高性能的Reactor模式通信框架Netty,springcloud Gateway 的目标提供统一的路由方式,且基于Filter链的方式提供了网关的基本功能,例如安全,监控、指标和限流。
Netty其实就是node.js的Java版本。
##### 他能干什么
- 反向代理鉴权
- 流量控制
- 熔断
- 日志监控
- 。。。。
##### 有了zuul我们为什么还要选择gateway
- zuul1.x 基于Servlet2.5 使用阻塞架构,所以它不支持任何长连接(如 websocket)
- Gateway是基于异步费组赛模型上进行开发的, 性能方面不需要担心,
- springcloud整合gateway,但没有整合zuul2
- gateway基于spring framework5 ,project Reactor 和spring boot 2.0 进行构建
- 动态路由: 能够匹配任何请求属性,可以对路由指定Predicate(断言)和filter(过滤器)
- 集成hystrix的断路器功能
- 集成spring cloud的服务发现功能
- 易于编写的Predicate(断言) 和 filter (过滤器)
- 请求限流功能
- 支持路径重写
##### webFlux是什么
WebFlux 内部使用的是响应式编程(Reactive Programming),以 Reactor 库为基础, 基于异步和事件驱动,可以让我们在不扩充硬件资源的前提下,提升系统的吞吐量和伸缩性。
看到这里,你是不是以为 WebFlux 能够使程序运行的更快呢?量化一点,比如说我使用 WebFlux 以后,一个接口的请求响应时间是不是就缩短了呢?
抱歉了,答案是否定的!以下是官方原话:
> Reactive and non-blocking generally do not make applications run faster.
WebFlux 并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。
上面说到了, Spring WebFlux 是一个异步非阻塞式的 Web 框架,所以,它特别适合应用在 IO 密集型的服务中,比如微服务网关这样的应用中。
PS: IO 密集型包括:磁盘IO密集型, 网络IO密集型,微服务网关就属于网络 IO 密集型,使用异步非阻塞式编程模型,能够显著地提升网关对下游服务转发的吞吐量。
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTE5ODUyMi8yMDE5MDcvMTE5ODUyMi0yMDE5MDcwNTExMzExMjk5OC02NDY0MDkwNjguanBn?x-oss-process=image/format,png)
首先你需要明确一点就是:WebFlux 不是 Spring MVC 的替代方案!,**虽然 WebFlux 也可以被运行在 Servlet 容器上(需是 Servlet 3.1+ 以上的容器)**,但是 WebFlux 主要还是应用在异步非阻塞编程模型,而 Spring MVC 是同步阻塞的,如果你目前在 Spring MVC 框架中大量使用非同步方案,那么,WebFlux 才是你想要的,否则,使用 Spring MVC 才是你的首选。
**在微服务架构中,Spring MVC 和 WebFlux 可以混合使用,**比如已经提到的,对于那些 IO 密集型服务(如网关),我们就可以使用 WebFlux 来实现。
选 WebFlux 还是 Spring MVC? This is not a problem!
#### 三大核心概念
##### 路由(route)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器构成,如果断言为true则匹配该路由
请求经过负载均衡Nginx后,需要找到一个微服务x ,需要符合路由的匹配规则,才能接受请求
##### 断言(predicate)
参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或者是请求参数),如果请求和断言相匹配则进行路由
##### 过滤(filter)
指的是spring框架中GateWayfilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改
![三大核心组件的工作示意图](https://img-blog.csdnimg.cn/20200728133006630.JPG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0pva2Vyb25lZQ==,size_16,color_FFFFFF,t_70#pic_center)
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制,
predicate就是我们的匹配条件;
而filter就可以理解为一个无所不能的拦截器,有了这两个元素,再加上目标uri,就可以实现一个具体的路由了。
![官网示意图](https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/images/spring_cloud_gateway_diagram.png)
客户端向spring cloud Gateway发出请求,然后再GatewayHandlerMapping 中找到与请求相匹配的路由,将其发送到Gateway web handler
handler再通过指定的过滤器链来讲请求发送到我们试驾的服务之星业务逻辑,然后返回
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之间("pre") 或之后(“post”)执行业务逻辑
Filter在“pre”之前的过滤器可以做参数校验,权限校验,流量监控,日志输出,协议转换等
在post类型的过滤器中可以做响应内容,响应头的修改,日志的输出,流量监控等有着非常重要的作用
#### 静态路由小demo
##### 新建modules cloud-gateway-gateway9527
##### pom
不能加 spring-boot-starter-web 否则会报错
```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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>tech.haonan</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-gateway-gateway9527</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>tech.haonan</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
```
##### yml文件
```yml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
# 这里是配置 将8001 的服务不主动暴露出来 通过网关来访问
- id: payment_routh1
uri: http://localhost:8001
predicates:
- Path=/payment/getById/**
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/**
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
```
##### 启动类
```java
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class,args);
}
}
```
测试通过 可以使用9527的端口访问8001 的微服务
```shell
http://localhost:8001/payment/getById/3
{"responseCode":200,"message":"查询成功,端口号8001","data":{"id":3,"serial":"adsf"}}
http://localhost:9527/payment/getById/1
{"responseCode":200,"message":"查询成功,端口号8001","data":{"id":1,"serial":"afsdasfasf"}}
```
#### 两种gateway的网关配置方法
##### 第一种就是上边这种
```yml
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
# 这里是配置 将8001 的服务不主动暴露出来 通过网关来访问
- id: payment_routh1
uri: http://localhost:8001
predicates:
- Path=/payment/getById/**
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/**
```
在yml文件中配置routes
##### 第二种配置方法 代码注入routeLocator的Bean
先注释掉 yml文件中的配置 然后新建一个类 GateWayConfig.java 目录结构如下图所示
```shell
├─main
│ ├─java
│ │ └─tech
│ │ └─haonan
│ │ │ GateWayMain9527.java
│ │ │
│ │ └─config
│ │ GateWayConfig.java
│ │
│ └─resources
│ application.yml
│
└─test
└─java
```
GateWayConfig.java 配置类 信息
```java
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 转发 :9527的请求 /payment/getById/** 到http://localhost:8001/payment/getById
.route("path_route1", r -> r.path("/payment/getById/**")
.uri("http://localhost:8001"))
// 转发 :9527的请求 /payment/lb/** 到http://localhost:8001/payment/lb/**
.route("path_route2", r -> r.path("/payment/lb/**")
.uri("http://localhost:8001")).build();
}
}
```
#### 动态路由小demo
##### 提前准备工作
实现负载均衡得小实验需要开启4个微服务
- Payment8001
- Payment8002
- EurekaMain7001
- GateWayMain9527
只需要修改9527的yml文件,**记得注释掉GateWayConfig的东西**
##### eureka 的图片
<img src="https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/springCloud%E5%AD%A6%E4%B9%A0/springcloud%E5%AD%A6%E4%B9%A0eurekaGateway%E7%A4%BA%E6%84%8F%E5%9B%BE.png" alt="springcloud学习eurekaGateway示意图" style="zoom:150%;" />
##### GateWayMain9527 application.yml
```yml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery: # 开启动态路由
locator:
enabled: true
routes:
# 这里是配置 将8001 的服务不主动暴露出来 通过网关来访问
- id: payment_routh1
# lb 是load balance的意思 8001 端口 和8002 端口的微服务都提供一样的服务
uri: lb://CLOUD-PAYMENT-SERVICE
predicates:
- Path=/payment/getById/**
- id: payment_routh2
uri: lb://CLOUD-PAYMENT-SERVICE
predicates:
- Path=/payment/lb/**
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
```
##### 测试结果
访问 http://localhost:9527/payment/lb 就可以看出 端口在更改 ,所以实现了轮询的负载均衡。
#### 三大核心的高级用法
##### 断言的使用
###### 时间的配置
```yml
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery: # 开启动态路由
locator:
enabled: true
routes:
# 这里是配置 将8001 的服务不主动暴露出来 通过网关来访问
- id: payment_routh1
uri: lb://CLOUD-PAYMENT-SERVICE # lb 是load balance的意思 8001 端口 和8002 端口的微服务都提供一样的服务
predicates:
- Path=/payment/getById/**
#当前时间和下边这个时间做对比 如果晚于这个时间 则可以访问 反正拒绝
- After=2010-10-30T18:25:10.272410800+08:00[Asia/Shanghai]
# - Before=2010-10-30T18:25:10.272410800+08:00[Asia/Shanghai]
# - Between=2010-10-30T18:25:10.272410800+08:00[Asia/Shanghai],2030-10-30T18:25:10.272410800+08:00[Asia/Shanghai]
```
###### cookie,Header方面的配置
```yml
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery: # 开启动态路由
locator:
enabled: true
routes:
# 这里是配置 将8001 的服务不主动暴露出来 通过网关来访问
- id: payment_routh1
uri: lb://CLOUD-PAYMENT-SERVICE # lb 是load balance的意思 8001 端口 和8002 端口的微服务都提供一样的服务
predicates:
- Path=/payment/getById/**
- Cookie=username,xxxx
-
# 需要提交cookie中包含 username=xxxx
# curl http://localhost:9527/payment/getById/1 --cookie "username=xxxx"
```
```shell
> curl http://localhost:9527/payment/getById/1 # 404 错误 找不到
{"timestamp":"2020-10-30T10:57:11.693+0000","path":"/payment/getById/1","status":404,"error":"Not Found"
> curl http://localhost:9527/payment/getById/1 --cookie "username=xxxx" # 可以通过断言
{"responseCode":200,"message":"查询成功,端口号8001","data":{"id":1,"serial":"afsdasfasf"}}
> curl http://localhost:9527/payment/getById/1 # 404 错误 找不到
{"timestamp":"2020-10-30T11:15:04.618+0000","path":"/payment/getById/1","status":404,"error":"Not Found",
> curl http://localhost:9527/payment/getById/1 -H "X-Request-Id:123123" # 可以通过断言
{"responseCode":200,"message":"查询成功,端口号8002","data":{"id":1,"serial":"afsdasfasf"}}
```
###### 其他 yml的配置
```yml
- Host=**.somehost.org,**.anotherhost.org #需要请求这个网址才能被接受 request Header的属性
- Method=GET,POST #请求方式 只允许 get 和 post
- Query=keep, pu. #要当请求中包含 keep 属性并且参数值是以 pu开头的长度为三位的字符串才会进行匹配和路由
# curl localhost:8080?keep=pub
# 当且仅当请求IP是192.168.1.1/24网段,例如192.168.1.10,才会转发到用户微服务
# eg. 访问http://localhost:8040/users/1 -> user-center的/users/1
- RemoteAddr=192.168.1.1/24
```
##### filter 的使用
###### 简单介绍
路由过滤器可用于修改进入的HTTP请求和返回的http响应,路由过滤器只能指定路由进行使用
spring cloud gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类产生
###### 生命周期
- pre
- post
###### 官方提供的过滤器
- 种类
- GatewayFilter 31种
- GlobalFilter 10种
###### 自定义过滤器
两个主要接口的介绍 implements GlobalFilter, Ordered
能干啥
- 全局日志记录
- 统一网关鉴权
- .........
目录结构
```shell
│ GateWayMain9527.java
│
├─config
│ GateWayConfig.java
│
└─filter
MyLogGateWayFilter.java # 自定义过滤器
```
新建一个类叫做 MyLogGateWayFilter
```java
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
// 过滤 如果里边没有uname这个属性 那么返回错误 并打印到日志中去
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("********** come in MyLogGateWayFilter: " + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null){
log.info("********** 姓名为null,非法用户");
// 如果没有uname这个属性 直接返回状态 406(NOT_ACCEPTABLE)
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
// 去下一个过滤链进行 过滤
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 加载过滤器的顺序 数字越小 优先级越高
return 0;
}
}
```
```shell
http://localhost:9527/payment/lb?uname=afs
访问是可以得到正确结果的
http://localhost:9527/payment/lb
This page isn’t working right nowIf the problem continues, contact the site owner.
HTTP ERROR 406 406错误
```
SpringCloud学习上篇