0
点赞
收藏
分享

微信扫一扫

SpringCloud基础篇

sullay 2022-11-11 阅读 82

一、 版本问题

在这个页面下可以找到springcloud的版本对应

在这个点击Refence

我们可以发现这里官方推荐 **springboot**为2.3.10.RELEASE版本

自学参考书:

  • SpringCloud Netflix 中文文档:https://springcloud.cc/spring-cloud-netflix.html
  • SpringCloud 中文API文档(官方文档翻译版):https://springcloud.cc/spring-cloud-dalston.html
  • SpringCloud中国社区:http://springcloud.cn/
  • SpringCloud中文网:https://springcloud.cc

    用户与都断的基本案例

二、创建springcloud父工程

设置字节编码

设置注解生效激活

选择java编译

设置pom.xml中

设置包的继承为pom不是默认的jar包

然后配置pom中其他的包

<?xml versinotallow="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:schemaLocatinotallow="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.lyd</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Maven</name>

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

<!-- 1、只是声明依赖,并不实际引入,子项目按需声明使用的依赖 -->
<!-- 2、子项目可以继承父项目的 version 和 scope -->
<!-- 3、子项目若指定了 version 和 scope,以子项目为准 -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

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

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

</project>

三、微服务模块

1、建module

2、改POM

3、写YML

4、主启动

5、业务服务

在环境中创建子项:cloud-provider-payment8001

子工程配置

<?xml versinotallow="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:schemaLocatinotallow="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>com.lyd.springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

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

<dependencies>
<!-- 服务注册中心的客户端端 eureka-client -->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

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

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

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

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

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

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

</dependencies>

</project>

yml

server:
port: 8001
spring:
application:
name: cloud-payment-service #唯一标识
datasource:
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&userSSl=false
username: root
password: lyd20010227+
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
#设置映射文件的路径。
mapper-locations: classpath:mapper/*.xml
#给实体起别名
type-aliases-package: com.lyd.springcloud.entities

配置完了以后我们写入我们的sql文件

Mysql

/*
Navicat Premium Data Transfer

Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80015
Source Host : localhost:3306
Source Schema : db2019

Target Server Type : MySQL
Target Server Version : 80015
File Encoding : 65001

Date: 06/03/2020 10:19:42
*/

SET NAMES utf8mb4;
SET
FOREIGN_KEY_CHECKS = 0;
CREATE
database db2019;
USE
db2019;

-- ----------------------------
-- Table structure for payment
-- ----------------------------
DROP TABLE IF EXISTS `payment`;
CREATE TABLE `payment`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`serial` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '支付流水号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '支付表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of payment
-- ----------------------------
INSERT INTO `payment`
VALUES (31, '尚硅谷111');
INSERT INTO `payment`
VALUES (32, 'atguigu002');
INSERT INTO `payment`
VALUES (34, 'atguigu002');
INSERT INTO `payment`
VALUES (35, 'atguigu002');

SET
FOREIGN_KEY_CHECKS = 1;

Entity层

import java.io.Serializable; // 序列化

@Data
@AllArgsConstructor //全参
@NoArgsConstructor //空参
public class Payment implements Serializable {
private Long id;
private String serial;
}

配置检验数据CommonResult

@Data
@AllArgsConstructor //全参
@NoArgsConstructor //空参
//返回给前端的类(JSON封装实体类)
public class CommonResult<T> {
private Integer code;
private String message;
private T data;

public CommonResult(Integer code,String message) {
this(code,message,null);
}
}

此处也可以在Controller层用map<String,Object> 进行传参,不用Result类,但是推荐使用Result类

eg:

@RequestMapping("/selectByPage")
public Map<String,Object> selectBuPage(int page, int limit,String aaa){
PageInfo<LayuiAnimal> pi = as.selectByPage(page, limit,aaa);
System.out.println(aaa);
Map<String,Object> map = new HashMap<>();
try {
map.put("code",0);
map.put("count",pi.getTotal());
map.put("data",pi.getList());
return map;
} catch (Exception e) {
e.printStackTrace();
map.put("code",1);
map.put("msg",e.getMessage());
return map;
}
}

Dao层

@Mapper
public interface PaymentDao
{
public int create(Payment payment);

public Payment getpaymentbyid(@Param("id") Long id);
}

Mapper层

<?xml versinotallow="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lyd.PayMent.dao.PaymentDao">
<insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
insert into db2019.payment(serial) values(#{serial});
</insert>
<!--推荐使用resultMap来自己写一个映射,并且实现id的映射-->
<resultMap id="BaseRequestMap" type="com.lyd.PayMent.entities.Payment">
<id column="id" property="id" jdbcType="BIGINT"/>
<id column="serial" property="serial" jdbcType="VARCHAR"/>
</resultMap>
<select id="getpaymentbyid" parameterType="Long" resultMap="BaseRequestMap">
SELECT * FROM db2019.payment WHERE id=#{id};
</select>
</mapper>

Service层

1、service接口

public interface PaymentService {
public int create(Payment payment);
public Payment getpaymentbyid(@Param("id") Long id);
}

2、service实现类

@Service
public class PaymentServiceImp implements PaymentService {
@Resource
private PaymentDao paymentDao;

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

@Override
public Payment getpaymentbyid(Long id) {
return paymentDao.getpaymentbyid(id);
}
}

Controller层

@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;

@PostMapping("payment/create")
public CommonResult create(@RequstBody 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("/payment/get/{id}")
public CommonResult getpaymentbyid(@PathVariable("id") Long id){
Payment payment= paymentService.getpaymentbyid(id);
log.info("******插入结果"+payment);
if(payment!=null){
return new CommonResult(200,"查询成功",payment);
}
else {
return new CommonResult(444,id+"Id查询失败",null);
}
}
}

这样我们的一个增加和查询的前后端分离的基本就写完了

配置热部署

首先是我们项目中添加热部署Devtools插件

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

然后添加插件到我们父类的pom.xml中

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

设置我们IDEA中的选项,添加热部署

开启热部署,开启我们的池

打勾我们要的两个文件

但是推荐不使用热部署,使用Ctrl+F9

四、用户模块

创建consumer80模块

配置

<?xml versinotallow="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:schemaLocatinotallow="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>com.lyd.springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

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

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

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

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

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

</project>

yml

server:
port: 端口号

注意:我们自己用的电脑端口号不要太低

因为这是用户端,所以我们在这个接口就只能有controller层,但是也要实体层

此时我们想要从80端口调用8081端口,我们就要用到RestTemplate

因为RestTemplate是外部文件,所以我们需要用config来进行

@Configuration
public class ApplicatioContextConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

Controller层

@Slf4j
@RestController
@RequestMapping("/consumer")
public class OrderController {
private static final String PAYMENT_URL="htttp://localhost:8001";
@Resource
// 通过 @Resource进行注入,并且实例化
private RestTemplate restTemplate;

@GetMapping("/payment/create")
public CommonRedult<Paymeny> create (Paymeny paymeny){
// 返回请求
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",paymeny,CommonRedult.class);
}
@GetMapping("/payment/get/{id}")
public CommonRedult<Paymeny> getpaymentbyid(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get"+id,CommonRedult.class);
}
}

我们还是要重写我们的Entities层

entities层

user类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Paymeny implements Serializable {
private Long id;
private String serial;
}

CommonResult类

@Data
@AllArgsConstructor //全参
@NoArgsConstructor //空参
//返回给前端的类(JSON封装实体类)
public class CommonResult<T> {
private Integer code;
private String message;
private T data;

public CommonResult(Integer code,String message) {
this(code,message,null);
}

这样我们把两个端口启动,通过consumer就可以访问到payment端口了

为什么没有端口号呢,因为我们的‘用户是不知道端口的,所以是直接进行访问

五、工程重构

因为我们80项目和8001中的entities中一样,我们应该他其中一个打包出来,让所有的都能适应

此时我门创建 cloud-api-commons工程

pom.xml

<?xml versinotallow="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:schemaLocatinotallow="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>com.lyd.springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

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

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

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

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

</project>

替代entities类

此时我们把我们重复的类进行复制,写入

clean一哈我们的maven工程,然后在install我们的工程

然后我们把别的包的entites都删除了,我们在依赖中导入我们自己的entitie

<!-- 导入我们自己的entities包 -->
<dependency>
<groupId>org.example</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>

这样我们的公共包就被替代了

服务注册

Eureka

一、Eureka基础知识

Euraka的两个组件:Euraka Server 和 Euraka Client

二、单机版Eureka

配置EurekaServer端口

配置pom.xml

<?xml versinotallow="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:schemaLocatinotallow="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>com.lyd.springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloud-euraka-server-7001</artifactId>

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

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

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

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

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

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

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

</dependencies>
</project>

application.yml

server:
port: 7001
eureka:
instance:
hostname: localhost # eureka的实例名称
client:
# 类比:物业公司不会收自己的物业
# fasle表示不会实例化注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是去维护服务中心,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与Eureka server交互的地址查询服务和服务注册都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eruka/

最后配置一个启动类,注意这里要多加上:@EnableEurekaServer

@SpringBootApplication
//表明这是euraka的服务中心
@EnableEurekaServer
public class eurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(eurekaMain7001.class,args);
}
}

启动servies

这时候我们访问我们的localhost:7001就会出现:

但是我们可以发现我们没有实例入住进来

因为我们还没有注册服务器-eurekaClient

配置client端口

添加我们Client端口到8001的pom.xml中

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

在yml中添加我们client的配置

eureka:
client:
#表示这是自己注册的EruekaServer,表示为true
register-with-eureka: true
#是否从EruekaServer抓取自己的注册信息,默认为true,单节点无所谓,
#集群必须为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#入住地址
defaultZone: http://localhost:7001/eureka

之前书写的子工程的唯一标识符,就是我们入住erueka中Appliaction的名字(别名)

spring:
application:
name: cloud-payment-service #唯一标识

如果有爆红的地方,那就是Erueka中的自我保护机制

三、集群版Eureka

问题:微服务RPC远程服务调用最核心的是什么?

高可用。如果调用中心只有一个,如果出现故障,会导致整个环境都不可用,所以我们搭建一个中心集群

搭建中心集群:搭建Eruka注册中心集群,实现负载均衡+故容错率

集群注册原理:相互注册,相互守望

配置7002主机,完成相互注册

修改windows的文件,模拟有两个主机集群

添加:127.0.0.1 eureka7001.com 和 127.0.0.1 eureka7002.com

修改yml

他们交互的地址就改为对象的地址

7001

server:
port: 7001

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

7002

server:
port: 7002

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

启动类

@SpringBootApplication
@EnableEurekaServer
public class eruakaMain7002 {
public static void main(String[] args) {
SpringApplication.run(eruakaMain7002.class,args);
}
}

这样之后,我们的两个Server就能相互交互了

我们要把Client加入我们的Server中(修改yml)

如果两个集群能个相互指向,那么就是搭建成功

8001 yml

service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

80 yml

记得乡80短端口的xml中,添加

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

eureka:
client:
#表示这是自己注册的EruekaServer,表示为true
register-with-eureka: true
#是否从EruekaServer抓取自己的注册信息,默认为true,单节点无所谓,
#集群必须为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

测试: 必须先启动7001,7002.不然没有办法进行配置

此时我们测试成功

##

配置8002端口:直接从8001里面粘贴。

注意:修改端口名

在controller中加入

//    获取出是那个服务端口
@Value("${server.port}")
private String serverPort;

测试

访问多端口

但是现在问题来了,我们通过80端口访问,此时只能是8001端口

原因:我们在80端口的contoller中端口写死了

改为我们eureka中的端口名字

public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

然后开启我们的负载均衡功能(给RestTemplate 添加注解)

通过@LoadBalanced赋予其均衡能力

这样我们的80端口就可以通过8002端口访问了

四、actuator微服务信息完善

主机名称,服务器名字的修改

修改cloud-provider-payment8001 和 cloud-provider-payment8002的 yml

希望访信息中有ip显示

只用咋在instance-id下加一个

prefer-ip-address: true #访问显示ip地址

五、发现Discovery

此时我们已经完成了自己内部微服务的搭建,但是微服务还需要对外提供功能

我们在controller中自己写一个discover方法,进行对外

import org.springframework.cloud.client.discovery.DiscoveryClient;

@Resource
private DiscoveryClient discoveryClient;

@GetMapping(value = "/discover")
public Object discover(){
//方法一、通过servies进行遍历
/* List<String> services = discoveryClient.getServices();
for (String element:services ) {
log.info("*****element"+element);*/
//方法二、根据微服务名称,你想往上推
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance: instances) {
log.info(instance.getInstanceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}

然后在启动类上加上注解:@EnableDiscoveryClient

测试:

六、Erueka的自我保护机制

某时刻,在进行的微服务不能使用了,但是不会马上进行删除

如何禁止自我保护

在默认状态下,这个是建立就开启的

我们在7001下关闭自我保护机制

server:
#关闭自我保护机制,保证不可用服务器马上被删除
enable-self-preservation: false
#设置多少毫秒的删除时间
eviction-interval-timer-in-ms: 2000

这是配置我们的内部微服务:8001

#    想服务器发送心跳的时间间隔,单位是为s(默认为30s)
lease-renewal-interval-in-seconds: 1
# 最服务器收到最后一个心跳的等待时间上线,默认为90s,超时就删除服务
lease-expiration-duration-in-seconds: 2

Zookeeper

一、什么是zookeeper:

是一个分布式协调工具,可以实现注册功能

开启Zookeeper之前记得关闭linux的防火墙,在启动Zookeeper:systemctl stop firewalld

现在我们使用zookeeper替代我们的Eureka

使用Springcloud整合的zookeeper客户端

二、provider结点

配置

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>

yml

#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8004
#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
#这里的localhost就是指我们的地址
connect-string: localhost:2181

这时候的我们的主启动类就不用Erueka进行修饰了,所以就没有@EnableEurekaClient

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class,args);
}
}

controller

这里和之前的controller一样,我们就只看到能不能调用到端口

@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private String ServerPort;

@RequestMapping("/zk")
public String paymentzk(){
//查看sever.port 和 他的流水号
return "Springcloud with zookeeper:"+ServerPort+"\t"+ UUID.randomUUID().toString();
}
}

此时我们会有启动报错,那么很可能就是环境的错误

此时我们会有jar包的冲突

解决方案

<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>

或者直接使用我们的高版本的zookeeper大于我们的jar包导入的zookeeper就没问题了

如果还报错,可能是Sl4j中的Llog4j12冲突,我们排除了就好了

测试

此时我们在虚拟机上get UUID,获取到那一大串的json

然后我们在用在线测试工具:tool.Iu/json 进行测试,那么测试成功

zookeeper是个临时结点 -e

三、consumer--80端口

配置

yml

server:
port: 80
spring:
application:
name: cloud-consumer-order
cloud:
# 注册地址
zookeeper:
connect-string: localhost:2181

pom.xml

<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

config

@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //自负加载
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

controller

@RequestMapping("/consumer")
public class OrderServerController {
@Resource
private RestTemplate restTemplate;

public static final String INVOKE_URL="http://cloud-provide-payment";
@GetMapping("/zk")
public String paymentInfo(){
//调用我们的8004端口进行连接
String result=restTemplate.getForObject(INVOKE_URL+"/payment/zk",String.class);
return result;
}
}

Consul

SpringCloud基础篇_maven

Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。

提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案

它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows

Consul下载连接:​​http://www.consul.io/downloads.html​​

spring的官方学习网:​​https://www.springcloud.cc/spring-cloud-consul.html​​

但是出现了小插曲:国内禁止使用。--->自学请看上面的官方学习文档。

#

Ribbon——服务调用

一、Ribbon的基础只是

Ribbon: 负载均衡服务调用+RestTemplate

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法

Ribbon是本地负载均衡,Nginx是服务端的负载均衡

Ribbon在工作时分成两步

第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.

第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权

配置xml

但是我们并不需要引入Rabbin,因为我们的Erueka已经引入了这个

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

springcloud 2020.0.1版本的同学 spring cloud删除了eureka中自带的ribbon搞的自带的LoadBalancer,LoadBalancer做自定义负载均衡和负载规则切换看这里 : ​​https://blog.csdn.net/qq_35799668/article/details/114534023​​

此时我们的controller的80端口调用,就存在两类方法

第一类方法/get

getForEntity方法

//返回的是Response对象,返回了了一些重要的东西
@GetMapping("/payment/getgetForEntity/{id}")
public CommonResult<Payment> getpaymentbyid2(@PathVariable("id") Long id){
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get"+id,CommonResult.class);
//获取详细信息
if (entity.getStatusCode().is2xxSuccessful()){
//返回请求体
return entity.getBody();
}
else{
return new CommonResult<>(444,"事物操作失败");
}
}

getForObject方法

//这是原来的,返回的是为json对象 
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getpaymentbyid(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get"+id, CommonResult.class);
}

第二类方法/post

postForObject

@GetMapping("/payment/create")
public CommonResult<Payment> create (Payment paymeny){
// 返回请求
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",paymeny, CommonResult.class);
}

二、Ribbion负载均衡演示

Ribbon自带的负载均衡,默认为轮询

官方文档明确给出了警告:

这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,

否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了

三、Ribbion核心组件IRule

这个时候我们看我们@SpringbootAppilcaion,发现里面就是带有@ComponentScan,说明我们一旦直接配置,那么所有的就会共享

此时,我们要新建我们自己的规则包

我们在我们启动类之外的包结构构件我们自己的ruler包

负载均衡替换

public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();//把轮询修改为随机
}
}

主启动类中添加@RibbonClient : 告诉集群,启用那个服务

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",cnotallow= MySelfRule.class)

四、Ribbion负载均衡算法

负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。

List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");

如: List [0] instances = 127.0.0.1:8002

   List [1] instances = 127.0.0.1:8001

8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:

当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001

当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002

当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001

当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002

如此类推......

OpenFeign

一、OpenFeign简介

官网解释:​​https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign​​

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。

它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

Feign能干什么:

Feign旨在使编写Java Http客户端变得更容易。

前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon:

利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用

简而言之:OpenFeign就是个服务端接口,让我们的编程更加面向对象,Feign接口面向我们的服务接口绑定。

原本我们的服务端是用的RestTemp+Ribbon进行的调用,现在因为OpenFegin继承了之前的用法,我们就只是用这个就Ok.

二、OpenFegin的使用

新建我们的服务端接口 cloud-consumer-fegin-order80

Pom.xml

此时我们的集群服务器还是用的Eureka

<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>org.example</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

YML

server:
port: 8000

eureka:
client:
# 表示没有配置进我们的Erueka中
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

主启动类

@EnableFeignClients 表示进行注册

@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80
{
public static void main(String[] args)
{
SpringApplication.run(OrderFeignMain80.class,args);
}
}

Service层

添加@FeignClient(value = "CLOUD-PAYMENT-SERVICE")

表示把这个进行调用地址,开始使用

//表示bean的注入
@Component
// Fegin调用地址,进行使用
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
//作为主键的接口,我们查询8001对应的接口
public interface PaymentFeginService {
@GetMapping ("/payment/create")
CommonResult create(@RequestBody Payment payment);

@GetMapping("/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);

}

controller层

@RestController
@RequestMapping("/consumer")
public class OrderFeginController {
@Resource
private PaymentFeginService paymentFeginService;

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

@GetMapping("/payment/create")
CommonResult create(@RequestBody Payment payment){
return paymentFeginService.create(payment);
}
}

这样就代表这我们通过客服端,通过FeiginClient的地址,找到其中对外调用的地址

三、OpenFegin超时处理

模拟停止,让其超时

@GetMapping("/timeout")
public String timeout(){
try {
TimeUnit.SECONDS.sleep(3);
}
catch (InterruptedException e){
e.printStackTrace();
}
return serverPort;
}

通过Fegin来调用

service

@GetMapping("timeout")
CommonResult timeout();

contrller

@GetMapping("timeout")
public CommonResult timeout(){
return paymentFeginService.timeout();
}

因为我们的OpenFegin默认等待时间是1s,我们有时业务量太大,或者可能需要超过1s。

我们需要进行Yml的配置

server:
port: 8000

eureka:
client:
# 表示没有配置进我们的Erueka中
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

四、OpenFegin日志增强

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。

说白了就是对Feign接口的调用情况进行监控和输出

日志级别

NONE:默认的,不显示任何日志;

BASIC:仅记录请求方法、URL、响应状态码及执行时间;

HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;

FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

配置config

表示开启了FULL级别(详细)的日志

@Configuration
public class FeginConfig {
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}

配置yml

logging:
level:
# feign日志以什么级别监控哪个接口,用日志进行打印
com.lyd.springcloud.service.PaymentFeginService: debug

Hystrix ——服务降级

虽然已经停止更新了,但是这个东西非常的强大,必须理解

一、 什么是Hystrix

是一个处理分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖不可避免的会调失败,比如超时之类的。Hystrix就是保证了我们一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性

分布式面临的问题:

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩

是一个处理分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖不可避免的会调失败,比如超时之类的。Hystrix就是保证了我们一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性

二、Hystrix的重要概念

1、服务降级:fallback

当服务器出现:程序运行异常,超时,服务熔断,线程池/信号量打满,会给出一个提示 (fallback)

2、服务熔断:break

类比保险丝达到最大值时候,直接拒绝访问,然后调用服务降级方法并且返回友好提示

先熔断,然后降级,最后恢复链路

3、服务限流:flowlimit

秒杀高并发的工作,严禁一窝蜂的拥挤,要么排队,要么杀死

三、Hystrix的案例

新建Module: cloud-provider-hystrix-payment8001

pom.xml

<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<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><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>org.example</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
#是否从其他 eureka-server获取注册信息
fetch-registry: true
service-url:
defaultZone: http://erueka7001.com:7001/eureka #单机版
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

主启动类

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentHystrixMain {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain.class,args);
}
}

Servies

@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentId"+id +"\t"+"O(∩_∩)O哈哈~";
}
public String timeOut(Integer id){
try {
TimeUnit.SECONDS.sleep(3);
}
catch (InterruptedException e){
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+"paymentId"+id +"\t"+"O(∩_∩)O哈哈~"+"用了3s";
}
}

controller

@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService;

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

@GetMapping("/hystrix/info/{id}")
public String paymentInfo_ok(@PathVariable("id") Integer id){
String s = paymentService.paymentInfo_OK(id);
log.info(s);
return s;
}
@GetMapping("/hystrix/timeout/{id}")
public String timeout(@PathVariable("id") Integer id){
String timeOut = paymentService.timeOut(id);
log.info(timeOut);
return timeOut;
}
}

此时我们的controllr都可以运行,timeout的页面要运行3s才进行跳转

因为我们有且仅有两个线程,不会发生熔断,我们此时在使用 jmeter 进行压力测试

我们开启了2w个线程请求,此时我们访问我们的Info 此时也会被托慢了。

结论:此时我们的线程已经把tomecat占满了,如果有80端口的消费者来访问,就只能等待,最终导致消费者不满意

客服端

使用OPenFegin的客户端

<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>org.example</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Yml

server:
port: 80

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/

servies

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

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

controller

@RestController
@RequestMapping("/consumer")
public class PaymentHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;

@GetMapping("/hytrix/info/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}

@GetMapping("/hytrix/timeOut/{id}")
public String timeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
}

通过80访问:此时我们就要开始等待了

或者错误

四、服务降级

降级配置:@HystrixCommand

8001端口:

我们首先设置自身调用业务时间的峰值,峰值内为正常的,峰值外为异常的,一旦超过就需要我们的降级处理 fallback 进行兜底

此时@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler")意思就是如果下面的运行失败,就进行paymentInfo_TimeOutHandler方法,

commandProperties是一个数组,其中@HystrixProperty表示这是超时的时间,在多少以内为正常

启动类:加上@EnableCircuitBreaker 表示熔断的意思

此时我们故意制造错误

上图故意制造两个异常:

1 int age = 10/0; 计算异常

2 我们能接受3秒钟,它运行5秒钟,超时异常。

当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_TimeOutHandler

80客户端

yml开启服务熔断的支持

feign:
hystrix:
enabled: true

此时我们同理需要在80写一个和8001一样的兜底的方法,和配置@HystrixCommand 和 @HystrixProperty

@RestController
@RequestMapping("/consumer")
public class PaymentHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;

@GetMapping("/hytrix/info/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}

@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
{
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
}

这里的客户端只等待1.5s小于我们服务端的3s,此时我们客户端的 paymentTimeOutFallbackMethod 方法进行兜底

启动类上加上:@EnableHystrix

@EnableFeignClients
@SpringBootApplication
@EnableHystrix
public class Order80Main {
public static void main(String[] args) {
SpringApplication.run(Order80Main.class,args);
}
}

代码问题

首先现在一个方法对应一个fallback,此时我们的代码就会像山一样裂开

统一配置和专属配置:@DefaultProperties(defaultFallback="")

方法可以设置一个统一的fallback,专属的设置专属的fallback

首先在8001端口添加一个payment_Global_FallbackMetthod 方法:

//全局兜底方法
public String payment_Global_FallbackMetthod(){
return "Global的fallback信息";
}

然后在controller层配置注解:表示全局返回这个payment_Global_FallbackMetthod 方法

@DefaultProperties(defaultFallback="payment_Global_FallbackMetthod")

之前我们配置的@HystrixCommand()就会及逆行冲突,我们先注释掉,这样我们就配置了全局方法

Hystrix的FeginFallback

我们配置完了8001端口,此时我们需要配置80端口,防止我们的客户端出现问题

此时,我们只需要针对我们的FeginClient接口添加统一的服务降级类就可以了:PaymentHystrixService添加

重写一个类,继承我们的接口:相等于我们的接口服务出现了错误,我们就使用我们的fallback类

PaymentFallbackService 类继承我们的 PaymentHystrixService接口。

@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "paymentInfo_OK 返回异常o(╥﹏╥)o";
}

@Override
public String paymentInfo_TimeOut(Integer id) {
return "paymentInfo_TimeOut 返回异常o(╥﹏╥)o";
}
}

然后把这个类,返回给我们的FeginClient中

@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)

五。服务熔断

熔断机制注解还是@HystrixCommand()

这是,我们开始我们的配置

service

涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。

1:快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。

2:请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。

3:错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开

@HystrixCommand(fallbackMethod = " payment123",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启熔断
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失败达到多少后,进行熔断
})
// 服务熔断
public String payment123(@PathVariable("id") Integer id){
if (id <0){
throw new RuntimeException("id不能小于0");
}
String simpleUUID = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功。流水号是:"+simpleUUID;
}
public String payment123_fallback(@PathVariable("id") Integer id){
return "方法调用错误";
}
}

controller

//    服务熔断
@GetMapping("/hystrix/circuit/{id}")
public String payment123(@PathVariable("id") Integer id){
String payment123 = paymentService.payment123(id);
log.info("result:"+payment123);
return payment123;
}

测试

此时,如果是正数,我们就可以返回我们的id和Hutool的流水号订单。

如果是负数,我们就会熔断,然后我们就开始我们的fallbac

如果我们的此时如果失败太多了,达到了我们设置的错误比例上限,我们如果在进行输入正确的,但是我们不会马上成功

小总结

熔断是半开状态进行的回复,所以分为我们的三个状态:开,关,半开 三种状态

开启或者关闭条件:

1、当满足一定条件的时候(默认10s内超过20个请求)

2、当失败率达到了一定的时候(默认设置为10s内超过50%的请求失败)

3、达到了以上阀值的时候,断路就会开启

4、当开启的时候,所有的请求都不会进行转发

5、一段时间后(默认是5s),这个时候的路由器是半开启的状态,会让其中的一个进行转发,、

如果成功,那么熔断会关闭,如果失败,就会继续开启熔断,并且进行4和5的自旋。

段落器打开之后

1:再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。

2:原来的主逻辑要如何恢复呢?

对于这一问题,hystrix也为我们实现了自动恢复功能。

当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,

当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,

主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

ALL

@HystrixCommand(fallbackMethod = "str_fallbackMethod",
groupKey = "strGroupCommand",
commandKey = "strCommand",
threadPoolKey = "strThreadPool",

commandProperties = {
// 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
// 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
// circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
// 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
// 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
// 如果成功就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
// 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
// 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
// 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
// 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
// 否则将使用 LinkedBlockingQueue 实现的队列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
// 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
}
)
public String strConsumer() {
return "hello 2020";
}
public String str_fallbackMethod()
{
return "*****fall back str_fallbackMethod";
}

Hystrix的限流我们就放在alibaba的Sentinel中说明

六、工作流程

蓝色为调用路径,红色为返回路径

七、HystrixDashboard

概素:

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。

仪表盘

我们自己的监控平台

我们创建新的module:cloud-consumer-hystrix-dashboard9001

pom,.xml

<?xml versinotallow="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:schemaLocatinotallow="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>com.lyd.springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

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

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

yml

server:
port: 9001

此时我们要开启我们的 HystrixDashboard ,所以我们要在启动类上加上:@EnableHystrixDashboard

@SpringBootApplication
@EnableHystrixDashboard
public class Dashboard9001Main {
public static void main(String[] args) {
SpringApplication.run(Dashboard9001Main.class,args);
}
}

此时我们之前所有 8001端口 pom.xml中要进行配置:

<!--web-->
<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>

此时启动我们的9001:localhost:9001/hystrix 这样就是我们图形化的配置成功了

注意:

此时我们Springcloud升级,我们要对我们自己的启动类进行升级,否则会出现 无法连接

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class PaymentHystrixMain {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain.class,args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}

测试

此时我们调用我们上面配置的服务熔断的两个地址:31和-31

我们再返回我们的web监控:

我们点的越多,此时的波越高,反之成立。并且我们的Cricuit表示是关闭的

那么怎么看这个图呢:

实现多服务的监控

GateWay--新一代网关

一、概素简介

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和 Project Reactor等技术。

Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等

SpringCloud Gateway 是 Spring Cloud 的一个全新项目,基于 Spring 5.0+Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

特性

Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

Spring Cloud Gateway 具有如下特性:

基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;

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

可以对路由指定 Predicate(断言)和 Filter(过滤器);

集成Hystrix的断路器功能;

集成 Spring Cloud 服务发现功能;

易于编写的 Predicate(断言)和 Filter(过滤器);

请求限流功能;

支持路径重写

二、三大核心

Route:路由 :是由构建网关的基本模块,它由于ID,目标URI,一系列的断言和滤器组成。如果断言为true 那么则匹配该路由

Predicate:断言:开发人员可以匹配HTTP请求中的所有内容,如果请求和断言匹配的话,进行路由

Filter:过滤:请求前或者请求后进行修改

三、工作流程

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。

Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

四。网关的配置

创建modle: cloud-gateway-gateway9527

pom.xml

<?xml versinotallow="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:schemaLocatinotallow="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>com.lyd.springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

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

<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>org.example</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--一般基础配置类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>

因为网关也是我们的服配置,我们也需要进行配置

yml

我们对之前的8001的方法进行配置,我们要把8001端口进行配置进9527

server:
port: 9527

spring:
application:
name: cloud-gateway
cloud:
gateway:
routes #此时我们可以配置多个路由:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由 ** 表示可能后面有Id等东西

- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka #单机版
defaultzone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

因为是网关,所以没有业务类

启动类

@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class,args);
}
}

此时启动中、存在异常:因为我们的Pom.xml中geteway中不需要boot的web和actuator

所以我们需要删除

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

此时我们启动7001 8001 9527

7001:payment中只有一个8001 consumer中有80 和9527

此时我们通过:​​http://localhost:9527/payment/get/31​​ 也能对我们的8001端口进行访问了

同时我们如果觉得我们的yml配置过于繁琐,我们可以通过编码方式进行配置

案例2

我们通过9527配置我们访问到外网

*配置了一个idroute-name的路由规则, 当访问地址​​http://localhost:9527/guonei​​时会自动转发到地址:​http://news.baidu.com/guonei​

创建我们的config类

@Configuration
//我们要先转发路由,RouteLocator:路由构建器
public class GateWayConfig {
@Bean
public RouteLocator CustomRouteLocator (RouteLocatorBuilder routeLocatorBuilder){
// 此处对应我们的编码中的 cloud.gateway.routes
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
// 访问我们的 http://news.baidu.guonei
// 我们网关的名字+访问的路径+实际路径+构建
routes.route("paht_route_lyd",r->r.path("/guonei")
.uri("http://news.baidu.guonei")).build();
return routes.build();
}

配置去 国外的:

public RouteLocator guowaiRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_rout_guowai",r->r.path("g/uowai")
.uri("http://news.baidu.guowai")).build();
return routes.build();
}

五、微服务的静动态路由

原本我们客户端口是通过Ribbon访问服务端口,但是现在出现了gateway网关,

我们就同过客户端访问我们的网关,通过网关来访问我们的客户端

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

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

Pom.xml 加入

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

yml

开启我们的动态路由,利用微服务进行路由,并且把我们写死的地址,换成提供微服务的路由地址

server:
port: 9527

spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service # 匹配原始的注册中心的地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由

- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service # 匹配原始的注册中心的地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka #单机版
defaultzone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

此时我们就是通过erueka中的注册中心,通过Application中的名字,来找到对应的服务器

此时我们访问 :​​http://localhost:9527/payment/lb​​ 不停的刷新就可以看到不同的服务器的切换

六、Predicate断言

启动我们的Gateway时,我们的idea后台会有:

我们yml中的配置就是使用的- Path=

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。

Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合

Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。

所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

常用的Predicate

1、时间级别:

After , Before, Between

如果我们配置时间,我们默认的配置的是美国时间,所以又需要一个配置类进行配置

yml配置After

cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service # 匹配原始的注册中心的地址
predicates:
- After=2017-01-20T17:42:47.789-07:00(American/Denver)

但是我们此时的 After是个时间串,所以我们要获取我们的时间点

import org.junit.Test;

import java.time.ZonedDateTime;
import java.time.ZoneId;


public class ZonedDateTimeDemo {
@Test
public static void main(String[] args) {
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
System.out.println(zbj);
// ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
// System.out.println(zny);
}
}

此时我们就获取了上海时间:2022-11-10T10:35:06.593+08:00[Asia/Shanghai]

此时我们在通过我们获取的时间对之前的After进行覆盖

这样我们这时候就可以开启我们自己的时间启动

要设置在那个时间段开启,我们就使用between:时间串就使用逗号分隔开

2、cookie级别

需要连个参数,一个是cookie name 一个是正则表达式。如果匹配上,那么就执行人,如果没有匹配上则不执行

- Cookie=username,zzyy

此时我们访问的时候如果不带cookie,那么就会拒绝访问

cur ​​http://localhost:9527/payment/lb​​ --cookie "username=zzyy"

3、Header级别

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

curl ​​http://localhost:9527/payment/lb​​ -H "X-Request-Id:123"

如果请求负数,那么就会报错404,因为我们的url为true

4、Host级别

Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。

它通过参数中的主机地址作为匹配规则。

- Host=**.lyd.com

curl ​​http://localhost:9527/payment/lb​​ -H "Host: ​​www.lyd.com​​"

curl ​​http://localhost:9527/payment/lb​​ -H "Host: java.lyd.com"

5、Method级别

此处就是我们的请求方式的限制

- Method=GET #只能通过GET方法进行访问

6、Path级别

此处就是通过我们的路径进行访问

