springcloudalibaba项目使用情况

cloud微服务开发手册

这个是自己给公司做的一套微服务框架,其中部分的组件开发手册

1.后端结构技术选型

技术说明官网地址
Spring Boot容器+MVC框架https//spring.io/projects/spring-boot
Spring Security认证和授权框架https//spring.io/projects/spring-security
MyBatis-PlusORM框架https://mp.baomidou.com/gu
MyBatisPlusGenerator数据层代码生成https://mp.baomidou.com/guide/generator.html#%E4%BD%BF%E7%94%A8%E6%95%
PageHelperMyBatis物理分页插件http//git.oschina.net/free/Mybatis_PageHelper
Swagger-UI (Knife4j)文档生产工具https://doc.xiaominfo.com/knife4j/documentation/
RabbitMq消息队列https//www.rabbitmq.com/
Redis分布式缓存https//redis.io/
MongoDbNoSql数据库https//www.mongodb.com/
Lombok简化对象封装工具https//github.com/rzwitserloot/lombo
Seata全局事务管理框架https//github.com/seata/seata
JWTJWT登录支持https//github.com/jwt
HikariCP数据库连接池https://github.com/brettwooldridge/HikariCP
Docker应用容器引擎https//www.docker.com/
Spring Cloud Alibaba微服务架构解决方案https://spring.io/projects/spring-cloud-alibaba/
Oracle关系数据https://www.oracle.com/database/
mysql关系型数据库https://www.mysql.com/
Nacos服务注册&发现和配置管理https://nacos.io/zh-cn/docs/what-is-nacos.html
Sentines-dashboard流量控制、熔断降级、系统负载保护https://github.com/alibaba/Sentinel/wiki

2.环境依赖

工具版本号下载地址
JDK1.8 (64)https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html
MYSQL8.0https://dev.mysql.com/downloads/
Oracle11ghttps://www.oracle.com/cn/database/technologies/instant-client/downloads.html
RabbitMq3.8.14https://www.rabbitmq.com/download.html
Redis5.0.12https://download.redis.io/releases/redis-5.0.12.tar.gz
MongoDb4.4https://www.mongodb.com/try/download/community
Spring Boot2.2.6.RELEASEhttps://spring.io/projects/spring-boot#learn
Spring Cloud Alibaba2.2.1.RELEASEhttps://spring.io/projects/spring-cloud-alibaba/#learn
Nginxnginx-1.19.10http://nginx.org/download/nginx-1.19.10.tar.gz
Nacos1.3.1https://github.com/alibaba/nacos/releases
Sentines-dashboard1.3.0https://github.com/alibaba/Sentinel/wiki

3.后端开发环境搭建

简易环境搭建流程:

1. 安装IDEA,JDK,Maven.mysql ,oracle等工具,并配置好相关的环境变量
2. 拉取项目导入到导入到idea中
3. 安装好项目需要使用的服务,redis,rabbitmq,nacos,Mognodb等环境
4. 进行项目编译启动。
3.1 Nacos 安装

微服务项目需要使用到注册中心和服务发现组件,我们必须要先安装启动好nacos-serve

下载地址:https://github.com/alibaba/nacos/releases
在这里插入图片描述
1.解压我们的 nacos

在这里插入图片描述

  1. cd 到nacos目录

在这里插入图片描述

  1. 使用 cd conf 进入conf路径下,修改配置, vim application.proproties 修改配置

在这里插入图片描述

数据库修改完成后 需要进行保存。 nacos默认是将数据持久化到本地文件,生产服务建议使用数据库配置。目前nacos只支持mysql数据库。注意如果是集群部署我们要确保每个服务都要链接 master节点。需要在数据库新建一个数据库 将 conf 目录下的 nacos-mysql.sql 导入到数据库

4.启动nacos cd bin 目录下,进入到bin目录下 执行命令(启动单机) sh startup.sh -m standalone

在这里插入图片描述

5.检查nacos启动的端口 lsof -i:8848

在这里插入图片描述

6.浏览器输入:http://192.168.163.129:8848/nacos 默认账号密码 : nacos/nacos

在这里插入图片描述

7.停止nocas 在nocas/bin目录下 执行 sh shutdown.sh

3.1.1 Window版本

在这里插入图片描述

其他操作修改配置同理 linux步骤 ,修改成功后 ,启动nacos,进入 bin 目录下,单机启动可以将startup.cmd文件的启动模式修改为单机模式:

在这里插入图片描述

保存 ,直接运行 startup.cmd 启动nacos

3.2 :Nacos client客户端的搭建
  1. 三板斧之一,依赖引入

    <!--注册中心客户端-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
  2. 三板斧之二: 第二板斧写注解(也可以不写) @EnableDiscoveryClient

    @SpringBootApplication
    @EnableDiscoveryClient
    public class CloudAdminApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(CloudAdminApplication.class, args);
    	}
    
    }
    
  3. 三板斧之三 :写配置文件 注意server-addr:不需要写协议

spring:
  application:
    name: @artifactId@
  cloud:
    nacos:
      config:
        ## 服务地址
        server-addr: 127.0.0.1:8848
        ## 命名空间 nacos的领域模型,可以设置不同的空间 进行配置隔离,NameSpace (默认的NameSpace是”public“ NameSpace可以进行资源隔离,比如我们dev环境下的NameSpace下的服务是调用不到prod的NameSpace下的微服务。我们可以配置好不同开发环境的配置,上线的时候只要切换对应的 NameSpace即可
        namespace: 4c203b07-7179-4712-90c1-f0710ef11f5c
  1. 启动 cloud-admin-biz 项目

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b5pnszmo-1621556997409)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210429142551163.png)]

3.3 Nacos 领域模型划分

在这里插入图片描述

NameSpace(默认的NameSpace是”public“ NameSpace可以进行资源隔离,比如我们dev环境下的NameSpace下的服务是调用不到prod的NameSpace下的微服务)
比如:  cloud-admin-biz 的配置文件中配置了注册中心namespace 地址:只会注册到对应的 namespace
server:
  port: 7020
spring:
  application:
    name: cloud-admin-biz
  cloud:
    nacos:
      discovery:
        namespace: 4c203b07-7179-4712-90c1-f0710ef11f5c  命名空间与管理页面中的一致
        server-addr: 127.0.0.1:8848

在这里插入图片描述

3.4 配置中心 动态刷新

随着服务越来越多,配置参数也越来越多。有些配置是公共的,无需在每个配置文件中配置。为了统一管理配置,和动态的修改一个业务参数。在码云使用配置中心之前,维护起来都会很复杂。

微服务接入配置中心的步骤:

1.添加依赖包spring-cloud-alibaba-nacos-config

 <dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring‐cloud‐alibaba‐nacos‐config</artifactId>
 </dependency>

2.编写配置文件,需要写一个bootstrap.yml配置文件 (

[^]:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kQDjIhXM-1621556997415)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210429155248807.png)])

server-addr: 127.0.0.1:8848  表示我微服务怎么去找我的配置中心
spring.application.name=cloud-admin-biz  表示当前微服务需要向配置中心索要cloud-admin-biz
的配置
spring.profiles.active=dev 表示我需要向配置中心索要cloud-admin-biz的生产环境的配置
   索要文件的格式为(data-id):
    ${application.name}- ${spring.profiles.active}.${file-extension}
   真正在nacos配置中心上 就是    cloud-admin-biz-dev.yml
shared-configs: 共享配置    用来配置公共属性的 必须每个项目需要连接redis ,mysql 我们可以吧这两部分做一个公共的文件可以进行全局使用 
   refresh-enabled :支持动态刷新
   
配置如下:     
server:
port: 7020

spring:
application:
  name: cloud-admin-biz
cloud:
  nacos:
    config:
      server-addr: 127.0.0.1:8848
      file-extension: yml
      shared-configs:
        - application-dev.yml
      namespace: 4c203b07-7179-4712-90c1-f0710ef11f5c
      refresh-enabled: true
    discovery:
      namespace: 4c203b07-7179-4712-90c1-f0710ef11f5c
      server-addr: 127.0.0.1:8848
profiles:
  active: dev  

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CnK1HD61-1621556997419)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210429155126435.png)]

启动项目,nacos 会自动的从配置中心去获取配置信息,本地就无需在进行配置了。

  1. 动态刷新

    1. 在我的cloud-admin-biz-dev.yml 配置文件中加入配置:
    
    edwin:
      text: yulang 
      
    2.在项目中需要编写:
    
    @RestController
    @RequestMapping("/index")
    @RefreshScope   //这个必须要写 动态刷新的注解
    public class DemoController {
    
    	@Value("${edwin.text}")
    	private String userName;
    
    
    	@GetMapping("/test")
    	public Object test(){
    		return userName;
    	}
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kak9jmFV-1621556997420)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210429162614172.png)]

直接在配置文件中修改:

edwin:
 text: yulang11111111

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QpGdLLyi-1621556997422)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210429162723485.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XsvsmcdP-1621556997423)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210429162751710.png)]

4.项目目录

D:.
├─cloud    项目父目录
├─cloud-admin  admin 平台服务
│  ├─cloud-admin-api 平台api
│  │  ├─src
│  │  │  └─main
│  │  │      └─java
│  │  │          └─com
│  │  │              └─cloud
│  │  │                  └─admin
│  │  │                      └─api
│  │  │                          ├─dto
│  │  │                          ├─entity
│  │  │                          ├─feign
│  │  │                          └─vo
│  └─cloud-admin-biz 平台业务服务
│      ├─src
│      │  ├─main
│      │  │  ├─java
│      │  │  │  └─com
│      │  │  │      └─cloud
│      │  │  │          └─admin
│      │  │  │              ├─config
│      │  │  │              ├─controller
│      │  │  │              ├─core
│      │  │  │              ├─handler
│      │  │  │              ├─mapper
│      │  │  │              └─service
│      │  │  │                  └─impl
│      │  │  └─resources 资源信息
│      │  │      ├─mapper
│      │  │      └─tenant
│      │  └─test  测试目录
│      │      └─java
│      │          └─com
│      │              └─cloud
│      │                  └─admin
├─cloud-auth  鉴权服务
│  ├─src
│  │  └─main
│  │      ├─java
│  │      │  └─com
│  │      │      └─cloud
│  │      │          └─auth 
│  │      │              ├─config
│  │      │              ├─endpoint
│  │      │              ├─handler
│  │      │              └─service
│  │      └─resources 资源文件
│  │          ├─static
│  │          │  └─css
│  │          └─templates 页面
│  │              └─ftl
├─cloud-common 项目公共服务
│  ├─cloud-common-bom 公共依赖
│  ├─cloud-common-core 核心依赖 提供了一些高级的java类库,Google Guava 和 hutool工具
│  │  ├─src
│  │  │  └─main
│  │  │      ├─java
│  │  │      │  └─com
│  │  │      │      └─cloud
│  │  │      │          └─common
│  │  │      │              └─core
│  │  │      │                  ├─config
│  │  │      │                  ├─constant
│  │  │      │                  │  └─enums
│  │  │      │                  ├─exception
│  │  │      │                  ├─jackson
│  │  │      │                  ├─sensitive
│  │  │      │                  └─util
│  │  │      └─resources
│  │  │          ├─i18n
│  │  │          └─META-INF
│  ├─cloud-common-data 公共处理数据相关
│  │  ├─src
│  │  │  └─main
│  │  │      ├─java
│  │  │      │  └─com
│  │  │      │      └─cloud
│  │  │      │          └─common
│  │  │      │              └─data
│  │  │      │                  ├─cache
│  │  │      │                  ├─datascope
│  │  │      │                  ├─enums
│  │  │      │                  ├─handler
│  │  │      │                  ├─mybatis
│  │  │      │                  ├─quene
│  │  │      │                  ├─resolver
│  │  │      │                  └─tenant
│  │  │      └─resources
│  │  │          └─META-INF
│  ├─cloud-common-datasource 数据源相关配置
│  │  └─src
│  │      └─main
│  │          ├─java
│  │          │  └─com
│  │          │      └─cloud
│  │          │          └─common
│  │          │              └─datasource
│  │          │                  ├─annotation
│  │          │                  ├─config
│  │          │                  └─support
│  │          └─resources
│  │              └─META-INF
│  ├─cloud-common-gateway 网关相关配置
│  │  ├─src
│  │  │  └─main
│  │  │      └─java
│  │  │          └─com
│  │  │              └─cloud
│  │  │                  └─common
│  │  │                      └─gateway
│  │  │                          ├─annotation
│  │  │                          ├─configuration
│  │  │                          ├─filter
│  │  │                          ├─rule
│  │  │                          ├─support
│  │  │                          └─vo
│  ├─cloud-common-log 日志相关配置
│  │  ├─src
│  │  │  └─main
│  │  │      ├─java
│  │  │      │  └─com
│  │  │      │      └─cloud
│  │  │      │          └─common
│  │  │      │              └─log
│  │  │      │                  ├─annotation
│  │  │      │                  ├─aspect
│  │  │      │                  ├─event
│  │  │      │                  ├─init
│  │  │      │                  └─util
│  │  │      └─resources
│  │  │          └─META-INF
│  ├─cloud-common-minio  文件服务相关
│  │  ├─src
│  │  │  └─main
│  │  │      ├─java
│  │  │      │  └─com
│  │  │      │      └─cloud
│  │  │      │          └─common
│  │  │      │              └─minio
│  │  │      │                  ├─http
│  │  │      │                  ├─service
│  │  │      │                  └─vo
│  │  │      └─resources
│  │  │          └─META-INF
│  ├─cloud-common-security 安全认证相关
│  │  ├─src
│  │  │  └─main
│  │  │      ├─java
│  │  │      │  └─com
│  │  │      │      └─cloud
│  │  │      │          └─common
│  │  │      │              └─security
│  │  │      │                  ├─annotation
│  │  │      │                  ├─component
│  │  │      │                  ├─exception
│  │  │      │                  ├─feign
│  │  │      │                  ├─handler
│  │  │      │                  ├─listener
│  │  │      │                  ├─mobile
│  │  │      │                  ├─service
│  │  │      │                  ├─social
│  │  │      │                  └─util
│  │  │      └─resources
│  │  │          ├─META-INF
│  │  │          └─org
│  │  │              └─springframework
│  │  │                  └─security
│  ├─cloud-common-sentinel 限流熔断相关
│  │  ├─src
│  │  │  └─main
│  │  │      ├─java
│  │  │      │  └─com
│  │  │      │      └─cloud
│  │  │      │          └─common
│  │  │      │              └─sentinel
│  │  │      │                  ├─handle
│  │  │      │                  └─parser
│  │  │      └─resources
│  │  │          └─META-INF
│  ├─cloud-common-sequence 分布式主键相关
│  │  └─src
│  │      └─main
│  │          ├─java
│  │          │  └─com
│  │          │      └─cloud
│  │          │          └─common
│  │          │              └─sequence
│  │          │                  ├─builder
│  │          │                  ├─exception
│  │          │                  ├─properties
│  │          │                  ├─range
│  │          │                  │  └─impl
│  │          │                  │      ├─db
│  │          │                  │      ├─name
│  │          │                  │      └─redis
│  │          │                  └─sequence
│  │          │                      └─impl
│  │          └─resources
│  │              └─META-INF
│  └─cloud-common-swagger api接口文档
│      ├─src
│      │  └─main
│      │      └─java
│      │          └─com
│      │              └─cloud
│      │                  └─common
│      │                      └─swagger
│      │                          ├─annotation
│      │                          └─config

├─cloud-gateway  网关服务,添加了动态路由功能
│  ├─src
│  │  └─main
│  │      ├─java
│  │      │  └─com
│  │      │      └─cloud
│  │      │          └─gateway
│  │      │              ├─config
│  │      │              ├─filter
│  │      │              └─handler
│  │      └─resources
4.1 cloud-admin
cloud-admin 项目为系统中的平台服务,主要分为 cloud-admin-api 和 cloud-admin-biz两个项目组成。

cloud-admin-api: 主要包含一些工程中的实体类(po,vo),服务接口 提供对外暴露的api 

cloud-admin-biz: 主要实现平台的核心业务逻辑,服务接口的实现类,api接口地址服务等

1.cloud-admin-api pom文件结构

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
		 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>
	<parent>
		<groupId>com.cloud</groupId>
		<artifactId>cloud-admin</artifactId>
		<version>1.0.0</version>
	</parent>

	<artifactId>cloud-admin-api</artifactId>
	<packaging>jar</packaging>

    只需要依赖核心公共组件
	<dependencies>
		<!--core 工具类-->
		<dependency>
			<groupId>com.cloud</groupId>
			<artifactId>cloud-common-core</artifactId>
		</dependency>
	</dependencies>
</project>

1.cloud-admin-biz pom文件结构 需要使用的对应功能组件 可以引入 cloud-common包下的对应的jar

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
		 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>
	<parent>
		<groupId>com.cloud</groupId>
		<artifactId>cloud-admin</artifactId>
		<version>1.0.0</version>
	</parent>

	<artifactId>cloud-admin-biz</artifactId>
	<packaging>jar</packaging>

	<description>通用用户权限管理系统业务处理模块</description>

	<dependencies>
		<!--upms api、model 模块-->
		<dependency>
			<groupId>com.cloud</groupId>
			<artifactId>cloud-admin-api</artifactId>
		</dependency>
        
		<!--日志处理-->
		<dependency>
			<groupId>com.cloud</groupId>
			<artifactId>cloud-common-log</artifactId>
		</dependency>
        
		<!--swagger api 相关配置-->
		<dependency>
			<groupId>com.cloud</groupId>
			<artifactId>cloud-common-swagger</artifactId>
		</dependency>
		
		<!--注册中心客户端-->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
		</dependency>
        
		<!--配置中心客户端-->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
		</dependency>
        
		<!--mybatis-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
		</dependency>
        
		<!--数据库-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<!--web 模块-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
        
		<!--undertow容器-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-undertow</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>io.fabric8</groupId>
				<artifactId>docker-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

  1. 配置文件中配置好注册中心地址:

    server:
      port: 7020   ## 服务端口号
    
    spring:
      application:
        name: cloud-admin-biz  ## 应用名称 
      cloud:
        nacos:
        discovery:
            namespace: 4c203b07-7179-4712-90c1-f0710ef11f5c
            server-addr: 127.0.0.1:8848
          config:
            server-addr: 127.0.0.1:8848   ## nacos 服务地址
            file-extension: yml  ## 配置文件扩展名
            shared-configs:  ## 共享资源配置
              - application-dev.yml
            namespace: 4c203b07-7179-4712-90c1-f0710ef11f5c ## 命名空间
      profiles:
        active: dev  # profiles 环境
    
    1. 启动 cloud-admin-biz 和网关项目

      访问接口文档:http://127.0.0.1:7000/doc.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-suthhpcq-1621556997425)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210429163156946.png)]

4.1.1 swagger组件使用:
  1. 项目中需要使用swagger的只需要在对应的项目中引入 :

    <!--swagger api 相关配置-->
    <dependency>
      <groupId>com.cloud</groupId>
      <artifactId>cloud-common-swagger</artifactId>
    </dependency>
    
    无需任何的配置文件 直接在项目中使用原生swagger注解即可使用
    
  2. 在项目中的 controller中编写 swaager注解:

    package com.cloud.admin.controller;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.cloud.admin.api.dto.UserDTO;
    import com.cloud.admin.api.dto.UserInfo;
    import com.cloud.admin.api.entity.SysUser;
    import com.cloud.admin.service.SysUserService;
    import com.cloud.common.core.util.R;
    import com.cloud.common.log.annotation.SysLog;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiOperation;
    import lombok.AllArgsConstructor;
    import org.springframework.web.bind.annotation.*;
    
    import javax.validation.Valid;
    
    @RestController
    @AllArgsConstructor
    @RequestMapping("/user")
    @Api(value = "user", tags = "用户管理模块")
    public class SysUserController {
    	private final SysUserService userService;
    
    	/**
    	 * 获取指定用户全部信息
    	 *
    	 * @return 用户信息
    	 */
    	@GetMapping("/info/{username}")
    	@ApiOperation(value = "获取指定用户全部信息")
    	@ApiImplicitParam(name = "username", value = "用户名", paramType = "String", dataTypeClass = UserInfo.class, required = true)
    	public R info(@PathVariable("username") String username) {
    		SysUser user = userService.getOne(Wrappers.<SysUser>query()
    				.lambda().eq(SysUser::getUsername, username));
    		if (user == null) {
    			return R.failed(null, String.format("用户信息为空 %s", username));
    		}
    		return R.ok(userService.findUserInfo(user));
    	}
    
    	/**
    	 * 通过ID查询用户信息
    	 *
    	 * @param id ID
    	 * @return 用户信息
    	 */
    	@GetMapping("/{id}")
    	@ApiOperation(value = "通过ID查询用户信息")
    	@ApiImplicitParam(name = "id", value = "用户ID", paramType = "String", required = true)
    	public R user(@PathVariable Integer id) {
    		return R.ok(userService.selectUserVoById(id));
    	}
    
    	/**
    	 * 根据用户名查询用户信息
    	 *
    	 * @param username 用户名
    	 * @return
    	 */
    	@GetMapping("/details/{username}")
    	@ApiOperation(value = "根据用户名查询用户信息")
    	@ApiImplicitParam(name = "username", value = "用户名", paramType = "String", required = true)
    	public R user(@PathVariable String username) {
    		SysUser condition = new SysUser();
    		condition.setUsername(username);
    		return R.ok(userService.getOne(new QueryWrapper<>(condition)));
    	}
    
    	/**
    	 * 删除用户信息
    	 *
    	 * @param id ID
    	 * @return R
    	 */
    	@SysLog("删除用户信息")
    	@DeleteMapping("/{id}")
    	@ApiOperation(value = "删除用户", notes = "根据ID删除用户")
    	@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "int", paramType = "path")
    	public R userDel(@PathVariable Integer id) {
    		SysUser sysUser = userService.getById(id);
    		return R.ok(userService.deleteUserById(sysUser));
    	}
    
    	/**
    	 * 添加用户
    	 *
    	 * @param userDto 用户信息
    	 * @return success/false
    	 */
    	@SysLog("添加用户")
    	@PostMapping
    	@ApiOperation(value = "删除用户", notes = "根据ID删除用户")
    	public R user(@RequestBody UserDTO userDto) {
    		return R.ok(userService.saveUser(userDto));
    	}
    
    	/**
    	 * 更新用户信息
    	 *
    	 * @param userDto 用户信息
    	 * @return R
    	 */
    	@SysLog("更新用户信息")
    	@PutMapping
    	@ApiOperation(value = "更新用户信息", notes = "根据ID删除用户")
    	public R updateUser(@Valid @RequestBody UserDTO userDto) {
    		return R.ok(userService.updateUser(userDto));
    	}
    
    	/**
    	 * 分页查询用户
    	 *
    	 * @param page    参数集
    	 * @param userDTO 查询参数列表
    	 * @return 用户集合
    	 */
    	@GetMapping("/page")
    	@ApiOperation(value = "分页查询用户", notes = "分页查询用户")
    	public R getUserPage(Page page, UserDTO userDTO) {
    		return R.ok(userService.getUsersWithRolePage(page, userDTO));
    	}
    
    	/**
    	 * 修改个人信息
    	 *
    	 * @param userDto userDto
    	 * @return success/false
    	 */
    	@SysLog("修改个人信息")
    	@PutMapping("/edit")
    	@ApiOperation(value = "修改个人信息", notes = "修改个人信息")
    	public R updateUserInfo(@Valid @RequestBody UserDTO userDto) {
    		return userService.updateUserInfo(userDto);
    	}
    
    	/**
    	 * @param username 用户名称
    	 * @return 上级部门用户列表
    	 */
    	@GetMapping("/ancestor/{username}")
    	@ApiOperation(value = "根据用户名称查询上级部门用户列表", notes = "根据用户名称查询上级部门用户列表")
    	public R listAncestorUsers(@PathVariable String username) {
    		return R.ok(userService.listAncestorUsers(username));
    	}
    }
    
    
4.1.2 cloud-common-core 使用
  1. 引入相对应依赖包:

    <!--core 工具类-->
    <dependency>
        <groupId>com.cloud</groupId>
        <artifactId>cloud-common-core</artifactId>
    </dependency>
    
    java 核心的工具类库 ,可以直接使用操作 java中集合,素组,等数据结构,以及反射,json,序列化,参数校验等功能
    
    
4.1.3 cloud-common-data 使用
  1. 引入相对应依赖包
<!--core 工具类-->
<dependency>
    <groupId>com.cloud</groupId>
    <artifactId>cloud-common-data</artifactId>
</dependency>

项目中封装好了,redisTemplate模板,和redis缓存的工具类。如果业务系统使用到reids缓存 可以直接集成

4.1.4 cloud-common-log 使用

1.引入相对应依赖包

<dependency>
  <groupId>com.cloud</groupId>
  <artifactId>cloud-common-log </artifactId>
</dependency>

1.日志依赖包,引入后,在应用系统中 配置日志模板 logback-spring.xml 配置到具体的项目resources目录下,系统会自动对controller层的日志加入trackId,方便在日志文件中排除错误;

  1. 提供了操作日志的入库操作,需要在使用的方法上 加上 @SysLog 注解,系统会自动将自动会对操作方法的日志进行入库操作。
4.1.5 cloud-common-minio (分布式文件存储)

1.引入相对应依赖包 (需要在需要使用文件上传存储业务中引入)

<dependency>
  <groupId>com.cloud</groupId>
  <artifactId>cloud-common-minio </artifactId>
</dependency>
  1. 启动 minio服务器

    需要先下载好服务端程序:https://dl.min.io/server/minio/release/windows-amd64/minio.exe
    
    启动服务:
     minio.exe server D:\        ## D:\  你启动的一些配置文件生成目录 可以自己制定
    

    启动完成后:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BqfphFYp-1621556997426)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210430103922844.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8z8oWmP8-1621556997428)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210430104019901.png)]

  1. 浏览器 输入 http://127.0.0.1:9000

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P6BT1fAv-1621556997429)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210430104135690.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LzMA7BQS-1621556997432)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210430104319563.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uHVRzvGw-1621556997433)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210430104752895.png)]

4.引入依赖后需要在业务系统中配置

	minio.url = http://127.0.0.1:9000 ## minio 服务地址 http://ip:port

	minio.accessKey =minioadmin   ## minio 用户名

	minio.secretKey =minioadmin   ##minio 密码
	minio.endpoint.enable =true ## 开启服务
  1. 代码中使用:

    @Autowired 
    private MinioTemplate minioTemplate;
    
    
    public void uploadImage(File file){
        // bucketName bucket名称 可以理解为对应的目录名称
        // objectName 你的文件名称
        //InputStream stream 对应文件的二进制流
       minioTemplate.putObject("/TEST","12",new FileInputStream(file));
    }
    
    MinioTemplate 中封装了 如下方法 可以直接调用:
    
    /**
    	 * 创建bucket
    	 *
    	 * @param bucketName bucket名称
    	 */
    	@SneakyThrows
    	public void createBucket(String bucketName) {
    		if (!client.bucketExists(bucketName)) {
    			client.makeBucket(bucketName);
    		}
    	}
    
    	/**
    	 * 获取全部bucket
    	 * <p>
    	 * https://docs.minio.io/cn/java-client-api-reference.html#listBuckets
    	 */
    	@SneakyThrows
    	public List<Bucket> getAllBuckets() {
    		return client.listBuckets();
    	}
    
    	/**
    	 * @param bucketName bucket名称
    	 */
    	@SneakyThrows
    	public Optional<Bucket> getBucket(String bucketName) {
    		return client.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    	}
    
    	/**
    	 * @param bucketName bucket名称
    	 */
    	@SneakyThrows
    	public void removeBucket(String bucketName) {
    		client.removeBucket(bucketName);
    	}
    
    	/**
    	 * 根据文件前置查询文件
    	 *
    	 * @param bucketName bucket名称
    	 * @param prefix     前缀
    	 * @param recursive  是否递归查询
    	 * @return MinioItem 列表
    	 */
    	@SneakyThrows
    	public List<MinioItem> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
    		List<MinioItem> objectList = new ArrayList<>();
    		Iterable<Result<Item>> objectsIterator = client
    				.listObjects(bucketName, prefix, recursive);
    
    		for (Result<Item> itemResult : objectsIterator) {
    			objectList.add(new MinioItem(itemResult.get()));
    		}
    		return objectList;
    	}
    
    	/**
    	 * 获取文件外链
    	 *
    	 * @param bucketName bucket名称
    	 * @param objectName 文件名称
    	 * @param expires    过期时间 <=7
    	 * @return url
    	 */
    	@SneakyThrows
    	public String getObjectURL(String bucketName, String objectName, Integer expires) {
    		return client.presignedGetObject(bucketName, objectName, expires);
    	}
    
    	/**
    	 * 获取文件
    	 *
    	 * @param bucketName bucket名称
    	 * @param objectName 文件名称
    	 * @return 二进制流
    	 */
    	@SneakyThrows
    	public InputStream getObject(String bucketName, String objectName) {
    		return client.getObject(bucketName, objectName);
    	}
    
    	/**
    	 * 上传文件
    	 *
    	 * @param bucketName bucket名称
    	 * @param objectName 文件名称
    	 * @param stream     文件流
    	 * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
    	 */
    	public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {
    		client.putObject(bucketName, objectName, stream, (long) stream.available(), null, null, "application/octet-stream");
    	}
    
    	/**
    	 * 上传文件
    	 *
    	 * @param bucketName  bucket名称
    	 * @param objectName  文件名称
    	 * @param stream      文件流
    	 * @param size        大小
    	 * @param contextType 类型
    	 * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
    	 */
    	public void putObject(String bucketName, String objectName, InputStream stream, long size, String contextType) throws Exception {
    		client.putObject(bucketName, objectName, stream, size, null, null, contextType);
    	}
    
    	/**
    	 * 获取文件信息
    	 *
    	 * @param bucketName bucket名称
    	 * @param objectName 文件名称
    	 * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#statObject
    	 */
    	public ObjectStat getObjectInfo(String bucketName, String objectName) throws Exception {
    		return client.statObject(bucketName, objectName);
    	}
    
    	/**
    	 * 删除文件
    	 *
    	 * @param bucketName bucket名称
    	 * @param objectName 文件名称
    	 * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#removeObject
    	 */
    	public void removeObject(String bucketName, String objectName) throws Exception {
    		client.removeObject(bucketName, objectName);
    	}
    
    
    
    
4.1.6 cloud-common-sequence 分布式序列组件

1.依赖引入

<dependency>
  <groupId>com.cloud</groupId>
  <artifactId>cloud-common-sequence</artifactId>
</dependency>

2.功能介绍

插件包中提供2中生成分布式序列化的规则:
1: SnowflakeSequence  基于雪花算法
2: RangeSequence  基于号段 生成的有序递增的id
   其中号段中 也可以支持两种拨号器 a:基于数据库 b:基于redis      

3.使用,在对应的配置 目前一个只系统支持一种生成方式

1.SnowflakeSequence开启方式:

cloud:
  xsequence:
    snowflake:
      enable: true ##开启 snowflake算法
      datacenterId: 1  ##数据中心ID,值的范围在[0,31]之间,一般可以设置机房的IDC[必选]  每个系统禁止使用一样的配置 默认值为 1
      workerId: 1 ## 工作机器ID,值的范围在[0,31]之间,一般可以设置机器编号[必选]   每个系统禁止使用一样的配置 默认值为 1
 注意:如果使用 snowflake 方式,一定要注意时钟回拨的问题。避免造成id重复
 
 代码中使用:
  	@Autowired
	private Sequence sequence;

	@GetMapping("/test")
	public Object test() {
		return sequence.nextValue();
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQUz1nNg-1621556997435)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210430160127382.png)]

  1. 基于DB号段模式:

    cloud:
      xsequence:
        range:
            enable: db   ## 开启号段模式 db 方式 严格区分大小写
            tableName:  tb_range    ## 表名称 默认 tb_range 表 
            retryTimes: 1     ## 重试次数 默认1次
            step: 1000   ## 获取range步长[可选,默认:1000]
            stepStart: 0 ## 序列号分配起始值[可选:默认:0]
            bizName: cloud ## 业务名称  每个业务对应的名称 禁止中文
    ### 数据源的而配置 可以使用默认的
    spring:
      datasource:
        hikari:
          minimum-idle: 1
          maximum-pool-size: 3
          connection-timeout: 30000
          initialization-fail-timeout: 30000
          idle-timeout: 30000
          max-lifetime: 30000
          connection-test-query: SELECT 1
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: 123456
        url: jdbc:mysql://127.0.0.1:3306/cloud-admin?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true      
    
    
    第一次启动会在对应的数据库中创建一张表 :
    CREATE TABLE `tb_range` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `value` bigint(20) NOT NULL,
      `name` varchar(32) NOT NULL,
      `gmt_create` datetime NOT NULL,
      `gmt_modified` datetime NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_name` (`name`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    
    第一次使用会生成一条区间数据:
    1	1000	cloud	2021-04-30 15:36:56	2021-04-30 15:37:42
    
    注意在使用的时候 不要手动去修改表中数据,避免序列重复。
    

    代码中使用:

      	@Autowired
    	private Sequence sequence;
    
    	@GetMapping("/test")
    	public Object test() {
    		return sequence.nextValue();
    	}
    
    1. 基于redis号段模式:

      cloud:
        xsequence:
          range:
              enable: redis   ## 开启号段模式 redis 方式 严格区分大小写  如果项目中已经引入了redis 则只需开启即可
           
      ##redis配置可以使用默认        
       spring:
        redis:
          host: 192.168.163.130
          port: 6379
          password: admin
          step:1000  ##获取range步长[可选,默认:1000]
          stepStart: 0  ##序列号分配起始值[可选:默认:0]
          bizName: cloud  ##业务名称  建议每个项目配置不同的
          
          
       代码中使用:
        	@Autowired
      	private Sequence sequence;
      
      	@GetMapping("/test")
      	public Object test() {
      		return sequence.nextValue();
      	}   
      	
      	reids中会存储当前 一轮的步长,主要如果项目重启的话,第一轮步长没有使用完,回自动进入一个步长。必须第一次步长 1000,项目中id只是用到了 500,项目重启了,id会从 1001开始
      	
      	redis 中步长key :前缀 + 业务名 (x_sequence_ + 业务名 )
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P1CSLgWn-1621556997437)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210430162557467.png)]

4.1.7 cloud-common-sentinel(容灾限流)

1.依赖引入 (https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D)

<dependency>
  <groupId>com.cloud</groupId>
  <artifactId>cloud-common-sentinel</artifactId>
</dependency>

2.启动服务 ,配置服务监控地址

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.0.jar  也可以直接: java -jar sentinel-dashboard-1.7.0.jar
  
spring:
cloud:
 sentinel:
    transport:
      dashboard: 127.0.0.1:8080    
  1. sentinel 控制台配置项:

    配置项类型默认值最小值描述
    auth.enabledbooleantrue-是否开启登录鉴权,仅用于日常测试,生产上不建议关闭
    sentinel.dashboard.auth.usernameStringsentinel-登录控制台的用户名,默认为 sentinel
    sentinel.dashboard.auth.passwordStringsentinel-登录控制台的密码,默认为 sentinel
    sentinel.dashboard.app.hideAppNoMachineMillisInteger060000是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭
    csp.sentinel.dashboard.serverStringlocalhost:8080指定控制台地址和端口

4.拦截详情日志

无论触发了限流、熔断降级还是系统保护,它们的秒级拦截详情日志都在 ${user_home}/logs/csp/sentinel-block.log里。如果没有发生拦截,则该日志不会出现。日志格式如下:

2014-06-20 16:35:10|1|sayHello(java.lang.String,long),FlowException,default,origin|61,0
2014-06-20 16:35:11|1|sayHello(java.lang.String,long),FlowException,default,origin|1,0

日志含义:

index例子说明
12014-06-20 16:35:10时间戳
21该秒发生的第一个资源
3sayHello(java.lang.String,long)资源名称
4XXXException拦截的原因, 通常 FlowException 代表是被限流规则拦截,DegradeException 则表示被降级,SystemBlockException 则表示被系统保护拦截
5default生效规则的调用来源(参数限流中代表生效的参数)
6origin被拦截资源的调用者,可以为空
761,061 被拦截的数量,0无意义可忽略

秒级监控日志

所有的资源都会产生秒级日志,它在 ${user_home}/logs/csp/${app_name}-${pid}-metrics.log里。格式如下:

1532415661000|2018-07-24 15:01:01|sayHello(java.lang.String)|12|3|4|2|295
  1. 1532415661000:时间戳
  2. 2018-07-24 15:01:01:格式化之后的时间戳
  3. sayHello(java.lang.String):资源名
  4. 12:表示到来的数量,即此刻通过 Sentinel 规则 check 的数量(passed QPS)
  5. 3:实际该资源被拦截的数量(blocked QPS)
  6. 4:每秒结束的资源个数(完成调用),包括正常结束和异常结束的情况(exit QPS)
  7. 2:异常的数量
  8. 295:资源的平均响应时间(RT)

业务日志

其它的日志在 ${user_home}/logs/csp/sentinel-record.log.xxx 里。该日志包含规则的推送、接收、处理等记录,排查问题的时候会非常有帮助。

集群限流日志

  • ${log_dir}/sentinel-cluster-client.log:Token Client 日志,会记录请求失败的信息

访问:http://127.0.0.1:8080/ (用户名/密码: sentinel /sentinel)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5L54tBER-1621556997438)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506110436001.png)]

  1. sentinel监控性能指标详解

    • 实时监控面板

      在这个面板中我们可以实时监控我们接口的**通过QPS拒绝的QPS**

      由于这没有配置拒绝的QPS,看到的都是正常的通过的QPS指标

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mVy2UqHJ-1621556997441)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506111100402.png)]

      设置流控规则为 2:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tYMY4NR-1621556997442)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506111521918.png)]

    ​ 当QPS大于 2 接口返回:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rPgXeSCz-1621556997443)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506111535755.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mtGDeR1b-1621556997445)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506111346852.png)]

    • 簇点链路 用来显示 服务锁监控的api

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vp6Cx04S-1621556997448)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506111916800.png)]

    • 流控设置

      簇点链路 点击具体访问的api 然后点击 流控 按钮

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZIQ51cYn-1621556997450)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506113005337.png)]

    含义:

    资源名称:为我们接口的API /index/test
    **针对来源:**这里是默认的default(标示不针对来源),还有一种情况就是
    假设微服务A需要调用这个资源,微服务B也需要调用这个资源,那么我们就可以单独的为微服务A和微服务B进行设置阈值。
    阈值类型: 分为QPS和线程数 假设阈值为2
    **QPS类型:**只得是每秒钟访问接口的次数>2就进行限流
    **线程数:**为接受请求该资源 分配的线程数>2就进行限流.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XXkKyP1d-1621556997451)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506114453078.png)]

    流控模式

    1. ​ 直接 : 这种很好理解,就是达到设置的阈值后直接被流控抛出异常
      疯狂的请求这个路径, 当达到 阈值的时候会报错

    2. ​ 关联 :业务场景 我们现在有二个api,第一个是 查询接口,假设我们希望优先操
      作是保存接口

      	/**
      	 *:模仿 流控模式【关联】 读接口
      	 * @author: 余浪
      	 * @Date: 2021/3/10 14:33
      	 * @Version: 1.0.0
      	 **/
      	@GetMapping("/test")
      	@Inner(value =false)
      	public Object test() {
      		return userName + sequence.nextValue() +" >>> " +sequence.nextValue() +"------------";
      	}
      
      	/**
      	 * 模仿流控模式【关联】 写接口(优先)
      	 * @author: 余浪
      	 * @Date: 2021/3/10 14:33
      	 * @Version: 1.0.0
      	 **/
      	@GetMapping("/save")
      	@Inner(value =false)
      	public Object save() {
      		return "success";
      	}
      
      
       测试代码: 写一个for循环一直调用我们的写接口,让写接口QPS达到阈值
      	@GetMapping("/batch")
      	@Inner(value = false)
      	public void batch() throws InterruptedException {
      		RestTemplate restTemplate =new RestTemplate();
      		for (int i = 0; i < 1000; i++) {
      			restTemplate.getForEntity("http://localhost:7020/index/save", String.class);
      			Thread.sleep(200);
      		}
      	}
      

      ​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOWPXEyr-1621556997452)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506115440921.png)]

      先访问:http://localhost:7020/index/batch,

      在访问 http://localhost:7020/index/test, 被限流了

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sSEODgXc-1621556997454)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506135120394.png)]

    3. 链路 (用法说明,本地实验没成功 用alibaba 未毕业版本0.9.0可以测试出效果 API级
      别的限制流量)

      ​ 测试代码

      编写一个service类:
      public interface SysLogService extends IService<SysLog> {
      	int selectCount();
      }
      
      @Service
      public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog> implements SysLogService {
      	
      	/**
      	*  SentinelResource 定义流控资源
      	/
      	@Override
      	@SentinelResource("message")
      	public int selectCount() {
      		return 1;
      	}
      }
      
      
      
      controller中分别定义2个接口调用
      
      /**
       * 测试链路 流控
       * @author: 余浪
       * @Date: 2021/3/10 14:33
       * @Version: 1.0.0
       **/
      @GetMapping("/findAll")
      public Object findAll(){
         int count = sysLogService.selectCount();
         return count;
      }
      
      /**
       * 测试链路 流控
       * @author: 余浪
       * @Date: 2021/3/10 14:33
       * @Version: 1.0.0
       **/
      @GetMapping("/findOne")
      public Object findOne(){
         int count = sysLogService.selectCount();
         return count;
      }
      
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EpkqkJvw-1621556997456)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506142704985.png)]

      • 根据流控规则来说: 只会限制/findAll的请求,不会限制/findOne规则 (目前我们版本 链路规则 使用issue 里面的方案未测试通过,官网视乎也有这样的问题,已经提issue)

      1.6.3 版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。
      1.7.0 版本开始(对应SCA的2.1.1.RELEASE),官方在CommonFilter 引入了
      WEB_CONTEXT_UNIFY 参数,用于控制是否收敛context。将其配置为 false 即可根据不同的URL 进行链路限流。
      SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false即可关闭收敛
      我们当前使用的版本是SpringCloud Alibaba 2.1.0.RELEASE,无法实现链路限流。
      目前官方还未发布SCA 2.1.2.RELEASE,所以我们只能使用2.1.1.RELEASE,需要写代码的形式实

    4. 流控效果:

      ​ ①:快速失败(直接抛出异常) 每秒的QPS 操过1 就直接抛出异常
      源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

      ​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9fwGhCT-1621556997457)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506172731744.png)]

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vJ42XC2j-1621556997460)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506172744270.png)]

      ②:预热(warmUp)
      当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系
      统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到
      达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。
      冷加载因子: codeFactor 默认是3
      默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SI2DZzZm-1621556997463)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506173127277.png)]

      上图设置: 就是QPS从3/3 = 1开始算 经过10秒钟,到达3 的QPS 才进行限制流量
      详情文档:https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81—
      %E5%86%B7%E5%90%AF%E5%8A%A8
      源码:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

      ③:排队等待
      这种方式适合用于请求以突刺状来到,这个时候我们不希望一下子把所有的请求都通过,这样可能会
      把系统压垮;同时我们也期待系统以稳定的速度,逐步处理这些请求,以起到“削峰填谷”的效果,
      而不是拒绝所有请求。
      选择排队等待的阈值类型必须是QPS

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jO4qMDt7-1621556997464)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506173316929.png)]

      单机阈值: 10表示 每秒通过的请求个数是10,那么每隔100ms通过一次请求.
      每次请求的最大等待时间为1000=1s,超过1S就丢弃请求。
      具体文档:
      https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6-
      %E5%8C%80%E9%80%9F%E6%8E%92%E9%98%9F%E6%A8%A1%E5%BC%8F
      具体源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

    5. 降级规则
      ①:rt(平局响应时间)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rDs6fuSH-1621556997468)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507081626398.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RQzURt4R-1621556997469)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507081611919.png)]

      平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒
      级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中
      的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛
      出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作
      4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置

      ②:异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中
      的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s
      为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% -
      100%。

        测试代码: 
         int a =1;
      
      	@GetMapping("/ex")
      	public String ex(){
      		  a++;
      		 if(a > 3){
                 throw new RuntimeException("非法运算");
      		 }
      		return sequence.nextNo();
      	}
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2cq65yI5-1621556997472)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507083314169.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eRoyEslf-1621556997475)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507083302024.png)]

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trubRUet-1621556997477)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507083419969.png)]③:异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后
      会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再
      进入熔断状态。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GvaCgSFL-1621556997478)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507083615131.png)]

    6. 热点参数:
      业务场景: 秒杀业务,比如商城做促销秒杀,针对苹果11(商品id=5)进行9.9秒杀活动,那么这个时候,我们去请
      求订单接口(商品id=5)的请求流量十分大,我们就可以通过热点参数规则来控制
      商品id=5的请求的并发量。而其他正常商品的请求不会收到限制。那么
      这种热点参数规则很使用 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ZCq0uua-1621556997479)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507084754043.png)]

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-846UuyXS-1621556997480)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507084946172.png)]

      ​ 热点规则的高级使用,修改刚刚新建的热点规则,配置 单独参数的QPS,id=22的阈值超过5限流,其他的id超过1限流

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i8ba0d4T-1621556997482)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507085516184.png)]

    7. 授权规则

      很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:

      若配置白名单,则只有请求来源位于白名单内时才可通过;

      若配置黑名单,则请求来源位于黑名单时不通过,其余的请求通过

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Frczno08-1621556997483)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507090910793.png)]

      上面的资源名和授权类型不难理解,但是流控应用怎么填写呢?

      其实这个位置要填写的是来源标识,Sentinel提供了RequestOriginParser 接口来处理来源。只要Sentinel保护的接口资源被访问,Sentinel就会调用RequestOriginParser 的实现类去解析访问来源。

      1)自定义来源处理规则

      package com.cloud.common.sentinel.parser;
      
      import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
      
      import javax.servlet.http.HttpServletRequest;
      
      /**
       * sentinel 请求头解析判断
       */
      public class CloudHeaderRequestOriginParser implements RequestOriginParser {
      
      	/**
      	 * Parse the origin from given HTTP request.
      	 *
      	 * @param request HTTP request
      	 * @return parsed origin
      	 */
      		@Override
      	public String parseOrigin(HttpServletRequest request) {
      		String serviceName = request.getParameter("serviceName");
      		return serviceName;
      	}
      }
      
      

      授权规则配置

      这个配置的意思是只有serviceName=pc不能访问(黑名单)

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YBLodKz3-1621556997484)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507091214409.png)]

    8. OpenFeign整合我们的Sentinel

      1.在对应的feign-api项目中

      com.cloud cloud-common-sentinel

      2.:在我们的Feign的声明式接口上添加fallback属性或者 fallbackFactory属

      1)为我们添加fallbackFactory属性的api ,fallbackFactory属性可以处理我们的异常,建议统一使用fallbackFactory

       package com.cloud.admin.api.feign;
      
      import com.cloud.admin.api.entity.SysDeptRelation;
      import com.cloud.admin.api.entity.SysRole;
      import com.cloud.admin.api.fallbackFactory.RemoteDataScopeServiceFallBackFactory;
      import com.cloud.common.core.constant.ServiceNameConstants;
      import com.cloud.common.core.util.R;
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      
      import java.util.List;
      
      /**
       * 远程数据权限调用接口
       */
      @FeignClient(contextId = "remoteDataScopeService", value = ServiceNameConstants.ADMIN_SERVICE,
      		fallbackFactory = RemoteDataScopeServiceFallBackFactory.class)
      public interface RemoteDataScopeService {
      
      	/**
      	 * 通过角色ID 查询角色列表
      	 *
      	 * @param roleIdList 角色ID
      	 * @return
      	 */
      	@PostMapping("/role/getRoleList")
      	R<List<SysRole>> getRoleList(@RequestBody List<String> roleIdList);
      
      	/**
      	 * 获取子级部门
      	 *
      	 * @param deptId 部门ID
      	 * @return
      	 */
      	@GetMapping("/dept/getDescendantList/{deptId}")
      	R<List<SysDeptRelation>> getDescendantList(@PathVariable("deptId") Integer deptId);
      }
      
      
      package com.cloud.admin.api.fallbackFactory;
      
      import com.cloud.admin.api.entity.SysDeptRelation;
      import com.cloud.admin.api.entity.SysRole;
      import com.cloud.admin.api.feign.RemoteDataScopeService;
      import com.cloud.common.core.util.R;
      import feign.hystrix.FallbackFactory;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Component;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * @author :yulang
       *  降级服务,抛出异常 给返回默认值。防止服务雪崩
       * @Date :  9:35
       * @Version : 1.0.0
       **/
      @Component
      @Slf4j
      public class RemoteDataScopeServiceFallBackFactory implements FallbackFactory<RemoteDataScopeService> {
      
      
      	@Override
      	public RemoteDataScopeService create(Throwable throwable) {
      		return new RemoteDataScopeService() {
      			/**
      			 * 通过角色ID 查询角色列表
      			 *  可以做一些 其他的邮件通知等服务
      			    如果通过fegin调用getRoleList 异常,会直接返回默认值
      			 * @param roleIdList 角色ID
      			 * @return
      			 */
      			@Override
      			public R<List<SysRole>> getRoleList(List<String> roleIdList) {
      				log.error("getRoleList error :{}",throwable);
      				return R.ok(new ArrayList<SysRole>());
      			}
      
      			/**
      			 * 获取子级部门
      			 *
      			 * @param deptId 部门ID
      			 * @return
      			 */
      			@Override
      			public R<List<SysDeptRelation>> getDescendantList(Integer deptId) {
      				log.error("getDescendantList error :{}",throwable);
      				return R.ok(new ArrayList<SysDeptRelation>());
      			}
      		};
      	}
      }
      
      

      3.开启sentinel对fegin的支持

      feign:
        sentinel:
          enabled: true
      
    9. ​ ```

    10. Sentinel 规则持久化
      我们经过第四节课知道我们的Sentinel-dashboard配置的规则,在我们的微服
      务以及控制台重启的时候就清空了,因为他是基于内存的. 4

      推送模式说明优点缺点
      原始模式API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource简单,无任何依赖不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境
      Pull 模式扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等简单,无任何依赖;规则持久化不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。
      Push 模式扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。规则持久化;一致性;快速引入第三方依赖

      ​ 原始模式

      如果不做任何修改,Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中:

      Original push rules from Sentinel Dashboard

      这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。

      Pull模式

      pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistry 中。以本地文件数据源为例:

      public class FileDataSourceInit implements InitFunc {
      
          @Override
          public void init() throws Exception {
              String flowRulePath = "xxx";
      
              ReadableDataSource<String, List<FlowRule>> ds = new FileRefreshableDataSource<>(
                  flowRulePath, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
              );
              // 将可读数据源注册至 FlowRuleManager.
              FlowRuleManager.register2Property(ds.getProperty());
      
              WritableDataSource<List<FlowRule>> wds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
              // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
              // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
              WritableDataSourceRegistry.registerFlowDataSource(wds);
          }
      
          private <T> String encodeJson(T t) {
              return JSON.toJSONString(t);
          }
      }
      

      本地文件数据源会定时轮询文件的变更,读取规则。这样我们既可以在应用本地直接修改文件来更新规则,也可以通过 Sentinel 控制台推送规则。以本地文件数据源为例,推送过程如下图所示:

      Push rules from Sentinel Dashboard to local file

      首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。使用 pull 模式的数据源时一般不需要对 Sentinel 控制台进行改造。

      这种实现方法好处是简单,不引入新的依赖,坏处是无法保证监控数据的一致性。

      Push模式

      生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非常清晰了:

      Remote push rules to config center

      我们提供了 ZooKeeper, Apollo, Nacos 等的动态数据源实现。以 ZooKeeper 为例子,如果要使用第三方的配置中心作为配置管理,您需要做下面的几件事情:

      1. 实现一个公共的 ZooKeeper 客户端用于推送规则,在 Sentinel 控制台配置项中需要指定 ZooKeeper 的地址,启动时即创建 ZooKeeper Client。
      2. 我们需要针对每个应用(appName),每种规则设置不同的 path(可随时修改);或者约定大于配置(如 path 的模式统一为 /sentinel_rules/{appName}/{ruleType},e.g. sentinel_rules/appA/flowRule)。
      3. 规则配置页需要进行相应的改造,直接针对应用维度进行规则配置;修改同个应用多个资源的规则时可以批量进行推送,也可以分别推送。Sentinel 控制台将规则缓存在内存中(如 InMemFlowRuleStore),可以对其进行改造使其支持应用维度的规则缓存(key 为 appName),每次添加/修改/删除规则都先更新内存中的规则缓存,然后需要推送的时候从规则缓存中获取全量规则,然后通过上面实现的 Client 将规则推送到 ZooKeeper 即可。
      4. 应用客户端需要注册对应的读数据源以监听变更,可以参考 相关文档

      从 Sentinel 1.4.0 开始,Sentinel 控制台提供 DynamicRulePublisherDynamicRuleProvider 接口用于实现应用维度的规则推送和拉取,并提供了相关的示例。Sentinel 提供应用维度规则推送的示例页面(/v2/flow),用户改造控制台对接配置中心后可直接通过 v2 页面推送规则至配置中心。改造详情可参考 应用维度规则推送示例

      部署多个控制台实例时,通常需要将规则存至 DB 中,规则变更后同步向配置中心推送规则。

      PULL改造方案
      微服务改造方案:

      package com.cloud.common.sentinel.core;
      
      import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler;
      import com.alibaba.csp.sentinel.datasource.*;
      import com.alibaba.csp.sentinel.init.InitFunc;
      import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
      import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
      import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
      import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
      import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
      import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
      import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
      import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
      import com.alibaba.csp.sentinel.slots.system.SystemRule;
      import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
      import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
      import com.alibaba.csp.sentinel.util.ConfigUtil;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.InitializingBean;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.context.EnvironmentAware;
      import org.springframework.core.env.Environment;
      import org.springframework.stereotype.Component;
      
      import java.io.File;
      import java.io.FileNotFoundException;
      import java.nio.charset.Charset;
      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.List;
      import java.util.Properties;
      
      /**
       *  sentinel 持久化
       * @author: 余浪
       * @Date: 2021/3/10 14:33
       * @Version: 1.0.0
       **/
      @Slf4j
      @Component
      public class PullModeByFileDataSource implements InitFunc {
      
          private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
      
          @Override
          public void init() throws Exception {
      
              log.info("time:{}读取配置",sdf.format(new Date()));
      
              try {
      
      			Properties p = ConfigUtil.loadProperties("classpath:bootstrap.yml");
      			String applicationName = p.getProperty("name","defalut");
      			PersistenceRuleConstant.init(applicationName);
      			//创建文件存储目录
                  RuleFileUtils.mkdirIfNotExits(PersistenceRuleConstant.storePath,applicationName);
      
                  //创建规则文件
                  RuleFileUtils.createFileIfNotExits(PersistenceRuleConstant.rulesMap);
      
                  dealFlowRules();
      
                  dealDegradeRules();
      
                  dealSystemRules();
      
                  dealParamFlowRules();
      
                  dealAuthRules();
              }catch (Exception e) {
                  log.error("错误原因:{}",e);
              }
      
          }
      
          /**
           * 方法实现说明:处理流控规则逻辑
           * @author:smlz
           * @return: void
           * @exception: FileNotFoundException
           * @date:2019/11/29 13:26
           */
          private void dealFlowRules() throws FileNotFoundException {
              String ruleFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.FLOW_RULE_PATH).toString();
      
              //创建流控规则的可读数据源
      
      /*        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource(
                      ruleFilePath,RuleListParserUtils.flowRuleListParser
              );*/
      
              ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource(
                      new File(ruleFilePath),RuleListParserUtils.flowRuleListParser,20000,1024 * 1024, Charset.forName("utf-8")
              );
      
              // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
              FlowRuleManager.register2Property(flowRuleRDS.getProperty());
      
      
              WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<List<FlowRule>>(
                      ruleFilePath, RuleListParserUtils.flowFuleEnCoding
              );
      
              // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
              // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
              WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
          }
      
          /**
           * 方法实现说明:处理降级规则
           * @author:smlz
           * @return:void
           * @exception: FileNotFoundException
           * @date:2019/11/29 13:42
           */
          private void dealDegradeRules() throws FileNotFoundException {
              //讲解规则文件路径
              String degradeRuleFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.DEGRAGE_RULE_PATH).toString();
      
              //创建流控规则的可读数据源
              ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource(
                      degradeRuleFilePath,RuleListParserUtils.degradeRuleListParse
              );
      
              // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
              DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
      
      
              WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
                      degradeRuleFilePath, RuleListParserUtils.degradeRuleEnCoding
              );
      
              // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
              // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
              WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
          }
      
          /**
           * 方法实现说明:处理系统规则
           * @author:smlz
           * @return:void
           * @exception: FileNotFoundException
           * @date:2019/11/29 13:42
           */
          private void dealSystemRules() throws FileNotFoundException {
              //讲解规则文件路径
              String systemRuleFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.SYSTEM_RULE_PATH).toString();
      
              //创建流控规则的可读数据源
              ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource(
                      systemRuleFilePath,RuleListParserUtils.sysRuleListParse
              );
      
              // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
              SystemRuleManager.register2Property(systemRuleRDS.getProperty());
      
      
              WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
                      systemRuleFilePath, RuleListParserUtils.sysRuleEnCoding
              );
      
              // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
              // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
              WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
          }
      
          /**
           * 方法实现说明:热点参数规则
           * @author:smlz
           * @return:
           * @exception:
           * @date:2019/11/29 13:50
           */
          private void dealParamFlowRules() throws FileNotFoundException {
              //讲解规则文件路径
              String paramFlowRuleFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.HOT_PARAM_RULE).toString();
      
              //创建流控规则的可读数据源
              ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource(
                      paramFlowRuleFilePath,RuleListParserUtils.paramFlowRuleListParse
              );
      
              // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
              ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
      
      
              WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
                      paramFlowRuleFilePath, RuleListParserUtils.paramRuleEnCoding
              );
      
              // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
              // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
              ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
          }
      
          /**
           * 方法实现说明:授权规则
           * @author:smlz
           * @return:
           * @exception:
           * @date:2019/11/29 13:56
           */
          private void dealAuthRules() throws FileNotFoundException {
              //讲解规则文件路径
              String authFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.AUTH_RULE_PATH).toString();
      
              //创建流控规则的可读数据源
              ReadableDataSource<String, List<AuthorityRule>> authRuleRDS = new FileRefreshableDataSource(
                      authFilePath,RuleListParserUtils.authorityRuleParse
              );
      
              // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
              AuthorityRuleManager.register2Property(authRuleRDS.getProperty());
      
      
              WritableDataSource<List<AuthorityRule>> authRuleWDS = new FileWritableDataSource<>(
                      authFilePath, RuleListParserUtils.authorityEncoding
              );
      
              // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
              // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
              WritableDataSourceRegistry.registerAuthorityDataSource(authRuleWDS);
          }
      }
      

      基于SPI配置:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lFYtTYlj-1621556997490)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210506172145575.png)]

      启动项目这样 配置的流控规则,在项目启动后 就不会失效。

4.1.8 cloud-gateway

​ 网关服务,主要使用做同一路由转发,和动态路由(新增加一个服务不需要重启网关服务),灰度发布等功能。网关工程已经统一配置好,直接拉取代码直接使用即可

1.基于 gateway+ nacos 的动态路由规则配置,依赖

<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-gateway-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
		</dependency>

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

		<!--gateway 网关依赖,内置webflux 依赖-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>

		<!--注册中心客户端-->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
		</dependency>
		<!--配置中心客户端-->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
		</dependency>

本项目采用的是基于配置文件开关配置

#nacos动态路由配置
nacos:
  gateway:
    route:
      config:
        data-id: gateway-routes  ##动态路由规则id
        group: DEFAULT_GROUP  ## 分组信息
    dynamicRoute:
      #是否开启动态网关路由
      enabled: true   ## true表示开启动态路由,false 默认按照网关配置文件配置 

动态路由配置的代码实现

/**
 * 动态路由配置
 */
@Configuration
public class GatewayConfig {

    public static final long DEFAULT_TIMEOUT = 30000;

    public static String NACOS_SERVER_ADDR;

    public static String NACOS_NAMESPACE;

    public static String NACOS_ROUTE_DATA_ID;

    public static String NACOS_ROUTE_GROUP;

    @Value("${spring.cloud.nacos.discovery.server-addr}")
    public void setNacosServerAddr(String nacosServerAddr){
        NACOS_SERVER_ADDR = nacosServerAddr;
    }

    @Value("${spring.cloud.nacos.discovery.namespace}")
    public void setNacosNamespace(String nacosNamespace){
        NACOS_NAMESPACE = nacosNamespace;
    }

    @Value("${nacos.gateway.route.config.data-id}")
    public void setNacosRouteDataId(String nacosRouteDataId){
        NACOS_ROUTE_DATA_ID = nacosRouteDataId;
    }

    @Value("${nacos.gateway.route.config.group}")
    public void setNacosRouteGroup(String nacosRouteGroup){
        NACOS_ROUTE_GROUP = nacosRouteGroup;
    }

}



package com.cloud.gateway.route;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

/**
 * 动态更新路由网关service
 * 1)实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
 * 2)提供动态路由的基础方法,可通过获取bean操作该类的方法。该类提供新增路由、更新路由、删除路由,然后实现发布的功能。
 */
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    /**
     * 发布事件
     */
    @Autowired
    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    /**
     * 删除路由
     * @param id
     * @return
     */
    public String delete(String id) {
        try {
            log.info("gateway delete route id {}",id);
            this.routeDefinitionWriter.delete(Mono.just(id));
            return "delete success";
        } catch (Exception e) {
            return "delete fail";
        }
    }
    /**
     * 更新路由
     * @param definition
     * @return
     */
    public String update(RouteDefinition definition) {
        try {
            log.info("gateway update route {}",definition);
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception e) {
            return "update fail,not find route  routeId: "+definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            return "update route fail";
        }
    }

    /**
     * 增加路由
     * @param definition
     * @return
     */
    public String add(RouteDefinition definition) {
        log.info("gateway add route {}",definition);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }
}




package com.cloud.gateway.route;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.cloud.gateway.config.dynamic.GatewayConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
* 通过nacos下发动态路由配置,监听Nacos中gateway-route配置
*
*/
@Component
@Slf4j
@ConditionalOnProperty(prefix = "nacos.gateway.dynamicRoute", name = "enabled", havingValue = "true")
@DependsOn({"gatewayConfig"}) // 依赖于gatewayConfig bean
public class DynamicRouteServiceImplByNacos {

    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;


