springcloudalibaba项目使用情况
cloud微服务开发手册
这个是自己给公司做的一套微服务框架,其中部分的组件开发手册
1.后端结构技术选型
技术 | 说明 | 官网地址 |
---|---|---|
Spring Boot | 容器+MVC框架 | https//spring.io/projects/spring-boot |
Spring Security | 认证和授权框架 | https//spring.io/projects/spring-security |
MyBatis-Plus | ORM框架 | https://mp.baomidou.com/gu |
MyBatisPlusGenerator | 数据层代码生成 | https://mp.baomidou.com/guide/generator.html#%E4%BD%BF%E7%94%A8%E6%95% |
PageHelper | MyBatis物理分页插件 | 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/ |
MongoDb | NoSql数据库 | https//www.mongodb.com/ |
Lombok | 简化对象封装工具 | https//github.com/rzwitserloot/lombo |
Seata | 全局事务管理框架 | https//github.com/seata/seata |
JWT | JWT登录支持 | 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.环境依赖
工具 | 版本号 | 下载地址 |
---|---|---|
JDK | 1.8 (64) | https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html |
MYSQL | 8.0 | https://dev.mysql.com/downloads/ |
Oracle | 11g | https://www.oracle.com/cn/database/technologies/instant-client/downloads.html |
RabbitMq | 3.8.14 | https://www.rabbitmq.com/download.html |
Redis | 5.0.12 | https://download.redis.io/releases/redis-5.0.12.tar.gz |
MongoDb | 4.4 | https://www.mongodb.com/try/download/community |
Spring Boot | 2.2.6.RELEASE | https://spring.io/projects/spring-boot#learn |
Spring Cloud Alibaba | 2.2.1.RELEASE | https://spring.io/projects/spring-cloud-alibaba/#learn |
Nginx | nginx-1.19.10 | http://nginx.org/download/nginx-1.19.10.tar.gz |
Nacos | 1.3.1 | https://github.com/alibaba/nacos/releases |
Sentines-dashboard | 1.3.0 | https://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
- cd 到nacos目录
- 使用
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客户端的搭建
三板斧之一,依赖引入
<!--注册中心客户端--> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
三板斧之二: 第二板斧写注解(也可以不写) @EnableDiscoveryClient
@SpringBootApplication @EnableDiscoveryClient public class CloudAdminApplication {public static void main(String[] args) {SpringApplication.run(CloudAdminApplication.class, args);}}
三板斧之三 :写配置文件 注意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
启动
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-bizcloud: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: 7020spring:
application:name: cloud-admin-biz
cloud:nacos:config:server-addr: 127.0.0.1:8848file-extension: ymlshared-configs:- application-dev.ymlnamespace: 4c203b07-7179-4712-90c1-f0710ef11f5crefresh-enabled: truediscovery:namespace: 4c203b07-7179-4712-90c1-f0710ef11f5cserver-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. 在我的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>
配置文件中配置好注册中心地址:
server:port: 7020 ## 服务端口号spring:application:name: cloud-admin-biz ## 应用名称 cloud:nacos:discovery:namespace: 4c203b07-7179-4712-90c1-f0710ef11f5cserver-addr: 127.0.0.1:8848config:server-addr: 127.0.0.1:8848 ## nacos 服务地址file-extension: yml ## 配置文件扩展名shared-configs: ## 共享资源配置- application-dev.ymlnamespace: 4c203b07-7179-4712-90c1-f0710ef11f5c ## 命名空间profiles:active: dev # profiles 环境
启动 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组件使用:
项目中需要使用swagger的只需要在对应的项目中引入 :
<!--swagger api 相关配置--> <dependency><groupId>com.cloud</groupId><artifactId>cloud-common-swagger</artifactId> </dependency>无需任何的配置文件 直接在项目中使用原生swagger注解即可使用
在项目中的 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 使用
引入相对应依赖包:
<!--core 工具类--> <dependency><groupId>com.cloud</groupId><artifactId>cloud-common-core</artifactId> </dependency>java 核心的工具类库 ,可以直接使用操作 java中集合,素组,等数据结构,以及反射,json,序列化,参数校验等功能
4.1.3 cloud-common-data 使用
- 引入相对应依赖包
<!--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,方便在日志文件中排除错误;
- 提供了操作日志的入库操作,需要在使用的方法上 加上
@SysLog
注解,系统会自动将自动会对操作方法的日志进行入库操作。
4.1.5 cloud-common-minio (分布式文件存储)
1.引入相对应依赖包 (需要在需要使用文件上传存储业务中引入)
<dependency><groupId>com.cloud</groupId><artifactId>cloud-common-minio </artifactId>
</dependency>
启动 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)]
浏览器 输入 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:portminio.accessKey =minioadmin ## minio 用户名minio.secretKey =minioadmin ##minio 密码minio.endpoint.enable =true ## 开启服务
代码中使用:
@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名称*/@SneakyThrowspublic void createBucket(String bucketName) {if (!client.bucketExists(bucketName)) {client.makeBucket(bucketName);}}/*** 获取全部bucket* <p>* https://docs.minio.io/cn/java-client-api-reference.html#listBuckets*/@SneakyThrowspublic List<Bucket> getAllBuckets() {return client.listBuckets();}/*** @param bucketName bucket名称*/@SneakyThrowspublic Optional<Bucket> getBucket(String bucketName) {return client.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();}/*** @param bucketName bucket名称*/@SneakyThrowspublic void removeBucket(String bucketName) {client.removeBucket(bucketName);}/*** 根据文件前置查询文件** @param bucketName bucket名称* @param prefix 前缀* @param recursive 是否递归查询* @return MinioItem 列表*/@SneakyThrowspublic 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*/@SneakyThrowspublic String getObjectURL(String bucketName, String objectName, Integer expires) {return client.presignedGetObject(bucketName, objectName, expires);}/*** 获取文件** @param bucketName bucket名称* @param objectName 文件名称* @return 二进制流*/@SneakyThrowspublic 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[必选] 每个系统禁止使用一样的配置 默认值为 1workerId: 1 ## 工作机器ID,值的范围在[0,31]之间,一般可以设置机器编号[必选] 每个系统禁止使用一样的配置 默认值为 1注意:如果使用 snowflake 方式,一定要注意时钟回拨的问题。避免造成id重复代码中使用:@Autowiredprivate 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)]
基于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: 1maximum-pool-size: 3connection-timeout: 30000initialization-fail-timeout: 30000idle-timeout: 30000max-lifetime: 30000connection-test-query: SELECT 1type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456url: 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注意在使用的时候 不要手动去修改表中数据,避免序列重复。
代码中使用:
@Autowiredprivate Sequence sequence;@GetMapping("/test")public Object test() {return sequence.nextValue();}
基于redis号段模式:
cloud:xsequence:range:enable: redis ## 开启号段模式 redis 方式 严格区分大小写 如果项目中已经引入了redis 则只需开启即可##redis配置可以使用默认 spring:redis:host: 192.168.163.130port: 6379password: adminstep:1000 ##获取range步长[可选,默认:1000]stepStart: 0 ##序列号分配起始值[可选:默认:0]bizName: cloud ##业务名称 建议每个项目配置不同的代码中使用:@Autowiredprivate 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.jarspring:
cloud:sentinel:transport:dashboard: 127.0.0.1:8080
sentinel 控制台配置项:
配置项 类型 默认值 最小值 描述 auth.enabled boolean true - 是否开启登录鉴权,仅用于日常测试,生产上不建议关闭 sentinel.dashboard.auth.username String sentinel - 登录控制台的用户名,默认为 sentinel
sentinel.dashboard.auth.password String sentinel - 登录控制台的密码,默认为 sentinel
sentinel.dashboard.app.hideAppNoMachineMillis Integer 0 60000 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 csp.sentinel.dashboard.server String localhost: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 | 例子 | 说明 |
---|---|---|
1 |
2014-06-20 16:35:10
|
时间戳 |
2 |
1
|
该秒发生的第一个资源 |
3 |
sayHello(java.lang.String,long)
|
资源名称 |
4 |
XXXException
|
拦截的原因, 通常 FlowException 代表是被限流规则拦截,DegradeException 则表示被降级,SystemBlockException 则表示被系统保护拦截
|
5 |
default
|
生效规则的调用来源(参数限流中代表生效的参数) |
6 |
origin
|
被拦截资源的调用者,可以为空 |
7 |
61,0
|
61 被拦截的数量,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
1532415661000
:时间戳2018-07-24 15:01:01
:格式化之后的时间戳sayHello(java.lang.String)
:资源名12
:表示到来的数量,即此刻通过 Sentinel 规则 check 的数量(passed QPS)3
:实际该资源被拦截的数量(blocked QPS)4
:每秒结束的资源个数(完成调用),包括正常结束和异常结束的情况(exit QPS)2
:异常的数量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)]
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)]流控模式:
直接 : 这种很好理解,就是达到设置的阈值后直接被流控抛出异常
疯狂的请求这个路径, 当达到 阈值的时候会报错 关联 :业务场景 我们现在有二个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)]
链路 (用法说明,本地实验没成功 用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,需要写代码的形式实
现流控效果:
①:快速失败(直接抛出异常) 每秒的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降级规则
①: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)]热点参数:
业务场景: 秒杀业务,比如商城做促销秒杀,针对苹果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)]
授权规则
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 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*/@Overridepublic 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)]
OpenFeign整合我们的Sentinel
1.在对应的feign-api项目中
com.cloud cloud-common-sentinel
2.:在我们的Feign的声明式接口上添加fallback属性或者 fallbackFactory属
性
1)为我们添加fallbackFactory属性的api ,fallbackFactory属性可以处理我们的异常,建议统一使用fallbackFactorypackage 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> {@Overridepublic RemoteDataScopeService create(Throwable throwable) {return new RemoteDataScopeService() {/*** 通过角色ID 查询角色列表* 可以做一些 其他的邮件通知等服务如果通过fegin调用getRoleList 异常,会直接返回默认值* @param roleIdList 角色ID* @return*/@Overridepublic R<List<SysRole>> getRoleList(List<String> roleIdList) {log.error("getRoleList error :{}",throwable);return R.ok(new ArrayList<SysRole>());}/*** 获取子级部门** @param deptId 部门ID* @return*/@Overridepublic R<List<SysDeptRelation>> getDescendantList(Integer deptId) {log.error("getDescendantList error :{}",throwable);return R.ok(new ArrayList<SysDeptRelation>());}};} }
3.开启sentinel对fegin的支持
feign:sentinel:enabled: true
```
Sentinel 规则持久化
我们经过第四节课知道我们的Sentinel-dashboard配置的规则,在我们的微服
务以及控制台重启的时候就清空了,因为他是基于内存的. 4推送模式 说明 优点 缺点 原始模式 API 将规则推送至客户端并直接更新到内存中,扩展写数据源( WritableDataSource
)简单,无任何依赖 不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境 Pull 模式 扩展写数据源( WritableDataSource
), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等简单,无任何依赖;规则持久化 不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。 Push 模式 扩展读数据源( ReadableDataSource
),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。规则持久化;一致性;快速 引入第三方依赖 原始模式
如果不做任何修改,Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中:
这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。
Pull模式
pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的
WritableDataSourceRegistry
中。以本地文件数据源为例:public class FileDataSourceInit implements InitFunc {@Overridepublic 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 控制台推送规则。以本地文件数据源为例,推送过程如下图所示:
首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。使用 pull 模式的数据源时一般不需要对 Sentinel 控制台进行改造。
这种实现方法好处是简单,不引入新的依赖,坏处是无法保证监控数据的一致性。
Push模式
生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非常清晰了:
我们提供了 ZooKeeper, Apollo, Nacos 等的动态数据源实现。以 ZooKeeper 为例子,如果要使用第三方的配置中心作为配置管理,您需要做下面的几件事情:
- 实现一个公共的 ZooKeeper 客户端用于推送规则,在 Sentinel 控制台配置项中需要指定 ZooKeeper 的地址,启动时即创建 ZooKeeper Client。
- 我们需要针对每个应用(appName),每种规则设置不同的 path(可随时修改);或者约定大于配置(如 path 的模式统一为
/sentinel_rules/{appName}/{ruleType}
,e.g.sentinel_rules/appA/flowRule
)。 - 规则配置页需要进行相应的改造,直接针对应用维度进行规则配置;修改同个应用多个资源的规则时可以批量进行推送,也可以分别推送。Sentinel 控制台将规则缓存在内存中(如
InMemFlowRuleStore
),可以对其进行改造使其支持应用维度的规则缓存(key 为 appName),每次添加/修改/删除规则都先更新内存中的规则缓存,然后需要推送的时候从规则缓存中获取全量规则,然后通过上面实现的 Client 将规则推送到 ZooKeeper 即可。 - 应用客户端需要注册对应的读数据源以监听变更,可以参考 相关文档。
从 Sentinel 1.4.0 开始,Sentinel 控制台提供
DynamicRulePublisher
和DynamicRuleProvider
接口用于实现应用维度的规则推送和拉取,并提供了相关的示例。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");@Overridepublic 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 ##动态路由规则idgroup: 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 {@Autowiredprivate RouteDefinitionWriter routeDefinitionWriter;/*** 发布事件*/@Autowiredprivate ApplicationEventPublisher publisher;@Overridepublic 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 {@Autowiredprivate DynamicRouteServiceImpl dynamicRouteService;private ConfigService configService;@PostConstructpublic 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() {@Overridepublic 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);}}@Overridepublic 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 {@Autowiredprivate NacosDiscoveryProperties nacosDiscoveryProperties;@Overridepublic void initWithNiwsConfig(IClientConfig iClientConfig) {}@Overridepublic Server choose(Object key) {try {//获取本地所部署集群的名称 NJ-CLUSTERString 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的服务注册发现组件的apiNamingService 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
springcloudalibaba项目使用情况相关推荐
- DSB2017项目grt123代码学习笔记一:项目基本情况
DSB2017项目grt123代码学习笔记一:项目基本情况 Kaggle上Data Science Bowl 2017年肺结节检测比赛第一名grt123团队的算法. github地址:https:// ...
- 项目进展情况如何更好地管理?
项目进度管理的主要目标是要在规定的时间内,制定出合理.经济的进度计划,然后在该计划的执行过程中,检查实际进度是否与计划进度相一致,保证项目按时完成. 智办事可以满足 包含任务协作.即时沟通.数据管理. ...
- 百度首席科学家吴恩达谈百度人工智能项目进展情况
吴恩达(Andrew Ng)是人工智能领域的大牛之一.他是Google人工智能部门的其中一位创始人,是2012年Google通过自主学习识别猫图像的深度学习神经网络"DistBelief&q ...
- 37个项目!2020年5G+智慧灯杆项目落地情况大汇总
春节将近,2020年已然成为历史.今天我们回顾2020年智慧灯杆的建设情况,一起看看具体都有哪些智慧路灯项目在2020年成功落地? 在本篇文章中,小编汇总了4个直辖市13个省份,共计37个项目.其中落 ...
- springcloudalibaba项目的搭建
第一步:搭建父项目,创建一个Maven项目,父项目不写代码,直接删除src 第二步:父项目需要的包 // 打包方式 <packaging>pom</packaging> < ...
- 系统试运行报告是谁写的_费控系统项目实施情况说明
费用控制系统项目为今年我司信息化建设重大项目,项目旨在对费用预算进行实时的系统管控和严格执行,促进财务管理和会计核算流程的标准化和精简化,同时为员工的费用报销.出差商旅等经济事项提供灵活高效的处理通道 ...
- Tomcat项目卡件情况:调整启动参数
tomcat项目有卡件情况,调整线程数,调整内存 进入tomcat/bin 目录下,修改catalina.sh或catalina.out文件(根据自己系统情况) JAVA_OPTS="-se ...
- 电脑重启后,eclipse可能会出现项目丢失情况
解决方法如下: 1.File-->Import-->Existing Projects into Workpace 选择项目所在的目录 2.勾选Search for nested proj ...
- linux环境下查看项目内存情况
2019独角兽企业重金招聘Python工程师标准>>> 1. 查看内存占用 2.查看cpu占用 3.查看gc信息 转载于:https://my.oschina.net/zhaolin ...
最新文章
- 深度学习核心技术精讲100篇(四十二)-阿里妈妈深度树匹配技术演进:TDM->JTM->BSAT
- ccf-csp #201703-2 学生排队
- 关于编译GITHUB上的工程
- php 源文件加密工具PHP Screw
- 从淘宝第1位程序员, 到阿里合伙人,20多年了,非科班出身的他还在编程,程序员的榜样
- 2018.12.18
- ES6中新增数组遍历方法
- 锐捷 重启计算机,关于锐捷客户端重安装后要求反覆重启的解决办法
- android qq轻聊版,Android QQ轻聊版怎么样 Android QQ轻聊版介绍
- 电子邮件工作原理及主要协议
- C/C++ int数组初始化
- ToolBar的返回键以及对应的图标自定义设置
- iOS第三方开源库的吐槽和备忘 - 王培
- 声学模型(语音识别中的)--学习笔记
- 洗地扫地机一体机好用吗、洗扫一体洗地机选购必看
- 按键精灵使用百度文字识别(百度ocr)教程
- python文件操作和绘制曲线
- 小说作者推荐:焦糖冬瓜合集
- 【Java中继承总结与练习 】
- eclipse Luna 用tomct7 部署项目
热门文章
- 资讯:微火荣获首批“支付宝数字化经营合作伙伴认证”
- 虹科白皮书 | 在工业4.0阶段,如何利用TSN时间敏感网络技术打造数字化工厂?
- 高温计通用协议 Data format UPP(Universal Pyrometer Protocol)
- 【产业互联网周报】百度AI产业化框架初成;微信公开课强调toB;谷歌或收购Salesforce;“女全经济”主导全家消费。...
- 【NER】CCL2021医疗命名体识别之数据预处理(处理.json文件)
- 生成excel文档即便没有安装微软Excel
- vb.net 教程 10-2 Excel操作1 Excel文件的打开
- C++实现Wlan自动连接(wpa2 enterprise)
- 获取任意微信公众号二维码方法
- 互联网日报 | 3月11日 星期四 | 小米10S正式发布;汽车之家将于3月15日登陆港交所;达达快送年配送订单量达11亿单...