- Path=/payment/lb/**         # 断言,路径相匹配的进行路由

7、Query级别

支持传入两个参数,一个是属性名,一个为属性值,属性值可以是正则表达式。

Query=username, \d+  # 要有参数名username并且值还要是整数才能路由

cul ​​http://localhost:9527/payment/lb?username=31​​

总结:

predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
- After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
#- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
#- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
- Method=GET
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由Jiu

就是我们的请求,就是通过我们自己的匹配规则,让请求过来找对应的Route进行处理

七、Filter --过滤器

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

Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

此时我们的官网给的配置

GateWayFilter 存在31钟:​​https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-addrequestparameter-gatewayfilter-factory​​

全局GlobalFilter也存在10多种

重点常用的是我们自己的自定义的全局配置

全局自定义拦截器

yml

routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service # 匹配原始的注册中心的地址
filters:
- AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
predicates:
- After=2022-11-10T10:35:06.593+08:00[Asia/Shanghai]
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- Cookie= username,zzyy

然后配置我们自己的fiter拦截器:

总的全局过滤器:

@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter,Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 返回的用户名是在内的就运行
log.info("*****come in myLogGateFilter:"+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname==null){
log.info("用户名为null");
// 回应:Http 没有被接受为非法用户
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
// 退出
return exchange.getResponse().setComplete();
}
// 合法用户就直接下一个,然后进行下一个链
return chain.filter(exchange);
}

@Override
public int getOrder() {
// 加载过滤器的序列越小,越先加载
return 0;
}
}

测试:只要umane中带有东西,此时就可以访问。但是如果什么都没有那么就会拒接访问

分布式配置中心

config-分布式配置

一。什么是配置中心

分布式系统面临的,我们的每个微服务都需要自己的配置才能运行,所以必须有一套集中的,动态的配置管理是必不可上的

所以Springcloud提供了:ConfigServer来解决这个问题,其中自带的application.yml

是什么:

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

怎么玩:

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

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

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

1、可以集中化的个管理文件。

2、不同环境配,动态化的配置更新,分布式的部署环境部署

3、运行期间动态化的调整配置,不同的需要在每个不同的服务部署的机器上编写文件。服务会向分配中心统一拉取自己的配置

4、当配置发生变化时,服务不需要重新启动即可感受到配置的变化并且应用新的配置、

5、将配置信息按照Rest接口进行暴露

二、配置与测试

流程:

此时我们在gitee上 克隆一个项目到我们的文件中 git clone 你的仓库地址

然后在这个仓库中添加表示多个环境配置的文件,记得使用UTF-8

然后使用 git add . git commit -m "inti yml" git push orgin master 把文件传送上去

创建我们的配置中心文件:

服务端

pom.xml

<?xml versinotallow="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:schemaLocatinotallow="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>com.lyd.springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

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

<dependencies>
<!--这就是我们的config配置中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>

yml

server:
port: 3344

spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: git@github.com:/lyd_java/com.lyd.work #GitHub上面的git仓库名字

####搜索目录
search-paths:
- 仓库名字
####读取分支
label: master

#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka

主启动类

@EnableConfigServer
@SpringBootApplication
public class ConfigCenterMain {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain.class,args);
}
}

此时访问我们不同的分支,我们就可以得到不同的配置,如果不写我们的lable(分支号)那么就默认为master分支

客服端

cloud-config-center-3355

pom.xml:因为是我们的客户端,所以就配置的为

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

此时我们使用 bootstrap.yml:是系统级别的,优先级更高

pring Cloud会创建一个“Bootstrap Context”,作为Spring应用的​​Application Context​​的父上下文。初始化的时候,​​Bootstrap Context​​负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的​​Environment​​。

​Bootstrap​​属性有高优先级,默认情况下,它们不会被本地配置覆盖。 ​​Bootstrap context​​和​​Application Context​​有着不同的约定,所以新增了一个​​bootstrap.yml​​文件,保证​​Bootstrap Context​​和​​Application Context​​配置的分离。

要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,

因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.ym

bootstrap.yml

server:
port: 3355

spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址

#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka

看图说明

因为是服务端,就使用的Erueka 所以启动类上应该是@EnableEurekaClient

@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class,args);
}
}

业务类:就是获取我们之前读取道德config:info

此时我们进行3344的自测 和3355 的测试:能不能通过3344得到3355的信息

三、动态配置

修改我们的3355端口配置

1、引入我们的图形化监控,让我们被修改后可以被别人监控到

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2、修改 yml,暴露监控端口。

# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"

3、在业务类中加上刷新的业务标签

@RefreshScope : 让他具备刷新的能力

问题来了,此时我们并没有可以刷新就获取我们的内容

我们需要外部进行刷新,发一个post请求: -X POST "localhost:3355/actuator/refresh"

这样我们的3355就刷新了

此时我们又出现了问题,我们需要运维工程师发post请求,假设有很多请求怎么办?

我们需要配置一个广播的发送请求。所以我们出现了广播——Bus

Bus-消息总线

是对分布式的加深和补充,支持两种消息代理:RabiitMQ 和 Kafka

什么是总线

在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。

基本原理

ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。

此处使用的是RibbMQ,首先要安装这个

一、动态全局广播

按照创建的cloud-config-client-3355 再次创建一个3366

此处的pom.xml和哦我们的3355是一样的

yml

spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址

#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka

# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"

controller

@RestController
@RefreshScope
public class ConfigClientController
{
@Value("${server.port}")
private String serverPort;

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

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

}

此时有两种设计思想:

1、Bus直接告知ConfigServer 通过它来全局广播 —— 触发服务端ConfigServer的/bus/refresh端点,而刷新所有的配置 (推荐)

2、Bus直接自己发POST请求 ——触发客服端/bus/refrresh

所以我们通过3344的配置中心服务端添加消息总线支持

服务端

pom.xml中添加

<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

yml中添加RIbbitMQ的配置

##rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
# 和bus的刷新相关
include: 'bus-refresh'

客户端

3355 和 3366 客服端的配置

pom,xml

<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

yml 中添加rabiitmq的支持

spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest

二、动态刷新的定点刷新

我们这时候又希望可以存在差异化的刷新

eg: 只通知3355,不通知3366

公式:lcoalhost:3344/actuator/bus-refresh/{destination}

总结


举报

相关推荐

0 条评论