    private ConfigService configService;

    @PostConstruct
    public void init() {
        log.info("初始化网关路由。。。");
        try{
            configService = initConfigService();
            if(configService == null){
                log.warn("initConfigService fail");
                return;
            }
            String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT);
            if(StrUtil.isNotBlank(configInfo)) {
                log.info("获取网关当前配置:\r\n{}", configInfo);
                List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
                for (RouteDefinition definition : definitionList) {
                    log.info("update route : {}", definition.toString());
                    dynamicRouteService.add(definition);
                }
            }else{
                log.error("没有从nacos获取到网关路由信息,请检查!");
            }
        } catch (Exception e) {
            log.error("初始化网关路由时发生错误",e);
        }
        dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP);
    }

    /**
     * nacos config  监听Nacos下发的动态路由配置
     * @param dataId
     * @param group
     */
    public void dynamicRouteByNacosListener (String dataId, String group){
        try {
            configService.addListener(dataId, group, new Listener()  {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("进行网关更新:\n\r{}",configInfo);
                    List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
                    for(RouteDefinition definition : definitionList){
                        log.info("update route : {}",definition.toString());
                        dynamicRouteService.update(definition);
                    }
                }
                @Override
                public Executor getExecutor() {
                    log.info("getExecutor\n\r");
                    return null;
                }
            });
        } catch (NacosException e) {
            log.error("从nacos接收动态路由配置出错!!!",e);
        }
    }

    /**
     * 初始化网关路由 nacos config
     * @return
     */
    private ConfigService initConfigService(){
        try{
            Properties properties = new Properties();
            properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR);
            properties.setProperty("namespace",GatewayConfig.NACOS_NAMESPACE);
            return configService= NacosFactory.createConfigService(properties);
        } catch (Exception e) {
            log.error("初始化网关路由时发生错误",e);
            return null;
        }
    }
}

网关中添加配置,只需要在nacos配置中心修改

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dw7WYbxa-1621556997493)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507131938224.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVz19NJw-1621556997495)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507132057323.png)]

点击发布 网关会自动刷新路由信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7f7VqoM4-1621556997496)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507132313748.png)]

我们也可以在nacos中的监听查询列表中查询

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1MT1oiyY-1621556997497)(C:\Users\yulan\AppData\Roaming\Typora\typora-user-images\image-20210507132429228.png)]

4.1.9 nacos+gateway 实现的跨集群 同版本调用

我们现在需要解决生产环境金丝雀发布问题
比如 server-A 存在二个版本 V1(老版本) V2(新版
本),server-B 也存在二个版本V1(老版本) V2新版本 现在需要
做到的是
server-A(V1)---->server-B(v1),server-A(V2)—
->server-B(v2)。记住v2版本是小面积部署的,用来测试用
户对新版本功能的。若用户完全接受了v2。我们就可以把V1版本卸载
完全部署V2版本

@Slf4j(topic = "theSameClusterPriorityWithVersionRule")
public class GrayTheSameClusterPriorityWithVersionRule extends AbstractLoadBalancerRule {

	@Autowired
	private NacosDiscoveryProperties nacosDiscoveryProperties;

	@Override
	public void initWithNiwsConfig(IClientConfig iClientConfig) {

	}

	@Override
	public Server choose(Object key) {
		try {
			//获取本地所部署集群的名称 NJ-CLUSTER
			String localClusterName = nacosDiscoveryProperties.getClusterName();

			//去nacos上获取和本地 相同集群   相同版本的所有实例信息
			List<Instance> theSameClusterNameAndTheSameVersionInstList = getTheSameClusterAndTheSameVersionInstances(nacosDiscoveryProperties);
			//声明被调用的实例
			Instance toBeChooseInstance;

			//判断同集群同版本号的微服务实例是否为空
			if (theSameClusterNameAndTheSameVersionInstList.isEmpty()) {
				//跨集群调用相同的版本
				toBeChooseInstance = crossClusterAndTheSameVersionInovke(nacosDiscoveryProperties);
			} else {
				//具有同集群  同版本号的实例
				toBeChooseInstance = CustomerWeightedBalancer.chooseInstanceByRandomWeight(theSameClusterNameAndTheSameVersionInstList);
				log.info("同集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}",
						localClusterName, toBeChooseInstance.getClusterName(), nacosDiscoveryProperties.getMetadata().get("version"),
						toBeChooseInstance.getMetadata().get("version"), toBeChooseInstance.getIp(), toBeChooseInstance.getPort());
			}

			return new NacosServer(toBeChooseInstance);

		} catch (NacosException e) {
			log.error("同集群优先权重负载均衡算法选择异常:{}", e);
			return null;
		}
	}



	/**
	 * 方法实现说明:获取相同集群下,相同版本的 所有实例
	 *
	 * @param nacosDiscoveryProperties nacos的配置
	 * @author:smlz
	 * @return: List<Instance>
	 * @exception: NacosException
	 */
	private List<Instance> getTheSameClusterAndTheSameVersionInstances(NacosDiscoveryProperties nacosDiscoveryProperties) throws NacosException {

		//当前的集群的名称
		String currentClusterName = nacosDiscoveryProperties.getClusterName();

		//当前的版本号
		String currentVersion = nacosDiscoveryProperties.getMetadata().get("version");

		//获取所有实例的信息(包括不同集群的,不同版本号的)
		List<Instance> allInstance = getAllInstances(nacosDiscoveryProperties);

		List<Instance> theSameClusterNameAndTheSameVersionInstList = new ArrayList<>();
		//过滤相同集群  同版本号的实例
		for (Instance instance : allInstance) {
			if (StringUtils.endsWithIgnoreCase(instance.getClusterName(), currentClusterName) &&
					StringUtils.endsWithIgnoreCase(instance.getMetadata().get("version"), currentVersion)) {
				theSameClusterNameAndTheSameVersionInstList.add(instance);
			}
		}
		return theSameClusterNameAndTheSameVersionInstList;
	}

	/**
	 * 方法实现说明:获取被调用服务的所有实例
	 *
	 * @param nacosDiscoveryProperties nacos的配置
	 * @author:smlz
	 * @return: List<Instance>
	 * @exception: NacosException
	 * @date:2019/11/21 16:42
	 */
	private List<Instance> getAllInstances(NacosDiscoveryProperties nacosDiscoveryProperties) throws NacosException {

		//第1步:获取一个负载均衡对象
		BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer();

		//第2步:获取当前调用的微服务的名称
		String invokedSerivceName = baseLoadBalancer.getName();

		//第3步:获取nacos clinet的服务注册发现组件的api
		NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();

		//第4步:获取所有的服务实例
		List<Instance> allInstance = namingService.getAllInstances(invokedSerivceName);

		return allInstance;
	}

	/**
	 * 方法实现说明:跨集群环境下 相同版本的
	 *
	 * @param nacosDiscoveryProperties
	 * @author:smlz
	 * @return: List<Instance>
	 * @exception: NacosException
	 * @date:2019/11/21 17:11
	 */
	private List<Instance> getCrossClusterAndTheSameVersionInstList(NacosDiscoveryProperties nacosDiscoveryProperties) throws NacosException {

		//版本号
		String currentVersion = nacosDiscoveryProperties.getMetadata().get("version");

		//被调用的所有实例
		List<Instance> allInstance = getAllInstances(nacosDiscoveryProperties);

		List<Instance> crossClusterAndTheSameVersionInstList = new ArrayList<>();

		//过滤相同版本
		for (Instance instance : allInstance) {
			if (StringUtils.endsWithIgnoreCase(instance.getMetadata().get("version"), currentVersion)) {

				crossClusterAndTheSameVersionInstList.add(instance);
			}
		}

		return crossClusterAndTheSameVersionInstList;
	}

	private Instance crossClusterAndTheSameVersionInovke(NacosDiscoveryProperties nacosDiscoveryProperties) throws NacosException {

		//获取所有集群下相同版本的实例信息
		List<Instance> crossClusterAndTheSameVersionInstList = getCrossClusterAndTheSameVersionInstList(nacosDiscoveryProperties);
		//当前微服务的版本号
		String currentVersion = nacosDiscoveryProperties.getMetadata().get("version");
		//当前微服务的集群名称
		String currentClusterName = nacosDiscoveryProperties.getClusterName();

		//声明被调用的实例
		Instance toBeChooseInstance = null;

		//没有对应相同版本的实例
		if (crossClusterAndTheSameVersionInstList.isEmpty()) {
			log.info("跨集群调用找不到对应合适的版本当前版本为:currentVersion:{}", currentVersion);
			throw new RuntimeException("找不到相同版本的微服务实例");
		} else {
			toBeChooseInstance = CustomerWeightedBalancer.chooseInstanceByRandomWeight(crossClusterAndTheSameVersionInstList);
			log.info("跨集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}",
					currentClusterName, toBeChooseInstance.getClusterName(), nacosDiscoveryProperties.getMetadata().get("current-version"),
					toBeChooseInstance.getMetadata().get("current-version"), toBeChooseInstance.getIp(), toBeChooseInstance.getPort());
		}

		return toBeChooseInstance;
	}
}

4.1.10
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值