# spring-cloud-alibaba-demo
**Repository Path**: springzb/spring-cloud-alibaba-demo
## Basic Information
- **Project Name**: spring-cloud-alibaba-demo
- **Description**: spring-cloud-alibaba-demo项目,软件架构说明,此demo主要版信息:
SpringBoot.2.3.12.RELEASE + SpringCloud Hoxton.SR12+ AlibabaCloud 2.2.7.RELEASE
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: develop
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 22
- **Forks**: 17
- **Created**: 2022-02-21
- **Last Updated**: 2025-02-02
## Categories & Tags
**Categories**: Uncategorized
**Tags**: SpringCloudAlibaba
## README
# spring-cloud-alibaba-demo
author superzheng
---
项目代码仓库地址:[https://gitee.com/springzb/spring-cloud-alibaba-demo](https://gitee.com/springzb/spring-cloud-alibaba-demo)
### 由于图床服务过期,详细文档请点击以下链接:
详细文档地址:[https://people.blog.csdn.net/article/details/123729595](https://people.blog.csdn.net/article/details/123729595)
此demo主要版信息:
SpringBoot.2.3.12.RELEASE + SpringCloud Hoxton.SR12+ AlibabaCloud 2.2.7.RELEASE
## 一、**版本信息说明:**
[https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明](https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明)
| Spring Cloud Alibaba Version | Sentinel Version | Nacos Version | RocketMQ Version | Dubbo Version | Seata Version |
| ---------------------------- | ---------------- | ------------- | ---------------- | ------------- | ------------- |
| 2.2.7.RELEASE | 1.8.1 | 2.0.3 | 4.6.1 | 2.7.13 | 1.3.0 |
| Spring Cloud Alibaba Version | Spring Cloud Version | Spring Boot Version |
| ---------------------------- | ------------------------ | ------------------- |
| 2.2.7.RELEASE | Spring Cloud Hoxton.SR12 | 2.3.12.RELEASE |
## 二、组件说明
SpringCloud
- 全家桶+轻松嵌入第三方组件(Netflix 奈飞)
- 官网:[https://spring.io/projects/spring-cloud](https://spring.io/projects/spring-cloud)
- 配套
- 通信方式:http restful
- 注册中心:eruka
- 配置中心:config
- 断路器:hystrix
- 网关:zuul/gateway
- 分布式追踪系统:sleuth+zipkin
- Spring Alibaba Cloud
- 全家桶+阿里生态多个组件组合+SpringCloud支持
- 官网 [https://spring.io/projects/spring-cloud-alibaba](https://spring.io/projects/spring-cloud-alibaba)
- 配套
- 通信方式:http restful
- 注册中心:nacos
- 配置中心:nacos
- 断路器:sentinel
- 网关:gateway
- 分布式追踪系统:sleuth+zipkin
## 三、新建聚合工程
### 3.1父工程pom文件
```Java
4.0.0
cn.mesmile
spring-cloud-alibaba-demo
0.0.1-SNAPSHOT
cloud-system
cloud-common
cloud-order
spring-cloud-alibaba-demo
springCloudAlibabaDemo
pom
UTF-8
UTF-8
1.8
1.8
1.8
1.18.20
2.3.12.RELEASE
Hoxton.SR12
2.2.7.RELEASE
3.4.2
UTF-8
2.17.1
org.springframework.boot
spring-boot-dependencies
${spring.boot.version}
pom
import
org.springframework.cloud
spring-cloud-dependencies
${spring.cloud.version}
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
${spring.cloud.alibaba}
pom
import
org.springframework.boot
spring-boot-maven-plugin
true
true
```
## 四、搭建整合nacos
docker-compose.yaml安装nacos
```Java
version: '3'
services:
nacos:
# 注意 2.0 不需要额外安装数据库了,docker run --name nacos-standalone -e MODE=standalone -d -p 8848:8848 -p 9848:9848 -p 9849:9849 nacos/nacos-server:2.0.3
image: 'nacos/nacos-server:2.0.3'
restart: always
container_name: nacos
environment:
# 启动模式 单机
MODE: 'standalone'
# nacos默认端口号
NACOS_SERVER_PORT: 8848
# # 是否开启权限系统
# NACOS_AUTH_ENABLE: 'true'
ports:
- '8848:8848'
- '9848:9848'
- '9849:9849'
#在当前目录打开终端,使用命令 docker-compose up -d 即可运行;
#在当前目录打开终端,使用命令 docker-compose down 即可运行;
```
安装完成后 [http://127.0.0.1:8848/nacos/](http://81.69.43.78:8848/nacos/) 默认用户名和密码都是 nacos 进入后台管理页面

## 五、 整合nacos 注册中心,新建cloud-system模块
cloud-system模块中pom.xml添加nacos依赖
```Java
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
```
配置文件中添加配置:
```Java
# nacos 地址
spring
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# 应用
application:
# 应用名称
name: cloud-system-service
```
启动类上加上注解开启 @EnableDiscoveryClient 服务注册
发现服务已经注册上来了:

```Java
@RequiredArgsConstructor
@RequestMapping("/v1/nacos")
@RestController
public class NacosController {
private final DiscoveryClient discoveryClient;
@GetMapping("/get")
public R get(){
List instances = discoveryClient.getInstances("cloud-system-service");
ServiceInstance serviceInstance = instances.get(0);
// 获取到nacos注册中心上的服务ip以及端口
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
return R.data(host+":"+port);
}
}
```
测试发送get请求获取信息:
```JSON
{
"code": 200,
"success": true,
"data": "10.113.229.45:8000",
"msg": "操作成功"
}
```

## 六、整合openFeign,新建 cloud-order 模块
cloud-system添加openFeign依赖:
```JSON
org.springframework.cloud
spring-cloud-starter-openfeign
```
cloud-system启动类上添加注解 @EnableFeignClients 开启openFeign
cloud-order模块提供接口
```Java
@RequestMapping("/v1/order")
@RequiredArgsConstructor
@RestController
public class OrderController {
private final OrderService orderService;
@GetMapping("/list")
public R listOrder(){
List list = orderService.list();
return R.data(list);
}
}
```
cloud-system模块添加feign
```Java
@FeignClient(value = "cloud-order-service")
public interface OrderFeignClient {
/**
* 获取订单列表
* @return
*/
@GetMapping("/v1/order/list")
R listOrder();
}
```
通过cloud-system模块的【OrderFeignClient 对象】调用 cloud-order模块
### 6.1 openFeign开启调试调试日志
openFeign的日志级别如下:
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
**第一步**配置类中配置日志隔离级别:
```Java
@Configuration
public class CloudOpenFeignConfig {
/**
* 日志级别定义
* import feign.Logger;
* @return
*/
@Profile("dev")
@Bean
Logger.Level feginLoggerLevel(){
return Logger.Level.FULL;
}
}
```
**第二步** 在配置信息中指定包下的日志级别
```YAML
logging:
level:
# 全局日志级别
root: info
# 局部包的日志级别, cn.mesmile.system.feign是openFeign接口所在的包名
cn.mesmile.system.feign: debug
```
调用openFeign测试是否开启日志结果如下:
```Java
2022-02-23 14:40:48.580 DEBUG 23620 --- [nio-8000-exec-3] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] <--- HTTP/1.1 200 (2ms)
2022-02-23 14:40:48.581 DEBUG 23620 --- [nio-8000-exec-3] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] connection: keep-alive
2022-02-23 14:40:48.581 DEBUG 23620 --- [nio-8000-exec-3] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] content-type: application/json
2022-02-23 14:40:48.581 DEBUG 23620 --- [nio-8000-exec-3] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] date: Wed, 23 Feb 2022 06:40:48 GMT
2022-02-23 14:40:48.581 DEBUG 23620 --- [nio-8000-exec-3] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] keep-alive: timeout=60
2022-02-23 14:40:48.581 DEBUG 23620 --- [nio-8000-exec-3] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] transfer-encoding: chunked
2022-02-23 14:40:48.581 DEBUG 23620 --- [nio-8000-exec-3] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder]
2022-02-23 14:40:48.581 DEBUG 23620 --- [nio-8000-exec-3] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] {"code":200,"success":true,"data":[],"msg":"操作成功"}
2022-02-23 14:40:48.581 DEBUG 23620 --- [nio-8000-exec-3] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] <--- END HTTP (58-byte body)
```
### 6.2 替换默认的httpclient
Feign在默认情况下使用的是JDK原生的**URLConnection**发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection
在生产环境中,通常不使用默认的http client,通常有如下两种选择:
- 使用**ApacheHttpClient**
- 使用**OkHttp**
这里演示替换为 **ApacheHttpClient**
在openFeign接口服务的pom文件maven添加如下依赖
```XML
org.apache.httpcomponents
httpclient
io.github.openfeign
feign-httpclient
```
org.springframework.cloud.openfeign.FeignAutoConfiguration.HttpClientFeignConfiguration

上述红色框中的生成条件,其中的`@ConditionalOnClass(ApacheHttpClient.class)`,必须要有`ApacheHttpClient`这个类才会生效,并且`feign.httpclient.enabled`这个配置要设置为`true`
应此需要添加配置:
```YAML
# 开启httpclient,默认值为 true
feign:
httpclient:
enabled: true
```
验证是否替换成功:
`feign.SynchronousMethodHandler#executeAndDecode()`这个方法中可以清楚的看出调用哪个client

### 6.3 开启通讯优化gzip
添加配置:
```YAML
feign:
## 开启压缩
compression:
request:
enabled: true
## 开启压缩的阈值,单位字节,默认2048,即是2k,这里为了演示效果设置成1字节
min-request-size: 1
mime-types: text/xml,application/xml,application/json
response:
enabled: true
```
验证已经开启 gzip : Accept-Encoding: gzip
```XML
2022-02-23 15:35:42.647 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] ---> GET http://cloud-order-service/v1/order/list HTTP/1.1
2022-02-23 15:35:42.647 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] Accept-Encoding: gzip
2022-02-23 15:35:42.647 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] Accept-Encoding: deflate
2022-02-23 15:35:42.647 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] ---> END HTTP (0-byte body)
2022-02-23 15:35:42.727 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] <--- HTTP/1.1 200 (80ms)
2022-02-23 15:35:42.727 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] connection: keep-alive
2022-02-23 15:35:42.727 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] content-type: application/json
2022-02-23 15:35:42.727 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] date: Wed, 23 Feb 2022 07:35:42 GMT
2022-02-23 15:35:42.727 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] keep-alive: timeout=60
2022-02-23 15:35:42.727 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] transfer-encoding: chunked
2022-02-23 15:35:42.727 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder]
2022-02-23 15:35:42.727 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] {"code":200,"success":true,"data":[{"id":1495409546527555585,"outTradeNo":"234234534","state":1,"createTime":"2022-02-20 09:53:41","totalFee":1003,"videoId":12443225,"videoTitle":"spring专题","videoImg":"https://www.baidu.com","userId":3242342342}],"msg":"操作成功"}
2022-02-23 15:35:42.727 DEBUG 22184 --- [nio-8000-exec-5] c.mesmile.system.feign.OrderFeignClient : [OrderFeignClient#listOrder] <--- END HTTP (272-byte body)
```
## 七、整合sentinel 流量卫兵
sentinel是面向分布式服务框架的轻量级流量控制框架,主要以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度来维护系统的稳定性.
官网 [https://github.com/alibaba/Sentinel/wiki](https://github.com/alibaba/Sentinel/wiki)
下载编译好的后台管理系统包:
[https://github.com/alibaba/Sentinel/releases](https://github.com/alibaba/Sentinel/releases)
这里使用的是 1.8.1 版本号:
### sentinel安装包
[sentinel-dashboard-1.8.1.jar](file/sentinel-dashboard-1.8.1.jar)
启动命令:
```Java
# 注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
# 其中 -Dserver.port=8080 用于指定 Sentinel 控制台端口为 8080。
# 从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。
# 可以参考 鉴权模块文档 配置用户名和密码。
# 启动成功后的后台地址 http://127.0.0.1:8080/
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
```
启动后台jar包后,访问http://127.0.0.1:8080/ 用户名和密码都是sentinel

项目中添加maven依赖
```Java
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
```
项目中添加sentinel配置:
```YAML
# sentinel 配置
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
port: 9900
#dashboard: 8080 控制台端口
#port: 9900 本地启的端口,随机选个不能被占用的,与dashboard进行数据交互,会在应用对应的机器上
#启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互, 若被占用,则开始+1一次扫描
```
### 7.1 **Sentinel自定义全局异常降级**
在cloud-system中添加配置类,完成自定义降级异常
```Java
@Component
public class CloudSentinelBlockHandler implements BlockExceptionHandler {
/*
FlowException //限流异常
DegradeException //降级异常
ParamFlowException //参数限流异常
SystemBlockException //系统负载异常
AuthorityException //授权异常
*/
/**
* V2.1.0 到 V2.2.0后,sentinel 里面依赖进行改动,且不向下兼容
* @param request
* @param response
* @param e
* @throws Exception
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
/*
request.getRequestURI() = /v1/user/list
request.getRemoteUser() = null
request.getContextPath() =
request.getMethod() = GET
*/
// 降级业务
Map backMap=new HashMap<>();
if (e instanceof FlowException){
backMap.put("code",-1);
backMap.put("msg","限流-异常啦");
}else if (e instanceof DegradeException){
backMap.put("code",-2);
backMap.put("msg","降级-异常啦");
}else if (e instanceof ParamFlowException){
backMap.put("code",-3);
backMap.put("msg","热点-异常啦");
}else if (e instanceof SystemBlockException){
backMap.put("code",-4);
backMap.put("msg","系统规则-异常啦");
}else if (e instanceof AuthorityException){
backMap.put("code",-5);
backMap.put("msg","认证-异常啦");
}
backMap.put("success",false);
// 设置返回json数据
response.setStatus(200);
response.setHeader("content-Type","application/json;charset=UTF-8");
response.getWriter().write(JSONObject.toJSONString(backMap));
}
}
```
cloud-system中写好接口:
```Java
@RequiredArgsConstructor
@RequestMapping("/v1/user")
@RestController
public class UserController {
private final UserService userService;
@GetMapping("/list")
public R listUser(){
List list = userService.list();
return R.data(list);
}
}
```

浏览器快速请求接口/v1/user/list验证自定义:**Sentinel自定义异常降级**

验证成功
### 7.2 Sentinel整合OpenFeign
cloud-system中添加配置:
```YAML
# 开启 sentinel 对feign 的支持,一旦出错就会进入兜底数据
feign:
sentinel:
enabled: true
```
cloud-system中定义feign接口请求 cloud-order
```Java
@FeignClient(value = "cloud-order-service",fallback = OrderFeignClientFallback.class)
public interface OrderFeignClient {
/**
* 获取订单列表
* @return
*/
@GetMapping("/v1/order/list")
R listOrder();
}
```
新建兜底类 OrderFeignClientFallback:
```Java
@Component
public class OrderFeignClientFallback implements OrderFeignClient {
@Override
public R listOrder() {
// 请求另外服务出错就会进入到这里的兜底方法
return R.fail("获取数据失败了,这是兜底数据哦。。。");
}
}
```
cloud-system中编写测试接口
```Java
@RequiredArgsConstructor
@RequestMapping("/v1/user")
@RestController
public class UserController {
private final OrderFeignClient orderFeignClient;
@GetMapping("/listOrder")
public R listOrder(){
R r = orderFeignClient.listOrder();
return r;
}
}
```
请求接口:/v1/user/listOrder
测试只开启cloud-system 关闭cloud-order:
测试结果,进入兜底类,Sentinel整合OpenFeign 成功

**单独设置:**

熔断降级规则(DegradeRule)包含下面几个重要的属性:
| Field | 说明 | 默认值 |
| ------------------ | ------------------------------------------------------------ | ---------- |
| resource | 资源名,即规则的作用对象 | |
| grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
| count | 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
| timeWindow | 熔断时长,单位为 s | |
| minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
| statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
| slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) | |
### 7.3 sentinel配置持久化
配置持久化到nacos
```Java
com.alibaba.csp
sentinel-datasource-nacos
```

Data ID为 spring应用的名称:

```JSON
[
{
"resource": "/findById",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
```
**naocs配置解读:**
```Java
resource:资源名称
limitApp:来源应用
grade:阀值类型 【0---线程数,1---QPS】
count:单机阀值
strategy:流控模式,【0---直接,1---关联,2---链路】
controlBehavior:流控效果,【0---快速失败,1---warmUp,2---排队等待】
clusterMode:是否集群
```
- 此时如果是Nacos集群,每个节点务必要配置到同一个数据库上。并且保证每个
节点都可用。如果有的节点宕掉了可能会导致配置持久化失败。
- 部署在nacos上的配置文件的名字并没有太多要求,只需要跟微服务项目中yml文件中配置的dataId一致即可。
## 八、网关springcloudgateway
**路由(route)**
路由是网关中最基础的部分, 路由信息包括一个ID、一个目的URI、一组**断言**工厂、一组**Filter**组成。如果**断言**为真, 则说明请求的URL和配置的路由匹配
**断言(predicates)**
Java 8中的断言函数, Spring Cloud Gateway中的断言函数类型是Spring 5.0框架中的Server Web Exchange断言函数允许开发者去定义匹配Httprequest中的任何信息, 比如请求头和参数等
**过滤器(Filter)**
Spring Cloud Gateway中的iter分为Gateway Fil r和Global Filter。Filter可以对请求和响应进行处理
- API Gateway,是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求、鉴权、监控、缓存、限流等功能
- 统一接入
- 智能路由
- AB测试、灰度测试
- 负载均衡、容灾处理
- 日志埋点(类似Nignx日志)
- 流量监控
- 限流处理
- 服务降级
- 安全防护
- 鉴权处理
- 监控
- 机器网络隔离
这里选用 springcloud gateway 网关
springcloud gateway: Spring公司专门开发的网关
新增maven依赖
```XML
org.springframework.cloud
spring-cloud-starter-gateway
```
**源码中已经配置了【gateway-adapter】的配置**,无需再次配置

添加配置:
```YAML
spring:
# nacos 地址
cloud:
nacos:
discovery:
server-addr: 81.69.43.78:8848
gateway:
routes: #数组形式
- id: system-service #路由唯一标识
#uri: http://127.0.0.1:8000 #想要转发到的地址
uri: lb://cloud-system-service # 从nacos获取名称转发,lb是负载均衡轮训策略
predicates: #断言 配置哪个路径才转发,不配置Path则不用添加前缀
- Path=/system-service/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀
# 请求 http://localhost:8002/system-service/v1/user/list 会转发到 http://cloud-system-service/v1/user/list
discovery:
locator:
#开启网关拉取nacos的服务 若不配置 routes 规则,则默认通过 服务名 开头去调用
# 注意这里配置后在请求开头添加服务名后,路由和断路器会失效
enabled: true
```
** # 请求 http://localhost:8002/system-service/v1/user/list 会转发到 http://cloud-system-service/v1/user/list
discovery:
locator:****
#开启网关拉取nacos的服务,则可以通过 服务名 开头去调用
# 注意这里配置后在【请求路径添加服务名后】,路由和断路器会失效
enabled: true**
### **内置断言工厂:**


```JSON
spring:
# nacos 地址
cloud:
nacos:
discovery:
server-addr: 81.69.43.78:8848
gateway:
routes: #数组形式
- id: system-service #路由唯一标识
#uri: http://127.0.0.1:8000 #想要转发到的地址
uri: lb://cloud-system-service # 从nacos获取名称转发,lb是负载均衡轮训策略
predicates: #断言 配置哪个路径才转发
- Query=name,test|test2|test3 #http://localhost:8002/v1/user/list?name=test
- After=2021-03-18T17:32:58.129+08:00[Asia/Shanghai]
# 运行网关拉取nacos服务
discovery:
locator:
#开启网关拉取nacos的服务,则可以通过 服务名 开头去调用
# 注意这里配置后在【请求路径添加服务名后】,路由和断路器会失效
enabled: true
```
### 自定义断言工厂
自定义路由断言工厂需要继承AbstractRoutePredicateFactory类, 重写apply方法的逻辑。在apply方法中可以通过exchange.getRequest() 拿到ServerHttpRequest对象, 从而可以获取到请求的参数、请求方式、请求头
等信息。
1、必须spring组件bean
2.类必须加上RoutePredicateFactory作为结尾
3.必须继承AbstractRoutePredicateFactory
4.必须声明静态内部类声明属性来接收配置文件中对应的断言的信息
5.需要结合shortcutFieldOrder进行绑定
6.通过apply进行逻辑判断true就是匹配成功false匹配失败
```Java
package cn.mesmile.gateway.predicate;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
/**
* @author zb
* @Description
* 配置
* predicates: #断言 配置哪个路径才转发
* - Query=name,test|test2|test3 #http://localhost:8002/v1/user/list?name=test
* - CheckParam=test
*/
@Component
public class CheckParamRoutePredicateFactory extends
AbstractRoutePredicateFactory {
/**
* Param key.
*/
public static final String PARAM_KEY = "param";
// /**
// * Regexp key.
// */
// public static final String REGEXP_KEY = "regexp";
public CheckParamRoutePredicateFactory() {
super(CheckParamRoutePredicateFactory.Config.class);
}
@Override
public List shortcutFieldOrder() {
return Arrays.asList(PARAM_KEY);
}
@Override
public Predicate apply(CheckParamRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
String param = config.getParam();
if (Objects.equals("test", param)){
return true;
}
// if (!StringUtils.hasText(config.regexp)) {
// // check existence of header
// return exchange.getRequest().getQueryParams()
// .containsKey(config.param);
// }
//
// List values = exchange.getRequest().getQueryParams()
// .get(config.param);
// if (values == null) {
// return false;
// }
// for (String value : values) {
// if (value != null && value.matches(config.regexp)) {
// return true;
// }
// }
return false;
}
};
}
@Validated
public static class Config {
@NotEmpty
private String param;
// private String regexp;
public String getParam() {
return param;
}
public CheckParamRoutePredicateFactory.Config setParam(String param) {
this.param = param;
return this;
}
// public String getRegexp() {
// return regexp;
// }
//
// public CheckParamRoutePredicateFactory.Config setRegexp(String regexp) {
// this.regexp = regexp;
// return this;
// }
}
}
```
### 内置过滤器

### 自定义过滤器
```Java
package cn.mesmile.gateway.filter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;
/**
* @author zb
* @Description
* 配置
* filters: #过滤器,请求在传递过程中通过过滤器修改
* - CheckParam=1
*/
@Component
public class CheckParamGatewayFilterFactory extends AbstractGatewayFilterFactory {
/**
* Prefix key.
*/
public static final String PREFIX_KEY = "prefix";
private static final Log log = LogFactory
.getLog(CheckParamGatewayFilterFactory.class);
public CheckParamGatewayFilterFactory() {
super(CheckParamGatewayFilterFactory.Config.class);
}
@Override
public List shortcutFieldOrder() {
return Arrays.asList(PREFIX_KEY);
}
@Override
public GatewayFilter apply(CheckParamGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
String prefix = config.getPrefix();
return chain.filter(exchange);
// boolean alreadyPrefixed = exchange
// .getAttributeOrDefault(GATEWAY_ALREADY_PREFIXED_ATTR, false);
// if (alreadyPrefixed) {
// return chain.filter(exchange);
// }
// exchange.getAttributes().put(GATEWAY_ALREADY_PREFIXED_ATTR, true);
//
// ServerHttpRequest req = exchange.getRequest();
// addOriginalRequestUrl(exchange, req.getURI());
// String newPath = config.prefix + req.getURI().getRawPath();
//
// ServerHttpRequest request = req.mutate().path(newPath).build();
//
// exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
//
// if (log.isTraceEnabled()) {
// log.trace("Prefixed URI with: " + config.prefix + " -> "
// + request.getURI());
// }
//
// return chain.filter(exchange.mutate().request(request).build());
}
// @Override
// public String toString() {
// return filterToStringCreator(CheckParamGatewayFilterFactory.this)
// .append("prefix", config.getPrefix()).toString();
// }
};
}
public static class Config {
private String prefix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}
}
```
### cloud-gateway模块自定义【全局】的过滤器
```Java
@Component
public class GlobalApiFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 写业务逻辑
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 根据业务鉴权
// if (StringUtils.isEmpty(token)) {
// exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// Mono voidMono = exchange.getResponse().setComplete();
// return voidMono;
// }
// 继续往下执行
Mono filter = chain.filter(exchange);
return filter;
}
@Override
public int getOrder() {
// 数字越小,优先级越高
return 0;
}
}
```
**框架自带的过滤器**

### 记录网关请求日志


### gateway跨越配置(cors gateway)
**方式一:配置文件的方式配置**

*号表示所有

**方式二:java代码的方式配置**



### gateway整合sentinel
导入依赖
```XML
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
com.alibaba.cloud
spring-cloud-alibaba-sentinel-gateway
```
添加配置:
```YAML
spring:
cloud:
# sentinel 配置
sentinel:
transport:
dashboard: 127.0.0.1:8080
port: 9904
#dashboard: 8080 控制台端口
#port: 9904 本地启的端口,随机选个不能被占用的,与dashboard进行数据交互,会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互, 若被占用,则开始+1一次扫描
```


### gateway全局sentinel限流异常返回
```Java
@Component
public class InitBlockHandle {
@PostConstruct
public void init(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono handleRequest(ServerWebExchange serverWebExchange, Throwable e) {
// 降级业务
Map backMap=new HashMap<>();
if (e instanceof FlowException){
backMap.put("code",-1);
backMap.put("msg","限流-异常啦");
}else if (e instanceof DegradeException){
backMap.put("code",-2);
backMap.put("msg","降级-异常啦");
}else if (e instanceof ParamFlowException){
backMap.put("code",-3);
backMap.put("msg","热点-异常啦");
}else if (e instanceof SystemBlockException){
backMap.put("code",-4);
backMap.put("msg","系统规则-异常啦");
}else if (e instanceof AuthorityException){
backMap.put("code",-5);
backMap.put("msg","认证-异常啦");
}
backMap.put("success",false);
return ServerResponse.status(HttpStatus.OK)
.header("content-Type","application/json;charset=UTF-8")
.body(BodyInserters.fromValue(backMap));
// 设置返回json数据
// response.setStatus(200);
// response.setHeader("content-Type","application/json;charset=UTF-8");
// response.getWriter().write(JSONObject.toJSONString(backMap));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
```
### gateway api 分组流控规则

## 九、链路追踪
### Sleuth链路追踪系统
- 分布式应用架构虽然满足了应用横向扩展的需求,但是运维和诊断的过程变得越来越复杂,例如会遇到接口诊断困难、应用性能诊断复杂、架构分析复杂等难题,传统的监控工具并无法满足,分布式链路系统由此诞生
- 核心:将一次请求分布式调用,使用GPS定位串起来,记录每个调用的耗时、性能等日志,并通过可视化工具展示出来
Sleuth和zipking(内部使用的鹰眼)
```Java
org.springframework.cloud
spring-cloud-starter-sleuth
```
调用网关【gateway-service】转发到【system-service】再到【order-service】
localhost:8002/system-service/v1/user/listOrder



```Java
第一个值,spring.application.name的值
第二个值,0faa27ad8177e6be,sleuth生成的一个ID,叫【Trace ID】,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID
第三个值,852ef4cfcdecabf3、【spanid】基本的工作单元,获取元数据,如发送一个http
第四个值:false,是否要将该信息输出到【zipkin 可视化 服务中来收集和展示】。
```
### **zipkin可视化 收集链路追踪**信息
- sleuth收集跟踪信息通过http请求发送给zipkin server
- zipkin server进行跟踪信息的存储以及提供Rest API即可
- Zipkin UI调用其API接口进行数据展示默认存储是内存,可也用mysql 或者elasticsearch等存储
- 官网
- [https://zipkin.io/](https://zipkin.io/)
- [https://zipkin.io/pages/quickstart.html](https://zipkin.io/pages/quickstart.html)
- 大规模分布式系统的APM工具(Application Performance Management),基于Google Dapper的基础实现,和sleuth结合可以提供可视化web界面分析调用链路耗时情况
- 同类产品
- 鹰眼(EagleEye)
- CAT
- twitter开源zipkin,结合sleuth
- Pinpoint,运用JavaAgent字节码增强技术
```Java
docker run -d -p 9411:9411 openzipkin/zipkin:2.23.0
```
- 访问入口:[http://127.0.0.1:9411/zipkin/](http://127.0.0.1:9411/zipkin/)
```Java
org.springframework.cloud
spring-cloud-starter-zipkin
```
配置
```Java
spring:
zipkin:
base-url: http://127.0.0.1:9411/ #zipkin地址
discovery-client-enabled: false #不用开启服务发现
sleuth:
sampler:
probability: 1.0 #采样百分比
```
默认为0.1,即10%,这里配置1,是记录全部的sleuth信息,是为了收集到更多的数据(仅供测试用)。
在分布式系统中,过于频繁的采样会影响系统性能,所以这里配置需要采用一个合适的值。
[http://127.0.0.1:9411/zipkin/](http://127.0.0.1:9411/zipkin/)


### **zipkin数据持久化**
- 服务重启会导致链路追踪系统数据丢失,数据是存储在内存中的
- 持久化配置:mysql或者elasticsearch
- 创建数据库表SQL脚本
- 启动命令
**方式一:持久化到mysql数据库**
```Java
java -jar zipkin-server-2.12.9-exec.jar \
--STORAGE_TYPE=mysql \
--MYSQL_HOST=127.0.0.1 \
--MYSQL_TCP_PORT=3306 \
--MYSQL_DB=zipkin_log \
--MYSQL_USER=root \
--MYSQL_PASS=root
```
数据库脚本
```Java
CREATE TABLE IF NOT EXISTS zipkin_spans (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL,
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`parent_id` BIGINT,
`debug` BIT(1),
`start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
`duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';
CREATE TABLE IF NOT EXISTS zipkin_annotations (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
`span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
`a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
`a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
`a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
`a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
`endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
`endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';
CREATE TABLE IF NOT EXISTS zipkin_dependencies (
`day` DATE NOT NULL,
`parent` VARCHAR(255) NOT NULL,
`child` VARCHAR(255) NOT NULL,
`call_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);
```
**方式二:持久化到elasticsearch**
```Java
java -jar zipkin-server-2.12.9-exec.jar \
--STORAGE_TYPE=elasticsearch \
--ES-HOST=localhost:9200
```
注:测试发现使用最新版的elasticsearch-7.9.1,会有以下异常
```Java
java.lang.IllegalStateException: response for update-template failed: {"error":{"root_cause":[{"type":"invalid_index_template_exception","reason":"index_template [zipkin:span_template]invalid, cause [Validation Failed: 1: index_pattern [zipkin:span-*] must not contain a ':';]"}],"type":"invalid_index_template_exception","reason":"index_template [zipkin:span_template] invalid, cause [Validation Failed: 1: index_pattern [zipkin:span-*] must not contain a ':';]"},"status":400}
```
解决方案:降低elasticsearch版本,使用elasticsearch-6.8.4数据可以正常持久化。
## 十、配置中心Nacos
- 现在微服务存在的问题
- 配置文件增多,不好维护
- 修改配置文件需要重新发布
- 统一管理配置, 快速切换各个环境的配置
- 相关产品:
- 百度的disconf 地址:[https://github.com/knightliao/disconf](https://github.com/knightliao/disconf)
- 阿里的diamand 地址:[https://github.com/takeseem/diamond](https://github.com/takeseem/diamond)
- springcloud的configs-server: 地址:[http://cloud.spring.io/spring-cloud-config/](http://cloud.spring.io/spring-cloud-config/)
- 阿里的Nacos:既可以当服务治理,又可以当配置中心,Nacos = Eureka + Config
- 官方文档
- [https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config](https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config)
添加依赖:
```Java
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
```

**注意配置文件优先级**
- **不能使用原先的application.yml, 需要使用bootstrap.yml作为配置文件**
- **配置读取优先级 bootstrap.yml > application.yml**
以网关服务(cloud-gateway-service)为例:
bootstrap.yml
```YAML
spring:
profiles:
# 使用 dev 分支配置
active: dev
application:
# 应用名称
name: cloud-gateway-service
cloud:
nacos:
config:
server-addr: 81.69.43.78:8848 #Nacos配置中心地址
file-extension: yaml #文件拓展格式
server:
port: 8002
```
将原来application.yml 注释,将原来application-dev.yml 拷贝到nacos中,将原来代码中的application-dev.yml注释



**nacos中导出的配置文件:**
[nacos_config_export_20220301224344.zip](file/nacos_config_export_20220301224344.zip)
**nacos动态刷新配置:**
我们修改了配置,程序不能自动更新,动态刷新就可以解决这个问题
@RefreshScope 动态刷新
在nacos中配置 useLocalCache 的值,然后再代码中动态获取
```Java
@RestController
@RequestMapping("/config")
@RefreshScope
public class ConfigController {
@Value("${useLocalCache:false}")
private boolean useLocalCache;
@RequestMapping("/get")
public boolean get() {
return useLocalCache;
}
}
```
## ~~十一、hystrix熔断降级(停止更新维护)~~
- 使用命令模式将所有对外部服务(或依赖关系)的调用包装再HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行。
- 每个依赖都维护一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。
- 记录请求成功,失败,超时和线程拒绝。
- 服务错误百分比超过阀值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。
- 请求失败,被拒绝,超时或熔断时执行降级逻辑。
- 近实时地讲课指标和配置的修改。
-
引入jar包
```XML
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
```
**@EnableHystrix //在启动类上添加@EnableHystrix注解开启Hystrix的熔断器功能。**

配置开启 feign对hystirx的支持:
```YAML
feign:
hystrix:
# feign熔断器开关
enabled: true
```
### 方法一:单独设置接口服务降级

### 方法二:统一设置接口服务降级(推荐)
OrderFeignClient 客户端
```Java
/**
* @author zb
* @Description
*
* 服务降级-》进而熔断-》恢复调用链路
* 服务降级 fallback
*/
@FeignClient(value = "cloud-order-service",fallback = OrderFeignClientFallback.class)
public interface OrderFeignClient {
/**
* 获取订单列表
* @return
*/
@GetMapping("/v1/order/list")
R listOrder();
}
```
OrderFeignClientFallback兜底类
```Java
/**
* @author zb
* @Description
*/
@Component
public class OrderFeignClientFallback implements OrderFeignClient {
@Override
public R listOrder() {
// 请求另外服务出错就会进入到这里的兜底方法
return R.fail("获取数据失败了,这是兜底数据哦。。。");
}
}
```
### 设置服务熔断(顺序:服务降级—> 服务熔断 —> 恢复调用链路)

开启熔断器,在10s内10次请求里面有6次失败的话
**配置****参数配置出处**

**熔断器的三个状态:关闭(正常状态)、打开(断开状态)、半打开(半打开状态)**
例如:
开启熔断器,在10s内10次请求里面有6次失败的话(依据配置),熔断器从【关闭】变为【打开】
过了一段时间后熔断器从【打开】变为【半打开】,尝试让请求通过,若请求没有超过配置限度熔断器则从【半打开】变为【关闭】

## 十二、Skywalking链路追踪
sky walking是一个国产开源框架, 2015年由吴晟开源, 2017年加入Apache孵化器。sky walking是分布式系统的应用程序性能监视工具,
专为微服务、云原
生架构和基于容器(Docker、K8s、Mesos) 架构而设计。它是一款优秀的APM(Application Performance Management) 工具, 包括了分布式追踪、性能
指标分析、应用和服务依赖分析等。
官网:http://skywalking.apache.org/
下载:http://skywalking.apache.org/downloads/
Git hub:https://aithub.com/apache/skywalking
文档:https://skywalking.apache.org/docs/main/v8.4.0/readme
中文文档:https://skyapm.github.io/document-cn-translation-of-skywalking/
版本:v8.3.0升级到v8.4.0

下载:http://skywalking.apache.org/downloads/
### skywalking**安装包**
[apache-skywalking-apm-es7-8.5.0.tar.gz](file/apache-skywalking-apm-es7-8.5.0.tar.gz)



**默认采用H2内存数据库**





```Java
-javaagent:C:\Users\SuperZheng\Desktop\apache-skywalking-apm-bin-es7\agent\skywalking-agent.jar
-DSW_AGENT_NAME=GatewayApplication
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800
```


### 持久化

mysql驱动:

将驱动复制到安装包:


### 自定义业务Skywalking链路追踪
```Java
org.apache.skywalking
apm-toolkit-trace
8.5.0
```



### 性能剖析

请求5次,每个10ms采样一次

### ~~**分析出了springboot第一次接口访问慢的问题**~~(暂时无效)
SpringBoot的接口第一次访问都很慢,通过日志可以发现,dispatcherServlet不是一开始就加载的,有访问才开始加载的,即懒加载。
```Java
2019-01-25 15:23:46.264 INFO 1452 --- [nio-8080-exec-1] Initializing Spring FrameworkServlet 'dispatcherServlet'
2019-01-25 15:23:46.265 INFO 1452 --- [nio-8080-exec-1] FrameworkServlet 'dispatcherServlet': initialization started
2019-01-25 15:23:46.395 INFO 1452 --- [nio-8080-exec-1] FrameworkServlet 'dispatcherServlet': initialization completed in 130 ms
```
这样对于我们来说是一个问题。
在SpringBoot的配置文件中添加以下配置即可:
```Java
spring:
mvc:
servlet:
# 当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet
load-on-startup: 1
```
### 日志记录
引入插件依赖
```Java
org.apache.skywalking
apm-toolkit-logback-1.x
8.5.0
```

```Java
# 假如skywalking没有部署在本地,就需要配置以下内容到 agent/config/agent.config
plugin.toolkit.log.grpc.reporter.server_host=${SW_GRPC_LOG_SERVER_HOST:127.0.0.1}
plugin.toolkit.log.grpc.reporter.server_port=${SW_GRPC_LOG_SERVER_PORT:11800}
plugin.toolkit.log.grpc.reporter.max_message_size=${SW_GRPC_LOG_MAX_MESSAGE_SIZE:10485760}
plugin.toolkit.log.grpc.reporter.upstream_timeout=${SW_GRPC_LOG_GRPC_UPSTREAM_TIMEOUT:30}
```
**文档地址**
https://[github.com/apache/skywalking/blob/v8.5.0/docs/en/setup/service-agent/java-agent/Application-toolkit-logback-1.x.md](http://github.com/apache/skywalking/blob/v8.5.0/docs/en/setup/service-agent/java-agent/Application-toolkit-logback-1.x.md)

**加上[%!t(MISSING)id]这个就为traceId**
logback-spring.xml
```XML
-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p})[%tid] %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
%d{yyyy-MM-dd HH:mm:ss.SSS} [%tid] [%thread] %-5level %logger{36} -%msg%n
```

### 告警规则
**文档地址**
[https://github.com/apache/skywalking/blob/v8.5.0/docs/en/setup/backend/backend-alarm.md](https://github.com/apache/skywalking/blob/v8.5.0/docs/en/setup/backend/backend-alarm.md)

#### 回调告警信息,当发送告警信息的时候,skywalking发送告警信息到其他地方



### Skywalking高可用部署





## 十三、分布式事务解决方案seata
**版本说明:**
[https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明](https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明)
**官方文档****:**
[https://seata.io/zh-cn/docs/overview/what-is-seata.html](https://seata.io/zh-cn/docs/overview/what-is-seata.html)
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了** AT、TCC、SAGA 和 XA **事务模式,为用户打造一站式的分布式解决方案。**seata推荐 AT 模式**

#### TC (Transaction Coordinator) - 事务协调者(单独部署服务)
维护全局和分支事务的状态,驱动全局事务提交或回滚。
#### TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
#### RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
常见分布式事务解决方案
1、seata阿里分布式事务框架
2、消息队列
3、saga
4、XA
**他们有一个共同点,都是两阶段(2PC)**。“两阶段“是指完成整个分布式事务,划分成两个步骤完成。
这四种常见的分布式事务解决方案, 分别对应着分布式事务的四种模式:**AT、TCC、Saga、XA**;
四种分布式事务模式,都有各自的理论基础,分别在不同的时间被提出;每种模式都有它的适用场景,同样每个模式也都诞生有各自的代表产品;而这些代表产品, 可能就是我们常见的(全局事务、基于可靠消息、最大努力通知、TCC) 。
### 写隔离
- 一阶段本地事务提交前,需要确保先拿到 **全局锁** 。
- 拿不到 **全局锁** ,不能提交本地事务。
- 拿 **全局锁** 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。


### 读隔离

### AT模式


### TCC模式

AT 模式([参考链接 TBD](https://seata.io/zh-cn/docs/overview/what-is-seata.html))基于 **支持本地 ACID 事务** 的 **关系型数据库**:
- 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
- 二阶段 commit 行为:马上成功结束,**自动** 异步批量清理回滚日志。
- 二阶段 rollback 行为:通过回滚日志,**自动** 生成补偿操作,完成数据回滚。
相应的,TCC 模式,不依赖于底层数据资源的事务支持:
- 一阶段 prepare 行为:调用 **自定义** 的 prepare 逻辑。
- 二阶段 commit 行为:调用 **自定义** 的 commit 逻辑。
- 二阶段 rollback 行为:调用 **自定义** 的 rollback 逻辑。
所谓 TCC 模式,是指支持把 **自定义** 的分支事务纳入到全局事务的管理中。
*国内主要的开源TCC分布式事务框架包括 框架名称 Github地址 star数量*
*tcc-transaction *[*https://github.com/changmingxie/tcc-transaction*](https://github.com/changmingxie/tcc-transaction)* 2446 *
*Hmily *[*https://github.com/yu199195/hmily*](https://github.com/yu199195/hmily)* 1381 *
*ByteTCC *[*https://github.com/liuyangming/ByteTCC*](https://github.com/liuyangming/ByteTCC)* 1300 *
*EasyTransaction *[*https://github.com/QNJR-GROUP/EasyTransaction*](https://github.com/QNJR-GROUP/EasyTransaction)* 904*

### 2PC的问题
1.同步阻塞参与者在等待协调者的指令时,其实是在等待其他参与者的响应,在此过程中,参与者是无法进行其他操作的,也就是阻塞了其运行,倘若参与者与协调者之间网络异常导致参与者一直收不到协调者信息,那么会导致参与者一直阻塞下去
2.单点在2PC中,一切请求都来自协调者,所以协调者的地位是至关重要的,如果协调者宕机,那么就会使参与者一直阻塞并一直占用事务资源如果协调者也是分布式,使用选主方式提供服务,那么在一个协调者挂掉后,可以选取另一个协调者继续后续的服务,可以解决单点问题。但是,新协调者无法知道上一个事务的
全部状态信息(例如已等待Prepare响应的时长等) , 所以也无法顺利处理上一个事务
3.数据不一致Commit事务过程中Commit请求/Rollback请求可能因为协调者宕机或协调者与参与者网络问题丢失, 那么就导致了部分参与者没有收到Commit/Rollback请求, 而其他参与者则正常收到执行了Commit/Rollback操作, 没有收到请求的参与者则继续阻塞。这时, 参与者之间的数据就不再一致了当参与者执行Commit/Rollback后会向协调者发送Ack, 然而协调者不论是否收到所有的参与者的Ack, 该事务也不会再有其他补救措施了, 协调者能做的也就是等待超时后像事务发起者返回一个*我不确定该事务是否成功
4, 环境可靠性依赖协调者Prepare请求发出后, 等待响应, 然而如果有参与者宕机或与协调者之间的网络中断, 都会导致协调者无法收到所有参与者的响应, 那么在2PC中,协调者会等待一定时间,然后超时后,会触发事务中断,在这个过程中,协调者和所有其他参与者都是出于阻塞的,这种机制对网络问题常见的现实环境来说太苛刻了
### 可靠事务的解决方案最终一致(消息队列)


### 部署Seata(Server端)
Seata分TC、TM和RM三个角色,**TC(Server端)**为单独服务端部署,**TM和RM(Client端)**由业务系统集成。
[https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html](https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html)
资源目录
[https://github.com/seata/seata/tree/1.3.0/script](https://github.com/seata/seata/tree/1.3.0/script)
- client
> 存放client端sql脚本 (包含 undo_log表) ,参数配置
- config-center
> 各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件
- server
> server端数据库脚本 (包含 lock_table、branch_table 与 global_table) 及各个容器配置
### 启动并安装Server
Server端存储模式(store.mode)现有file、db、redis三种(后续将引入raft,mongodb),
file模式无需改动,直接启动即可
注: file模式为单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高;
db**(db version 5.7+)**模式为高可用模式,全局事务会话信息通过db共享,相应性能差些;
redis模式Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置合适当前场景的redis持久化配置.
**下载地址:**
[https://github.com/seata/seata/releases](https://github.com/seata/seata/releases)
**已经配置好的服务端包:**
[seata-1.3.0-server.zip](file/seata-1.3.0-server.zip)
**服务端表:**
```Java
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
```
启动包: seata-->conf-->file.conf,修改store.mode="db或者redis"

启动包: seata-->conf-->file.conf,修改store.db或store.redis相关属性。

**修改注册中心**

**修改配置中心**







**将配置推送到nacos**
**script文件夹是从源码拉下来的**


```Java
sh nacos-config.sh -h 81.69.43.78 -p 8848 -g SEATA_GROUP
```

**检查已经成功推送到nacos**

**需要config.txt的数据源改为db 然后通过nacos-config.sh注册到配置中心**

### 启动Seata Server




### 客户端整合seata
涉及到分布式事务的服务引入依赖:
```Java
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
```
【**各个微服务】对应的数据库添加 undo_log 表**
```SQL
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
```
涉及到分布式事务的客户端配置:
```YAML
# 分布式事务的分组 和 配置文件中要一致
spring:
cloud:
alibaba:
seata:
tx-service-group: chengdu
# 分布式事务seata相关配置
seata:
registry:
# 配置 seata的注册中心,告诉 seata client 怎么去访问 seata server(TC)
type: nacos
nacos:
server-addr: 81.69.43.78:8848 # nacos地址
application: seata-server # seata server的服务名
username: nacos
password: nacos
group: SEATA_GROUP
```
### seata遇到的异常错误
### seata 1.4.x版本在MySQL8.0下DATETIME类型转换错误的问题
```Java
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.LocalDateTime`
```

客户端引入jar包
```Java
com.esotericsoftware
kryo
4.0.2
de.javakaffee
kryo-serializers
0.44
```
### seata测试结果


| 服务端 | feign端 | 服务端是否手动抛出异常 | 服务端全局事务 | feign全局事务 | 是否全部都回滚 |
| ------ | ------- | ---------------------- | -------------- | ------------------ | ------------------------- |
| 正确 | 出错 | 是 | 是 | 否 | 回滚 |
| 正确 | 出错 | 否 | 是 | 是 | 不回滚 |
| 错误 | 正确 | 是 | 是 | 是 | 回滚 |
| 正确 | 出错 | 否 | 是 | 否(单体事务注解) | feign端回滚,服务端不回滚 |
**综上所述:**
**服务端必须检查 feign 是否调用正确,若feign调用失败则抛出异常**,让两边都回滚
## 十四、springsecurity安全认证授权
引入依赖
```Java
org.springframework.boot
spring-boot-starter-security
```
第一次引入依赖后,所有的接口访问都需要认证
会默认跳转到登录接口http://ip:port/login

默认退出登录接口 http://ip:port/logout
### 登录校验流程

### springSecurity完整流程

**UsernamePasswordAuthenticationFilter**:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的**认证**工作主要有它负责。
**ExceptionTranslationFilter**:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException
**FilterSecuritylnterceptor**:负责**权限校验**的过滤器


### 认证流程

自定义UserDetailService从数据库中通过登录账号查询密码,返回后与Authentication对象里面的密码对比


**登录**
①自定义登录接口
调用ProviderManager的方法进行认证如果认证通过生成jwt
把用户信息存入redis中
②自定义UserDetailsService
在这个实现列中去查询数据
**校验**
①定义Jwt认证过滤器
②获取token
③解析token获取其中的userid
④从redis中获取用户信息
⑤存入SecurityContextHolder(定义过滤器存入这里面)
```Java
org.springframework.boot
spring-boot-starter-data-redis
com.alibaba
fastjson
1.2.78
com.auth0
java-jwt
3.16.0
```
### redis使用fastjson序列化
```Java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* @author zb
* @date 2022/3/10 0:25
* @Description Redis 使用 FastJson 序列化
*/
public class FastJsonRedisSerializer implements RedisSerializer {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private Class tClass;
static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJsonRedisSerializer(Class tClass){
super();
this.tClass = tClass;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
try {
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
} catch (Exception e) {
throw new SerializationException("Could not serialize: " + e.getMessage(), e);
}
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0){
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
try {
return JSON.parseObject(str, tClass);
} catch (Exception e) {
throw new SerializationException("Could not deserialize: " + e.getMessage(), e);
}
}
protected JavaType getJavaType(Class> tClass){
return TypeFactory.defaultInstance().constructType(tClass);
}
}
```
```Java
import cn.mesmile.system.serializer.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author zb
* @date 2022/3/10 0:35
* @Description fastjson序列化
* KryoRedisSerializer的压缩率和速度最优,fastJson次之,默认的则最差
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer